我4年前开始使用
cloudformation 。 从那时起,我破坏了许多基础设施,甚至那些已经投入生产的基础设施。 但是每当我宠坏某些东西时,我都会学到新东西。 得益于这些经验,我将分享一些我学到的最重要的经验教训。

第1课:在部署之前验证更改
我一开始使用
cloudformation就学到了这一课。 我不记得当时发生了什么,但我确实记得我使用了
aws cloudformation update命令 。 此命令仅推出模板,而无需验证将要部署的更改。 我认为不需要解释,因此您需要在部署之前检查所有更改。
失败之后,我立即更改了
部署管道 ,用
create-change-set命令替换了update命令。
# OPERATION is either "UPDATE" or "CREATE" changeset_id=$(aws cloudformation create-change-set \ --change-set-name "$CHANGE_SET_NAME" \ --stack-name "$STACK_NAME" \ --template-body "$TPL_PATH" \ --change-set-type "$OPERATION" \ --parameters "$PARAMETERS" \ --output text \ --query Id) aws cloudformation wait \ change-set-create-complete --change-set-name "$changeset_id"
创建更改集后,它不会影响现有堆栈。 与update命令不同,变更集方法实际上并未部署。 相反,它会创建一个更改列表,您可以在部署之前查看这些更改。 您可以在aws控制台界面中查看更改。 但是,如果您希望自动化所有可能的操作,请在CLI中检查它们:
# this command is presented only for demonstrational purposes. # the real command should take pagination into account aws cloudformation describe-change-set \ --change-set-name "$changeset_id" \ --query 'Changes[*].ResourceChange.{Action:Action,Resource:ResourceType,ResourceId:LogicalResourceId,ReplacementNeeded:Replacement}' \ --output table
此命令应产生类似于以下内容的输出:
-------------------------------------------------------------------- | DescribeChangeSet | +---------+--------------------+----------------------+------------+ | Action | ReplacementNeeded | Resource | ResourceId | +---------+--------------------+----------------------+------------+ | Modify | True | AWS::ECS::Cluster | MyCluster | | Replace| True | AWS::RDS::DBInstance| MyDB | | Add | None | AWS::SNS::Topic | MyTopic | +---------+--------------------+----------------------+------------+
请特别注意Action为
Replace ,
Delete或
ReplacementNeeded为True的更改。 这些是最危险的更改,通常会导致信息丢失。
查看更改后,可以将其扩展
aws cloudformation execute-change-set --change-set-name "$changeset_id" operation_lowercase=$(echo "$OPERATION" | tr '[:upper:]' '[:lower:]') aws cloudformation wait "stack-${operation_lowercase}-complete" \ --stack-name "$STACK_NAME"
第2课:使用堆栈策略防止有状态地替换或删除资源
有时仅查看更改是不够的。 我们都是人类,我们都会犯错误。 在我们开始使用变更集后不久,我的队友在不知不觉中执行了部署,这导致了数据库升级。 没有什么可怕的事情发生,因为这是一个测试环境。
尽管我们的脚本显示了更改列表并要求确认,但由于更改列表太大而无法在屏幕上显示,所以跳过了替换更改。 并且由于这是测试环境中的定期更新,因此对更改的关注不大。
有些资源将永远不会取代或删除。 这些是有状态的服务,例如RDS数据库实例或Elastichsearch集群等。如果执行的操作需要删除这样的资源,而aws会自动拒绝部署,那将是很好的选择。 幸运的是,cloudformation具有内置的方法。 这称为堆栈策略,您可以在
文档中阅读有关此内容的更多信息:
STACK_NAME=$1 RESOURCE_ID=$2 POLICY_JSON=$(cat <<EOF { "Statement" : [{ "Effect" : "Deny", "Action" : [ "Update:Replace", "Update:Delete" ], "Principal": "*", "Resource" : "LogicalResourceId/$RESOURCE_ID" }] } EOF ) aws cloudformation set-stack-policy --stack-name "$STACK_NAME" \ --stack-policy-body "$POLICY_JSON"
第3课:在使用秘密参数更新堆栈时使用UsePreviousValue
创建RDS实体时,mysql AWS要求您提供MasterUsername和MasterUserPassword。 由于最好不要在源代码中保密,并且我想绝对自动化,所以我实现了一个“智能机制”,其中在部署之前从s3获取凭据,如果找不到凭据,则会生成新凭据并将其存储在s3中。
然后,这些凭证将作为参数传递给cloudformation create-change-set命令。 在使用脚本进行实验的过程中,碰巧丢失了与s3的连接,而我的“智能机制”将其视为生成新凭据的信号。
如果我在生产环境中开始使用此脚本,并且再次出现连接问题,它将使用新凭据更新堆栈。 在这种特殊情况下,不会发生任何不良情况。 但是,我放弃了这种方法,而是开始使用另一种方法,在创建堆栈时仅提供一次凭据。 然后,当堆栈需要更新时,而不是指定参数的秘密值,我只需使用
UsePreviousValue = true即可 :
aws cloudformation create-change-set \ --change-set-name "$CHANGE_SET_NAME" \ --stack-name "$STACK_NAME" \ --template-body "$TPL_PATH" \ --change-set-type "UPDATE" \ --parameters "ParameterKey=MasterUserPassword,UsePreviousValue=true"
第4课:使用回滚配置
与我合作的另一个团队正在使用称为
回滚配置的
cloudformation函数。 我以前从未见过她,并很快意识到这将使我的堆栈部署变得更好。 现在,每次使用cloudformation在lambda或ECS中部署代码时,我都会使用。
工作原理:创建更改集时,可以在
--rollback-configuration参数中指定
CloudWatch警报arn 。 稍后,当您完成更改集时,aws会跟踪警报至少一分钟。 如果在此期间警报将状态更改为ALARM,它将回退部署。
以下是
cloudformation模板的摘录示例,其中我创建了一个
cloudwatch警报 ,该
警报监视用户的云度量标准作为云日志中的错误数(该度量标准是通过
MetricFilter创建的):
Resources: # this metric tracks number of errors in the cloudwatch logs. In this # particular case it's assumed logs are in json format and the error logs are # identified by level "error". See FilterPattern ErrorMetricFilter: Type: AWS::Logs::MetricFilter Properties: LogGroupName: !Ref LogGroup FilterPattern: !Sub '{$.level = "error"}' MetricTransformations: - MetricNamespace: !Sub "${AWS::StackName}-log-errors" MetricName: Errors MetricValue: 1 DefaultValue: 0 ErrorAlarm: Type: AWS::CloudWatch::Alarm Properties: AlarmName: !Sub "${AWS::StackName}-errors" Namespace: !Sub "${AWS::StackName}-log-errors" MetricName: Errors Statistic: Maximum ComparisonOperator: GreaterThanThreshold Period: 1 # 1 minute EvaluationPeriods: 1 Threshold: 0 TreatMissingData: notBreaching ActionsEnabled: yes
现在,在执行一组工具时,
警报可以用作
回滚触发器:
ALARM_ARN=$1 ROLLBACK_TRIGGER=$(cat <<EOF { "RollbackTriggers": [ { "Arn": "$ALARM_ARN", "Type": "AWS::CloudWatch::Alarm" } ], "MonitoringTimeInMinutes": 1 } EOF ) aws cloudformation create-change-set \ --change-set-name "$CHANGE_SET_NAME" \ --stack-name "$STACK_NAME" \ --template-body "$TPL_PATH" \ --change-set-type "UPDATE" \ --rollback-configuration "$ROLLBACK_TRIGGER"
第5课:确保您部署了模板的最新版本
部署最新版本的cloudformation模板并不容易,但是会造成很多损害。 一旦与我们在一起:开发人员没有从Git发送最新更改,并且在不知不觉中部署了堆栈的先前版本。 这导致使用该堆栈的简单应用程序。
简单的事情(例如在部署之前添加检查分支是否相关)的检查就可以了(假设git是您的版本控制工具):
git fetch HEADHASH=$(git rev-parse HEAD) UPSTREAMHASH=$(git rev-parse master@{upstream}) if [[ "$HEADHASH" != "$UPSTREAMHASH" ]] ; then echo "Branch is not up to date with origin. Aborting" exit 1 fi
第6课:不要重新发明轮子
使用
cloudformation进行部署似乎很容易。 您只需要一堆执行aws cli命令的bash脚本即可。
4年前,我从称为aws cloudformation create-stack命令的简单脚本开始。 不久,脚本不再简单。 吸取的每一个教训都使脚本变得越来越复杂。 这不仅很困难,而且还有很多错误。
现在,我在一个小型IT部门工作。 经验表明,每个团队都有自己的部署云信息堆栈的方式。 这很不好。 如果每个人都使用一种方法会更好。 幸运的是,有许多工具可以帮助您部署和配置云信息堆栈。
这些课程将帮助您避免错误。