如何阻止我的 Amazon EC2 Windows 实例在完成引导之前发回 CREATE_COMPLETE 信号?

上次更新时间:2020 年 2 月 11 日

我正在使用 AWS CloudFormation 中的 cfn-init (cfn-init.exe) 和 cfn-signal (cfn-signal.exe) 帮助程序脚本引导 Amazon Elastic Compute Cloud (Amazon EC2) Windows 实例。我的 cfn-init 脚本会过早返回信号。然后,AWS CloudFormation 在我的 Windows 实例完成引导之前便将其标记为 CREATE_COMPLETE。

简短描述

在 Windows 实例中,UserData 脚本由 Ec2ConfigService 进程执行。UserData 会调用 cfn-init.exe,它作为 Ec2ConfigService 的子进程运行。

您的 Windows 实例发回 CREATE_COMPLETE 信号可能是出于以下原因:

  • 如果 cfn-init.exe 执行的步骤之一需要系统重启,则系统会关闭,然后将执行权交还给 Ec2ConfigService 进程。系统会继续处理 UserData 脚本,然后执行 cfn-signal.exe 来向 AWS CloudFormation 发回信号。
  • cfn-signal 在重启后未发回信号,因为 UserData 仅运行一次。

在以下代码示例中,cfn-signal.exe 直接从 UserData 调用。如果 cfn-init.exe 进程执行重启,则无法调用 cfn-signal.exe 命令,因为 UserData 仅运行一次。

JSON 示例:

"UserData": {
  "Fn::Base64": {
    "Fn::Join": [
      "",
      [
        "<script>\n",
        "cfn-init.exe -v -s ",
        {
          "Ref": "AWS::StackId"
        },
        " -r WindowsInstance",
        " --configsets ascending",
        " --region ",
        {
          "Ref": "AWS::Region"
        },
        "\n",
        "cfn-signal.exe -e %ERRORLEVEL% --stack ",
        {
          "Ref": "AWS::StackId"
        },
        " --resource WindowsInstance --region ",
        {
          "Ref": "AWS::Region"
        },
        "\n",
        "</script>"
      ]
    ]
  }
}

YAML 示例:

UserData:
  Fn::Base64: !Sub |
    <script>
    cfn-init.exe -v -s ${AWS::StackId} -r WindowsInstance --configsets ascending --region ${AWS::Region}
    cfn-signal.exe -e %ERRORLEVEL% --stack ${AWS::StackId} --resource WindowsInstance --region ${AWS::Region}
    </script>

解决方法

1.    使用模板中 cfn-init Metadata 部分的 configsets 将需要重启的配置和不需要重启的配置分开。

2.    将 cfn-signal.exe 从 AWS::EC2::InstanceAWS::AutoScaling::LaunchConfiguration 资源的 UserData 部分移至模板的 AWS::CloudFormation::Init Metadata 部分。

3.    执行 cfn-signal.exe 作为最后一个 configset 运行的最后一个命令。

4.    在 JSON 或 YAML 模板中将 UserData 更改为 cfn-init 并制定升序 configset。

JSON 示例:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "cfn-init example using configsets",
  "Parameters": {
    "AMI": {
      "Type": "AWS::EC2::Image::Id"
    }
  },
  "Resources": {
    "WindowsInstance": {
      "Type": "AWS::EC2::Instance",
      "Metadata": {
        "AWS::CloudFormation::Init": {
          "configSets": {
            "ascending": [
              "config1",
              "config2",
              "config3"
            ]
          },
          "config1": {
            "files": {
              "C:\\setup\\setenvironment.ps1": {
                "content": {
                  "Fn::Join": [
                    "",
                    [
                      "$Folder = 'C:\\Program Files\\Server\\packages\\bin.20182.18.0826.0815\\'\n",
                      "$OldPath = [System.Environment]::GetEnvironmentVariable('path')\n",
                      "$NewPath = $OldPath + ';' + $Folder\n",
                      "[System.Environment]::SetEnvironmentVariable('path',$NewPath,'Machine')"
                    ]
                  ]
                }
              }
            }
          },
          "config2": {
            "commands": {
              "0-restart": {
                "command": "powershell.exe -Command Restart-Computer",
                "waitAfterCompletion": "forever"
              }
            }
          },
          "config3": {
            "commands": {
              "01-setenvironment": {
                "command": "powershell.exe -ExecutionPolicy Unrestricted C:\\setup\\setenvironment.ps1",
                "waitAfterCompletion": "0"
              },
              "02-signal-resource": {
                "command": {
                  "Fn::Join": [
                    "",
                    [
                      "cfn-signal.exe -e %ERRORLEVEL% --resource WindowsInstance --stack ",
                      {
                        "Ref": "AWS::StackName"
                      },
                      "         --region ",
                      {
                        "Ref": "AWS::Region"
                      }
                    ]
                  ]
                }
              }
            }
          }
        }
      },
      "Properties": {
        "ImageId": {
          "Ref": "AMI"
        },
        "InstanceType": "t2.medium",
        "UserData": {
          "Fn::Base64": {
            "Fn::Join": [
              "",
              [
                "<script>\n",
                "cfn-init.exe -v -s ",
                {
                  "Ref": "AWS::StackId"
                },
                " -r WindowsInstance",
                " --configsets ascending",
                " --region ",
                {
                  "Ref": "AWS::Region"
                },
                "</script>"
              ]
            ]
          }
        }
      },
      "CreationPolicy": {
        "ResourceSignal": {
          "Count": "1",
          "Timeout": "PT30M"
        }
      }
    }
  }
}

YAML 示例:

AWSTemplateFormatVersion: '2010-09-09'
Description: cfn-init example using configsets
Parameters:
  AMI:
    Type: 'AWS::EC2::Image::Id'
Resources:
  WindowsInstance:
    Type: 'AWS::EC2::Instance'
    Metadata:
      AWS::CloudFormation::Init:
        configSets:
          ascending:
            - config1
            - config2
            - config3
        config1:
          files:
            C:\setup\setenvironment.ps1:
              content: !Sub |
                $Folder = 'C:\Program Files\Server\packages\bin.20182.18.0826.0815\'
                $OldPath = [System.Environment]::GetEnvironmentVariable('path')
                $NewPath = $OldPath + ';' + $Folder
                [System.Environment]::SetEnvironmentVariable('path',$NewPath,'Machine')
        config2:
          commands:
            0-restart:
              command: powershell.exe -Command Restart-Computer
              waitAfterCompletion: forever
        config3:
          commands:
            01-setenvironment:
              command: powershell.exe -ExecutionPolicy Unrestricted C:\setup\setenvironment.ps1
              waitAfterCompletion: '0'
            02-signal-resource:
                command: !Sub >
                  cfn-signal.exe -e %ERRORLEVEL% --resource WindowsInstance --stack ${AWS::StackName} --region ${AWS::Region}
    Properties:
      ImageId: !Ref AMI
      InstanceType: t2.medium
      UserData:
        Fn::Base64: !Sub |
          <script>
          cfn-init.exe -v -s ${AWS::StackId} -r WindowsInstance --configsets ascending --region ${AWS::Region}
          </script>
    CreationPolicy:
      ResourceSignal:
        Count: 1
        Timeout: PT30M

在前面的模板中,信号不再在 UserData 中运行,这意味着您无法检索 cfn-init 进程提供的退出代码。默认情况下,如果未从 UserDataMetadata 部分收到信号,AWS CloudFormation 无法创建或更新堆栈。然后堆栈会返回一个“timeout exceeded”错误。

提示:要进行问题排查,请使用 Windows 实例中位于 c:\cfn\log 位置的日志。

5.    将 waitAfterCompletion 参数设置为 forever

注意:waitAfterCompletion 的默认值为 60 秒。如果您将该值更改为 forever,cfn-init 将退出,然后仅在重启完成后才恢复。


这篇文章对您有帮助吗?

我们可以改进什么?


需要更多帮助?