AWS CloudFormation - Variabel khusus dalam template

18

Apakah ada cara untuk mendefinisikan pintasan untuk nilai yang sering digunakan yang berasal dari parameter template CloudFormation?

Sebagai contoh - Saya punya skrip yang membuat tumpukan Proyek Multi-AZ dengan nama ELB projectdan dua contoh di belakang ELB yang dipanggil project-1dan project-2. Saya hanya meneruskan ELBHostNameparameter ke template dan kemudian menggunakannya untuk membangun:

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

Konstruksi ini atau sangat mirip diulang berkali-kali di seluruh templat - untuk membuat nama host EC2, catatan Route53, dll.

Alih-alih mengulangi itu berulang-ulang saya ingin menetapkan output itu Fn::Joinke variabel semacam dan hanya merujuk itu, sama seperti saya bisa dengan "Ref":pernyataan.

Idealnya seperti:

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

atau sesuatu yang serupa sederhana.

Apakah itu mungkin dengan Amazon CloudFormation?

MLu
sumber
Apakah ELBHostName penuh dengan parameter yang Anda berikan secara eksplisit ke Cloudformation? Jika demikian, mengapa menggunakan Ref? Mungkin bisa menggunakan Kumis untuk memasukkan variabel dalam templat Anda dan mengubahnya menjadi JSON sebelum mengirimnya ke Cloudformation. Tergantung seperti apa proses penyediaan Anda.
Canuteson

Jawaban:

5

Saya mencari fungsi yang sama. Menggunakan tumpukan bersarang seperti yang disarankan SpoonMeiser muncul di benak, tetapi kemudian saya menyadari bahwa apa yang sebenarnya saya butuhkan adalah fungsi khusus. Untungnya CloudFormation memungkinkan penggunaan AWS :: CloudFormation :: CustomResource yang, dengan sedikit kerja, memungkinkan seseorang untuk melakukan hal itu. Ini terasa seperti berlebihan untuk variabel-variabel yang adil (sesuatu yang saya berpendapat bahwa seharusnya ada di CloudFormation di tempat pertama), tetapi itu menyelesaikan pekerjaan, dan, di samping itu, memungkinkan untuk semua fleksibilitas (pilihlah Anda python / node /Jawa). Perlu dicatat bahwa fungsi lambda membutuhkan biaya, tetapi kami berbicara tentang uang di sini kecuali jika Anda membuat / menghapus tumpukan Anda beberapa kali per jam.

Langkah pertama adalah membuat fungsi lambda pada halaman ini yang tidak melakukan apa-apa selain mengambil nilai input dan menyalinnya ke output. Kita bisa meminta fungsi lambda melakukan semua hal gila, tetapi begitu kita memiliki fungsi identitas, hal lain mudah. Atau kita bisa membuat fungsi lambda dibuat di stack itu sendiri. Karena saya menggunakan banyak tumpukan dalam 1 akun, saya akan memiliki banyak fungsi dan peran lambda yang tersisa (dan semua tumpukan harus dibuat dengan --capabilities=CAPABILITY_IAM, karena juga membutuhkan peran.

Buat fungsi lambda

  • Buka beranda lambda , dan pilih wilayah favorit Anda
  • Pilih "Fungsi Kosong" sebagai templat
  • Klik "Selanjutnya" (jangan konfigurasikan pemicu apa pun)
  • Mengisi:
    • Nama: CloudFormationIdentity
    • Deskripsi: Mengembalikan apa yang didapatnya, dukungan variabel dalam Formasi Cloud
    • Runtime: python2.7
    • Jenis Entri Kode: Edit Inline Kode
    • Kode: lihat di bawah
    • Handler: index.handler
    • Peran: Buat Peran Kustom. Pada titik ini sembulan terbuka yang memungkinkan Anda untuk membuat peran baru. Terima semua yang ada di halaman ini dan klik "Izinkan". Ini akan membuat peran dengan izin untuk memposting ke log cloudwatch.
    • Memori: 128 (ini adalah minimum)
    • Batas waktu: 3 detik (harus banyak)
    • VPC: Tidak ada VPC

Kemudian salin dan tempel kode di bawah ini di bidang kode. Bagian atas fungsi adalah kode dari modul python cfn-response , yang hanya diinstal secara otomatis jika fungsi lambda dibuat melalui CloudFormation, untuk beberapa alasan aneh. The handlerfungsi cukup jelas.

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")
  • Klik "Selanjutnya"
  • Klik "Buat Fungsi"

Anda sekarang dapat menguji fungsi lambda dengan memilih tombol "Test", dan pilih "CloudFormation Create Request" sebagai templat contoh. Anda harus melihat dalam log Anda bahwa variabel yang dimasukkan ke dalamnya, dikembalikan.

Gunakan variabel dalam templat CloudFormation Anda

Sekarang kita memiliki fungsi lambda ini, kita dapat menggunakannya dalam templat CloudFormation. Pertama-tama catat fungsi lambda Arn (buka halaman lambda , klik fungsi yang baru saja dibuat, Arn harus berada di kanan atas, kira-kira seperti arn:aws:lambda:region:12345:function:CloudFormationIdentity).

Sekarang di templat Anda, di bagian sumber daya, tentukan variabel Anda seperti:

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

Pertama saya tentukan Identityvariabel yang berisi Arn untuk fungsi lambda. Menempatkan ini dalam variabel di sini, berarti saya hanya perlu menentukannya sekali. Saya membuat semua variabel tipe saya Custom::Variable. CloudFormation memungkinkan Anda menggunakan nama jenis apa pun yang dimulai dengan Custom::sumber daya khusus.

Perhatikan bahwa Identityvariabel berisi Arn untuk fungsi lambda dua kali. Sekali untuk menentukan fungsi lambda untuk digunakan. Kedua kalinya sebagai nilai variabel.

Sekarang saya memiliki Identityvariabel, saya dapat mendefinisikan variabel baru menggunakan ServiceToken: !GetAtt [Identity, Arn](saya pikir kode JSON harus seperti "ServiceToken": {"Fn::GetAtt": ["Identity", "Arn"]}). Saya membuat 2 variabel baru, masing-masing dengan 2 bidang: Nama dan Arn. Di sisa template saya, saya dapat menggunakan !GetAtt [ClientBucketVar, Name]atau !GetAtt [ClientBucketVar, Arn]kapan pun saya membutuhkannya.

Kata hati-hati

Saat bekerja dengan sumber daya khusus, jika fungsi lambda macet, Anda mandek antara 1 dan 2 jam, karena CloudFormation menunggu balasan dari fungsi (macet) selama satu jam sebelum menyerah. Oleh karena itu mungkin baik untuk menentukan batas waktu singkat untuk tumpukan sambil mengembangkan fungsi lambda Anda.

Claude
sumber
Jawaban yang luar biasa! Saya membacanya dan menjalankannya di tumpukan saya, meskipun bagi saya, saya tidak khawatir tentang proliferasi fungsi lambda di akun saya dan saya suka templat yang berdiri sendiri (saya modulasi menggunakan cloudformation-toolpermata), jadi saya kemas kreasi lambda ke dalam templat dan kemudian dapat menggunakannya secara langsung alih-alih membuat Identitysumber daya khusus. Lihat di sini untuk kode saya: gist.github.com/guss77/2471e8789a644cac96992c4102936fb3
Guss
Ketika Anda "... Anda macet antara 1 dan 2 jam ..." karena lambda crash dan tidak membalas kembali dengan cfn-respons, Anda bisa mendapatkan templat bergerak lagi dengan secara manual menggunakan curl / wget on URL yang ditandatangani. Pastikan untuk selalu mencetak acara / URL di awal lambda sehingga Anda dapat pergi ke CloudWatch dan mendapatkan URL jika itu menggantung.
Taylor
12

Saya tidak punya jawaban, tetapi ingin menunjukkan bahwa Anda dapat menyelamatkan diri dari banyak rasa sakit dengan menggunakannya Fn::Subsebagai gantinyaFn::Join

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

Menggantikan

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

Tidak. Saya mencobanya, tetapi kosong. Cara yang masuk akal bagi saya adalah membuat entri pemetaan yang disebut "CustomVariables" dan memiliki semua variabel saya di rumah itu. Ini berfungsi untuk Strings sederhana, tetapi Anda tidak dapat menggunakan Intrinsics (Refs, Fn :: Joins, etc.) di dalam Mappings .

Bekerja:

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

Tidak akan bekerja:

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

Itu hanya sebuah contoh. Anda tidak akan menempatkan Ref mandiri dalam Variabel.

rampok
sumber
1
Dokumentasi mengatakan bahwa nilai pemetaan harus berupa string literal.
Ivan Anishchuk
3

Anda bisa menggunakan stack bersarang yang menyelesaikan semua variabel Anda di output itu, dan kemudian gunakan Fn::GetAttuntuk membaca output dari tumpukan itu

SpoonMeiser
sumber
2

Anda mungkin menggunakan templat bersarang tempat Anda "menyelesaikan" semua variabel di templat luar dan meneruskannya ke templat lain.

JoseOlcese
sumber