AWS CloudFormation: variabili personalizzate nei modelli


18

Esiste un modo per definire le scorciatoie per i valori utilizzati di frequente derivati ​​dai parametri del modello CloudFormation?

Ad esempio - Ho uno script che crea uno stack di progetto Multi-AZ con nome ELB projecte due istanze dietro ELB chiamato project-1e project-2. Passo solo il ELBHostNameparametro al modello e successivamente lo uso per costruire:

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

Questa costruzione o molto simile viene ripetuta più volte nel modello: per creare il nome host EC2, i record Route53, ecc.

Invece di ripeterlo più e più volte vorrei assegnare l'output di quello Fn::Joina una variabile di qualche tipo e fare riferimento solo a quello, proprio come posso fare con l' "Ref":istruzione.

Idealmente qualcosa come:

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

o qualcosa di altrettanto semplice.

È possibile con Amazon CloudFormation?


ELBHostName è un parametro che stai esplicitamente passando a Cloudformation? In tal caso, perché usare un riferimento? Potrebbe usare Moustache per includere variabili nel modello e trasformarlo in JSON prima di inviarlo a Cloudformation. Dipende dall'aspetto del processo di provisioning.
Canuteson,

Risposte:


5

Stavo cercando la stessa funzionalità. Mi è venuto in mente l'utilizzo di uno stack nidificato come suggerito da SpoonMeiser, ma poi mi sono reso conto che ciò di cui avevo effettivamente bisogno erano funzioni personalizzate. Fortunatamente CloudFormation consente l'uso di AWS :: CloudFormation :: CustomResource che, con un po 'di lavoro, consente di fare proprio questo. Questo sembra eccessivo solo per le variabili (qualcosa che direi che avrebbe dovuto essere in CloudFormation in primo luogo), ma ottiene il lavoro fatto e, inoltre, consente tutta la flessibilità di (scegli il tuo python / nodo /Giava). Va notato che le funzioni lambda costano denaro, ma qui stiamo parlando di centesimi a meno che tu non crei / elimini le tue pile più volte all'ora.

Il primo passo è fare una funzione lambda in questa pagina che non fa altro che prendere il valore di input e copiarlo nell'output. Potremmo avere la funzione lambda fare ogni sorta di cose folli, ma una volta che abbiamo la funzione di identità, qualsiasi altra cosa è facile. In alternativa, potremmo avere la funzione lambda creata nello stack stesso. Dal momento che uso molti stack in 1 account, avrei un sacco di funzioni e ruoli lambda rimanenti (e tutti gli stack devono essere creati con --capabilities=CAPABILITY_IAM, poiché ha anche bisogno di un ruolo.

Crea la funzione lambda

  • Vai alla home page lambda e seleziona la tua regione preferita
  • Seleziona "Funzione vuota" come modello
  • Fai clic su "Avanti" (non configurare alcun trigger)
  • Compilare:
    • Nome: CloudFormationIdentity
    • Descrizione: restituisce ciò che ottiene, supporto variabile in Cloud Formation
    • Runtime: python2.7
    • Tipo di inserimento codice: modifica codice in linea
    • Codice: vedi sotto
    • handler: index.handler
    • Ruolo: creare un ruolo personalizzato. A questo punto si apre un popup che consente di creare un nuovo ruolo. Accetta tutto su questa pagina e fai clic su "Consenti". Creerà un ruolo con le autorizzazioni per pubblicare nei log di cloudwatch.
    • Memoria: 128 (questo è il minimo)
    • Timeout: 3 secondi (dovrebbe essere abbondante)
    • VPC: nessun VPC

Quindi copia e incolla il codice qui sotto nel campo del codice. La parte superiore della funzione è il codice del modulo python cfn-response , che viene installato automaticamente solo se la funzione lambda viene creata tramite CloudFormation, per qualche strano motivo. La handlerfunzione è piuttosto autoesplicativa.

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")
  • Fai clic su "Avanti"
  • Fai clic su "Crea funzione"

Ora puoi testare la funzione lambda selezionando il pulsante "Test" e selezionando "CloudFormation Create Request" come modello di esempio. Dovresti vedere nel tuo registro che le variabili che gli sono state fornite vengono restituite.

Usa la variabile nel tuo modello CloudFormation

Ora che abbiamo questa funzione lambda, possiamo usarla nei modelli CloudFormation. Prima prendi nota della funzione lambda Arn (vai alla home page lambda , fai clic sulla funzione appena creata, l'Arn dovrebbe essere in alto a destra, qualcosa del genere arn:aws:lambda:region:12345:function:CloudFormationIdentity).

Ora nel tuo modello, nella sezione delle risorse, specifica le tue variabili come:

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]]]]

Per prima cosa specifico una Identityvariabile che contiene l'Arn per la funzione lambda. Mettendo questo in una variabile qui, significa che devo specificarlo solo una volta. Faccio tutte le mie variabili di tipo Custom::Variable. CloudFormation ti consente di utilizzare qualsiasi nome-tipo a partire da Custom::per risorse personalizzate.

Si noti che la Identityvariabile contiene due volte Arn per la funzione lambda. Una volta per specificare la funzione lambda da usare. La seconda volta come valore della variabile.

Ora che ho la Identityvariabile, posso definire nuove variabili usando ServiceToken: !GetAtt [Identity, Arn](penso che il codice JSON dovrebbe essere qualcosa di simile "ServiceToken": {"Fn::GetAtt": ["Identity", "Arn"]}). Creo 2 nuove variabili, ognuna con 2 campi: Nome e Arn. Nel resto del mio modello posso usare !GetAtt [ClientBucketVar, Name]o !GetAtt [ClientBucketVar, Arn]quando ne ho bisogno.

Avvertenza

Quando lavori con risorse personalizzate, se la funzione lambda si arresta in modo anomalo, rimani bloccato tra 1 e 2 ore, perché CloudFormation attende una risposta dalla funzione (arrestata) per un'ora prima di arrendersi. Pertanto potrebbe essere utile specificare un timeout breve per lo stack durante lo sviluppo della funzione lambda.


Risposta fantastica! L'ho letto e eseguito nelle mie pile, anche se per me non mi preoccupo della proliferazione delle funzioni lambda nel mio account e mi piacciono i modelli che sono autonomi (modularizzo usando la cloudformation-toolgemma), quindi impacco la creazione lambda in il modello e quindi può usarlo direttamente invece di creare la Identityrisorsa personalizzata. Vedi qui per il mio codice: gist.github.com/guss77/2471e8789a644cac96992c4102936fb3
Guss

Quando sei "... sei bloccato tra 1 e 2 ore ..." perché un lambda si è bloccato e non ha risposto con una risposta cfn, puoi far muovere di nuovo il modello manualmente usando curl / wget su l'URL firmato. Assicurati di stampare sempre l'evento / URL all'inizio del lambda in modo da poter andare su CloudWatch e ottenere l'URL se si blocca.
Taylor,

12

Non ho una risposta, ma volevo sottolineare che puoi risparmiare molto dolore usando Fn::Subal posto diFn::Join

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

Sostituisce

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

3

No. L'ho provato, ma mi è venuto in mente vuoto. Il modo che aveva senso per me era creare una voce Mapping chiamata "CustomVariables" e avere quella casa con tutte le mie variabili. Funziona con stringhe semplici, ma non puoi usare Intrinsics (Refs, Fn :: Joins, ecc.) All'interno di Mapping .

Lavori:

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

Non funzionerà:

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

Questo è solo un esempio. Non inseriresti un riferimento autonomo in una variabile.


1
La documentazione afferma che i valori di mappatura devono essere stringhe letterali.
Ivan Anishchuk,

3

È possibile utilizzare uno stack nidificato che risolve tutte le variabili nei suoi output e quindi utilizzare Fn::GetAttper leggere gli output da quello stack


2

È possibile utilizzare modelli nidificati in cui "risolvere" tutte le variabili nel modello esterno e passarle a un altro modello.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.