这可能会有点复杂,但我会尽力简化。
我有一个CloudFormation
模板设置了 3 台相同的EC2
机器,并cfn-init
在脚本中使用UserData
它从中提取一些自动化代码S3
,并运行它以将这些机器设置为与此处不相关的特定于产品的高可用性配置。
它看起来像这样:
"commands" : {
"0-Tester" : {
"command" : "echo \"I am OK.\" > \"d:\\test.txt\"",
"waitAfterCompletion": 5
},
"1-Pullcode" : {
"command" : "aws s3 cp s3://some-bucket/code.zip d:/code.zip > d:/s3sync.log",
"waitAfterCompletion": 5
},
"2-UnpackCode" : {
"command" : "powershell Expand-Archive -Path d:\\code.zip -DestinationPath d:\\dev",
"waitAfterCompletion": 5
},
"3-ResetLicensing" : {
"command" : "\"C:/Program Files/something/iisnodeModule/node.exe\" d:/dev/aws-automation/service.js --service Licensing.Service --action restart > d:/oxy_restart.log",
"waitAfterCompletion": 5
},
"4-RunAutomation" : {
"command" : "\"C:/Program Files/something/iisnodeModule/node.exe\" d:/dev/aws-automation/automate.js --config c:/servers.conf --all > d:/automation.log",
"waitAfterCompletion": 5
}
}
为了实现这种自动化,每台机器都需要知道所有 3 台机器的 IP 或 DNS 名称(例如,考虑创建一个 mongodb 副本集),并且为了实现这一点,我已使用模板为Route53
这些 EC2 创建 3 个 DNS 记录(在私有托管区域中)并在每个 EC2 实例上的文件中生成这 3 个可预测的 DNS 名称。
事情是这样的:
"config": {
"files": {
"c:\\servers.conf": {
"content": {
"role": "app",
"servers": [
{"Fn::Join": [".",["build1",{"Ref": "AWS::StackName"},{"Ref": "HostedZone"}]]},
{"Fn::Join": [".",["app1",{"Ref": "AWS::StackName"},{"Ref": "HostedZone"}]]},
{"Fn::Join": [".",["app2",{"Ref": "AWS::StackName"},{"Ref": "HostedZone"}]]}
],
"replSetName": { "Ref": "ReplicaSetName" },
"ecFolder": { "Ref": "ElasticubeFolder" }
}
}
},
而且当然
"DNSRecordBuild1": {
"Type": "AWS::Route53::RecordSet",
"Properties": {
"HostedZoneName": {"Ref": "HostedZone"},
"Name": {
"Fn::Join": [".",["build1",{"Ref": "AWS::StackName"},{"Ref": "HostedZone"}]]
},
"Type": "A",
"TTL": "900",
"ResourceRecords": [{"Fn::GetAtt": ["build1","PrivateIp"]}]
}
}
创建 DNS 记录。
因此,此时堆栈已完美创建 -CFN
启动 3 个实例,json
在每个实例上使用最终存在的 DNS 名称呈现配置,从 提取代码S3
,然后开始运行脚本。现在R53
记录显然依赖于这些实例的启动,但它们会在实例准备就绪后立即创建,也就是前脚本开始运行 - 因此当脚本引用所述 DNS 名称时,它们已经存在于R53
。这个设置工作正常。
现在,我想将一个添加ELB
到堆栈中,并且我希望它被创建只有当 3 台机器完全配置并准备好进行通信时。因此,我向资源添加了一个DependsOn
属性ELB
,效果很好,然后向每个实例添加了cfn-signal
和,CreationPolicy
以确保只有在自动化脚本完成时才将其标记为完成:
"cfn-signal.exe -e %ERRORLEVEL% --stack ", { "Ref" : "AWS::StackName" }, " --resource build1 --region ", { "Ref" : "AWS::Region" }
和
"CreationPolicy" : {
"ResourceSignal" : {
"Timeout" : "PT10M"
}
}
但这会立即破坏整个过程 - 因为现在R53
直到机器发送后才会创建记录signal
- 而机器不会发送它,因为它试图运行依赖于这些 DNS 名称的脚本,因此失败。
目前,我只是删除了和signal
,policy
让 ELB 在实例上线后立即启动,但这并不理想。所以问题是:我怎样才能将 ELB 的创建延迟到脚本完成后,而不延迟 R53 资源或创建任何上述依赖循环?
目前我的想法是:
- 向模板添加
wait
资源,向其发出信号,并让ELB
资源拥有DependsOn
这个wait
- 不确定这是否可行或可能产生什么不利影响 - 根本不创建
ELB
viaCFN
,而是在可以验证所有其他机器都已准备就绪时在其中一台机器上创建 viaAWS CLI
。真的不喜欢这种方法,因为它需要在我的自动化脚本中添加大量额外代码,并使此资源更难管理(即在删除堆栈时需要手动删除 ELB) - 让每个实例使用替代方法“发出信号”,例如将文件或标志放在某个地方(如 S3),然后
lambda
对其做出反应并创建 ELB - 但这具有与#2 相同的缺点......
答案1
如何从完全不同的角度来攻击它并在创建所有资源(即 3x EC2、DNS 和 ELB)后使用 AWS EC2 系统管理器(SSM)来配置实例?
SSM 将依赖于上述所有内容的创建,然后登录到每个实例并启动应用程序。这应该可以解决依赖性问题。
查看AWS::SSM::关联和AWS::SSM::文档用于 EC2 Systems Manager 的 CloudFormation 支持。
希望有帮助:)