CloudFormation 巨集是提升 IaC 效率的利器,它讓開發者能以程式化方式處理範本,減少重複程式碼並提升可維護性。本文除了介紹巨集的基本運作方式與請求回應結構外,更以自動填入 EC2 AMI ID 的巨集開發為例,逐步說明如何撰寫 Lambda 函式、佈署巨集,並在 CloudFormation 範本中使用。同時,文章也點出巨集的限制,例如區域支援、範本驗證以及不支援迭代呼叫等,提醒開發者注意。更進一步,文章示範瞭如何開發一個名為 StandardApplication 的巨集,它能根據簡化的輸入引數自動生成 ECS、RDS 和 ELB 等複雜資源的 CloudFormation 定義,大幅簡化範本撰寫的複雜度,讓開發者更專注於應用程式邏輯。

使用巨集(Macros)擴充套件 CloudFormation 範本的實務應用與開發

AWS CloudFormation 為基礎設施即程式碼(IaC)提供了強大的支援,而巨集(Macros)則進一步擴充套件了其功能,使得範本更具彈性與可重用性。本文將探討 CloudFormation 巨集的運作原理、開發自定義巨集的步驟,以及在實際場景中的應用。

瞭解 CloudFormation 巨集的基本原理

CloudFormation 巨集是一種 Lambda 函式,能夠在範本處理期間對範本片段進行自定義處理。它接收 CloudFormation 發出的事件,執行特定的邏輯,並傳回處理後的範本內容。這種機制使得範本能夠動態生成,解決了範本中重複或動態內容的問題。

巨集的請求與回應結構

當 CloudFormation 呼叫巨集時,會傳送一個包含範本片段、引數等資訊的事件給 Lambda 函式。該事件的結構如下:

{
  "region": "...",
  "accountId": "...",
  "fragment": {...},
  "transformId": "...",
  "params": {...},
  "requestId": "...",
  "templateParameterValues": {...}
}

其中,fragment 是待處理的範本片段,params 是呼叫巨集時傳遞的引數。

Lambda 函式處理完畢後,需要傳回一個包含處理結果的回應:

{
  "requestId": "...",
  "status": "...",
  "fragment": {...}
}

開發自定義巨集:AMI ID 自動填充範例

在實際應用中,我們經常需要為 EC2 例項指定 AMI ID。然而,AMI ID 會隨著時間更新,直接寫死在範本中並不理想。本文將展示如何開發一個自動填充 AMI ID 的巨集。

步驟 1:建立 Lambda 函式

首先,我們需要建立一個 Lambda 函式來處理巨集邏輯。以下是一個 Python 示例:

# amifinder.py
import boto3

image_names = {
    'amazonlinux': 'al2023-ami-2023.1.20230809.0-kernel-6.1-x86_64',
    'ubuntu': 'ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20230516',
    'rhel': 'RHEL-9.2.0_HVM-20230503-x86_64-41-Hourly2-GP2',
    'sles': 'suse-sles-15-sp5-v20230620-hvm-ssd-x86_64'
}

def get_image(img_name):
    client = boto3.client('ec2')
    resp = client.describe_images(Filters=[{'Name': 'name', 'Values': [img_name]}])
    return resp['Images'][0]['ImageId']

def lambda_handler(event, context):
    response = {}
    response['requestId'] = event['requestId']
    response['fragment'] = {'ImageId': ''}
    response['status'] = 'SUCCESS'
    osfamily = event['params']['OSFamily']
    if osfamily not in image_names.keys():
        response['status'] = 'FAILURE'
        return response
    image_id = get_image(image_names[osfamily])
    response['fragment']['ImageId'] = image_id
    return response

步驟 2:佈署巨集

接下來,我們需要將 Lambda 函式註冊為 CloudFormation 巨集。這可以透過建立一個包含 AWS::CloudFormation::Macro 資源的 CloudFormation 範本來完成。

# macro.yaml
Resources:
  AMIFillerMacro:
    Type: 'AWS::CloudFormation::Macro'
    Properties:
      Name: 'AMIFiller'
      FunctionName: !GetAtt AMIFillerLambda.Arn

使用 AWS CLI 佈署該範本:

$ aws cloudformation deploy --stack-name amimacro --template-file macro.yaml --capabilities CAPABILITY_IAM

步驟 3:測試巨集

建立一個簡單的 CloudFormation 範本來測試我們的巨集:

# lt.yaml
Resources:
  Lt:
    Type: "AWS::EC2::LaunchTemplate"
    Properties:
      LaunchTemplateData:
        Fn::Transform:
          Name: AMIFiller
          Parameters:
            OSFamily: "ubuntu"

佈署該範本:

$ aws cloudformation deploy --stack-name lt --template-file lt.yaml

檢查 CloudFormation 控制檯,可以看到 Fn::Transform 已被正確替換為對應的 AMI ID。

巨集的限制與注意事項

雖然巨集提供了強大的範本擴充套件能力,但也有一些限制需要注意:

  • 巨集僅在支援 Lambda 的區域可用
  • 處理後的範本必須透過驗證檢查
  • 巨集不支援迭代呼叫(即巨集中呼叫另一個巨集)
  • 巨集不支援 Fn::ImportValue
  • 巨集不適用於 StackSets
內容解密:

上述範例展示瞭如何使用 CloudFormation 巨集自動填充 EC2 例項的 AMI ID。這種方法避免了手動查詢和更新 AMI ID 的麻煩,提高了範本的可維護性。

  1. Lambda 函式設計:

    • image_names 字典儲存了不同作業系統的 AMI 名字串。
    • get_image 函式透過呼叫 EC2 的 describe_images API 根據名字串查詢對應的 AMI ID。
    • lambda_handler 是 Lambda 的入口函式,負責處理 CloudFormation 發出的事件,呼叫 get_image 取得 AMI ID,並建構回應傳回給 CloudFormation。
  2. 巨集的佈署與測試:

    • 將 Lambda 函式註冊為 CloudFormation 巨集。
    • 透過簡單的 CloudFormation 範本測試巨集的功能,驗證其是否能正確替換 Fn::Transform 為 AMI ID。
  3. 注意事項:

    • 使用巨集時需要注意其限制,如區域支援、範本驗證等。
    • 設計巨集時應考慮錯誤處理和日誌記錄,以便於除錯和維護。

透過這種方式,開發者可以根據具體需求開發各種自定義巨集,進一步擴充套件 CloudFormation 的功能,提升 IaC 管理的效率和靈活性。

圖表翻譯:

此圖表展示了 CloudFormation 巨集的運作流程,包括 Lambda 函式的呼叫和範本的處理過程。

內容解密:

此圖表說明瞭 CloudFormation 巨集如何與 Lambda 函式互動,實作範本的動態處理和更新。

  1. 事件發出: CloudFormation 發出包含範本片段的事件給指定的 Lambda 函式。
  2. 範本處理: Lambda 函式根據事件中的引數和範本片段執行自定義邏輯,生成處理後的範本內容。
  3. 結果傳回: Lambda 將處理後的範本內容傳回給 CloudFormation。
  4. 範本更新: CloudFormation 使用 Lambda 傳回的處理結果更新原始範本,完成巨集的呼叫。

透過這種機制,巨集能夠在範本處理階段動態生成所需的組態內容,增強了範本的靈活性和可重用性。

隨著雲端運算技術的不斷進步,CloudFormation 巨集的功能和應用場景也將進一步擴充套件。未來,我們可以期待以下幾個方面的發展:

  1. 更豐富的巨集功能: AWS 可能會提供更多內建的巨集功能,以滿足不同使用者的需求。
  2. 更好的除錯工具: 為了方便開發者除錯巨集,AWS 可能會提供更完善的除錯工具和日誌記錄功能。
  3. 更廣泛的整合: 巨集可能會與其他 AWS 服務進一步整合,提供更強大的功能。

開發者應持續關注 AWS 的最新動態,並根據業務需求不斷最佳化自定義巨集的實作,以充分利用 CloudFormation 提供的靈活性和擴充套件性。

使用巨集(Macro)擴充套件 CloudFormation 範本的深度解析

在現代雲端基礎設施的開發與管理中,AWS CloudFormation 扮演著至關重要的角色。透過 CloudFormation,開發者能夠以程式碼的形式定義和管理基礎設施,從而實作基礎設施即程式碼(Infrastructure as Code, IaC)的理念。然而,隨著應用程式的複雜度增加,單純的 CloudFormation 範本可能變得冗長且難以維護。為瞭解決這一問題,AWS 提供了巨集(Macro)的功能,允許開發者自定義範本處理邏輯,從而簡化範本的編寫與管理。

本文將探討如何利用 AWS CloudFormation 的巨集功能來簡化範本編寫,並透過一個實際的範例來展示如何建立一個自定義的巨集,以自動生成複雜的 CloudFormation 資源定義。

為何需要自定義巨集?

在開發複雜的雲端應用時,開發者往往需要定義大量的 CloudFormation 資源,如 ECS 任務定義、RDS 資料函式庫例項、ELB 負載平衡器等。這些資源的定義可能涉及多個屬性與組態項,導致範本檔案變得冗長且難以維護。透過自定義巨集,開發者可以將這些複雜的資源定義抽象化,簡化範本內容,提高可讀性與可維護性。

建立自定義巨集:StandardApplication

本範例中,我們將建立一個名為 StandardApplication 的巨集,用於根據簡化的輸入引數自動生成完整的 CloudFormation 範本。該巨集將根據輸入的屬性自動決定需要建立的資源型別,並生成相應的資源定義。

巨集的輸入範本

首先,我們定義一個簡化的 CloudFormation 範本,該範本使用 StandardApplication 巨集進行轉換:

# app.yaml
Transform: StandardApplication
Resources:
  Application:
    Properties:
      ApplicationImage: "your-docker-image-url"
      ApplicationPort: 8080
      TaskCount: 2
      Memory: 512
      CPU: 256
      RDS: "your-rds-instance-type"
      RDSSize: "db.t2.micro"
      RDSMultiAz: True
      NeedsBalancer: True
      PubliclyAvailable: True

巨集的實作邏輯

接下來,我們需要實作 StandardApplication 巨集的邏輯。該巨集將在 AWS Lambda 函式中執行,負責解析輸入範本的屬性,並根據這些屬性生成相應的 CloudFormation 資源定義。

# standard_app.py
def render_ecs(input_props):
    ecs_definition = {
        'Type': 'AWS::ECS::TaskDefinition',
        'Properties': {
            'Family': input_props['ApplicationImage'].split(':')[0],
            'Cpu': input_props['CPU'],
            'Memory': input_props['Memory'],
            'NetworkMode': 'awsvpc',
            'RequiresCompatibilities': ['FARGATE'],
            'ExecutionRoleArn': 'arn:aws:iam::123456789012:role/ecsTaskExecutionRole',
            'ContainerDefinitions': [{
                'Name': input_props['ApplicationImage'].split(':')[0],
                'Image': input_props['ApplicationImage'],
                'PortMappings': [{
                    'ContainerPort': input_props['ApplicationPort']
                }]
            }]
        }
    }
    return ecs_definition

def render_rds(input_props):
    if not input_props.get('RDS'):
        return None
    
    rds_definition = {
        'Type': 'AWS::RDS::DBInstance',
        'Properties': {
            'DBInstanceClass': input_props.get('RDSSize', 'db.t2.micro'),
            'Engine': input_props['RDS'],
            'MultiAZ': input_props.get('RDSMultiAz', False)
        }
    }
    return rds_definition

def render_elb(input_props):
    if not input_props.get('NeedsBalancer'):
        return None
    
    elb_definition = {
        'Type': 'AWS::ElasticLoadBalancingV2::LoadBalancer',
        'Properties': {
            'Scheme': 'internet-facing' if input_props.get('PubliclyAvailable') else 'internal',
            'Type': 'application'
        }
    }
    return elb_definition

def lambda_handler(event, context):
    required_properties = ['ApplicationImage', 'ApplicationPort', 'TaskCount', 'Memory', 'CPU']
    input_props = event['fragment']['Resources']['Application']['Properties']
    
    for prop in required_properties:
        if prop not in input_props:
            return {'requestId': event['requestId'], 'status': 'FAILED'}
    
    resources = {
        'ECSTaskDefinition': render_ecs(input_props),
        'RDSInstance': render_rds(input_props),
        'ELB': render_elb(input_props)
    }
    
    # 過濾掉未定義的資源
    resources = {k: v for k, v in resources.items() if v is not None}
    
    return {'requestId': event['requestId'], 'status': 'SUCCESS', 'fragment': resources}

#### 內容解密:

上述程式碼定義了三個主要函式:render_ecsrender_rdsrender_elb,分別負責生成 ECS 任務定義、RDS 資料函式庫例項和 ELB 負載平衡器的 CloudFormation 資源定義。

  1. render_ecs 函式:根據輸入屬性生成 ECS 任務定義,包括任務的 CPU、記憶體、容器映像等組態。
  2. render_rds 函式:根據輸入屬性決定是否建立 RDS 資料函式庫例項,並生成相應的資源定義。
  3. render_elb 函式:根據輸入屬性決定是否建立 ELB 負載平衡器,並生成相應的資源定義。
  4. lambda_handler 函式:作為 Lambda 函式的入口,負責解析輸入事件,呼叫上述函式生成資源定義,並傳回處理結果。

圖表翻譯:

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title CloudFormation 巨集實務應用開發

package "Terraform 工作流程" {
    actor "DevOps" as dev

    package "本地開發" {
        component [.tf 設定檔] as tf
        component [terraform.tfvars] as vars
        component [.tfstate] as state
    }

    package "Terraform Core" {
        component [Init] as init
        component [Plan] as plan
        component [Apply] as apply
        component [Destroy] as destroy
    }

    package "Providers" {
        cloud "AWS" as aws
        cloud "GCP" as gcp
        cloud "Azure" as azure
    }

    database "Remote State" as remote
}

dev --> tf : 編寫配置
tf --> init : 初始化
init --> plan : 規劃變更
plan --> apply : 執行變更
apply --> aws : 建立資源
apply --> gcp : 建立資源
apply --> azure : 建立資源
apply --> state : 更新狀態
state --> remote : 同步狀態

@enduml

圖表翻譯: 此圖示展示了 StandardApplication 巨集的處理流程。輸入範本經過巨集處理後,根據輸入屬性決定需要建立的資源型別,並生成相應的 CloudFormation 資源定義,最終傳回完整的 CloudFormation 範本。