CodeCommit+CodeBuild+CodeDeploy+Lightsailでデプロイの自動化(CodePipeline)

CodeCommit+CodeBuild+CodeDeploy+EC2という記事は結構ありますが、Lightsailはなかったようなので記事にします。Lightsailも`ssh`で接続できるので基本的にEC2の場合と同じです。

公式の記事です(英語)。この記事ではソースにGithubを選択しています。
[Using AWS CodeDeploy and AWS CodePipeline to Deploy Applications to Amazon Lightsail](https://aws.amazon.com/jp/blogs/compute/using-aws-codedeploy-and-aws-codepipeline-to-deploy-applications-to-amazon-lightsail/)

# 環境

## Lightsail

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/590a3de5-e065-51f8-d368-1b54315af357.png" width="65%" />

- Ubuntu 16.04.6
- Apache 2.4.41
- PHP 7.3.15
- ruby[^1]
- wget[^1]

[^1]: Codedeployエージェントをインストールするために必要です。`wget`はあると思いますが、`ruby`はないかもしれません。ない場合は`apt-get`でインストールしてください。

```shell
$ cat /etc/issue
Ubuntu 16.04.6 LTS \n \l

$ httpd -v
Server version: Apache/2.4.41 (Unix)
Server built:   Feb 27 2020 12:22:22

$ php -v
PHP 7.3.15 (cli) (built: Feb 27 2020 12:33:13) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.15, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.15, Copyright (c) 1999-2018, by Zend Technologies

$ ruby -v
ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]

$ wget -V
GNU Wget 1.17.1 built on linux-gnu.
...
```

# デプロイまでのフロー

コードのプッシュ～デプロイまでのフローは @leomaro7 さんの記事[AWS Code シリーズ][code-flow]の通りです。ここでは最後のデプロイ先がLightsailになります。

1点補足すると、CodeCommitに`push`されるとCloudWatchのイベントが発火し、CodepPipelineが起動されるようになっています。このCloudWatchイベントはCodepPipelineを作成する際に作られます。

[code-flow]:https://qiita.com/leomaro7/items/41cbe8aa7c32298ec665#aws%E3%81%AEcode-%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA

# 作業リスト

1. S3バケット作成
2. IAMロール作成
1. IAMユーザー作成
1. CodeDeployエージェントのインストール
1. CodeCommitのレポジトリ作成
1. CodeBuildプロジェクトの作成
1. CodeDeployの作成
1. CodePipelineの作成

# S3バケット作成

CodeBuildによってコンパイルなどがされたアーティファクトの格納先になるS3バケットを作成します。パブリックアクセスをすべてブロックにチェックを付けてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/21ca9de6-4d2b-965e-1427-34906f19741a.png)

# IAMロール作成

CodeDeployにアタッチするIAMロールを作成します。新規作成からユースケースでCodeDeployを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/e04a2193-0f84-ac6d-24de-8ba5b88fadb9.png)

# IAMユーザー作成

LightsailのサーバーがS3に保存されたアーティファクトを取得するためのIAMユーザーを作成します。このユーザーはAWSコンソールにはログインできず、S3へのアクセス権限しか持たせません。

- アクセスの種類を「プログラムによるアクセス」のみにして、コンソールのアクセスは許可させません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/c5d717dc-f4d3-b4dd-850d-77b78e5590e0.png)

- 先程作成したS3バケットへのアクセス権のあるIAMポリシーを新規作成してをIAMユーザーにアタッチします。
 
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": [
                "arn:aws:s3:::<作成したバケット名>/*",
            ]
        }
    ]
}
```

# CodeDeployエージェントのインストール

Lightsailにsshでログインして、CodeDeployエージェントをインストールします。

## CodeDeployエージェントのアクセスユーザー設定

上記で作成したユーザーの設定ファイル(codedeploy.onpremises.yml)を作成します。

- Access Key ID<br>
e.g. AKAFUUCHINOHOTOCOCOA7
- Secret Access Key<br>
e.g. yCX0nl9aPqSUNtWLnqM8euLcAupOWTWFUg1g+8r/
- IAM User ARN<br>
e.g. arn:aws:iam::012345678901:user/LightSailCodeDeployUser

```shell
$ mkdir /etc/codedeploy-agent/
$ mkdir /etc/codedeploy-agent/conf
$ cat <<EOT >> /etc/codedeploy-agent/conf/codedeploy.onpremises.yml 
---
aws_access_key_id: <Access Key ID>
aws_secret_access_key: <Secret Access Key>
iam_user_arn: <IAM User ARN>
region: ap-northeast-1
EOT
```

## CodeDeployエージェントをインストール

aws-codedeploy-ap-northeast-1.s3.ap-northeast-1.amazonaws.comの`aws-codedeploy-ap-northeast-1`の部分はインストーラーの置いてあるS3バケット名で、ap-northeast-1はリージョンになっています。（一応、東京リージョンにしていますが、どこでもよさそう？）
[Ubuntu Server 用の CodeDeploy エージェントをインストールする](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/codedeploy-agent-operations-install-ubuntu.html)

```
$ wget https://aws-codedeploy-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto

```

インストール後、起動確認をします。AWSのドキュメントには「The AWS CodeDeploy agent is running」と表示されるとありますが、私の環境では以下のように表示されました。
これで起動できています。もし起動していない場合は、`sudo service codedeploy-agent start`で起動してください。

```
sudo service codedeploy-agent status
● codedeploy-agent.service - LSB: AWS CodeDeploy Host Agent
   Loaded: loaded (/etc/init.d/codedeploy-agent; bad; vendor preset: enabled)
   Active: active (exited) since Sat 2020-07-25 03:18:20 UTC; 6s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 10122 ExecStart=/etc/init.d/codedeploy-agent start (code=exited, status=0/SUCCESS)

Jul 25 03:18:20 ip-172-26-8-250 systemd[1]: Starting LSB: AWS CodeDeploy Host Agent...
Jul 25 03:18:20 ip-172-26-8-250 codedeploy-agent[10122]: Starting codedeploy-agent:
Jul 25 03:18:20 ip-172-26-8-250 systemd[1]: Started LSB: AWS CodeDeploy Host Agent.
```

## CodeDeployにインスタンスを登録

CodeDeployにLightsailのインスタンスを登録します。
codedeploy.onpremises.ymlの設定と同じIAMユーザーとリージョンを指定します。

```
aws deploy register-on-premises-instance --instance-name <Lightsailのインスタンス名> --iam-user-arn <IAM User ARN> --region ap-northeast-1

aws deploy add-tags-to-on-premises-instances --instance-names <Lightsailのインスタンス名> --tags Key=Name,Value=CodeDeployMyLightsail --region ap-northeast-1
```

コマンド実行後オンプレミスインスタンスに登録されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/34d514ee-0e8c-534e-bb5b-dce694cf2678.png)
詳細画面です。ここのタグが重要で、CodeDeployのデプロイ先の判断に使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/3ba88e29-2065-2081-c5ec-050b52ba5093.png)

# CodeCommitのレポジトリ作成

これについては省略します。
[AWS CodeCommit リポジトリの作成](https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/how-to-create-repository.html)

# CodeBuildプロジェクトの作成

- ソースはCodeCommitを選択します。ブランチやタグ、IDを選択できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/12ae8fbb-a196-f964-397b-76987dfad0b8.png)
- 環境はこんな感じにしました（正直どうするべきかよくわかっていないです）。ロールは上で作成したロールを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/d2a62cb4-cb94-e9dc-1f93-713460e8c368.png)


- Buildspecは「buildspec ファイルを使用する」を選択しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/c76e37d2-5788-21d2-2ff3-18cbdb71b540.png)

## buildspec.yml
buildspec.ymlを作成し、レポジトリのルートに配置します。このファイルを見てCodeBuildがテストやビルドを行います。ファイル内に書かれているパスはbuildspec.ymlファイルからの相対パスです。
[CodeBuild のビルド仕様に関するリファレンス](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-spec-ref.html)

- 「phases - install - runtime-versions」以下をビルドする環境にインストールされます。Javaの場合なら、`java: openjdk8`などになります。
- 「phases - commands」を上から順番に実行します。composerをインストール&実行して、phpunitでテストを行うようにしています。echo以下は実行画面に標準出力されるので、後で確認用に使用できます。
- 「artifacts - files」はビルド後にS3にアップするファイルを指定します。「/*」を指定した場合、その階層のファイルが全て対象になりますが、サブディレクトリーは対象になりません。「\*\*/\*」とするとサブディレクトリーまで対象になります。
後述しますが、Codebuildで使用するappspec.ymlとappspec.ymlから呼ばれるシェルスクリプト(hooks以下)は忘れずに入れてください。

```yaml:buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      php: 7.3
  build:
    commands:
      - cd app/composer
      - composer install
      - ./vendor/bin/phpunit ../tests/
      - echo Build started on `date`
artifacts:
  files:
    - app/composer/composer.json
    - app/composer/composer.lock
    - app/**/*
    - hooks/**/*
    - appspec.yml
```

# CodeDeployの作成

## アプリケーションを作成
コンピューティングプラットフォームは、今回Lightsailなので「EC2/オンプレミス」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/cc9027ca-8e3c-8908-2d36-04369f5e81e9.png)

## デプロイグループを作成
デプロイグループはデプロイ方法の設定です。

- サービスロールには上で作成したIAMロールを選択します。
- デプロイタイプはデプロイの方法です。「インプレース」を選択しました。
- 環境設定はデプロイ先になります。先程Lightsailを登録したオンプレミスインスタンスを選択します。タグはインスタンスに付けた「CodeDeployMyLightsail」とします。
- デプロイ設定はデフォルトの「AllAtOnce」。
- Load balancerは未使用です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/007e6a8a-0512-ce67-7519-373aa250e1cb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/e776243f-8a4d-d79d-efa6-62cf1c0e80c8.png)

## appspec.yml

appspec.ymlを作成し、レポジトリのルートに配置します。このファイルを見てCodeDeployがデプロイを行います。
[CodeDeploy AppSpec File のリファレンス](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/reference-appspec-file.html)

hooksでイベント前後に処理をフックできます。ここではInstall後にcomposerを実行しています。CodeDeploynのInstallイベントとはS3からダウンロードした一時ファイルを本来の位置に配置することです。

```yaml:appspec.yml
version: 0.0
os: linux
files:
  - source: app/oauth
    destination: /home/bitnami/htdocs/oauth
  - source: app/composer
    destination: /home/bitnami/htdocs/composer
permissions:
  - object: /home/bitnami/htdocs
    owner: bitnami
    group: bitnami-admins
hooks:
  AfterInstall:
    - location: hooks/after_install.sh
```
```shell:after_install.sh
#!/bin/bash

cd /home/bitnami/htdocs/composer
composer update --no-dev
```

# CodePipelineの作成
ここまでで作成したS3、CodeCommit、CodeBuild、CodeDeploy、LightsailをCodePipelineで繋げます。ここはあまり迷うことが無いと思います。CodeCommitで変更がプッシュされたら起動するようにします。

## CodePipelineを動かしてみる

CodeCommitにプッシュすると、CodePipelineが動いてデプロイされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/26091018-e3e2-76da-53f6-41e1fba6fbd9.png)

## CloudWatchの内容

Codecommit作成時に自動作成されるCloudWatch EventはCodeCommitのmaster(ブランチの指定は可能です)にプッシュされるとCodePipelineがトリガーされるようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29324/98a6ccef-219e-c581-48e6-a959e633f141.png)

```json:登録されたイベントパターン
{
  "source": [
    "aws.codecommit"
  ],
  "detail-type": [
    "CodeCommit Repository State Change"
  ],
  "resources": [
    "arn:aws:codecommit:ap-northeast-1:0000000000000:<brunch名>"
  ],
  "detail": {
    "event": [
      "referenceCreated",
      "referenceUpdated"
    ],
    "referenceType": [
      "branch"
    ],
    "referenceName": [
      "master"
    ]
  }
}
```