参考
- cfn101-workshop
- aws cli cloudformation
cloudformation是aws的iac工具,以下简称cfn
环境搭建——cfn命令行工具
创建堆栈
aws cloudformation create-stack --stack-name testtemp \
--template-body file://testtemp.yaml
# --parameters ParameterKey=KeyPairName,ParameterValue=TestKey
删除堆栈
aws cloudformation delete-stack \
--stack-name testtemp
查看堆栈event
aws cloudformation describe-stack-events \
--stack-name \
--max-items 2
切换格式,json和yaml的转换
pip install cfn-flip
cfn-flip --version
cfn-flip example_parameter.json example_parameter.yaml
方便起见可以编写脚本将常用测试命令整合在一起,只需要修改参数即可快速重复测试模板
#!/bin/bash
WEBURL=$2
FILE_NAME=resources.yaml
BUCKET_NAME=zhaojiew-test
FILEPATH=cfn
BUCKET_FILEPATH=s3://$BUCKET_NAME/$FILEPATH/
STACK_NAME=aws
CHANGESET_NAME=awsset
ROLE_ARN=arn:aws-cn:iam::xxxxxxxx:role/MyCloudfotmationRole
echo STACK_NAME "====>" $STACK_NAME
echo FILE_NAME "====>" $FILE_NAME
echo ROLE_ARN "====>" $ROLE_ARN
echo BUCKET_NAME "====>" $BUCKET_NAME
echo FILEPATH "====>" $FILEPATH
case $1 in
"create")
echo " =================== create stack ===================";
aws cloudformation create-stack --stack-name $STACK_NAME\
--template-body file://$FILE_NAME \
--role-arn $ROLE_ARN \
--capabilities CAPABILITY_IAM
# --tags Key=who,Value=justtest \
# --parameters ParameterKey=InstanceType,ParameterValue=t2.small \
# ParameterKey=SubnetIDs,ParameterValue=SubnetID1\\,SubnetID2
;;
"urlcreate")
echo " =================== create stack from url ===================";
aws s3 cp $FILE_NAME $BUCKET_FILEPATH
TEMPURL=https://$BUCKET_NAME.s3.cn-north-1.amazonaws.com.cn/$FILEPATH/$FILE_NAME
echo $TEMPURL
aws cloudformation create-stack --stack-name $STACK_NAME\
--template-url $TEMPURL \
--role-arn $ROLE_ARN
# --tags Key=who,Value=justtest \
# --parameters ParameterKey=InstanceType,ParameterValue=t2.small \
# ParameterKey=SubnetIDs,ParameterValue=SubnetID1\\,SubnetID2
;;
"delete")
echo " =================== delete stack ==================="
aws cloudformation delete-stack --stack-name $STACK_NAME
;;
"createset")
echo " =================== create stackset ==================="
aws cloudformation create-change-set \
--stack-name $STACK_NAME \
--change-set-name $CHANGESET_NAME \
--template-body file://$FILE_NAME\
--capabilities CAPABILITY_IAM
;;
"event")
echo " =================== describe stack event ==================="
aws cloudformation describe-stack-events --stack-name aws --max-items 5 \
--query 'StackEvents[].{CStatus: ResourceStatus,Rreason: ResourceStatusReason}' --output table
;;
"download")
echo " =================== download external url ==================="
wget -nc $WEBURL -O $FILE_NAME
echo "file name is =====>>>>> " ${WEBURL##*/}
;;
"upload")
echo " =================== upload file to bucket ==================="
aws s3 cp $FILE_NAME $BUCKET_FILEPATH
echo https://$BUCKET_NAME.s3.cn-north-1.amazonaws.com.cn/$FILEPATH/$FILE_NAME
;;
"valid")
echo " =================== validate templaet ==================="
aws cloudformation validate-template --template-body file://$FILE_NAME
;;
*)
echo "Input Args Error..."
;;
esac
cfn基础——顶级字段
cfn顶级字段,只有resource是必须的,关于cloutformation的模板剖析
AWSTemplateFormatVersion: 'version date' (optional) # version of the CloudFormation template. Only accepted value is '2010-09-09'
Description: 'String' (optional) # a text description of the Cloudformation template
Metadata: 'template metadata' (optional) # objects that provide additional information about the template
Parameters: 'set of parameters' (optional) # a set of inputs used to customize the template
Rules: 'set of rules' (optional) # a set of rules to validate the parameters provided at deployment/update
Mappings: 'set of mappings' (optional) # a mapping of keys and associated values
Conditions: 'set of conditions' (optional) # conditions that control whether certain resources are created
Transform: 'set of transforms' (optional) # for serverless applications
Resources: 'set of resources' (required) # a components of your infrastructure
Hooks: 'set of hooks' (optional) # Used for ECS Blue/Green Deployments
Outputs: 'set of outputs' (optional) # values that are returned whenever you view your stack's properties
堆栈是cfn模板的部署,单个cfn模板可以创建多个stack
如果堆栈创建失败会回滚,删除所有已创建的资源。如果无法删除则会保留资源直到能够成功删除堆栈
创建堆栈——cfn 权限
通过iam权限控制cfn访问,控制台使用cfn需要比cli和api更多的额外权限,例如上传文件到s3(每个区域都有默认的cf模板的存储桶),列出下拉参数所需的desctibe*权限。可见如果通过cli创建堆栈,实际上并不会将文件上传到s3桶中。
cfn创建资源的api调用来自cfn的ip地址,因此不要使用aws:SourceIp
条件键进行限制
通过--role-arn
可以指定cfn可以使用的角色,cfn的所有堆栈操作都通过该角色完成。如果不指定,则使用之前关联过的角色。如果没有可用角色,则会从用户凭证生成临时会话
aws cloudformation create-stack --stack-name testtemp \
--role-arn arn:aws-cn:iam::xxxxxxx:role/MyCloudfotmationRole
--template-body file://testtemp.yaml
Amazon CloudFormation will use this role for all stack operations. Other users that have permissions to operate on this stack will be able to use this role, even if they don’t have permission to pass it. Ensure that this role grants least privilege.
这意味着如果其他用户有权访问堆栈,即使cfn的角色没有传递,其他用户也能通过cfn间接使用该角色
创建资源——简单创建s3桶
简单s3 bucket 模板
#testtemp.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Template and stack
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
VersioningConfiguration:
Status: Enabled
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
使用cli创建堆栈
aws cloudformation create-stack --stack-name temptest \
--template-body file://temptest.yaml
内置函数——具备profile的ec2实例
由于该行为会创建iam 角色,因此在cli命令和console中要进行确认 --capabilities CAPABILITY_IAM
在模板中使用了内置函数ref
引用其他资源,可以在output中验证
- 如果引用资源会返回物理id(对特定资源例如ec2实例会返回实例id)
- 如果是参数会返回参数值
policy策略有两种写法,内联和托管策略,以下为用户创建的托管策略
AWSTemplateFormatVersion: '2010-09-09'
Resources:
myEC2Instance:
Type: AWS::EC2::Instance
Version: '2009-05-15'
Properties:
ImageId: ami-0ab68f4313c5aff87
InstanceType: t2.micro
Monitoring: 'true'
DisableApiTermination: 'false'
IamInstanceProfile:
!Ref RootInstanceProfile
RootRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com.cn
Action:
- sts:AssumeRole
Path: "/"
RolePolicies:
Type: AWS::IAM::Policy
Properties:
PolicyName: s3readonly
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "s3:Get*"
- "s3:List*"
Resource: "*"
Roles:
- !Ref RootRole
RootInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: "/"
Roles:
- !Ref RootRole
Outputs:
InstanceId:
Description: Instance ID of the instance you create
Value: !Ref myEC2Instance
RoleID:
Description: role ID of the instance you create
Value: !Ref RootRole
伪参数——打包lambda获取ssm参数
使用伪参数获取堆栈id,区域和账号等信息,例如以下使用AWS::Partition
获取中国区分区为aws-cn
意味着可以动态适配不同账号和区域的环境进行堆栈部署。
创建lambada函数使用zip对inline function进行打包,大小不超过4MB
使用内置函数sub
对伪参数进行替换,sub可以分为有mapping和无mapping两种,对于伪参数的用法为无map。
在sub
函数中使用${paramater}
能间接获取ref
和Fn::GetAttr
等函数的调用结果
$(!parameter)
将原样解析不进行替换
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Pseudo parameters (uksb-1q9p31idr).
Parameters:
DatabaseUsername:
Description: Value to be used with the dbUsername SSM parameter. The default value is set to 'alice', which users can override when creating a CloudFormation stack.
Type: String
Default: alice
AllowedPattern: ^[a-z0-9]{5,12}$
S3BucketNamePrefix:
Description: The prefix to use for your S3 bucket
Type: String
Default: my-demo-bucket
AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$
ConstraintDescription: Bucket name prefix can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-).
MinLength: 3
Resources:
BasicParameter:
Type: AWS::SSM::Parameter
Properties:
Name: dbUsername
Type: String
Value: !Ref DatabaseUsername
Description: SSM Parameter for database username.
DemoRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: ssm-least-privilege
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: ssm:GetParameter
Resource: !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${BasicParameter}'
DemoLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.lambda_handler
Role: !GetAtt DemoRole.Arn
Runtime: python3.8
Code:
ZipFile: |
import boto3
client = boto3.client('ssm')
def lambda_handler(event, context):
response = client.get_parameter(Name='dbUsername')
print(f'SSM dbUsername parameter value: {response["Parameter"]["Value"]}')
DemoBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub '${S3BucketNamePrefix}-${AWS::Region}-${AWS::AccountId}'
映射——动态指定实例类型
map是个键值对,通过Fn::FindInMap
(简写为!FindInMap
)获取对应键的值
以下获取ec2实例的id实际是对应键EnvironmentType.Test.InstanceType
的值而已
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Mappings (uksb-1q9p31idr).
Parameters:
EnvironmentType:
Description: 'Specify the Environment type of the stack.'
Type: String
Default: Test
AllowedValues:
- Test
- Prod
ConstraintDescription: 'Specify either Test or Prod.'
Mappings:
EnvironmentToInstanceType:
Test:
InstanceType: t2.micro
Prod:
InstanceType: t2.small
Resources:
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0ab68f4313c5aff87
InstanceType: !FindInMap [EnvironmentToInstanceType, !Ref EnvironmentType, InstanceType]
输出——带eip的ec2实例
可以从cloudformation资源和属性参考中查找对应资源的返回值信息,可以查看ref
和getattr
分别能够获取的值
getattr
、ref
和sub
的区别在于
- ref返回特定参数(参数值)和资源(物理id/资源id/资源名称)的值
- getattr返回特定资源的属性值
- 能够通过
${}
简介使用以上两者
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Outputs (uksb-1q9p31idr).
Resources:
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-xxxxxx
InstanceType: t2.micro
WebServerEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
InstanceId: !Ref WebServerInstance
Outputs:
WebServerPublicDNS:
Description: Public DNS of EC2 instance
Value: !GetAtt WebServerInstance.PublicDnsName
WebServerElasticIP:
Description: Elastic IP assigned to EC2
Value: !Ref WebServerEIP
返回值——带策略s3桶和带安全组ec2实例
创建ec2实例时,可以指定从ssm参数中获取最新的ami
每个ami都拥有公开的ssm参数空间,如下
# 获取公共ami列表
aws ssm get-parameters-by-path --path "/aws/service/ami-amazon-linux-latest"
# 使用公共ssm参数查询ami
aws ssm get-parameters --names /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
模板如下
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Resource Return Values Lab (uksb-1q9p31idr).
Parameters:
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: Purpose
Value: AWS CloudFormation Workshop
S3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- s3:*
Effect: Deny
Resource:
- !GetAtt S3Bucket.Arn
- !Sub '${S3Bucket.Arn}/*'
Principal: '*'
Condition:
Bool:
aws:SecureTransport: false
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t2.micro
SecurityGroups:
- !Ref InstanceSecurityGroup
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow http to client host
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Outputs:
S3BucketDomainName:
Description: IPv4 DNS name of the bucket.
Value: !GetAtt S3Bucket.DomainName
InstanceID:
Description: The ID of the launched instance
Value: !Ref Ec2Instance
PublicIP:
Description: Public IP of the launched instance
Value: !GetAtt Ec2Instance.PublicIp
SecurityGroupId:
Description: ID of the security group created
Value: !GetAtt InstanceSecurityGroup.GroupId
开启ssm和使用userdata——定制ec2实例
开启ssm控制台登录功能的ec2实例
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Session manager (uksb-1q9p31idr).
Resources:
SSMIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws-cn:iam::aws:policy/AmazonSSMManagedInstanceCore
WebServerInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref SSMIAMRole
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref WebServerInstanceProfile
ImageId: ami-0ab68f4313c5aff87
InstanceType: t2.micro
Outputs:
WebServerPublicDNS:
Description: Public DNS of EC2 instance
Value: !GetAtt WebServerInstance.PublicDnsName
使用userdata,userdata需要通过内置函数base64
进行编码
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - User data (uksb-1q9p31idr).
Resources:
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref WebServerInstanceProfile
ImageId: ami-xxxxxx
InstanceType: t2.micro
UserData: !Base64 |
#!/bin/bash
yum update -y
yum install -y httpd php
systemctl start httpd
systemctl enable httpd
usermod -a -G apache ec2-user
chown -R ec2-user:apache /var/www
chmod 2775 /var/www
find /var/www -type d -exec chmod 2775 {} \;
find /var/www -type f -exec chmod 0664 {} \;
cat << 'EOF' > /var/www/html/index.php
<!DOCTYPE html>
<html>
<body>
<center>
<?php
# Get the instance ID from meta-data and store it in the $instance_id variable
$url = "http://169.254.169.254/latest/meta-data/instance-id";
$instance_id = file_get_contents($url);
# Get the instance's availability zone from metadata and store it in the $zone variable
$url = "http://169.254.169.254/latest/meta-data/placement/availability-zone";
$zone = file_get_contents($url);
?>
<h2>EC2 Instance ID: <?php echo $instance_id ?></h2>
<h2>Availability Zone: <?php echo $zone ?></h2>
</center>
</body>
</html>
EOF
helper脚本——监听cfn资源变动
helper脚本能够在原来的模板上对应用程序进行微调(不需要重新部署和创建堆栈资源),默认helper可以不需要凭证,请求会被限制在堆栈中的实例中
帮助脚本比较难理解一共有4个
- cfn-init,通过获取stack元数据对实例进行配置,包括安装包,创建文件和启动服务
- cfn-signal,当ec2创建和配置完毕向cfn发送信号。当结合creationpolicy和asg的WaitOnResourceSignals策略,会形成类似waitgroup的机制,暂停执行堆栈操作,收到指定数量的信号之后继续执行堆栈行为。因此signal程序可以发送信号给cfn
- cfn-get-metadata,获取cfn元数据,可以手动获取看看和cfn中的配置一致
sudo /opt/aws/bin/cfn-get-metadata --region cn-north-1 --stack aws -r WebServerInstance
{
"AWS::CloudFormation::Init": {
"config": {
"files": {
"/etc/cfn/cfn-hup.conf": {
"mode": "256",
"owner": "root",
"content": "[main]\nstack=arn:aws-cn:cloudformation:cn-north-1:xxxxxxx:stack/aws/aa931910-6ee0-11ed-9669-0e2f05fc8512\nregion=cn-north-1\ninterval=1\n",
"group": "root"
},
"/var/www/html/index.php": {
"mode": "420",
"owner": "apache",
"content": "<!DOCTYPE html>\n<html>\n<body>\n <center>\n <h2>EC2 Instance ID: <?php echo hello world ?></h2>\n </center>\n</body>\n</html>\n",
"group": "apache"
},
"/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
"content": "[cfn-auto-reloader-hook]\ntriggers=post.update\npath=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init\naction=/opt/aws/bin/cfn-init --stack aws --resource WebServerInstance --region cn-north-1\nrunas=root\n"
}
},
"services": {
"sysvinit": {
"cfn-hup": {
"files": [
"/etc/cfn/cfn-hup.conf",
"/etc/cfn/hooks.d/cfn-auto-reloader.conf"
],
"ensureRunning": "true",
"enabled": "true"
},
"httpd": {
"ensureRunning": "true",
"enabled": "true"
}
}
},
"packages": {
"yum": {
"php": [],
"httpd": []
}
}
}
}
}
- cfn-hup,后台进程检测cfn元数据的变更,配置文件为
cfn-hup.conf
,钩子文件在hooks.d
以下将模板精简之后可以看到在WebServerInstance
资源中主要分为Metadata
和Properties
两部分
-
Metadata
是对cfn资源元数据的设置,通过init对实例进行配置,分为packages, groups, users, sources, files, commands, services
,当调用/opt/aws/bin/cfn-init
时会执行全部配置 -
在实例内部安装
aws-cfn-bootstrap
,在al2类型实例上已经预装 -
cfn-hup
后台启动并读取配置文件,指定检查元数据间隔interval
为1分钟,定期调用钩子。可以检查cfn资源,cfn资源物理id和cfn元数据。action
指定检测到更改后执行的行为,实际上就是个shell脚本,下面的配置表明重新执行了cfn-init文件。此时如果在更改集中修改了ec2实例元数据中的php脚本则会触发更改刷新网页内容[cfn-auto-reloader-hook] triggers=post.update path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init action=/opt/aws/bin/cfn-init --stack ${AWS::StackName} --resource WebServerInstance --region runas=root
-
cfn-signal
使用cfn-init
的返回结果$?
进行调用,此时如果实例配置失败则堆栈会回滚 -
创建
WebServerInstance
资源使用了CreationPolicy
策略,此时只有ec2实例使用cfn-signal
发送信号之后,才会完成ec2实例资源的创建。指定需要收到信号数量为1,超时时间为10分钟
(https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/aws-attribute-creationpolicy.html)资源策略WebServerInstance: CreationPolicy: ResourceSignal: Count: 1 Timeout: PT10M
-
cfn-signal
可以和资源策略相结合,但是只有部分资源支持CreationPolicy。同样指定UpdatePolicy配置WaitOnResourceSignals
可以在更新asg时触发信号等待,确保asg中的每个实例都已经配置完成
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Helper scripts (uksb-1q9p31idr).
Resources:
WebServerInstance:
CreationPolicy:
ResourceSignal:
Count: 1
Timeout: PT10M
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
httpd: []
php: []
files:
/var/www/html/index.php:
content: |
<!DOCTYPE html>
<html>
<body>
<center>
<h2>EC2 Instance ID: <?php echo helloworld ?></h2>
</center>
</body>
</html>
mode: 000644
owner: apache
group: apache
/etc/cfn/cfn-hup.conf:
content: !Sub |
[main]
stack=${AWS::StackId}
region=${AWS::Region}
interval=1
mode: 000400
owner: root
group: root
/etc/cfn/hooks.d/cfn-auto-reloader.conf:
content: !Sub |
[cfn-auto-reloader-hook]
triggers=post.update
path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init
action=/opt/aws/bin/cfn-init --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region}
runas=root
services:
sysvinit:
httpd:
enabled: true
ensureRunning: true
cfn-hup:
enabled: true
ensureRunning: true
files:
- /etc/cfn/cfn-hup.conf
- /etc/cfn/hooks.d/cfn-auto-reloader.conf
Properties:
ImageId: ami-02c8191b43515df27
KeyName: "temp-key"
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: "0"
InstanceType: t2.micro
UserData: !Base64
Fn::Sub: |
#!/bin/bash -xe
# Update aws-cfn-bootstrap to the latest
yum install -y aws-cfn-bootstrap
# Call cfn-init script to install files and packages
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region}
# Call cfn-signal script to send a signal with exit code
/opt/aws/bin/cfn-signal --exit-code $? --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region}
条件判断——有条件地创建ec2的挂载卷
根据参数不同指定资源的创建与否,还可以根据条件指定属性和输出值
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Conditions at resource level (uksb-1q9p31idr).
Parameters:
EnvType:
Description: Specify the Environment type of the stack.
Type: String
AllowedValues:
- test
- prod
Default: test
ConstraintDescription: Specify either test or prod.
Conditions:
IsProduction: !Equals
- !Ref EnvType
- prod
Resources:
EC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: !If [IsProduction, t2.small, t2.micro]
MountPoint:
Type: AWS::EC2::VolumeAttachment
Properties:
InstanceId: !Ref EC2Instance
VolumeId: !Ref Volume
Device: /dev/sdh
Condition: IsProduction
Volume:
Type: AWS::EC2::Volume
Properties:
Size: 2
AvailabilityZone: !GetAtt EC2Instance.AvailabilityZone
Encrypted: true
Condition: IsProduction
Outputs:
VolumeId:
Value: !Ref Volume
Condition: IsProduction
资源依赖——指定资源创建的现后顺序
在cfn中资源的依赖可以分为
- 显式依赖,使用
dependon
参数 - 隐式依赖,使用
ref
和getattr
参数
在没有依赖的情况下,cfn创建资源的方式时并行的。下面的sg隐式依赖ingressrule,sns显式依赖s3桶
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Resource Dependencies Lab with Ref and Fn::GetAtt Intrinsic Functions (uksb-1q9p31idr).
Resources:
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Example Security Group
SecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !GetAtt SecurityGroup.GroupId
IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
S3Bucket:
Type: AWS::S3::Bucket
Properties:
SNSTopic:
Type: AWS::SNS::Topic
DependsOn: S3Bucket
Properties:
动态引用——设置lamda获取secret和parameter
提前在ssm中创建参数
aws ssm put-parameter \
--name "/golden-images/amazon-linux-2" \
--value YOUR_AMI_ID \
--type "String" \
--region YOUR_REGION
在cfn中动态引用参数,固定写法
Resources:
Instance:
Type: AWS::EC2::Instance
Properties:
AvailabilityZone: !Select ["0", !GetAZs ""]
InstanceType: t2.micro
ImageId: '{{resolve:ssm:/golden-images/amazon-linux-2}}'
DatabaseConnParams:
Type: AWS::SecretsManager::Secret
Properties:
Description: Database Connection Parameters.
Name: DatabaseConnParams
SecretString: !Sub |
{
"RDS_HOSTNAME": "${Database.Endpoint.Address}",
"RDS_PORT": "${Database.Endpoint.Port}",
"RDS_USERNAME": "${DBUsername}",
"RDS_PASSWORD": "${DBPassword}"
}
FunctionExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Version: "2012-10-17"
ManagedPolicyArns:
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Path: /
HelloWorldFunction:
Type: AWS::Lambda::Function
Properties:
Role: !GetAtt FunctionExecutionRole.Arn
Handler: index.handler
Environment:
Variables:
RDS_HOSTNAME: '{{resolve:secretsmanager:DatabaseConnParams:SecretString:RDS_HOSTNAME}}'
RDS_PORT: '{{resolve:secretsmanager:DatabaseConnParams:SecretString:RDS_PORT}}'
Runtime: python3.7
Code:
ZipFile: |
import os
def handler(event, context):
RDS_HOSTNAME=os.getenv('RDS_HOSTNAME')
RDS_PORT=os.getenv('RDS_PORT')
return "Database: {}:{}".format(RDS_HOSTNAME,RDS_PORT)
嵌套堆栈——将模板化整为零
所谓嵌套堆栈就是将原本cfn模板中的资源单独分离出来,便于复用和灵活组合。使用时只需要在root template中进行引用即可。
例如创建一个root堆栈,引用vpc,ec2和iam的堆栈
对于需要引用的堆栈资源需要使用AWS::CloudFormation::Stack
类型
以下模板创建stack资源,并指定了模板所在的s3 url和初始参数。stack资源之间相互引用output,通过Fn: : GetAtt
将值从子堆栈传递到根堆栈
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Nested stacks - Root template (uksb-1q9p31idr).
Parameters:
S3BucketName:
Type: String
AvailabilityZones:
Type: List<AWS::EC2::AvailabilityZone::Name>
VPCName:
Type: String
Default: cfn-workshop-vpc
VPCCidr:
Type: String
Default: 10.0.0.0/16
PublicSubnet1Cidr:
Type: String
Default: 10.0.0.0/24
PublicSubnet2Cidr:
Type: String
Default: 10.0.1.0/24
Resources:
VpcStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub https://${S3BucketName}.s3.amazonaws.com.cn/vpc.yaml
TimeoutInMinutes: 20
Parameters:
AvailabilityZones: !Join
- ','
- !Ref AvailabilityZones
VPCCidr: !Ref VPCCidr
VPCName: !Ref VPCName
PublicSubnet1Cidr: !Ref PublicSubnet1Cidr
PublicSubnet2Cidr: !Ref PublicSubnet2Cidr
IamStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub https://${S3BucketName}.s3.amazonaws.com.cn/iam.yaml
TimeoutInMinutes: 10
EC2Stack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub https://${S3BucketName}.s3.amazonaws.com.cn/ec2.yaml
TimeoutInMinutes: 20
Parameters:
EnvironmentType: Dev
VpcId: !GetAtt VpcStack.Outputs.VpcId
SubnetId: !GetAtt VpcStack.Outputs.PublicSubnet1
WebServerInstanceProfile: !GetAtt IamStack.Outputs.WebServerInstanceProfile
Outputs:
WebsiteURL:
Value: !GetAtt EC2Stack.Outputs.WebsiteURL
vpc/ec2和iam的堆栈模板如同往常一样,由于在参数中使用ref进行了引用,实际上形成了隐式依赖关系
创建嵌套堆栈需要开启权限CAPABILITY_AUTO_EXPAND
If you want to create a stack from a stack template that contains macros and nested stacks, you must create the stack directly from the template using this capability.
开启nested视图后可以看到标记
删除嵌套堆需要删除root堆栈,否则会导致不一致
Deleting this stack will delete all stack resources. Resources will be deleted according to their DeletionPolicy.
It is recommended to delete through the root stack
Deleting a nested stack may result in an unstable state where the nested stack is out-of-sync with its root stack.
分层堆栈——堆栈间互通有无
嵌套堆栈的问题在于无法创建一对多关系,因为所有资源都在root中进行维护,本质上其实还是一个堆栈。分层堆栈就是创建多个堆栈,并实现跨堆栈的资源引用。
创建iam堆栈,并export instance profile
AWSTemplateFormatVersion: "2010-09-09"
Resources:
SSMIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
WebServerInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref SSMIAMRole
Outputs:
WebServerInstanceProfile:
Value: !Ref WebServerInstanceProfile
Export:
Name: cfn-workshop-WebServerInstanceProfile
导出的值可以在控制台看到
创建ec2并引用iam堆栈的instance profile
AWSTemplateFormatVersion: "2010-09-09"
Resources:
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
SubnetId: sub-xxxx
IamInstanceProfile: !ImportValue cfn-workshop-WebServerInstanceProfile
ImageId: ami-xxxx
InstanceType: t2.micro
打包和部署
我们在之前部署过lambda资源以及嵌套堆栈。lambda使用zip的方式将code打包,嵌套堆栈则是提前将模板上传到s3中进行引用,如果这样的组件很多工作量会非常大。
使用cfn打包功能可以简化步骤
package-and-deploy
├── infrastructure.template
└── lambda/
├── lambda_function.py
└── requirements.txt
查看程序
cat lambda_function.py
from datetime import datetime
from pytz import timezone, utc
def handler(event, context):
payload = event["time_zone"]
message = "Current date/time in TimeZone *{}* is: {}".format(
payload, _timezone(payload)
)
return {"message": message}
def _timezone(time_zone):
utc_now = utc.localize(datetime.utcnow())
compare_to_utc = utc_now.astimezone(timezone(time_zone))
return compare_to_utc.strftime("%Y-%m-%d %H:%M")
cat requirements.txt
pytz==2021.3
模板如下
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Package and deploy (uksb-1q9p31idr).
Resources:
LambdaBasicExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
PythonFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: cfn-workshop-python-function
Description: Python Function to return specific TimeZone time
Runtime: python3.8
Role: !GetAtt LambdaBasicExecutionRole.Arn
Handler: lambda_function.handler
Code: lambda/
对模板打包
aws cloudformation package \
- -template-file ./infrastructure.template \
--s3-bucket "${BUCKET_NAME}" \
--s3-prefix "${FILEPATH}" \
--output-template-file ./infrastructure-packaged.template
打包后的模板,bucketname中的prefix/9497159249ecec70572a11a73f15e601文件实际上就是lambda/
目录的压缩包
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation workshop - Package and deploy (uksb-1q9p31idr).
Resources:
LambdaBasicExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
PythonFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: cfn-workshop-python-function
Description: Python Function to return specific TimeZone time
Runtime: python3.8
Role:
Fn::GetAtt:
- LambdaBasicExecutionRole
- Arn
Handler: lambda_function.handler
Code:
S3Bucket: buckname
S3Key: prefix/9497159249ecec70572a11a73f15e601
Outputs:
LambdaFunction:
Description: AWS Lambda Python Function
Value:
Ref: PythonFunction
更新堆栈
堆栈的更新行为有以下几种
- Update with No Interruption,
- Updates with Some Interruption,
- Replacement
AWS资源类型参考中介绍了每个属性的更新行为
使用更改集更新堆栈能够预览堆栈的变动并确认修改