使用 CloudFormation 和 CFN-SIGNAL 管理依赖项

使用 CloudFormation 和 CFN-SIGNAL 管理依赖项

这可能会有点复杂,但我会尽力简化。

我有一个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 名称的脚本,因此失败。

目前,我只是删除了和signalpolicy让 ELB 在实例上线后立即启动,但这并不理想。所以问题是:我怎样才能将 ELB 的创建延迟到脚本完成后,而不延迟 R53 资源或创建任何上述依赖循环?

目前我的想法是:

  1. 向模板添加wait资源,向其发出信号,并让ELB资源拥有DependsOn这个wait- 不确定这是否可行或可能产生什么不利影响
  2. 根本不创建ELBvia CFN,而是在可以验证所有其他机器都已准备就绪时在其中一台机器上创建 via AWS CLI。真的不喜欢这种方法,因为它需要在我的自动化脚本中添加大量额外代码,并使此资源更难管理(即在删除堆栈时需要手动删除 ELB)
  3. 让每个实例使用替代方法“发出信号”,例如将文件或标志放在某个地方(如 S3),然后lambda对其做出反应并创建 ELB - 但这具有与#2 相同的缺点......

答案1

如何从完全不同的角度来攻击它并在创建所有资源(即 3x EC2、DNS 和 ELB)后使用 AWS EC2 系统管理器(SSM)来配置实例?

SSM 将依赖于上述所有内容的创建,然后登录到每个实例并启动应用程序。这应该可以解决依赖性问题。

查看AWS::SSM::关联AWS::SSM::文档用于 EC2 Systems Manager 的 CloudFormation 支持。

希望有帮助:)

相关内容