在 AWS 雲端環境中,自動化組態管理對於維護系統一致性和可靠性至關重要。CloudFormation 的 cfn-init 工具提供了一個有效的方法,讓開發者能夠以程式化的方式定義和管理 EC2 例項的組態。透過 cfn-init,我們可以自動安裝軟體套件、設定檔案、啟動服務,並確保所有組態步驟都按照預期執行。此外,cfn-signal 的整合讓 CloudFormation 能夠準確掌握資源的佈署狀態,避免因資源尚未完全初始化而導致的錯誤。這對於需要長時間初始化的應用程式,例如 Web 服務或資料函式庫,尤其重要。結合 cfn-initcfn-signal,可以建立更穩固、可重複且易於管理的雲端基礎架構。

使用 cfn-init 進行 EC2 例項的組態管理

在現代化的雲端基礎架構中,自動化組態管理是確保系統一致性和可靠性的關鍵。本文將探討如何利用 AWS CloudFormation 的 cfn-init 工具來自動化組態和管理 EC2 例項。我們將透過具體範例,展示如何佈署一個簡單的 Flask Web 應用程式,以及如何建立一個完整的 LNMP(Linux, NGINX, MySQL, PHP)堆積疊。

佈署 Flask Web 應用程式

首先,我們將建立一個簡單的 Flask Web 應用程式,並使用 cfn-init 自動佈署到 EC2 例項上。

步驟 1:建立 CloudFormation 範本

Resources:
  MyEC2Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: !Ref ImageId
      InstanceType: t2.micro
      KeyName: !Ref KeyName
      UserData:
        'Fn::Base64': !Sub |
          #!/bin/bash -xe
          /opt/aws/bin/cfn-init -v \
            --stack ${AWS::StackName} \
            --resource MyEC2Instance \
            --configsets InstallAndRun \
            --region ${AWS::Region}

Metadata:
  'AWS::CloudFormation::Init':
    configSets:
      InstallAndRun:
        - "InstallFlask"
        - "ConfigureService"
    InstallFlask:
      packages:
        yum:
          python3: []
          python3-pip: []
      commands:
        install_flask:
          command: "pip3 install flask"
    ConfigureService:
      files:
        /opt/helloworld.py:
          content: !Sub |
            from flask import Flask
            app = Flask(__name__)

            @app.route("/")
            def hello():
                return "Hello, World, from AWS!"

            if __name__ == "__main__":
                app.run(host='0.0.0.0', port=80)
          mode: '755'
          owner: root
          group: root
        /etc/systemd/system/helloworld.service:
          content: !Sub |
            [Unit]
            Description=HelloWorld service
            After=network.target

            [Service]
            Type=simple
            User=root
            ExecStart=/opt/helloworld.py
            Restart=on-abort

            [Install]
            WantedBy=multi-user.target
          mode: '755'
          owner: root
          group: root
      commands:
        reload_systemd:
          command: "systemctl daemon-reload"
      services:
        sysvinit:
          helloworld:
            enabled: 'true'
            ensureRunning: 'true'

步驟 2:上傳應用程式原始碼到 S3

aws s3 cp hello-world-flask.py s3://hello-world-prep-sourcebucket-16l5b2vvpr5js

步驟 3:佈署 CloudFormation 堆積疊

aws cloudformation deploy \
  --stack-name helloworld \
  --template-file hello-world-app.yaml \
  --capabilities CAPABILITY_IAM

#### 內容解密:

  1. CloudFormation 範本解析:範本定義了一個 EC2 例項,並使用 cfn-init 在例項啟動時執行組態。
  2. cfn-init 組態集:定義了兩個組態步驟:InstallFlaskConfigureService。第一步安裝必要的軟體包,第二步組態並啟動 Flask 應用程式服務。
  3. 服務組態:使用 systemd 管理服務,確保 Flask 應用程式在例項啟動時自動執行。

建立 LNMP 堆積疊

接下來,我們將建立一個更複雜的 LNMP 堆積疊,展示如何使用 cfn-init 組態多個服務。

步驟 1:定義 EC2 例項和 cfn-init 組態

Resources:
  Lnmp:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: !Ref ImageId
      InstanceType: t2.micro
      KeyName: !Ref KeyName
      UserData:
        'Fn::Base64': !Sub |
          #!/bin/bash -xe
          /opt/aws/bin/cfn-init -v \
            --stack ${AWS::StackName} \
            --resource Lnmp \
            --configsets Configure \
            --region ${AWS::Region}

Metadata:
  'AWS::CloudFormation::Init':
    configSets:
      Configure:
        - "Mysql"
        - "DbSetup"
        - "Php"
        - "Nginx"
    # ... 其他組態省略

#### 內容解密:

  1. cfn-init 組態集:定義了一個名為 Configure 的組態集,包含四個組態步驟:MySQL 安裝、資料函式庫設定、PHP 組態和 NGINX 組態。
  2. 命令執行順序:在 commands 部分,使用數字字首確保命令按特定順序執行,因為 cfn-init 預設按字母順序執行命令。
  3. 服務管理:使用 services 部分確保必要的服務(如 MySQL 和 NGINX)在組態完成後啟動並執行。
LNMP 堆積疊架構圖
  graph LR
    A[Client] -->|HTTP Request| B[NGINX]
    B -->|Forward Request| C[PHP-FPM]
    C -->|Query| D[MySQL]
    D -->|Return Data| C
    C -->|Return Response| B
    B -->|HTTP Response| A

圖表翻譯: 此圖示展示了 LNMP 堆積疊的架構。客戶端傳送 HTTP 請求到 NGINX,NGINX 將請求轉發給 PHP-FPM 處理。PHP-FPM 可能需要查詢 MySQL 資料函式庫,將結果傳回給客戶端。整個流程展示了各元件之間的互動關係。

#### 內容解密:

  1. 架構概述:LNMP 堆積疊由 NGINX、PHP-FPM 和 MySQL 組成,共同處理客戶端請求。
  2. 請求處理流程:客戶端請求首先到達 NGINX,然後轉發給 PHP-FPM。PHP-FPM 可能需要與 MySQL 互動以取得資料,最終將處理結果傳回給客戶端。

使用cfn-signal通知CloudFormation資源就緒狀態

在前面的章節中,我們已經學習瞭如何使用cfn-init進行EC2例項的初始組態。現在,我們將進一步瞭解如何使用cfn-signal通知CloudFormation有關資源的就緒狀態。

為什麼需要cfn-signal?

CloudFormation通常會在資源建立後立即報告資源就緒狀態。然而,這種方法對於根據EC2例項的服務可能不適用。因為CloudFormation只會檢查EC2例項是否處於執行狀態,而不會等待例項上的應用程式完全啟動。

問題示例

假設我們有一個使用AutoScaling群組的Web應用程式。在這種情況下,如果例項需要時間來安裝軟體包和啟動服務,AutoScaling可能會因為例項未透過健康檢查而終止它。這樣會導致一個惡性迴圈:新的例項啟動,然後被終止,迴圈重複。

解決方案:使用cfn-signal

為瞭解決這個問題,AWS提供了一個名為cfn-signal的輔助指令碼。這個指令碼可以向CloudFormation傳送訊號,報告資源是否成功建立。

如何使用cfn-signal?

步驟1:新增CreationPolicy

首先,我們需要在EC2例項資源上新增CreationPolicy。這個策略定義了CloudFormation等待訊號的數量和超時時間。

// lnmp-signal.yaml
Lnmp:
  Type: AWS::EC2::Instance
  CreationPolicy:
    ResourceSignal:
      Count: 1
      Timeout: PT5M
  Properties:
    # ...
  Metadata:
    # ...

在這個例子中,Timeout被設定為5分鐘(PT5M),這意味著CloudFormation將等待最多5分鐘來接收訊號。

步驟2:在UserData中使用cfn-signal

接下來,我們需要在UserData中呼叫cfn-signal指令碼,以傳送訊號給CloudFormation。

// lnmp-signal.yaml
Lnmp:
  Type: AWS::EC2::Instance
  CreationPolicy:
    # ...
  Properties:
    ImageId: !Ref ImageId
    InstanceType: t2.micro
    KeyName: !Ref KeyName
    UserData:
      Fn::Base64:
        Fn::Sub: |
          #!/bin/bash -xe
          /opt/aws/bin/cfn-init -v \
            --stack ${AWS::StackName} \
            --resource Lnmp \
            --configsets Configure \
            --region ${AWS::Region}
          # 傳送訊號
          /opt/aws/bin/cfn-signal -e $? \
            --stack ${AWS::StackName} \
            --resource Lnmp \
            --region ${AWS::Region}

在這個指令碼中,cfn-signal會根據cfn-init的離開碼(exit code)傳送相應的訊號。如果cfn-init成功執行(離開碼為0),cfn-signal會傳送成功訊號;否則,它會傳送失敗訊號。

cfn-signal與AutoScaling群組的結合使用

cfn-signalCreationPolicy也可以與AutoScaling群組結合使用,以實作滾動更新。

MyAsg:
  Type: AWS::AutoScaling::AutoScalingGroup
  CreationPolicy:
    AutoScalingCreationPolicy:
      MinSuccessfulInstancesPercent: 50

在這個例子中,AutoScaling群組將等待至少一半的例項傳送成功訊號後,才會報告群組建立成功。

重點回顧

  1. cfn-signal的作用:向CloudFormation傳送訊號,報告資源是否成功建立。
  2. CreationPolicy的組態:定義CloudFormation等待訊號的數量和超時時間。
  3. 在UserData中使用cfn-signal:呼叫cfn-signal指令碼,以傳送訊號給CloudFormation。
  4. 與AutoScaling群組結合使用:實作滾動更新和提高佈署的可靠性。

思考題

  1. 如果組態集有多個組態項,它們的執行順序是什麼?
  2. 是否可以有多個組態集?
  3. cfn-init是否需要IAM策略來存取資源後設資料?
  4. 是否可以使用cfn-init建立目錄?
  5. 什麼是WaitCondition

進一步閱讀

內容解密:

本章節詳細介紹了cfn-signal的使用方法和重要性。透過結合cfn-initcfn-signal,我們可以實作更可靠的資源佈署和管理。同時,本章節也提供了與AutoScaling群組結合使用的示例,以實作滾動更新和提高佈署的可靠性。

圖表說明

  graph LR
    F[F]
    A[啟動EC2例項] --> B[執行cfn-init]
    B --> C[組態應用程式]
    C --> D[傳送cfn-signal]
    D --> E[CloudFormation接收訊號]
    E --> F{訊號是否成功?}
    F -->|是| G[報告資源建立成功]
    F -->|否| H[報告資源建立失敗]

圖表翻譯:

此圖示展示了使用cfn-signal通知CloudFormation資源就緒狀態的流程。首先,啟動EC2例項並執行cfn-init進行初始組態。然後,傳送cfn-signal給CloudFormation。CloudFormation接收訊號後,根據訊號的內容(成功或失敗)報告資源的建立狀態。

程式碼範例

Resources:
  MyInstance:
    Type: 'AWS::EC2::Instance'
    CreationPolicy:
      ResourceSignal:
        Count: 1
        Timeout: PT5M
    Properties:
      ImageId: !Ref ImageId
      InstanceType: t2.micro
      KeyName: !Ref KeyName
      UserData:
        Fn::Base64:
          Fn::Sub: |
            #!/bin/bash -xe
            /opt/aws/bin/cfn-init -v \
              --stack ${AWS::StackName} \
              --resource MyInstance \
              --configsets InstallAndConfigure \
              --region ${AWS::Region}
            /opt/aws/bin/cfn-signal -e $? \
              --stack ${AWS::StackName} \
              --resource MyInstance \
              --region ${AWS::Region}

內容解密:

此程式碼範例展示瞭如何在CloudFormation範本中使用CreationPolicycfn-signal。首先,定義了一個EC2例項資源,並設定了CreationPolicy以等待訊號。然後,在UserData中使用cfn-init進行初始組態,並使用cfn-signal傳送訊號給CloudFormation。這個範例確保了CloudFormation在資源完全就緒後才會報告資源建立成功。