更新 CloudFormation 堆栈
注意
本教程基于 在 Amazon EC2 上部署应用程序 教程中的概念生成。如果您尚未完成该教程,我们建议您先完成该教程,以了解使用 CloudFormation 进行的 EC2 引导的过程。
本主题演示了正在运行的堆栈的简单更新进展。我们将带您逐步了解以下步骤:
-
创建起始堆栈:使用基础 Amazon Linux 2 AMI 创建堆栈,从而使用 CloudFormation 帮助程序脚本安装 Apache Web Server 和简单的 PHP 应用程序。
-
更新应用程序:使用 CloudFormation 更新应用程序中的一个文件并部署软件。
-
添加密钥对:向实例中添加 Amazon EC2 密钥对,然后更新安全组以允许 SSH 访问实例。
-
更新实例类型:更改底层 Amazon EC2 实例的实例类型。
-
更新 AMI:在您的堆栈中更改 Amazon EC2 实例的亚马逊机器映像(AMI)。
注意
CloudFormation 使用免费,但您需要为自己创建的 Amazon EC2 资源付费。如果您不熟悉 Amazon,可以利用免费套餐
第 1 步:创建起始堆栈
我们将从创建可在本主题剩下的所有内容中使用的堆栈开始。我们已提供了一个简单的模板来启动在 Apache Web Server 中托管并在 Amazon Linux 2 AMI 上运行的简单实例 PHP Web 应用程序。
Apache Web Server、PHP 和简单的 PHP 应用程序全部都由默认安装在 Amazon Linux 2 AMI 上的 CloudFormation 帮助程序脚本进行安装。以下模板代码段显示描述待安装软件包和文件的元数据,此情况下为 Amazon Linux 2 AMI 的 Yum 存储库中的 Apache Web Server 和 PHP 基础设施。代码段还显示 Services 部分,以确保 Apache Web Server 处于运行状态。
WebServerInstance: Type: AWS::EC2::Instance Metadata: AWS::CloudFormation::Init: config: packages: yum: httpd: [] php: [] files: /var/www/html/index.php: content: | <?php echo '<h1>Hello World!</h1>'; ?> mode: '000644' owner: apache group: apache services: systemd: httpd: enabled: true ensureRunning: true
应用程序本身是“Hello World”示例,在模板中进行了完整定义。对于现实工作中的应用程序,文件可存储在 Amazon S3、GitHub 或另一个存储库中,并通过模板进行参考。CloudFormation 可下载软件包(如 RPM 或 RubyGem),并能引用单个的文件以及展开 .zip 和 .tar 文件,以在 Amazon EC2 实例上创建应用程序项目。
模板用于启用和配置 cfn-hup 进程守护程序以侦听 Amazon EC2 实例元数据中所定义配置的更改。您可以使用 cfn-hup 进程守护程序更新应用程序软件,如 Apache 或 PHP 的版本,也可通过 CloudFormation 更新 PHP 应用程序文件。来自该模板中同一 Amazon EC2 资源的以下代码片段展示了配置 cfn-hup 所需的必要部分,即每两分钟调用 cfn-init 一次以通知并应用元数据的更新。否则,cfn-init 只能在启动时运行一次。
files: /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} # The interval used to check for changes to the resource metadata in minutes. Default is 15 interval=2 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 -s ${AWS::StackId} -r WebServerInstance --region ${AWS::Region} runas=root services: systemd: cfn-hup: enabled: true ensureRunning: true files: - /etc/cfn/cfn-hup.conf - /etc/cfn/hooks.d/cfn-auto-reloader.conf
为了完成堆栈,在 Amazon EC2 实例定义中的 Properties 部分,UserData 属性包含调用 cfn-init 以安装软件包和文件的 cloud-init 脚本。有关更多信息,请参阅《Amazon CloudFormation 模板参考指南》中的 CloudFormation 帮助程序脚本参考。该模板还创建了一个 Amazon EC2 安全组。
AWSTemplateFormatVersion: 2010-09-09 Parameters: LatestAmiId: Description: The latest Amazon Linux 2 AMI from the Parameter Store Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' InstanceType: Description: WebServer EC2 instance type Type: String Default: t3.micro AllowedValues: - t3.nano - t3.micro - t3.small - t3.medium - t3a.nano - t3a.micro - t3a.small - t3a.medium - m5.large - m5.xlarge - m5.2xlarge - m5a.large - m5a.xlarge - m5a.2xlarge - c5.large - c5.xlarge - c5.2xlarge - r5.large - r5.xlarge - r5.2xlarge - r5a.large - r5a.xlarge - r5a.2xlarge ConstraintDescription: must be a valid EC2 instance type. Resources: WebServerInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref LatestAmiId InstanceType: !Ref InstanceType SecurityGroupIds: - !Ref WebServerSecurityGroup UserData: Fn::Base64: !Sub | #!/bin/bash -xe # Get the latest CloudFormation package yum update -y aws-cfn-bootstrap # Run cfn-init /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region} || error_exit 'Failed to run cfn-init' # Start up the cfn-hup daemon to listen for changes to the EC2 instance metadata /opt/aws/bin/cfn-hup || error_exit 'Failed to start cfn-hup' # Signal success or failure /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region} Metadata: AWS::CloudFormation::Init: config: packages: yum: httpd: [] php: [] files: /var/www/html/index.php: content: | <?php echo "<h1>Hello World!</h1>"; ?> mode: '000644' owner: apache group: apache /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} # The interval used to check for changes to the resource metadata in minutes. Default is 15 interval=2 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 -s ${AWS::StackId} -r WebServerInstance --region ${AWS::Region} runas=root services: systemd: 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 CreationPolicy: ResourceSignal: Timeout: PT5M WebServerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Enable HTTP access via port 80 SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 Outputs: WebsiteURL: Value: !Sub 'http://${WebServerInstance.PublicDnsName}' Description: URL of the web application
要使用此模板启动堆栈
-
复制该模板并将其作为文本文件本地保存到您的系统中。注意保存位置,因为您需要在后续步骤中使用此文件。
-
登录到 Amazon Web Services 管理控制台 并打开 Amazon CloudFormation 控制台 https://console.aws.amazon.com/cloudformation
。 -
依次选择创建堆栈、使用新资源(标准)。
-
选择选择现有模板。
-
在指定模板下,选择上传模板文件,浏览到您在第一步中创建的文件,然后选择下一步。
-
在指定堆栈详细信息页面上,输入
UpdateTutorial作为堆栈名称。 -
在参数下,保持所有参数不变,然后选择两次下一步。
-
在审核并创建屏幕上,选择提交。
当您的堆栈状态变成 CREATE_COMPLETE 后,输出选项卡会显示网站的 URL。如果您选择 WebsiteURL 的输出值,您将看到新 PHP 应用程序在工作。
第 2 步:更新应用程序
现在您已部署好堆栈,接下来更新应用程序吧。我们将对应用程序所打印出的文本进行简单更改。要执行此操作,我们将添加回显命令至 index.php 文件,如此模板代码段所示:
files: /var/www/html/index.php: content: | <?php echo "<h1>Hello World!</h1>";echo "<p>This is an updated version of our application.</p>";?> mode: '000644' owner: apache group: apache
使用文本编辑器手动编辑您保存在本地的模板文件。
现在,更新堆栈。
要使用更新后的模板更新堆栈
-
在 CloudFormation 控制台中,选择您的
UpdateTutorial堆栈。 -
选择更新、直接更新。
-
选择替换现有模板。
-
在指定模板下,选择上传模板文件,上传修改的模板文件模板,然后选择下一步。
-
在指定堆栈详细信息页面上,保持所有参数相同,然后选择两次下一步。
-
在审核页面上,审核您的更改。在更改下,您应该会看到 CloudFormation 将更新
WebServerInstance资源。 -
选择提交。
当您的堆栈处于 UPDATE_COMPLETE 状态时,您可以再次选择 WebsiteURL 输出值以验证应用程序的更改已生效。默认状态下,cfn-hup 进程守护程序每 2 分钟运行一次,因此最多能花 2 分钟在堆栈更新后更改应用程序。
要查看已更新资源集,请转至 CloudFormation 控制台。在事件选项卡上,查看堆栈事件。在此特别示例中,Amazon EC2 实例 WebServerInstance 的元数据已更新,这导致 CloudFormation 也会重新评估其他资源(WebServerSecurityGroup)以确保没有其他更改。其他堆栈资源都未修改。CloudFormation 将只更新堆栈中受堆栈的任何更改影响的资源。此类更改可直接进行,如属性或元数据更改,也可由依赖项或 Ref、GetAtt 中的数据流或其他内部模板函数导致。有关更多信息,请参阅内置函数参考。
这一简单更新对此过程进行了阐述。但是,您可以对您的 Amazon EC2 实例中所部署文件和软件包进行更复杂的更改。例如,您可能会确定需要将 MySQL 与 MySQL 的 PHP 支持一起添加到实例中。要执行此操作,只需要将附加软件包和文件与任何附加服务一起添加到配置中,然后更新堆栈以部署更改。
packages: yum: httpd: [] php: []mysql: []php-mysql: []mysql-server: []mysql-libs: []...services: systemd: httpd: enabled: true ensureRunning: true cfn-hup: enabled: true ensureRunning: true files: - /etc/cfn/cfn-hup.conf - /etc/cfn/hooks.d/cfn-auto-reloader.confmysqld:enabled: trueensureRunning: true
您可以更新 CloudFormation 元数据,将应用程序所使用的软件包更新到新版本。前述示例中,每个软件包的版本属性都为空,这表示 cfn-init 应安装最新版的软件包。
packages: yum: httpd: [] php: []
您可以视需要指定软件包的版本字符串。如果您在后续更新堆栈调用中更改版本字符串,则会部署新版软件包。此处显示 RubyGems 软件包版本号的使用示例。支持版本化的任何软件包都可以有特定版本。
packages: rubygems: mysql: [] rubygems-update: - "1.6.2" rake: - "0.8.7" rails: - "2.3.11"
第 3 步:添加使用密钥对的 SSH 访问权限
您还可以更新模板中的资源,以添加原先未在模板中指定的属性。为了阐明上述操作,我们将会添加 Amazon EC2 密钥对到现有 EC2 实例中,然后在 Amazon EC2 安全组中打开端口 22,从而使您可以使用 Secure Shell(SSH)访问实例。
向现有 Amazon EC2 实例添加 SSH 访问权限
-
向模板额外添加两个参数,从而以现有 Amazon EC2 密钥对和 SSH 位置的名称进行传递。
Parameters: KeyName: Description: Name of an existing EC2 KeyPair to enable SSH access to the instance Type: AWS::EC2::KeyPair::KeyName ConstraintDescription: must be the name of an existing EC2 KeyPair. SSHLocation: Description: The IP address that can be used to SSH to the EC2 instances in CIDR format (e.g. 203.0.113.1/32) Type: String MinLength: 9 MaxLength: 18 Default: 0.0.0.0/0 AllowedPattern: '^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$' ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x. -
将
KeyName属性添加到 Amazon EC2 实例。WebServerInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref LatestAmiId InstanceType: !Ref InstanceType KeyName: !Ref KeyName SecurityGroupIds: - !Ref WebServerSecurityGroup -
将端口 22 和 SSH 位置添加到 Amazon EC2 安全组的入口规则。
WebServerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Enable HTTP access via port 80 and SSH access via port 22 SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref SSHLocation -
使用与 第 2 步:更新应用程序 中所述的相同步骤更新堆栈。
第 4 步:更新实例类型
现在,让我们演示一下如何通过更改实例类型来更新底层基础设施。
我们到目前为止所建立的堆栈使用 t3.micro Amazon EC2 实例。假设您新建的网站获取的流量比 t3.micro 实例能处理的流量多,且您现在想移动到 m5.large Amazon EC2 实例类型中。如果实例类型的架构发生变化,则必须使用不同的 AMI 创建实例。但是,t3.micro 和 m5.large 都使用相同的 CPU 架构并运行 Amazon Linux 2(x86_64)AMI。有关更多信息,请参阅《Amazon EC2 用户指南》中的更改实例类型的兼容性。
让我们使用在上一步中进行修改的模板更改实例类型。由于 InstanceType 是模板的输入参数,我们不需要修改模板;我们能在指定堆栈详细信息页面上更改参数值。
要使用新的参数值更新堆栈
-
在 CloudFormation 控制台中,选择您的
UpdateTutorial堆栈。 -
选择更新、直接更新。
-
选择使用当前模板,然后选择下一步。
-
在指定堆栈详细信息页面上,将文本框中 InstanceType 的值从
t3.micro更改为m5.large。然后,选择两次下一步。 -
在审核页面上,审核您的更改。在更改下,您应该会看到 CloudFormation 将更新
WebServerInstance资源。 -
选择提交。
对于 EBS 支持的 Amazon EC2 实例,可以通过启动和停止实例来动态更改实例类型。CloudFormation 会尝试通过更新实例类型并重启实例来优化更改,因此实例 ID 不会更改。但是,实例重启时,实例的公用 IP 地址会更改。为了确保在更改后正确绑定弹性 IP 地址,CloudFormation 还会更新弹性 IP 地址。您可以在事件选项卡上的 CloudFormation 控制台中看到更改。
要通过 Amazon Web Services 管理控制台 检查实例类型,请打开 Amazon EC2 控制台并在其中查找您的实例。
第 5 步:更新 AMI
现在,让我们更新堆栈以使用下一代 Amazon Linux,即 Amazon Linux 2023。
更新 AMI 是一项重大更改,需要更换实例。我们不能只通过启动和停止实例来修改 AMI;CloudFormation 会将此视为对资源不可变属性的更改。要更改不可变属性进,CloudFormation 必须启动替代资源,在此例中是运行新 AMI 的新 Amazon EC2 实例。
让我们来看看如何更新堆栈模板以使用 Amazon Linux 2023。关键更改包括更新 AMI 参数和从 yum 更改为 dnf 程序包管理器。
AWSTemplateFormatVersion: 2010-09-09 Parameters: LatestAmiId: Description: The latest Amazon Linux 2023 AMI from the Parameter Store Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> Default: '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64' InstanceType: Description: WebServer EC2 instance type Type: String Default: t3.micro AllowedValues: - t3.nano - t3.micro - t3.small - t3.medium - t3a.nano - t3a.micro - t3a.small - t3a.medium - m5.large - m5.xlarge - m5.2xlarge - m5a.large - m5a.xlarge - m5a.2xlarge - c5.large - c5.xlarge - c5.2xlarge - r5.large - r5.xlarge - r5.2xlarge - r5a.large - r5a.xlarge - r5a.2xlarge ConstraintDescription: must be a valid EC2 instance type. KeyName: Description: Name of an existing EC2 KeyPair to enable SSH access to the instance Type: AWS::EC2::KeyPair::KeyName ConstraintDescription: must be the name of an existing EC2 KeyPair. SSHLocation: Description: The IP address that can be used to SSH to the EC2 instances in CIDR format (e.g. 203.0.113.1/32) Type: String MinLength: 9 MaxLength: 18 Default: 0.0.0.0/0 AllowedPattern: '^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$' ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x. Resources: WebServerInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref LatestAmiId InstanceType: !Ref InstanceType KeyName: !Ref KeyName SecurityGroupIds: - !Ref WebServerSecurityGroup UserData: Fn::Base64: !Sub | #!/bin/bash -xe # Get the latest CloudFormation package dnf update -y aws-cfn-bootstrap # Run cfn-init /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region} || error_exit 'Failed to run cfn-init' # Start up the cfn-hup daemon to listen for changes to the EC2 instance metadata /opt/aws/bin/cfn-hup || error_exit 'Failed to start cfn-hup' # Signal success or failure /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region} Metadata: AWS::CloudFormation::Init: config: packages: dnf: httpd: [] php: [] files: /var/www/html/index.php: content: | <?php echo "<h1>Hello World!</h1>"; echo "<p>This is an updated version of our application.</p>"; echo "<p>Running on Amazon Linux 2023!</p>"; ?> mode: '000644' owner: apache group: apache /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} # The interval used to check for changes to the resource metadata in minutes. Default is 15 interval=2 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 -s ${AWS::StackId} -r WebServerInstance --region ${AWS::Region} runas=root services: systemd: 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 CreationPolicy: ResourceSignal: Timeout: PT5M WebServerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Enable HTTP access via port 80 and SSH access via port 22 SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref SSHLocation Outputs: WebsiteURL: Value: !Sub 'http://${WebServerInstance.PublicDnsName}' Description: URL of the web application
使用与 第 2 步:更新应用程序 中所述的相同步骤更新堆栈。
在新实例运行之后,CloudFormation 将更新堆栈中的其他资源以指向新资源。创建所有新资源并删除旧资源的过程称为 UPDATE_CLEANUP。此时,您将注意到堆栈中实例的实例 ID 和应用程序 URL 已随着更新而更改。事件表中的事件包含描述“Requested update has a change to an immutable property and hence creating a new physical resource”,以指示资源已被替代。
或者:如果您已将应用程序代码写入您想更新的 AMI 中,您可以使用同一堆栈更新机制更新 AMI 以加载您的新应用程序。
要使用自定义应用程序代码更新 AMI
-
创建含有应用程序或操作系统更改的新 AMI。有关更多信息,请参阅《Amazon EC2 用户指南》中的创建 Amazon EBS-backed AMI。
-
更新您的模板以合并新 AMI ID。
-
使用与 第 2 步:更新应用程序 中所述的相同步骤更新堆栈。
在您更新堆栈时,CloudFormation 检测到 AMI ID 已更改,然后用我们启动前一个更新所使用的方法触发堆栈更新。
可用性和影响注意事项
不同的属性会对堆栈中的资源造成不同的影响。您可以使用 CloudFormation 更新任何属性,但是您应该在进行任何更改之前考虑以下问题:
-
更新会如何影响资源本身? 例如,更新警报阈值会使警报在更新期间处于非活动状态。正如我们所见,更改实例类型时需要停止并重启实例。CloudFormation 使用底层资源的更新或修改操作来对资源进行更改。要了解更改的影响,您应该查看特定资源的文档。
-
更改可变还是不可变? 对资源属性的某些更改,如更改 Amazon EC2 实例上的 AMI,不受基础服务的支持。如果更改可变,CloudFormation 将使用适用于基础资源的“Update”或“Modify”类型 API。对于不可变的属性更改,CloudFormation 将用更新后的属性创建新资源,然后再删除旧资源之前将此资源链接至堆栈。虽然 CloudFormation 尝试减少堆栈资源的停机时间,但替代资源是一个多步骤过程,需要时间。重新配置堆栈期间,您的应用程序不能全面运行。例如,它可能不能为请求提供服务或访问数据库。
相关资源
有关使用 CloudFormation 启动应用程序的更多信息以及集成其他配置与 Puppet 和 Opscode Chef 等部署服务的更多信息,请参阅以下白皮书: