Bitbucket PipelinesをトリガーにSSMドキュメントをキックする

Bitbucket PipelinesをトリガーにSSMドキュメントをキックする仕組みを実装したので手順をメモに残す。

#実装した仕組み
Bitbucket Cloudのリポジトリで管理しているCFn Templateファイルを使ってAWS上で一時サーバのEC2を繰り返し作成/削除したいという要件があったため、CFnでのEC2作成の処理に関して以下の自動化の仕組みを実装した。
この仕組みを使うと、BitbucketのCommit IDへのタグ作成の1アクションだけでS3へのファイル転送からEC2作成までの一連の処理を自動で実行できる。


![Untitled Diagram(3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/205fd380-6a56-0036-243e-66f572af2a30.png)



##Bitbucket CloudからS3へCFn Templateファイルを転送
![Untitled Diagram(6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/06f0b94b-e30d-b51f-c760-76cdbd38cfe6.png)


CFn Templateファイルを配置しているBitbucket CloudをAWSと連携するために、Bitbucket PipelinesというBitbucket Cloudで利用可能なパイプラインの機能を利用して、S3バケットへBitbucket Cloud上のファイルを転送する実装としている。
Bitbucket Pipelinesではパイプラインのトリガーとして、ブランチの更新とCommit IDへのタグ作成のどちらかを指定可能で、今回はタグ作成をトリガーとすることにした。

参考までに、ビルドを実現するAWS CodeBuildではBitbucketとの連携をサポートしており、Bitbucket WebhookをトリガーにCodeBuildのビルドプロジェクトを起動し、ビルドプロジェクトにてBitbucketから取得したファイルをS3に配置するという実装も可能。
BitbucketとCodeBuildを連携する場合、CodeBuildからBitbucketへの接続を許可する必要があるが、セキュリティポリシー上外部からBitbucketへの接続が不可であったために今回は不採用とした。
[(参考) CodeBuild の Bitbucket プルリクエストとウェブフックフィルタのサンプル](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/sample-bitbucket-pull-request.html)

##S3の更新をトリガーにCFnでEC2を作成
![Untitled Diagram(7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/3c876d0e-9ea5-353c-f074-e6304a0224b1.png)

BitbucketによるS3へのファイル転送をCloudTrailにてログ記録し、それをEventBridgeで検知、EventBridgeからSSMドキュメントを起動、SSMドキュメント内でS3へ転送したCFn Templateを利用してEC2を作成する実装としている。

SSMドキュメントの代わりに、CodePipelineからCloudFormationを呼び出してEC2を作成することも検討したが、一時サーバー上で実行する処理を既にSSMドキュメント化していたため、運用作業のインターフェースを一本化する目的でEC2作成自体もSSMドキュメントによる実行という方針に決まった。

#前提
- AWSのアカウントを作成していること
- Bitbucket Cloudのリポジトリを作成し、以下のEC2インスタンス作成用のCFn Template「ec2-cfn.yml」をトップディレクトリ直下に配置済みであること

```yml:ec2-cfn.yml
Resources:
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      AvailabilityZone: ap-northeast-1a  #EC2インスタンスを起動するアベイラビリティーゾーンを指定
      ImageId: ami-06cd52961ce9f0d85  #EC2インスタンスのAMI IDを指定。ここで指定しているのはAmazon Linux AMI
      Tags: 
        - Key: Name
          Value: automation-test  #EC2インスタンスの名前を指定
```



#実装の手順

##1. S3バケット作成
Bitbucket Pipelinesから送られたCFn Templateファイルを保管するためのS3バケットを作成する。
###1-1. S3のコンソール > バケット にて「バケットの作成」をクリックする。
![図11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/41057f66-1330-67c3-3b6d-c49cd0aa0890.png)
###1-2. 以下を指定して「バケットを作成」をクリックする。
- バケット名：任意の名前
- リージョン：ap-north-east1
- バケットのバージョニング：有効にする
![図12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/b8006723-bc5f-d03f-9033-ff818ad2a281.png)


##2. IAM定義追加
##2-1. Bitbucket Pipelines用のIAMユーザーとIAMポリシー作成
Bitbucket PipelinesからS3バケットにCFn Templateファイルを配置するためのIAMユーザーとIAMポリシーを追加する。
###2-1-1. IAMポリシー作成
1. IAMのコンソール > ポリシー にて「ポリシーの作成」をクリックする
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/8b740ae3-37ea-08b4-8315-4497e1b532bb.png)
2. 「JSON」をクリックし、以下のポリシーの内容を入力した上で、「ポリシーの確認」をクリックする

```json:ポリシーの内容
{
 "Version": "2012-10-17",
 "Statement": [
     {
         "Effect": "Allow",
         "Action": [
             "s3:PutObject",
             "s3:ListBucket"
         ],
         "Resource": [
             "arn:aws:s3:::<手順1で作成したS3バケット名に置き換え>/*", 
             "arn:aws:s3:::<手順1で作成したS3バケット名に置き換え>"
         ]
     }
 ]
}
```
![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/5027fc0c-4c0d-2968-87d8-88d4c695b961.png)
3. 「名前」に「S3_<バケット名>_Access」を指定し、「ポリシーの作成」をクリックする
![図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/b27bf004-5a7d-8cd3-b328-28c7380299a7.png)

###2-1-2. IAMユーザー作成
1. IAMのコンソール > ユーザー にて「ユーザーを追加」をクリックする
![図4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/c57d48b0-c093-d369-90cf-529b25aba882.png)
2. 「ユーザー」に「bitbucket-pipeline」を指定し「アクセスの種類」で「プログラムによるアクセス」を選択し、た上で、「次のステップ：アクセス権限」をクリックする
![図10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/0c457d63-7344-5db5-7422-ed48449337ed.png)

3. 「アクセス許可の設定」で「既存のポリシーを直接アタッチ」をクリックし、「ポリシーのフィルタ」で手順2-1-1で作成したポリシー「S3_<バケット名>_Access」を検索して指定した上で、「次のステップ：タグ」をクリックする
![図6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/2a5f6145-6010-2a2f-fedf-fa2bfee75dcc.png)
4. 「タグの追加（オプション）」画面では何も変更せずに「次のステップ：確認」をクリックする
![図7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/6f7b82b6-cb3d-5764-48ad-36e04e7e636c.png)
5. 「確認」画面で「ユーザーの作成」をクリックする
![図8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/1cf3ad95-dbf5-2553-e535-9f6aeb2f5b9a.png)
6. 作成したIAMユーザーのアクセスキーIDとシークレットアクセスキーを記録する。記録した情報は手順3のBitbucket Pipelines作成時に利用する
![図9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/a0dcd638-f6cc-c830-2f0b-20e50dcaf5b6.png)

##2-2. Systems Manager用のサービスロールとIAMポリシー作成
EC2インスタンスのCFn Stack作成用のSSMドキュメントをSystems Managerに実行させるためのサービスロールとIAMポリシーを追加する。
###2-2-1. IAMポリシー作成
ポリシーの内容には以下を指定し、ポリシーの名前は「SSMServiceAccess」を指定する。あとは手順2-1-1のIAMポリシー作成と同様。

```json:ポリシーの内容
{
 "Version": "2012-10-17",
 "Statement": [
     {
         "Effect": "Allow",
         "Action": [
             "cloudformation:CreateStack",
             "cloudformation:DescribeStacks",
             "ec2:CreateTags",
             "ec2:DescribeInstances",
             "ec2:RunInstances",
             "ec2:TerminateInstances",
             "s3:GetObject"
         ],
         "Resource": "*"
     }
 ]
}
```
###2-2-2. サービスロール作成
1. IAMコンソール > ロール にて「ロールの作成」をクリックする
![図26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/1b5f4bf8-810c-730a-268b-86f8c091fa7e.png)

2. 「ユースケースの選択」でサービスから「Systems Manager」、「Systems Manager Allows SSM to call AWS services on your behalf」を選択した後に「次のステップ：アクセス権限」をクリックする
![図27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/6c430a8e-bd02-d43b-e70b-7887824638e0.png)

3. 「ポリシーのフィルタ」で手順2-2-1で作成した「SSMServiceAccess」を検索して指定した上で、「次のステップ：タグ」をクリックする
![図28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/e12bcea8-19ff-d41f-793a-2db6a99c0cd4.png)

4. 「タグの追加（オプション）」画面では何も変更せずに「次のステップ：確認」をクリックする
![図29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/6d910c63-4429-5c11-97bc-feef3df818fd.png)


5. 「ロール名」に「SSMServiceRole」と入力し、「ロールの作成」をクリックする
![図30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/cbe248dd-3d01-ecab-6835-7928bb7fd9a1.png)



##3. Bitbucket Pipelines作成
Bitbucketリポジトリで管理するCFn Templateファイルを手順1で作成したS3バケットに配置するためにBitbucket Pipelinesを作成する。
###3-1. 以下のbitbucket-pipelines.ymlファイルをリポジトリのトップディレクトリ直下に追加する
bitbucket-pipelines.ymlはBitbucket Pipelinesの挙動を指定するためのファイル。詳細は以下を参照。
https://support.atlassian.com/bitbucket-cloud/docs/configure-bitbucket-pipelinesyml/

```yml:bitbucket-pipelines.yml
image: python:3.8

pipelines:
  tags:
    automation_test*:  #ここで指定した名前通りにCommit IDにタグを追加すると、Bitbucket Pipelinesが稼働する
      - step:
          caches:
            - pip
          script:
            - DIR_NAME=build_$(date "+%Y%m%d%H%M%S")
            - BUCKET_NAME=<S3バケット名>  #手順1で作成したS3バケットの名前に置き換える
            - OBJECT_KEY=test  #CFn Templateファイルを配置したいS3バケットのフォルダを指定する。予めフォルダを作成しておく必要はない
            - mkdir -p ./${DIR_NAME}/${OBJECT_KEY}
            - cp -p ./ec2-cfn.yml ./${DIR_NAME}/${OBJECT_KEY}
            - pipe: atlassian/aws-s3-deploy:0.4.5
              variables:
                S3_BUCKET: '${BUCKET_NAME}'
                LOCAL_PATH: './${DIR_NAME}'
```
###3.2 Bitbucketのコンソール > Deployments にて「Add variables」をクリックする
![図13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/7c8f8c47-a6b0-f600-2e4b-c554dbd2fead.png)

###3.3 Add Variables > Repositoryにて以下の変数を追加する
|Name  |Value  |
|---|---|
|AWS_ACCESS_KEY_ID  |手順2-1-1の6で記録したアクセスキーID  |
|AWS_SECRET_ACCESS_KEY  |手順2-1-1の6で記録したシークレットアクセスキー  |
|AWS_DEFAULT_REGION　 |ap-north-east1 （S3バケット作成時のリージョンと合わせる） |

![図14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/a37164b5-dc2a-1f96-526d-0681a0364b10.png)

##4. SSMドキュメント作成
###4-1. Systems Managerのコンソール > ドキュメント にて「自己所有」をクリックし、「オートメーションを作成する」をクリックする
![図22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/825e5769-d6f1-4ac7-45ad-3f2d72f990ad.png)

###4-2. 「名前」に「Create-EC2」と入力し、「エディター」をクリックした上で、「編集」をクリックする
![図23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/a60bf5b4-8e5b-c093-fd12-f33d51b94f24.png)

###4-3. ポップアップの「OK」をクリックする
![図24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/fbb6efff-8885-c007-5974-7d7756d3b60a.png)

###4-4. 「ドキュメントビルダー」に以下のymlを修正して入力し、「オートメーションを作成する」をクリックする

```yml
description: Launch EC2 Instance by creating CloudFormation stack
schemaVersion: '0.3'
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
  AutomationAssumeRole:
    type: String
    description: (Required) The ARN of the role that allows Automation to perform the actions on your behalf.
    default: 'arn:aws:iam::{{global:ACCOUNT_ID}}:role/SSMServiceRole'  
mainSteps:
  - name: CreateStackForEC2
    action: 'aws:createStack'
    inputs:
      TemplateURL: 'https://nkrk-automation-test.s3-ap-northeast-1.amazonaws.com/test/ec2-cfn.yml' #CFn Templateファイル配置先のS3のURLに置き換え
      StackName: 'ec2-test'
```
![図25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/7ff2cd6e-08cc-92cc-b34e-f9324614dae9.png)


詳細は[SSMドキュメントでCloudFormation Stackを作成してみた](https://qiita.com/nkrk/items/03995b41a024a38bb38e)参照。

##5. Event Bridge
###5-1. EventBridgeのコンソール > ルール にて、「ルールを作成」をクリックする。
![図32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/c2c67051-8d76-15cc-fb05-ef39fefedf03.png)

###5-2. 以下を指定して、「作成」をクリックする。
- 名前：「InvokeSSMAutomation_Create-EC2」を入力
- パターンを定義：「イベントパターン」を選択
- イベント一致パターン：「カスタムパターン」を選択し、イベントパターンに以下のJSONを修正して入力し、「保存」をクリック

```json
{
  "detail-type": [
    "AWS API Call via CloudTrail"
  ],
  "source": [
    "aws.s3"
  ],
  "detail": {
    "eventSource": [
      "s3.amazonaws.com"
    ],
    "requestParameters": {
      "bucketName": [
        "<手順1で作成したS3バケット名に置き換え>"
      ],
      "key": [
        "<Bitbucketから受け取るCFn TemplateファイルのS3内のパスを指定。手順3-1のbitbucket-pipelines.ymlの指定内容と一致させる必要あり>"
      ]
    },
    "eventName": [
      "PutObject",
      "CompleteMultipartUpload",
      "CopyObject"
    ]
  }
}
```
- ターゲット：「SSM Automation」を選択
- ドキュメント：手順4で作成したSSMドキュメント「Create-EC2」を指定
- ロール：「この特定のリソースに対して新しいロールを作成する」を選択し、名前を「EventBridgeServiceRole」に変更

![図33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/8940ab8a-037b-217e-308c-20d81e34b116.png)

##6. CloudTrail
###6-1.「証跡の作成」をクリックする
![図15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/8855505f-8d0b-5669-3cb0-a43c24773ce8.png)

###6-2.設定を何も変更せずに「証跡の作成」をクリックする
![図18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/c2961c5d-c11e-0b44-3043-206faa210a28.png)

###6-3. 作成した証跡「management-events」をクリックする
![図21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/6b939246-5b88-42fc-c55b-ec3dcd51a895.png)

###6-4. 「データイベント」の「編集」をクリックする
![図19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/759b29be-6937-db3e-b3c3-a9d8749758ca.png)

###6-5. 以下の内容を設定して、「変更の保存」をクリックする
-  「イベントタイプ」の「データイベント」にチェックを入れる
-  「現在および将来のすべてのS3バケット」の読み取りと書き込みのチェックを外す
-  「バケット/プレフィックス」に「xxxx-automation-test/test/ec2-cfn.yml」と入力し、読み取りのチェックを外す
![図20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/00dcc77a-2f7c-62ca-24d8-dac435b4d0d3.png)


#実行の手順
###1. Bitbucketのコンソール > コミットにて、S3にファイルを送付に利用するCommit IDをクリックする
![図34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/b4499d7c-d9dd-db37-3d92-2bdb06b782d4.png)

###2. 「+」をクリックし、「タグ名」に「automation_test*（*以降は任意の文字列）」と入力し、「タグの作成」をクリックする
![図35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/12db5058-59b7-6a9c-695f-eb38d5556d1b.png)


###3. Bitbucketのコンソール > Pipelines より、作成したタグ名により開始されたBitbucket Pipelinesの「Status」が「Successful」になることを確認する
![図36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/4a886e5f-7117-f7f6-d776-6081fe354b96.png)


###4. Systems Managerのコンソール > 自動化 よりSSMドキュメント「Create-EC2」の実行が開始され「成功」することを確認する
![図38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/1ef88562-57e8-ea25-3820-a4d454c2f27b.png)


###5. EC2 > インスタンス より「automation-test」という名前のインスタンスの状態が「実行中」となることを確認する
![図37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271327/bf3455df-119d-27d7-da94-051db447b3a5.png)