EC2へのデプロイをCodePipelineでCICD

# 1. CodeCommit

AWS環境でGitリポジトリを利用できるサービス。GitHubみたいなもの。
実体としてはバージョニングされたS3で`codepipeline-ap-northeast-1-000011112222`のようなバケットが作成されている。

## 1-1. CodeCommitでコード管理

__前提__ 

* Admin権限のIAMユーザを作成している。(adminである必要はないがとりあえず面倒なので)
* コンソールに上記ユーザでサインインしている。
* gitインストール済。

### ■ 秘密鍵と公開鍵の作成

```sh
$ ssh-keygen -t rsa -C "hogehoge@gmail.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/hoge/.ssh/id_rsa): ./codecommit_rsa
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in codecommit_rsa.
Your public key has been saved in codecommit_rsa.pub.
The key fingerprint is:
SHA256:SG3cs50mRNPFKE0AMJP0s0rij9QUnh3fQRhaPENVSsY hogehoge@gmail.com
The key's randomart image is:
+---[RSA 2048]----+
|         .* + .B#|
|     = *.%.  o.oB|
```

### ■ IAMユーザと紐付け

IAM > ユーザ > 自分のユーザ名 > 認証情報(タブ) > AWS CodeCommitのSSHキー に公開鍵をUploadする。以下で中身をまるっとコピペする。

```
$ cat ~/.ssh/codecommit_rsa.pub
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/719897bd-250e-b0f8-7e2b-87a19c5c5482.png)

アップロードするとSSHキーIDが発行される。

### ■ configファイルの設定

必須ではないと思うが設定しておくと便利。
`User`はIAMにキーをUploadしたときに発行されるSSHキーID。

```:.ssh/cinfig
Host git-codecommit.*.amazonaws.com
  User APKA****************
  IdentityFile ~/.ssh/codecommit_rsa
```

configファイルを作成した場合はアクセス権限を666にする。

### ■ リポジトリ作成

CodeCommitコンソールから`リポジトリを作成`をクリックし、リポジトリ名を入力。

ローカルにクローンしたいので、接続した後クローン。
今回はSSH接続する。(CodeCommitコンソールから丸ごとコピペでok)

```
$ ssh git-codecommit.リージョン名.amazonaws.com
Warning: Permanently added the RSA host key for IP address '**.***.***.**' to the list of known hosts.
Enter passphrase for key '/Users/hogehoge/.ssh/codecommit_rsa': 
You have successfully authenticated over SSH. You can use Git to interact with AWS CodeCommit. Interactive shells are not supported.Connection to git-codecommit.ap-northeast-1.amazonaws.com closed by remote host.
Connection to git-codecommit.ap-northeast-1.amazonaws.com closed.

$ git clone ssh://git-codecommit.リージョン名.amazonaws.com/v1/repos/リポジトリ名
Cloning into 'リポジトリ名'...
Enter passphrase for key '/Users/hogehoge/.ssh/codecommit_rsa': 
warning: You appear to have cloned an empty repository.

# Reactアプリを作成
$ npx create-react-app testapp
$ tree -L 1
.
├── README.md
├── node_modules
├── package-lock.json
├── package.json
├── public
└── src

$ git add .
$ git commit -m "first commit"
$ git push
```

# 2. CodeBuild

ビルド環境。
指定の場所からビルド対象のファイルを取得し、コンテナを立てて指定されたコマンドを順次実行する。
ここで指定するコマンド等はbuildspec.ymlで定義し、ビルド対象のファイルと同じ場所に置いておく。
ビルド結果などの成果物(アーティファクト)を指定のS3に送る。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/5ab46a29-1730-f140-ca1e-af98e5cf0ffc.png)

## 2-1. CodeCommitのコードをビルドする。

### ■ buildspec.yml作成

ビルドの設定を書くファイル。
設定できることは、基本以下3つ。

* パラメータや環境変数の設定
* インストール時、ビルド前、ビルド時、ビルド後の各フェーズでの設定/実行したいコマンド。
* 出力の設定。ビルドした成果物(アーティファクト)の何をどこに送るのか。

今回は以下のように指定。使用するランタイムを指定し、npmの準備。`build`階層以下のディレクトリだけを全て送る。

```yml:buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 10
  pre_build:
    commands:
      - npm update -g npm
      - npm install
  build:
    commands:
      - npm run build
artifacts:
  files:
    - 'build/**/*'
```

(なお、拡張子がyamlだと認識されなかったみたいな話を聞いたことがある。試してはいない。)

上記の設定ファイルを最上位の階層に配置してCodeCommitに追加する。

```
$ tree -L 1
.
├── README.md
├── buildspec.yml    # <- New!
├── node_modules
├── package-lock.json
├── package.json
├── public
└── src

$ git add .
$ git commit -m "add buildspec"
$ git push
```

### ■ ビルドプロジェクトの作成

* 「ソース」ではCodeCommitの自身のリポジトリを選択する。なお、ここでビルド対象をどれにするかを設定できる。今回はmasterブランチを対象として追いかけてもらう。
* 「環境」はDockerコンテナのスペックなどを指定する。特になんでもいい。
    - osはUbuntu、イメージはStandardで最新のversionを選択。
* 「Buildspec」では先ほど作成したファイルがどこにあるかを指定する。(デフォルトはルートから探してくれる。)
* 「アーティファクト」では出力先を指定。S3。バケットはあらかじめ作っておいてそれを指定する。
    - 「アーティファクトのパッケージ化」はzipを指定。この先使うCodeDeployはzipしてないと受け付けてくれないので。

### ■ ビルドを手動実行

コンソールからビルド実行。
正常に終了するとS3の出力先バケットにzipが作成されている。試しにダウンロードして中を見てみると正しくビルドできているかを確認できる。

# 3. CodeDeploy

デプロイ処理の実行。
指定の場所からファイルを取得し、EC2やオンプレサーバにデプロイする。AutoScalingしている場合はELBなど関連のリソースもいい感じに調整できる。
デプロイ先のEC2にAgentを仕込んおくことが前提であり、デプロイの実行をメインで行うのはこのAgent自身なので、ここでの設定はどこにArtifactがあるか位しか設定しない。
取得したArtifactの中の何をどのパスにデプロイするかなどはappspec.ymlで定義される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/c3ddbde5-8985-2fe9-0ca9-8ea4107e1a97.png)

つまり、EC2には事前にAgentを入れ、S3にアクセスするロールが当たっていないとデプロイに失敗する。

__用語整理__

__デプロイメント(デプロイ)__ : 1回1回のデプロイ自体を指す。それぞれのデプロイにはランダムな英数字のIDが振られ、各デプロイを区別できるようになっている。CodeDeployのデプロイメントからこれらのステータス(成功/失敗)や内容を閲覧することができる。

__デプロイグループ__ : デプロイする先(タグで指定)や、デプロイ方法(Blue&Greenなど)を指定したもの。

__リビジョン__ : デプロイに必要なデータ類をパッケージにした一まとまりのもの。CodeDeployではデプロイの仕方やファイル類を一つにまとめたリビジョンという形でデプロイ対象のEC2に配布し、中のAgentがデプロイを行う。

__アプリケーション__ : デプロイグループとリビジョンを束ねたもの。

## 3-1. ビルドしたファイルをEC2にデプロイする。

### ■ デプロイ先のEC2の用意

* `Amazon Linux 2 AMI (HVM), SSD Volume Type`
* インスタンスサイズは何でもいい。t2.microで実施。
* IAMロールはS3FullAccessとCodeDeployFullAccessを当てたロールを当てる。(本来は`AmazonEC2RoleforAWSCodeDeploy`ポリシーをアタッチしたロールを当てるので十分らしい。)
* ポート80は開けておく。デプロイの確認としてブラウザからアクセスするため。

* CodeDeployからデプロイするためにエージェントを入れる必要があるためSSHで入り設定。ついてにWebサーバも用意。

```:sshでEC2に入りセットアップ
$ ssh -i Download/xxx.pem ec2-user@xx.xxx.xxx.xxx
$ sudo yum update -y
$ sudo yum install httpd -y
$ sudo service httpd start
Starting httpd:                                            [  OK  ]
$ 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
$ sudo chkconfig httpd on
```

SSHするのが面倒であれば`高度な詳細`で初回起動時に実行するコマンドを列挙してもOK

```sh
#!/bin/sh

sudo yum update -y
sudo yum install ruby -y
sudo yum install wget -y
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 yum install httpd -y
sudo service httpd start
sudo chkconfig httpd on
```

なお、AmazonLinux2ではデフォルトでSSMagentが含まれているので、EC2のロールに`AmazonEC2RoleforSSM`をアタッチした状態で起動するとSSMのセッションマネージャからアクセス可能になる。(=22番ポートを開けなくてもssm-userとしてコンソールからインスタンスにアクセスできるようになる。)

### ■ appspec.yml作成

Artifactの中身のbuild以下を丸ごと`/var/www/html`配下に置く。

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

```
$ tree -L 1
.
├── README.md
├── appspec.yml    # <- New!
├── build
├── buildspec.yml
├── node_modules
├── package-lock.json
├── package.json
├── public
└── src
```

### ■ buildspec.yml編集

EC2ではappspec.ymlに従ってデプロイ処理を行うので、CodeDeployに指定したS3バケットにはappspec.ymlが含まれていないといけない。
ビルド時の出力にappspec.ymlを含めるように追記する。

```yml:build.yml
version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 10
  pre_build:
    commands:
      - npm update -g npm
      - npm install
  build:
    commands:
      - npm run build
artifacts:
  files:
    - 'build/**/*'
    - 'appspec.yml'      # <- New!
```

### ■ アプリケーション(CodeDeploy)の作成

コンソール上でアプリケーション名を決めて作成。

|設定項目|設定内容/値|
|:---:|:---:|
|アプリケーション名|_ApplicationName_|
|コンピューティングプラットフォーム|EC2/オンプレミス|

### ■ デプロイグループ(CodeDeploy)の作成

上記で作成したアプリケーションを選択し、同じくコンソールからデプロイグループの作成を選択。

* `AWSCodeDeployRole`ポリシーをアタッチしたIAMロールを作成する。
* デプロイグループを作成する。

|設定項目|設定内容/値|
|:---:|:---:|
|デプロイグループ名|_DeployGroupName_|
|サービスロール|上記作成したIAMロールを指定|
|環境設定|EC2/オンプレミス|
|キー|Name|
|値|デプロイ対象のインスタンス名|
|デプロイ設定|CodeDeployDefault.OnceAtTime|
|Load balancer|ロードバランシングを無効にする|

### ■ ビルド手動実行&デプロイ手動実行

```
$ git add .
$ git commit -m "add appspec"
$ git push
```

コンソールよりビルドを実行(先ほどと同じ手順)。S3のzipファイルが上書きされ更新される。
続いて、CodeDeployのデプロイメントグループからデプロイを作成。ここではArtifactの場所(S3のバケット名とzip名)のみ指定して作成(=デプロイ実行)
成功し、EC2の80番ポートにアクセスしてReactのサンプルアプリが見れたら確認OK

# 4. CodePipeline

コードのソースへのアクションをトリガに種々の処理を走らせる。
CodeCommitへのPushをトリガとしてEC2へのデプロイを自動化する。

## 4-1. CodePipelineの作成 (&実行)

CodePipelineを作成する。
パイプライン名を入力し、サービスロールはここで作成する。
ソースやビルド環境、デプロイ先はこれまでに作成したものを指定する。

作成完了するとデプロイされる。また、これ以降は`git push`によってもCodeCommitのmasterブランチを更新することでもデプロイが走る。コンソールから成功したことを確認したらブラウザからEC2にアクセスしてみる。

## 4-2. 承認プロセスの追加

好き勝手デプロイされると困るのでビルド後にデプロイするまでに承認を追加する。
作成したパイプラインの`編集`から`ステージを追加`。→`アクショングループの追加`を選択し、`アクションプロバイダ`から`手動承認`を選択。
なお、SNSを利用してE-mailなどで承認リクエストを送る場合は予めSNSトピックとサブスクリプションの設定を行っておくこと。
他は必要項目を適当に埋めて保存するだけ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/c7f4d173-5fcf-dffb-0d44-676b0f84bdc0.png)

試しに背景を変えてpushし、承認メールが来るかを確認。

```
$ git add .
$ git commit -m "change background-color"    
$ git push
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/b153adb3-77ea-4966-4e1c-fa1e199306c1.png)

ここでのメッセージはCodePipelineの履歴から見れる。
なお、誰が承認したのかのユーザ名などはここには出ないのでCloudTrailのイベント履歴から`PutApprovalResult`イベントの実行ユーザを確認すると見れる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/5db9982d-b1bd-8dd7-ad71-cb5cc24894a2.png)

また、承認ステージで`却下`を選択されるとパイプラインは承認ステージで`失敗した`となる。

## 4-3. 再デプロイ(ロールバック)

一度デプロイしたパッケージに不具合が見つかり以前のversionにロールバックする必要があるような場合に実施する。
CodePipelineは一連の実行を管理するので、部分的に実行したりはできないため、CodeDeployから過去のデプロイを直接実行(=デプロイのみ実行)する。
CodeDeployで過去にデプロイしたリビジョンはzipでS3に保存されている(デプロイ履歴の一覧から閲覧できる)。

これを再利用して新たにデプロイし直すことで実質過去のversionにロールバックする。
デプロイ履歴からロールバック対象を選択し`コードのデプロイ`を実行。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/79168693-ff25-a20a-efa7-9bd3ad4aa1dc.png)

## 4-4. AutoScalingGroup&EC2のデプロイ(ELBなしver)

AutoScalingGroup(ASG)に属するEC2にデプロイする場合。

### ■ ASGの起動テンプレート作成

大事なのは`高度な設定`において毎回起動時にwebサーバを起動できるようにすることと、agentを入れておくこと。それ以外はなんでも。

|設定項目|設定内容/値|
|:---:|:---:|
|起動テンプレート名|任意の名前|
|AutoScaling のガイダンス|☑︎|
|AMI|任意(ここではAmazonLinux2)|
|インスタンスタイプ|任意(ここではt2.micro)|
|キーペア(ログイン)|任意|
|ネットワーク設定-VPC|デフォルト|
|ネットワーク設定-SG|任意|
|高度な詳細-IAMインスタンスプロフィール|S3へのアクセス権を持つIAM|
|ユーザーデータ|以下|

```sh
#!/bin/sh

sudo yum update -y
sudo yum install ruby -y
sudo yum install wget -y
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 yum install httpd -y
sudo service httpd start
sudo chkconfig httpd on
```

### ■ ASG作成

特にCICDに特化した設定はなし。

|設定項目|設定内容/値|
|:---:|:---:|
|ASG名|任意|
|起動テンプレート|上記で作成したもの|
|インスタンスの購入オプション|起動テンプレートに準拠する(デフォルト)|
|ネットワーク設定-VPC|デフォルト|
|ネットワーク設定-サブネット|全て選択|
|ロードバランシング|なし(デフォルト)|
|グループサイズ|希望1 (最小0〜最大2)|
|スケーリングポリシー|なし|

### ■ アプリケーション(CodeDeploy)の作成

→前と同じ

### ■ デプロイメントグループ作成(変更点のみ)

EC2へのデプロイと大差ない。デプロイ対象をASGにするだけ。

|設定項目|設定内容/値|
|:---:|:---:|
|デプロイタイプ|インプレース(Blue/Greenを選ぶとELB必須となってしまう)|
|環境設定|Amazon EC2 Auto Scaling グループ (上で作成したASGを選択)|
|Load balancer|無効|

### ■ デプロイ作成

→前と同じ

デプロイ後にASGのインスタンス数を増やすと、ちゃんとデプロイ後の設定でデプロイされていることが確認できる。
なお、ASGのコンソールを見ると、以下のようなイベントが紐づけられているのが確認できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/de18cd96-ed06-33a1-a390-209d02656bc7.png)

CodeDeployのターゲットとしてASGを指定するとスケールアウトの際に新規インスタンスにLifeCycleHook(CodeDeploy-managed-automatic-launch-deployment-hook)を自動的に登録してくれるため、ASGのインスタンスにcodedeploy-agentを起動する設定のみしておけば良い。

### ■ CodePipelineの設定。

4-3と同じように設定する。`CodeCoomit`と`CodeBuild`はそのまま、上で作成した`CodeDeploy`を指定する。

## 4-5. AutoScalingGroup&EC2のデプロイ(ELBありver)

### ■ IAMへのASGアクセス権限追加

CodeDeployにアタッチするロールには以下のポリシーが必要。(今回はEC2FullAccess、IAMFullAccessを与えて実行した。)

* AWSCodeDeployRole
* ec2:RunInstances
* ec2:CreateTags
* iam:PassRole

https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/getting-started-create-service-role.html

### ■ ターゲットグループの作成

|設定項目|設定内容/値|
|:---:|:---:|
|プロトコル|HTTP|
|ポート|80|

### ■ ALBの作成

|設定項目|設定内容/値|
|:---:|:---:|
|スキーム|インターネット向け|
|リスナー|HTTP 80|
|AZ|全て選択|
|セキュリティグループ(SG)|任意のSGを設定。|
|ターゲットグループ|上記で作成したもの|

### ■ ASGの作成(ELBに紐づける)

ロードバランサの選択のところで`既存のロードバランサーにアタッチ`を選択する。
ASGのSGには上で設定したALBのSGからのport80番へのアクセスを許可しておく。

### ■ アプリケーション(CodeDeploy)の作成

→前と同じ

### ■ デプロイメントグループ作成(変更点のみ)

|設定項目|設定内容/値|
|:---:|:---:|
|デプロイタイプ|Blue/Green|
|環境設定|Amazon EC2 Auto Scaling グループの自動コピー (上で作成したASGを選択)|
|デプロイ設定|すぐにトラフィックを再ルーティング<br/>デプロイグループの置き換え元インスタンスを終了(時間指定)|
|デプロイ設定|CodeDeployDefault.AllAtOnce|
|Load balancer|Application Load Balancer またはNetwork Load Balancer|

### ■ デプロイ作成

→前と同じ

デプロイ完了後、ALBに対してブラウザからアクセスするとデプロイ後のReactの画面が待ち受けていることがわかる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/bbbaff70-7714-3e91-b366-67f2858c1544.png)

### ■ CodePipelineの作成

4-3/4-4と同じように設定する。`CodeCoomit`と`CodeBuild`はそのまま、上で作成した`CodeDeploy`を指定する。

なお、これまでに設定したパイプラインも動いてしまうのでそれを止めておくには`移行を無効にする`を押すとpendingにできる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/42f5e6e9-8de6-a939-d18e-4e2e6bed790e.png)

pendingにしたデプロイがあるとパイプラインとしては`進行中`のステータスとなるので、このデプロイを破棄したい場合はインバウンド実行の詳細から`停止して中止`を選択する。パイプラインの表示上はこの実行が残るが、ステータスは`停止`となっており、次にパイプラインを走らせると中止した実行は表示されなくなる。

# 4-6. EC2/ELB/ASG構成で再デプロイする。

EC2インスタンスに直でデプロイする時と同じように旧リビジョンに再デプロイしてみる。
同じように`履歴`から戻したいリビジョンを選らび再度デプロイすることで実行可能。