CloudFormation 範本是 AWS 基礎設施即程式碼的核心,其正確性直接影響佈署結果。除了 AWS 提供的範本驗證功能外,使用靜態檢查工具能更有效地找出潛在問題,例如資源屬性缺失、語法錯誤或不符合最佳實務的設定。cfn-lint 提供了豐富的內建規則,並支援自定義規則和區域特定檢查,可更精準地控制檢查範圍。cloudformation-guard 則以根據策略的方式簡化規則的撰寫和維護,提升檢查效率。兩種工具各有優勢,開發者可根據專案需求選擇合適的工具,或結合使用以達到最佳效果。在 CI/CD 流程中整合靜態檢查,更能確保範本的品質,減少佈署錯誤並提升自動化程度。

驗證、靜態檢查與佈署堆積疊

範本驗證的重要性

雖然範本驗證是堆積疊佈署前的必要步驟,但許多問題仍可能導致佈署失敗,即使驗證成功也不例外。常見問題包括:

  • 缺少必要的資源屬性
  • 資源屬性名稱的語法錯誤或拼寫錯誤
  • 資源屬性值不存在

執行範本驗證的指令非常簡單:

$ aws cloudformation validate-template --template-body file://path_to_your_template

例如,若要驗證核心範本,我們需要執行以下指令:

$ aws cloudformation validate-template --template-body file://core.yaml

如果範本中有錯誤,我們將看到類別似以下的輸出(在此範例中,我將PublicSubnet1資源型別改為完全不存在的型別——即AWS::EC2::DoesNotExist):

An error occurred (ValidationError) when calling the ValidateTemplate operation: Template format error: Unrecognized resource types: [AWS::EC2::DoesNotExist]

若我們在內建函式中模擬拼寫錯誤,將會得到另一個錯誤(在此範例中,我將Fn::Select內建函式改為不存在的Fn::NoSelect內建函式):

An error occurred (ValidationError) when calling the ValidateTemplate operation: Template format error: YAML not well-formed. (line 31, column 18)

然而,如果範本有效,我們將看到範本的引數、描述以及IAM許可權及其原因,這表明需要這些許可權的資源:

{
  "Parameters": [
    {
      "ParameterKey": "VpcCidr",
      "NoEcho": false
    },
    {
      "ParameterKey": "Environment",
      "NoEcho": false
    }
  ],
  "Description": "Core template. Contains network and IAM roles",
  "Capabilities": [
    "CAPABILITY_IAM"
  ],
  "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::Role]"
}

驗證器總是在堆積疊佈署前執行,但為了節省時間並盡早捕捉所有錯誤,您應該總是考慮在佈署堆積疊前執行驗證,並將其作為CI/CD流程中的一個步驟(我們將在下一章節中介紹)。

使用靜態檢查工具最佳化範本

靜態檢查工具(linter)是一種用於靜態原始碼分析的工具,用於標記明顯的程式錯誤和臭蟲,以及風格和非慣用問題。靜態檢查工具通常根據一組預定義的規則和風格進行操作。CloudFormation有兩款可用的靜態檢查工具:cfn-lint,一款由Python編寫的前一代靜態檢查工具,仍在維護中;以及cloudformation-guard,一款由Rust編寫的根據策略的靜態檢查工具。在本章節中,我們將探討這兩款工具,並找出哪一款最適合我們的應用場景。

使用cfn-lint評估範本

cfn-lint是一款命令列工具,能夠檢查您的範本,並將宣告的資源與大量編寫的規則進行對比。 與範本驗證器不同,cfn-lint能夠檢測出更複雜的問題,例如缺少資源屬性或內建函式中的引數。 CloudFormation的靜態檢查工具是一款外部工具,可以使用pip安裝:

$ pip install cfn-lint

它將自動出現在$PATH中,因此我們可以立即執行:

$ cfn-lint core.yaml

如果範本符合所有規則要求,我們將看不到任何輸出(並且命令的傳回碼將為0)。然而,如果存在錯誤,靜態檢查工具將立即通知我們。

例子:使用cfn-lint檢查範本錯誤

在此範例中,我註解掉了VpcId屬性,並刪除了Fn::Cidr函式中的遮罩位元。PublicSubnet2資源如下所示:

PublicSubnet2:
  Type: "AWS::EC2::Subnet"
  Properties:
    # 缺少遮罩位元引數!!!
    # 正確格式應為:
    # !Select [1, !Cidr [!Ref VpcCidr, 12, 8]]
    CidrBlock: !Select [1, !Cidr [!Ref VpcCidr, 12]]
    # VpcId: !Ref Vpc # 缺少屬性!
    MapPublicIpOnLaunch: True
    AvailabilityZone: !Select
      - 1
      - Fn::GetAZs: !Ref "AWS::Region"
    Tags:
      - Key: "Name"
        Value: !Sub "${Environment}-public-subnet02"
      - Key: "Env"
        Value: !Ref "Environment"

執行靜態檢查工具後的輸出如下:

$ cfn-lint core_broken.yaml
E0002 Unknown exception while processing rule E1024: list index out of range
core.yaml:1:1
E3003 Property VpcId missing at Resources/PublicSubnet2/Properties
core.yaml:45:5

傳回碼將為1或2(這些是Linux系統中常見的錯誤傳回碼)。

重要注意事項

  • 您可能已經注意到,cfn-lint也會顯示它發現錯誤的行號。然而,當您修改內建函式的引數時,您將看到錯誤出現在第一行,這並不是實際的錯誤位置。
  • 錯誤並不是靜態檢查工具直接指出的實際錯誤,而是一個Python例外。

自訂靜態檢查規則

cfn-lint不僅能夠根據內建規則檢查範本,還支援自訂和調整。

針對特定區域進行靜態檢查

當您執行cfn-lint時,它將根據預設區域(即us-east-1)檢查您的範本。若要檢查其他區域,您可以手動指定:

$ cfn-lint core.yaml --regions 'eu-west-1'
# 或...
$ cfn-lint core.yaml --regions 'eu-west-1','us-east-1'
# 或甚至...
$ cfn-lint core.yaml --regions 'ALL_REGIONS'

在前面的範例中,您將獲得更為有趣的結果:

E3001 Invalid or unsupported Type AWS::SSM::Parameter for resource MiddlewareAsgMaxSizeParameter in us-gov-west-1
core.yaml:393:5
E3001 Invalid or unsupported Type AWS::SSM::Parameter for resource MiddlewareAsgMaxSizeParameter in us-gov-east-1
core.yaml:393:5
E3001 Invalid or unsupported Type AWS::SSM::Parameter for resource MiddlewareAsgMaxSizeParameter in eu-north-1
core.yaml:393:5
E3001 Invalid or unsupported Type AWS::SSM::Parameter for resource MiddlewareAsgMaxSizeParameter in ap-east-1
core.yaml:393:5

出現此錯誤是因為所提到的區域不支援此資源型別。如果您計劃將範本佈署到多個區域,建議包含所有或部分割槽域進行檢查,以確保您想要提供的服務和資源在CloudFormation中得到支援。

在範本中新增區域檢查

另一種方式是在範本的後設資料中新增區域檢查:

// core.yaml
Metadata:
  cfn-lint:
    regions:
      - us-east-1
      - eu-west-1
      - eu-central-1

然後,cfn-lint將在您執行檢查時自動選取這些區域。

重要注意事項

請注意,範本後設資料中的值總是會被命令列引數覆寫。

圖表翻譯:靜態檢查流程圖

  graph LR
    A[開始靜態檢查] --> B{檢查範本語法}
    B -->|正確| C[檢查資源屬性]
    B -->|錯誤| D[傳回錯誤資訊]
    C -->|正確| E[檢查區域支援]
    C -->|錯誤| F[傳回錯誤資訊]
    E -->|正確| G[完成檢查]
    E -->|錯誤| H[傳回錯誤資訊]

圖表翻譯:

此圖示展示了靜態檢查的主要流程,包括語法檢查、資源屬性檢查和區域支援檢查。每一步驟都可能傳回錯誤資訊或完成檢查。

使用 Linter 提升 CloudFormation 範本最佳實踐

在開發和佈署 CloudFormation 範本時,保持範本的正確性和最佳實踐至關重要。本章節將探討如何使用 cfn-lint 進行範本檢查,以及如何自定義規則來滿足特定需求。同時,我們還會介紹 cloudformation-guard,一個根據策略的 Linter 工具。

瞭解 cfn-lint 與其重要性

cfn-lint 是一個用於檢查 CloudFormation 範本語法正確性和最佳實踐的工具。它能夠幫助開發者識別範本中的錯誤和潛在問題,從而確保範本的品質和可靠性。

為什麼使用 cfn-lint?

  1. 範本驗證cfn-lint 能夠驗證範本的語法是否正確,確保範本可以成功佈署。
  2. 最佳實踐檢查:除了語法檢查,cfn-lint 還能檢查範本是否遵循 AWS 推薦的最佳實踐。
  3. 自定義規則:開發者可以根據組織需求自定義檢查規則,進一步提升範本品質。

忽略特定規則

在某些情況下,開發者可能需要忽略某些 cfn-lint 規則。例如,當使用某些無法被 Linter 解析的宏(Macros)時,就需要忽略相關的檢查警告。

如何忽略特定規則?

  1. 使用 --ignore-checks 引數:執行 cfn-lint 時,可以透過 --ignore-checks 引數指定要忽略的規則。
    cfn-lint core.yaml --ignore-checks 'W2001'
    
  2. 在範本後設資料中指定:也可以在範本的後設資料(Metadata)中指定要忽略的規則。
    Metadata:
      cfn-lint:
        regions:
          - us-east-1
          - eu-west-1
          - eu-central-1
        ignore-checks:
          - W2001
    

建立自定義規則

除了內建規則,cfn-lint 還支援開發者建立自定義規則,以滿足特定的需求。

開發自定義規則步驟

  1. 確定規則需求:首先,明確自定義規則的目的和檢查內容。例如,檢查 RDS 資源是否具有正確的 DeletionPolicy
  2. 建立 Python 類別:自定義規則需要寫成 Python 類別,並且繼承自 CloudFormationLintRule
  3. 實作 match 方法:在 match 方法中實作具體的檢查邏輯。

示例:檢查 RDS 資源的 DeletionPolicy

# rdsdeletionpolicy.py
from cfnlint import CloudFormationLintRule, RuleMatch

class RdsDeletionPolicy(CloudFormationLintRule):
    id = 'W9001'
    shortdesc = '檢查 RDS Deletion Policy'
    description = '檢查 RDS 資源的 DeletionPolicy 是否為 Snapshot 或 Retain'
    RDS_RESOURCES = [
        'AWS::RDS::DBInstance',
        'AWS::RDS::DBCluster'
    ]

    def match(self, cfn):
        matches = []
        resources = cfn.get_resources(self.RDS_RESOURCES)
        for resource_name, resource in resources.items():
            deletion_policy = resource.get('DeletionPolicy')
            path = ['Resources', resource_name]
            if deletion_policy is None:
                message = f'資源 {resource_name} 沒有 Deletion Policy!'
                matches.append(RuleMatch(path, message))
            elif deletion_policy not in ('Snapshot', 'Retain'):
                message = f'資源 {resource_name} 的 Deletion Policy 不是 Snapshot 或 Retain!'
                matches.append(RuleMatch(path, message))
        return matches

執行自定義規則檢查

cfn-lint database_failing.yaml -a custom_rules

使用根據策略的 Linter:cloudformation-guard

cloudformation-guard 是另一個 Linter 工具,它根據策略(Policy-based)進行範本檢查。這使得規則的編寫和維護更加簡單。

為什麼選擇 cloudformation-guard?

  1. 簡潔的規則cloudformation-guard 的規則通常比 cfn-lint 的自定義規則更簡潔。
  2. 易於維護:根據策略的方法使得規則的維護更加容易。
重要注意事項
  1. 自定義規則的命名:確保自定義規則的類別名與檔名一致,這是 cfn-lint 正確處理自定義規則的前提。

  2. 規則的測試:在實際使用自定義規則之前,進行充分的測試以確保規則的正確性和有效性。

  3. 整合更多工具:探索將 cfn-lintcloudformation-guard 與其他 DevOps 工具整合的可能性,進一步提升範本開發和佈署的自動化水平。

  4. 規則分享:鼓勵在團隊或社群中分享自定義規則,促進最佳實踐的傳播和採用。

參考資源

進一步閱讀