AWS CloudFormation - Zmienne niestandardowe w szablonach

18

Czy istnieje sposób zdefiniowania skrótów do często używanych wartości pochodzących z parametrów szablonu CloudFormation?

Na przykład - mam skrypt, który tworzy stos projektu Multi-AZ o nazwie ELB projecti dwóch instancjach za ELB o nazwie project-1i project-2. Przekazuję ELBHostNameparametr tylko do szablonu, a później wykorzystuję go do skonstruowania:

"Fn::Join": [
    ".", [
        { "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
        { "Ref": "EnvironmentVersioned" },
        { "Ref": "HostedZone" }
    ]
]

Ta konstrukcja lub bardzo podobna jest powtarzana wiele razy w całym szablonie - w celu utworzenia nazwy hosta EC2, rekordów Route53 itp.

Zamiast powtarzać to w kółko, chciałbym przypisać wynik tego Fn::Joindo jakiejś zmiennej i odwoływać się do tego, tak jak mogę z "Ref":instrukcją.

Idealnie coś takiego:

Var::HostNameFull = "Fn::Join": [ ... ]
...
{ "Name": { "Ref": "Var::HostNameFull" } }

lub coś podobnego prostego.

Czy to możliwe dzięki Amazon CloudFormation?

MLu
źródło
Czy ELBHostName jest parametrem, który jawnie przekazujesz do Cloudformation? Jeśli tak, dlaczego warto skorzystać z Ref? Mógłby użyć Wąsów, aby uwzględnić zmienne w szablonie i przekształcić je w JSON przed wysłaniem go do Cloudformation. Zależy, jak wygląda proces obsługi administracyjnej.
Canuteson

Odpowiedzi:

5

Szukałem tej samej funkcjonalności. Przyszło mi na myśl użycie zagnieżdżonego stosu, jak sugerował SpoonMeiser, ale potem zdałem sobie sprawę, że tak naprawdę potrzebowałem funkcji niestandardowych. Na szczęście CloudFormation pozwala na użycie AWS :: CloudFormation :: CustomResource, który przy odrobinie pracy pozwala to zrobić. To wydaje się przesadne w przypadku samych zmiennych (coś, co argumentowałbym, że powinno być w CloudFormation w pierwszej kolejności), ale wykonuje zadanie, a ponadto pozwala na całą elastyczność (wybierz Python / node) /Jawa). Należy zauważyć, że funkcje lambda kosztują pieniądze, ale mówimy tutaj o pensach, chyba że utworzysz / usuniesz swoje stosy wiele razy na godzinę.

Pierwszym krokiem jest utworzenie funkcji lambda na tej stronie , która nie robi nic poza pobraniem wartości wejściowej i skopiowaniem jej na wyjście. Moglibyśmy sprawić, aby funkcja lambda robiła różne szalone rzeczy, ale kiedy już mamy funkcję tożsamości, wszystko inne jest łatwe. Alternatywnie możemy stworzyć funkcję lambda w samym stosie. Ponieważ używam wielu stosów na jednym koncie, miałbym całą resztę funkcji i ról lambda (i wszystkie stosy muszą zostać utworzone za pomocą --capabilities=CAPABILITY_IAM, ponieważ również potrzebuje roli.

Utwórz funkcję lambda

  • Przejdź do strony głównej lambda i wybierz swój ulubiony region
  • Wybierz „Pustą funkcję” jako szablon
  • Kliknij „Dalej” (nie konfiguruj żadnych wyzwalaczy)
  • Wypełnić:
    • Nazwa: CloudFormationIdentity
    • Opis: Zwraca to, co dostaje, zmienne wsparcie w Cloud Formation
    • Środowisko wykonawcze: python2.7
    • Typ wpisu kodu: Edytuj kod w tekście
    • Kod: patrz poniżej
    • Treser: index.handler
    • Rola: Utwórz niestandardową rolę. W tym momencie otwiera się okno, które pozwala utworzyć nową rolę. Zaakceptuj wszystko na tej stronie i kliknij „Zezwól”. Stworzy rolę z uprawnieniami do publikowania w dziennikach cloudwatch.
    • Pamięć: 128 (to minimum)
    • Limit czasu: 3 sekundy (powinno wystarczyć)
    • VPC: Brak VPC

Następnie skopiuj i wklej poniższy kod w polu kodu. Na górze tej funkcji znajduje się kod z modułu python-odpowiedź cfn , który jest instalowany automatycznie tylko wtedy, gdy funkcja lambda została utworzona za pomocą CloudFormation, z jakiegoś dziwnego powodu. Ta handlerfunkcja jest dość oczywista.

from __future__ import print_function
import json

try:
    from urllib2 import HTTPError, build_opener, HTTPHandler, Request
except ImportError:
    from urllib.error import HTTPError
    from urllib.request import build_opener, HTTPHandler, Request


SUCCESS = "SUCCESS"
FAILED = "FAILED"


def send(event, context, response_status, reason=None, response_data=None, physical_resource_id=None):
    response_data = response_data or {}
    response_body = json.dumps(
        {
            'Status': response_status,
            'Reason': reason or "See the details in CloudWatch Log Stream: " + context.log_stream_name,
            'PhysicalResourceId': physical_resource_id or context.log_stream_name,
            'StackId': event['StackId'],
            'RequestId': event['RequestId'],
            'LogicalResourceId': event['LogicalResourceId'],
            'Data': response_data
        }
    )
    if event["ResponseURL"] == "http://pre-signed-S3-url-for-response":
        print("Would send back the following values to Cloud Formation:")
        print(response_data)
        return

    opener = build_opener(HTTPHandler)
    request = Request(event['ResponseURL'], data=response_body)
    request.add_header('Content-Type', '')
    request.add_header('Content-Length', len(response_body))
    request.get_method = lambda: 'PUT'
    try:
        response = opener.open(request)
        print("Status code: {}".format(response.getcode()))
        print("Status message: {}".format(response.msg))
        return True
    except HTTPError as exc:
        print("Failed executing HTTP request: {}".format(exc.code))
        return False

def handler(event, context):
    responseData = event['ResourceProperties']
    send(event, context, SUCCESS, None, responseData, "CustomResourcePhysicalID")
  • Kliknij Następny"
  • Kliknij „Utwórz funkcję”

Możesz teraz przetestować funkcję lambda, wybierając przycisk „Test”, a następnie „Próbka tworzenia CloudFormation” jako przykładowy szablon. Powinieneś zobaczyć w swoim dzienniku, że zmienne wprowadzone do niego są zwracane.

Użyj zmiennej w szablonie CloudFormation

Teraz, gdy mamy tę funkcję lambda, możemy jej używać w szablonach CloudFormation. Najpierw zanotuj funkcję lambda Arn (przejdź do strony głównej lambda , kliknij właśnie utworzoną funkcję, Arn powinien być w prawym górnym rogu, coś w stylu arn:aws:lambda:region:12345:function:CloudFormationIdentity).

Teraz w swoim szablonie, w sekcji zasobów, podaj swoje zmienne, takie jak:

Identity:
  Type: "Custom::Variable"
  Properties:
    ServiceToken: "arn:aws:lambda:region:12345:function:CloudFormationIdentity"
    Arn: "arn:aws:lambda:region:12345:function:CloudFormationIdentity"

ClientBucketVar:
  Type: "Custom::Variable"
  Properties:
    ServiceToken: !GetAtt [Identity, Arn]
    Name: !Join ["-", [my-client-bucket, !Ref ClientName]]
    Arn: !Join [":", [arn, aws, s3, "", "", !Join ["-", [my-client-bucket, !Ref ClientName]]]]

ClientBackupBucketVar:
  Type: "Custom::Variable"
  Properties:
    ServiceToken: !GetAtt [Identity, Arn]
    Name: !Join ["-", [my-client-bucket, !Ref ClientName, backup]]
    Arn: !Join [":", [arn, aws, s3, "", "", !Join ["-", [my-client-bucket, !Ref ClientName, backup]]]]

Najpierw określam Identityzmienną zawierającą Arn dla funkcji lambda. Umieszczenie tego w zmiennej tutaj oznacza, że ​​muszę to określić tylko raz. Robię wszystkie moje zmienne typu Custom::Variable. CloudFormation pozwala na użycie dowolnej nazwy typu zaczynającej się na Custom::dla niestandardowych zasobów.

Zauważ, że Identityzmienna zawiera dwa razy Arn dla funkcji lambda. Raz, aby określić funkcję lambda do użycia. Drugi raz jako wartość zmiennej.

Teraz, gdy mam Identityzmienną, mogę definiować nowe zmienne za pomocą ServiceToken: !GetAtt [Identity, Arn](myślę, że kod JSON powinien być podobny "ServiceToken": {"Fn::GetAtt": ["Identity", "Arn"]}). Tworzę 2 nowe zmienne, każda z 2 polami: Nazwa i Arn. Z pozostałej części mojego szablonu mogę korzystać !GetAtt [ClientBucketVar, Name]lub !GetAtt [ClientBucketVar, Arn]kiedykolwiek będę go potrzebować.

Słowo ostrzeżenia

Podczas pracy z niestandardowymi zasobami, jeśli funkcja lambda ulegnie awarii, utkniesz na 1 do 2 godzin, ponieważ CloudFormation czeka godzinę na odpowiedź z (zawieszonej) funkcji przed poddaniem się. Dlatego dobrze jest określić krótki limit czasu dla stosu podczas rozwijania funkcji lambda.

Claude
źródło
Świetna odpowiedź! Przeczytałem go i uruchomiłem w stosach, chociaż dla mnie nie martwię się o mnożenie funkcji lambda na moim koncie i lubię szablony, które są samodzielne (moduluję za pomocą cloudformation-toolklejnotu), więc pakuję kreację lambda do szablon, a następnie można go użyć bezpośrednio zamiast tworzyć Identityniestandardowy zasób. Zobacz tutaj mój kod: gist.github.com/guss77/2471e8789a644cac96992c4102936fb3
Guss
Gdy jesteś „... utkniesz na 1 do 2 godzin ...”, ponieważ lambda się zawiesiła i nie odpowiedziała odpowiedzią cfn, możesz ponownie uruchomić szablon, ręcznie używając curl / wget na podpisany adres URL. Pamiętaj tylko, aby zawsze wydrukować zdarzenie / adres URL na początku lambda, abyś mógł przejść do CloudWatch i uzyskać adres URL, jeśli się zawiesi.
Taylor
12

Nie mam odpowiedzi, ale chciałem zauważyć, że możesz zaoszczędzić sobie dużo bólu, używając Fn::SubzamiastFn::Join

{ "Fn::Sub": "${ELBHostName"}-1.${EnvironmentVersioned}.${HostedZone}"}

Zastępuje

"Fn::Join": [
    ".", [
        { "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
        { "Ref": "EnvironmentVersioned" },
        { "Ref": "HostedZone" }
    ]
]
Kevin Audleman
źródło
3

Nie. Próbowałem, ale wyszedłem pusty. Dla mnie sensowne było utworzenie wpisu Mappings o nazwie „CustomVariables” i posiadanie w nim wszystkich moich zmiennych. Działa dla prostych ciągów, ale nie można używać Intrinsics (Refs, Fn :: Joins itp.) Wewnątrz Mappings .

Pracuje:

"Mappings" : {
  "CustomVariables" : {
    "Variable1" : { "Value" : "foo" },
    "Variable2" : { "Value" : "bar" }
  }
}

Nie będzie działać:

  "Variable3" : { "Value" : { "Ref" : "AWS::Region" } }

To tylko przykład. Nie umieściłbyś samodzielnego Ref w zmiennej.

Obrabować
źródło
1
Dokumentacja mówi, że wartości mapowania muszą być literałami.
Ivan Anishchuk
3

Możesz użyć zagnieżdżonego stosu, który rozpoznaje wszystkie zmienne w wynikach, a następnie użyć Fn::GetAttdo odczytania wyników z tego stosu

SpoonMeiser
źródło
2

Możesz użyć zagnieżdżonych szablonów, w których „rozwiązujesz” wszystkie zmienne w zewnętrznym szablonie i przekazujesz je do innego szablonu.

JoseOlcese
źródło