CloudFormation 提供多種內建資源型別,但對於特定需求,我們需要自定義資源型別。私有擴充套件讓開發者得以擴充 CloudFormation 的功能,整合自定義資源或第三方服務。建立私有擴充套件涉及定義資源架構(包含屬性和行為)、實作資源處理程式(處理資源的生命週期操作)以及編寫測試案例。透過 AWS CLI 工具,我們可以編譯、測試、提交和佈署這些擴充套件。文章中提供的程式碼範例展示瞭如何使用 Python 實作資源處理程式,並使用 JSON 定義資源架構和測試資料。此外,文章也說明瞭如何使用巨集簡化範本撰寫,例如自動填入 AMI ID、新增額外資源以及讓資源宣告更易於理解。巨集的使用能提升範本的可讀性和可維護性,並減少程式碼重複。
使用私有擴充套件自訂 CloudFormation 資源型別
在前面的章節中,我們探討瞭如何使用 CloudFormation 來管理 AWS 資源。本章節將探討如何建立和使用私有擴充套件來擴充 CloudFormation 的功能,使其能夠支援自訂資源型別。
建立私有資源型別的必要性
CloudFormation 提供了豐富的資源型別來管理 AWS 資源,但有時我們需要管理自訂資源或第三方服務。這時,私有擴充套件就變得非常有用。私有擴充套件允許我們定義自訂資源型別,使其能夠與 CloudFormation 無縫整合。
建立私有資源型別的步驟
1. 定義資源架構
首先,我們需要定義自訂資源型別的架構。這個架構描述了資源的屬性和行為。以下是一個範例:
{
"typeName": "Org::Storage::Database",
"description": "A custom database resource",
"properties": {
"DatabaseName": {
"type": "string",
"description": "The name of the database"
},
"DatabaseUser": {
"type": "string",
"description": "The username for the database"
},
// ...
}
}
2. 實作資源處理程式
接下來,我們需要實作資源處理程式,以處理資源的建立、更新和刪除操作。以下是一個 Python 範例:
@resource.handler(Action.CREATE)
def create_handler(session, request, callback_context):
try:
model = request.desiredResourceState
# 建立資料函式庫的邏輯
progress = ProgressEvent(
status=OperationStatus.IN_PROGRESS,
resourceModel=model,
)
# ...
progress.status = OperationStatus.SUCCESS
return progress
except Exception as e:
return ProgressEvent.failed(
HandlerErrorCode.HandlerInternalFailure,
f'Failed to create resource. Reason: {e}')
@resource.handler(Action.DELETE)
def delete_handler(session, request, callback_context):
try:
model = request.desiredResourceState
# 刪除資料函式庫的邏輯
progress = ProgressEvent(
status=OperationStatus.IN_PROGRESS,
resourceModel=model,
)
# ...
progress.status = OperationStatus.SUCCESS
return progress
except Exception as e:
return ProgressEvent.failed(
HandlerErrorCode.HandlerInternalFailure,
f'Failed to delete resource. Reason: {e}')
3. 新增測試資料
為了執行合約測試,我們需要新增一些測試資料。以下是一個範例:
// inputs_1_create.json
{
"DatabaseName": "myDatabase",
"DatabaseUser": "MyDatabaseUser",
"DatabasePassword": "SuperUser12",
"RdsHost": "db_identifier.us-east-1.rds.amazonaws.com",
"RdsUser": "rdsuser",
"RdsPassword": "barfoo12344321"
}
測試和佈署私有資源型別
1. 編譯資源型別
執行以下命令來編譯資源型別:
$ cfn submit --dry-run
2. 啟動本地 Lambda 函式
執行以下命令來啟動本地 Lambda 函式:
$ sam local start-lambda
3. 執行測試
執行以下命令來執行測試:
$ cfn test
4. 提交資源型別
執行以下命令來提交資源型別:
$ cfn submit -v --region us-east-1
使用私有資源型別
建立一個新的 CloudFormation 範本,使用自訂資源型別:
// database.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Custom DB
Parameters:
# ...
Resources:
Database:
Type: Org::Storage::Database
Properties:
DatabaseName: !Ref DBName
DatabaseUser: !Ref DBUser
DatabasePassword: !Ref DBPassword
RdsHost: !ImportValue RdsEndpoint
RdsUser: !Ref RdsUser
RdsPassword: !Ref RdsPassword
執行以下命令來佈署範本:
$ aws cloudformation deploy \
--stack-name mydatabase \
--template-file database.yaml
清理資源
執行以下命令來清理資源:
$ aws cloudformation delete-stack \
--stack-name mydatabase
$ aws cloudformation delete-stack \
--stack-name rds
$ aws cloudformation deregister-type \
--type RESOURCE \
--type-name 'Org::Storage::Database'
測驗題
- 公有擴充套件和私有擴充套件之間的區別是什麼?
- 資源登入檔中有哪些擴充套件可用?
- 資源型別處理程式中需要執行哪些動作?
- 哪些命令列工具可用於註冊和登出私有擴充套件?
- 哪個命令根據架構生成資原始碼?
延伸閱讀
- 公有 AWS 社群擴充套件:https://github.com/aws-cloudformation/community-registry-extensions/tree/main
- 從 Cloudsoft 建立自訂資源型別的經驗:https://cloudsoft.io/blog/what-we-learned-from-building-60-third-party-resources-for-aws-cloudformation
程式碼解析
@resource.handler(Action.CREATE)
def create_handler(session, request, callback_context):
try:
model = request.desiredResourceState
# 建立資料函式庫的邏輯
progress = ProgressEvent(
status=OperationStatus.IN_PROGRESS,
resourceModel=model,
)
# ...
progress.status = OperationStatus.SUCCESS
return progress
except Exception as e:
return ProgressEvent.failed(
HandlerErrorCode.HandlerInternalFailure,
f'Failed to create resource. Reason: {e}')
此段程式碼定義了一個資源處理程式,用於處理資源的建立操作。它首先取得所需的資源狀態,然後執行建立資料函式庫的邏輯。最後,它傳回一個 ProgressEvent 物件,表示操作的結果。
圖表說明
此圖示展示了私有資源型別的建立和佈署流程。
graph LR
A[定義資源架構] --> B[實作資源處理程式]
B --> C[新增測試資料]
C --> D[編譯資源型別]
D --> E[啟動本地 Lambda 函式]
E --> F[執行測試]
F --> G[提交資源型別]
G --> H[使用私有資源型別]
H --> I[清理資源]
圖表翻譯: 此圖表展示了建立和佈署私有資源型別的步驟。首先,我們需要定義資源架構和實作資源處理程式。接下來,我們新增測試資料並編譯資源型別。然後,我們啟動本地 Lambda 函式並執行測試。測試透過後,我們提交資源型別並使用它。最後,我們清理資源。
使用巨集、巢狀堆積疊和模組擴充套件範本
在處理範本時,我們經常需要動態地為資源指定,或是在堆積疊操作過程中快速更改範本。同時,我們也希望為堆積疊建立模組化的結構,以提高可重用性。
本章將探討範本巨集的使用案例,並開發自己的巨集。接著,我們將簡要介紹巢狀堆積疊,並說明為何它們的使用頻率不如以前,以及模組如何成功取代它們。最後,我們將建立自己的模組,並使用 SAM CLI 佈署堆積疊。
本章將涵蓋以下主題:
- 瞭解範本巨集的使用案例
- 介紹範本巨集
- 編寫自己的巨集
- 瞭解巢狀堆積疊的歷史
- 建立和使用自己的 CloudFormation 模組
瞭解範本巨集的使用案例
在探討巨集的內部工作原理之前,我們需要了解可以使用它們解決哪些問題。讓我們來看看幾個案例和範例。
自動填充資源屬性值
假設我們有一個啟動範本,需要定義 Amazon Machine Image(AMI)ID。對於 Amazon Linux AMI,我們可以使用 AWS 的 Parameter Store:
Parameters:
ImageId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
Resources:
LaunchTemplate:
Type: 'AWS::EC2::LaunchTemplate'
Properties:
LaunchTemplateData:
ImageId: !Ref ImageId
但是,如果我們不使用 Amazon Linux,而是使用 Ubuntu,我們就必須手動指定 AMI ID 並使用對映:
Mappings:
RegionMap:
us-east-1: "ami-123"
us-west-1: "ami-456"
# ...
Resources:
Inst:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [RegionMap, !Ref AWS::Region]
使用巨集填寫AMI ID
或者,我們可以建立一個巨集來查詢 AMI ID,並在範本中使用它。以下範本是使用巨集填寫 AMI ID 的範例:
Resources:
LaunchTemplate:
Type: 'AWS::EC2::LaunchTemplate'
Properties:
LaunchTemplateData:
ImageId:
Fn::Transform:
Name: FindAmi
Parameters:
OSFamily: "ubuntu"
如前面的範例所示,巨集名稱在內建函式 Fn::Transform 之後提供,後面跟著其引數。在堆積疊操作過程中,巨集將儲存必要的 AMI ID。
新增額外的資源
假設我們有一些資源對於每個堆積疊都是相同的,但必須是每個堆積疊的一部分。例如,應用程式的 CloudWatch 規則和警示。我們可以複製並貼上相同的資源宣告,但這被視為程式碼重複,是不好的做法。
對於這種情況,有一個名為 AWS::Include 的 CloudFormation 巨集。AWS::Include 巨集會在 S3 上查詢範本,並將當前範本與額外的範本合併。AWS::Include 巨集不是全域巨集,因此必須在 Resources 區段中宣告,如下所示:
Resources:
# ... 一些資源...
'Fn::Transform':
Name: 'AWS::Include'
Parameters:
Location: 's3://mybucket/my_set_of_resources.yaml'
雖然這是一個常見的巨集,但我們應該避免使用重複的資源,遵循「不要重複自己」(DRY)模式,並正確組織我們的堆積疊。
使資源宣告更容易被開發人員理解
我們有一種標準的應用程式開發方式。假設每個應用程式都是在 Fargate 上執行的 Elastic Container Service(ECS)任務。它可能有也可能沒有額外的資源,並且它將始終在與其他應用程式相同的 ECS 叢集上執行。我們可以建立一個龐大的範本,包含條件元素,並要求開發人員提供許多引數。或者,我們可以使用巨集為開發人員建立一個簡單易用的範本。
以下範例範本顯示了這種標準應用程式的外觀:
Transform: StandardApplication
Resources:
Application:
Properties:
TaskCount: 1
Memory: 512
CPU: 256
RDS: "postgresql"
RDSSize: "db.t2.micro"
# ...
在轉換過程中,巨集將評估屬性並呈現新的範本,例如:
Resources:
EcsTaskDefinition:
Type: # ...
Properties:
Cpu: 256
Memory: 512
# ...
EcsService:
Type: #...
Properties:
DesiredCount: 1
# ...
# ...
開發人員將始終對其應用程式的基礎架構和資源擁有控制權,但不必深入瞭解 CloudFormation 和 AWS。
這一切聽起來不錯,但我們如何實作這一點?讓我們來看看範本巨集究竟是什麼。
內容解密:
在上述範例中,我們使用了 Fn::Transform 內建函式來呼叫巨集。巨集名稱和引數在 Fn::Transform 中指定。在堆積疊操作過程中,巨集將根據提供的引數執行相應的操作,並傳回結果。
探討範本巨集
範本巨集是一種強大的工具,允許我們在 CloudFormation 範本中執行自定義邏輯。透過使用巨集,我們可以簡化範本,提高可重用性,並使範本更易於維護。
巨集的工作原理
當 CloudFormation 處理範本時,它會遇到 Fn::Transform 內建函式。Fn::Transform 內建函式指定要呼叫的巨集名稱和引數。CloudFormation 會將範本的相關部分傳遞給巨集,巨集執行自定義邏輯並傳回結果。然後,CloudFormation 使用巨集傳回的結果繼續處理範本。
編寫自己的巨集
要編寫自己的巨集,我們需要建立一個 AWS Lambda 函式,該函式將執行自定義邏輯。Lambda 函式將接收範本的相關部分作為輸入,並傳回結果。
以下是一個簡單的巨集範例,該巨集根據提供的引數傳回 AMI ID:
import boto3
def lambda_handler(event, context):
# 取得引數
os_family = event['params']['OSFamily']
# 查詢 AMI ID
ec2 = boto3.client('ec2')
response = ec2.describe_images(
Filters=[
{'Name': 'name', 'Values': [f'amzn2-ami-hvm-*-{os_family}-gp2']},
{'Name': 'architecture', 'Values': ['x86_64']},
{'Name': 'state', 'Values': ['available']}
]
)
# 傳回 AMI ID
return {
'requestId': event['requestId'],
'status': 'success',
'fragment': response['Images'][0]['ImageId']
}
使用巨集
要在範本中使用巨集,我們需要在 Fn::Transform 內建函式中指定巨集名稱和引數。以下是一個使用上述巨集的範例:
Resources:
LaunchTemplate:
Type: 'AWS::EC2::LaunchTemplate'
Properties:
LaunchTemplateData:
ImageId:
Fn::Transform:
Name: FindAmi
Parameters:
OSFamily: "ubuntu"
在這個範例中,FindAmi 巨集將根據提供的 OSFamily 引數傳回 AMI ID。