CloudFormationのロールバック時に設定ファイルも元に戻す


# 前置き

## CloudFormationのロールバック処理

CloudFormationはスタック更新に失敗するとロールバックが行われる。
EC2インスタンスの更新に失敗してロールバックが行われた場合、更新前の状態でインスタンスを起動しようとする。

~~その時、更新前の状態に戻せないとロールバックも失敗して詰む(ROLLBACK_FAILEDの状態)。
ドキュメントにはサポートに連絡しろって書いてある。無慈悲。~~
慈悲によりロールバックを継続する機能がもたらされた。
失敗の原因を取り除くことでロールバックを継続できる。
[Continue Rolling Back an Update](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-continueupdaterollback.html)

## ロールバックに失敗するパターン

EC2インスタンス起動時に、設定ファイル類をS3から取得しているとする。
更新した設定ファイルに不備があり、それでスタック更新に失敗すると設定ファイルの不備は残っているのでロールバックも失敗してしまう。

## ロールバックに対応する

ロールバック時、スタックのパラメータは自動的に更新前のものが使われる。
当然といえば当然だが、CloudFormation管理外のリソースは利用者がロールバックできるようにしなければならない。
S3であればバージョニングの機能があるので、それを使ってロールバックに対応できる。

# 試す

## 構成

* S3バケットのバージョニングは有効
* CloudFormationで Auto Scaling Group と Launch Configuration を定義
* インスタンスx2起動時にS3からnginxの設定ファイルを取得(VersionID指定)
* 設定ファイルを取得したらnginx起動
* nginxの起動に失敗したらCloudFormationにインスタンス起動失敗を通知
* 2台とも起動成功しなかったらロールバック

```json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "CloudFormation rollback test",
  "Parameters": {
    "VersionId": {
      "Type": "String"
    }
  },
  "Resources": {
    "Group": {
      "Type": "AWS::AutoScaling::AutoScalingGroup",
      "CreationPolicy": {
        "ResourceSignal": {
          "Count": "2",
          "Timeout": "PT10M"
        }
      },
      "UpdatePolicy": {
        "AutoScalingRollingUpdate": {
          "MaxBatchSize": "1",
          "MinInstancesInService": "1",
          "WaitOnResourceSignals": "true",
          "PauseTime": "PT10M"
        },
        "AutoScalingScheduledAction": {
          "IgnoreUnmodifiedGroupSizeProperties": "true"
        }
      },
      "Properties": {
        "LaunchConfigurationName": { "Ref": "LaunchConfig" },
        "VPCZoneIdentifier": [
          "subnet-XXXXXXXX"
        ],
        "HealthCheckType": "EC2",
        "DesiredCapacity": "2",
        "MinSize": "2",
        "MaxSize": "2"
      }
    },
    "LaunchConfig": {
      "Type": "AWS::AutoScaling::LaunchConfiguration",
      "Properties": {
        "BlockDeviceMappings": [
          {
            "DeviceName": "/dev/xvda",
            "Ebs": {
              "VolumeType": "gp2",
              "VolumeSize": "8",
              "DeleteOnTermination": "true"
            }
          }
        ],
        "IamInstanceProfile": "test",
        "ImageId": "ami-383c1956",
        "InstanceType": "t2.micro",
        "InstanceMonitoring": "false",
        "UserData": {
          "Fn::Base64": {
            "Fn::Join": [
              "", [
                "#!/bin/bash\n",
                "yum update -y aws-cfn-bootstrap\n",
                "yum install -y nginx\n",
                "aws s3api get-object",
                " --bucket XXXXXXX",
                " --key nginx.conf",
                " --version-id '", {"Ref": "VersionId"}, "'",
                " /etc/nginx/nginx.conf\n",
                "service nginx start\n",
                "/opt/aws/bin/cfn-signal -e $?",
                " --stack ", {"Ref": "AWS::StackName"},
                " --resource Group",
                " --region ", {"Ref": "AWS::Region"}
              ]
            ]
          }
        }
      }
    }
  }
}
```

## 実行

### 1. S3のバージョニング有効化

あらかじめバケットの設定でバージョニングを有効にしておく。

### 2. S3に設定ファイルをアップロード

nginx.confは空でも起動はするので空にした。

### 3. スタック作成→成功

nginx.confのVersionIDを指定してスタック作成。
作成に成功し(CREATE_COMPLETE)、インスタンスが2台起動する。

```
Received SUCCESS signal with UniqueId i-XXXXXXXX
Received SUCCESS signal with UniqueId i-XXXXXXXX
```

### 4. S3の設定ファイル更新

nginx.confにhogeと書いてS3にアップロード。

### 5. スタック更新→失敗

nginx.confのVersionIDを指定してスタック更新。
VersionIDはuser-data内で使用されているので、LaunchConfigurationに変更があったとみなされ自動的にローリングアップデートが始まる。

1台目のnginxの起動が失敗し、スタックの更新が中止される(UPDATE_FAILED)。

```
Received 1 FAILURE signal(s) out of 2. Unable to satisfy 100% MinSuccessfulInstancesPercent requirement
```

### 6. ロールバック→成功

スタック更新失敗後、ロールバックが始まる(UPDATE_ROLLBACK_IN_PROGRESS)。
更新前のVersionIDで再度インスタンスが起動する。
旧バージョンの設定ファイルは正しいので起動に成功する(UPDATE_ROLLBACK_COMPLETE)。

```
Received SUCCESS signal with UniqueId i-XXXXXXXX
```

# まとめ

CloudFormation管理外のリソースをロールバック対応させるためには利用者側の考慮が必要。
S3ならVersionIDが使える。
複数の設定ファイルはLambdaで自動的に*.gz化してバージョニングすると楽そう。