超ミニマムなチュートリアルを通してCodeDeployを理解する。

CodeDeployチュートリアルに失敗した記事のリベンジです。

wordpressとかdbとか余計なものをいれる過程でエラーして本質が見えなくなるのが嫌だったので内容を削ぎ落として超ミニマムなチュートリアルを勝手に作りました。
apacheが動いてるEC2にindex.htmlをデプロイするという簡単な進行でCodeDeployを理解していきます。

# そもそもの理解。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/9d5eb216-5662-37e1-e4e4-ee35e82a2a6d.png)

上図、クラメソさんを参考にパクりました（本家参考：[CodeDeployによるバージョン管理とステージング](https://dev.classmethod.jp/cloud/aws/versionig_and_staging_via_codedeploy/)）

CodeDeployはデプロイ対象のデータをS3等に置いておき、手動/自動のタイミングでEC2へ流すというものです。その過程で、シェルを動かしたりなど細かな設定をS3に同時に格納するappspec.ymlに書くことで実現できます。（ここでEC2にS3への適切なアクセス権が必要。）
CodeDeployではアプリケーションという一番大きな単位がありその中にデプロイメントグループ、デプロイメントという粒度で構成されています。

__デプロイメント__
デプロイメントというのが、1回1回のデプロイに当たります。それぞれに結果や処理時間、エラーなどが見れます。

__デプロイメントグループ__
デプロイメントグループとは、どこにデプロイするのか（EC2）、どこにデータはあるのか（S3＝リビジョン）、デプロイ方式は何かなどを設定しておき、これをもとにデプロイメントを行います。（デプロイメントの設計図のようなものかな？）

__リビジョン__
デプロイするデータがどこに置かれているかを指し示すものです。S3上に、デプロイするデータをバージョン管理していき、リビジョンでそれらの最新版を指定することで新しい物をデプロイすることができます。

__アプリケーション__
これらのデプロイ作業を機能単位などで分けるためにある？と思います。

# まずはやってみる。ミニマムなチュートリアル。

# IAMユーザを作成する。

アクセスキー/シークレットキーを設定しAWS CLIからAWSへアクセスできるユーザを1つ作成する必要あり。このユーザには少なくとも以下のポリシーを割り振ってください。

* AWSCodeCommitFullAccess (多分不要かも)
* AWSCodeDeployFullAccess
* AWSS3FullAccess
* AWSIAMFullAccess

# EC2インスタンスを用意する。

Amazon Linuxを用いてインスタンスを作成。

```
AMI: Amazon Linux
インスタンスタイプ：任意のものでOK
IAMロール：`S3FullAccess`を含むロールを当てること。
EBS：デフォルトのままでOK
タグ：のちのち必要になるのでのでインスタンスには名前をつけてあげましょう。今回は`codedeploy`と命名。
セキュリティ：SSHとHTTP(S)が必要になります。以下を参考に。

SSH → マイIP(xx.xxx.xxx.xxx/32)
HTTP、HTTPS → 任意(0.0.0.0/0)
```

pemファイルはssh接続時に必要です。

# EC2にApacheを入れる。

sshで侵入します。

```
$ ssh -i Download/xxx.pem ec2-user@xx.xxx.xxx.xxx
Warning: Permanently added 'xx,xxx,xxx,xxx' (ECDSA) to the list of known hosts.
       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
13 package(s) needed for security, out of 26 available
Run "sudo yum update" to apply all updates.
$ sudo yum update -y
$ sudo yum install httpd -y
$ sudo service httpd start
Starting httpd:                                            [  OK  ]
```

IPv4 パブリックIPにアクセスするとApatchのデフォルトのページが出迎えてくれることを確認します。

# EC2にCodeDeploy用のAgentを入れる。
前回ここがうまくいかないポイントの一つだったのですが、公式にちゃんとAmazon Linux用のセットアップ方法がありました。（参考：https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/codedeploy-agent-operations-install-linux.html)

なお、下記は東京リージョンでのケースなので、他のリージョンを使う場合には、install元のURIを適切なものに変える必要があります。（→[一覧](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/resource-kit.html#resource-kit-bucket-names)）

```shell
$ sudo yum install ruby
$ sudo yum install wget
$ cd /home/ec2-user
$ wget https://aws-codedeploy-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto
$ sudo service codedeploy-agent status
The AWS CodeDeploy agent is running as PID 14515
```

EC2の準備はOKです。

# デプロイするファイルを準備する。

ローカルでやることは3つです。

* AWS CLIを入れるorアップデートする。
* デプロイ対象のindex.htmlを作成する。
* CodeDeployのデプロイ設定を記述したappspec.ymlを作成する。

## AWS CLIを入れるorアップデートする。

まだ入れていない場合は、以下公式参照。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html

```shell:アップデート
$ sudo pip install --upgrade awscli
```

## index.html/appspec.ymlを作成する。

```
$ mkdir testCodeDeploy
$ cd testCodeDeploy
$ touch index.html
$ vi index.html    ←"Hello World!"とでも書いて保存。
$ touch appspec.yml
$ vi appspec.yml   ←中身は下記。
```

```yaml:appspec.yml
version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/html
```

今回は超ミニマムな内容なので、デプロイのみです。本当は`hooks`でインストール前後や要所要所のタイミングを指定して任意のシェルを実行したりする設定も可能です。→この辺は今後やります。

* `source`はリビジョン（＝S3バケット）の中のデプロイするファイルの位置を示します。
* `destination`にはデプロイ先を指定します。

# S3バケットの準備

デプロイ対象を上げるS3バケットを作成します。

* バケット名はここでは`testcodedeploy`とします。
* パブリックアクセスはすべてブロック。（＝デフォルトの設定）

バケットポリシーを以下のように設定します。
アクセス元を示す`Principal`にはローカル環境のAWS CLI configureに設定したアクセスキー/シークレットキーのIAMユーザのARNを書くこと。後々、AccessDeniedで悲しい気持ちになります。
なお、IAMユーザのARNはIAMコンソール画面からコピペできます。


```:バケットポリシー
{
    "Version": "2012-10-17",
    "Id": "ExamplePolicy01",
    "Statement": [
        {
            "Sid": "testcodedeploy",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::xxxxxxxxxxxx:user/xxx",
                    "arn:aws:iam::xxxxxxxxxxxx:user/xxx"
                ]
            },
            "Action": [
                "s3:GetObject",
                "s3:GetBucketLocation",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::testcodedeploy/*",
                "arn:aws:s3:::testcodedeploy"
            ]
        }
    ]
}
```

# デプロイ

現在、testCodeDeployディレクトリの中身は以下のようになっているはずです。

```shell
$ ls
appspec.yml	index.html
```

まずは、アプリケーションを作成します。ここでは`Test`とします。

```shell
$ aws deploy create-application --application-name Test
{
    "applicationId": "f33128d3-81bd-49f7-9b97-28f635995xxx"
}
```

デプロイ対象をzip化して、まるごと先ほど作成したS3バケットへ放り込みます。（1つのコマンドでできる。）
ここではファイル名を`testCodeDeploy.zip`とします。

```shell
$ aws deploy push \
--application-name Test \
--s3-location s3://testcodedeploy/testCodeDeploy.zip \
--ignore-hidden-files
To deploy with this revision, run:
aws deploy create-deployment --application-name Test --s3-location bucket=testcodedeploy,key=testCodeDeploy.zip,bundleType=zip,eTag=2dbc9aab3836f21317a9eed7a36f1xxx --deployment-group-name <deployment-group-name> --deployment-config-name <deployment-config-name> --description <description>
```

なお、この処理は以下のことをやっているのと同じです。（参考：[CodeDeploy pushコマンドは裏で何をやっている?](https://dev.classmethod.jp/cloud/aws/codedeploy-push-behind-the-scenes/)）

```shell:上記のコマンドと一緒
$ zip -r testCodeDeploy.zip .
$ aws s3api put-object --bucket testcodedeploy --key testCodeDeploy.zip --body testCodeDeploy.zip
$ aws deploy register-application-revision \
  --application-name Test \
  --s3-location bucket=testcodedeploy,key=testCodeDeploy.zip,bundleType=zip
```

デプロイメントグループの作成です。ここでは`TestGroup`という命名をします。

```shell
$ aws deploy create-deployment-group \
--application-name Test \
--deployment-group-name TestGroup \
--deployment-config-name CodeDeployDefault.OneAtATime \
--ec2-tag-filters Key=Name,Value=codedeploy,Type=KEY_AND_VALUE \
--service-role-arn arn:aws:iam::xxxxxxxxxxxx:role/CodeDeployServiceRole
{
    "deploymentGroupId": "d4b9e016-b175-49eb-af9f-6ee6c0c55xxx"
}
```

`deployment-config-name`ではデプロイ方式を選択しています。
`service-role-arn`には、AWSがデフォルトで提供しているロール`CodeDeployServiceRole`を使用。

いよいよデプロイです。

```shell
aws deploy create-deployment --application-name Test \
--deployment-config-name CodeDeployDefault.OneAtATime \
--deployment-group-name TestGroup \
--s3-location bucket=testcodedeploy,bundleType=zip,key=testCodeDeploy.zip
{
    "deploymentId": "d-18TVA1BXD"
}
```

これで実際に対象のEC2の`/var/www/html`下に`index.html`が置かれているはずです。
先ほどアクセスしたEC2のIPアドレスにブラウザからアクセスしてみましょう。`Hello World！`が表示されれば成功です。

実際に`CodeDeploy`コンソールから`デプロイメント`を見てみると成功となっているでしょう。

# おしまい

今後は、AutoScalingグループやlambdaへの応用、appspec.ymlの探索をやっていこうと思います。

# 追記

続編書きました。

* [超ミニマムなチュートリアルを通してCodeDeployを理解する2(AutoScaling編)](https://qiita.com/Kept1994/items/8abeeb8fa0980f95ab56)

# トラブルシューティング

1) create-applicationで失敗する。

```shell
$ aws deploy create-application --application-name Test
ModuleNotFoundError: No module named 'yaml'
```

`$ pip install pyyaml`しろと書かれているページを発見。実行するも効果なし。
結局AWS CLIをアップデートしたところ解消しました。

2) `deploy push`でAccessDeniedとなる。

```shell
$ aws deploy push \
--application-name Test \
--s3-location s3://testcodedeploy/testCodeDeploy.zip \
--ignore-hidden-files
Failed to upload '.' to 's3://testcodedeploy/testCodeDeploy.zip': An error occurred (AccessDenied) when calling the PutObject operation: Access Denied
```

AccessDeniedに弾き倒される。S3のバケットポリシーの設定なので、自身のAWS CLIのconfigureとそのIAMユーザがS3へアクエスできるバケットポリシー設定になっているかを確認してください。

3) デプロイされない！（deploymentIdは返されている。）

→ `CodeDeploy`コンソールから`デプロイメント`を見て失敗していないか確認します。失敗の場合、エラー原因が出ているのでそれを修正します。
→ もしくは、EC2上からログを辿っても見られます。特に`unknown error`などであればここを見てもいいかも。

```
$ tail -f -n 200  /var/log/aws/codedeploy-agent/codedeploy-agent.log

2019-12-17 13:30:46 ERROR [codedeploy-agent(14520)]: InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Error during perform: Aws::S3::Errors::AccessDenied - Access Denied 
```

こんな感じでエラーの理由がわかります。なお、上記は、EC2からS3へのアクセス許可がないためにでたエラーでした。EC2のロールにS3FullAccessをアタッチして再実行すると解決しました。