【AWS】FargateとCodePipelineを使った話【CICD】

# TL;DR
CodePipelineでCodeCommitからECR、ECSへデプロイするために必要な
設計ポイントが書いてあります。
**実際の構築手順を期待している方には残念な記事です。**

# 事のはじまり
よくあるオーダーなんですが
**「今まで手作業だった業務をシステム化したい」**
に追加して
**「コンテナ化してCICDで自動的にデプロイしたい」**
というオーダーになり
 ~~突貫工事で作ることになったので~~　やっつけてきました。

# やったこと
* CodeCommitをリポジトリにしたよ
* CodeBuildでテストコードを実行させたよ
* CodeBuildでdocker buildしてECRにPUSHしたよ
* CodeCommitでマージしたら**自動で**Fargateまでローリングデプロイする仕組みにしたよ
* 本番環境が別AWSアカウントでも自動デプロイ対応だよ
* SSMパラメータストアを活用することでマルチアカウント間でもパイプラインが動作するよ

# 用意するもの
* AWSアカウント
* dockerfile
* イメージ定義ファイル
* buildspec.yml

# 詳しく掘らないもの
* webコンソールの操作方法
* pytestの使い方
* コンパイル言語でビルドした出力アーティファクトをパイプラインに乗せてデプロイする方法
* オシャンなコンテナの実装
* オシャンなパイプラインの構成
* オシャンなbuildspec.ymlの実装
* イケてるNginxの設定
* イケてるuWSGIの設定
* ABデプロイ
* デプロイ種別で変わるイメージ定義ファイル
* 1Container1process

# 構成
## 使用するアカウント種別
1. 開発環境AWSアカウント
1. 本番環境AWSアカウント

※開発と本番のアーティファクトのデータ移動は手動でやるものとします。
※ステージング環境が増えても、やることは変わらんです。

## 使用するAWSのサービス
1. VPC
1. ECS/Fargate
1. ECR
1. SystemsManager
1. ELB
1. CodeCommit
1. CodeBuild
1. CodePipeline
1. S3
1. IAM

### １．VPC
イイカンジに作ってください。
Public Subnet、Private Subnetがあるなら
**Private SubnetはNATゲートウェイもしくはVPCエンドポイントが必須です**

### ２．ECS/Fargate
常時稼働するか？バッチ的にタスクが起動するだけか？で設定が変わります。
#### ①常時起動が必要なコンテナ
1. クラスター
1. サービス
1. タスク定義

いわゆるWEBサービス的に起動するサービス。
このあたりの設定が必要です。
タスク定義を作ってクラスター作ってサービスを作ります。
※先に下記ELBから作っておくと面倒が少なくてよいかも。

#### ②バッチ的に起動するコンテナ
1. クラスター
1. タスク定義

バッチ処理の場合EventBridgeから起動することが多いとおもいます。
その場合
**イベントソース→EventBridge→ECS/Fargate**
という経路になるため、ECSサービスを定義する必要はないです。

### ３．ECR
ECRへのPUSHをトリガーにFargateへデプロイします。
ECRリポジトリを作っといてください。

### ４．SystemsManager
パラメータストアに辞書形式で色々と保存しておくと、
開発/商用間の環境差分を吸収するのがシンプルに楽です。

アプリ側もパラメータストアを参照してDB等のエンドポイントを参照するようにしてください。
**DBへのアクセスをIAMで認証する場合、ユーザ情報の保存は不要です。**

### ５．ELB
FargateがローリングデプロイするためのALBとターゲットグループを作成しておいてください。
コンテナがデプロイされるまで、ターゲットは空っぽでよいです。

### ６．CodeCommit
リポジトリ作っておいてください。
パイプラインが発火するブランチ名を決めておくとイイカンジです。

- 特定ブランチは消させない
- 特定ブランチへのPUSHを拒否する

といった運用はIAMロールで制御してください。

### ７．CodeBuild
テスト実行用のコンテナを用意しておくと色々と使い勝手が良いですが、
このあたりは設計に合わせてください。

また、CodeBuildに渡される入力アーティファクトはenvに渡されます。
何度か```echo $env```などを繰り返して、入念に期待する動作するか？を試してください。

- webコンテナ
- APPコンテナ
- testexeコンテナ

こんなような構成が好きです。

Buildspec.ymlは別個に書くのはあんまり好きくなくて
CodeBuildには軽く書いといて、実際の処理はbashなどに書くのが好きです。
Dockerのデーモン化に結構苦しめられたので、UPしておきます。

```bash
version: 0.2

phases:
  install:
    commands:
      - nohup /usr/bin/dockerd -H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock &
      - timeout 15 sh -c "until docker info; do echo .; sleep 1; done"
  pre_build:
    commands:
       - git config --global credential.helper '!aws codecommit credential-helper $@' &&git config --global credential.UseHttpPath true &&git config --global user.email "example@example.com" &&git config --global user.name "UNKO"
  build:
    commands:
      - bash boot-ctrbld.sh
```

ちなみにgit


### ８．CodePipeline
ざっくりですが、下記ステージの構成を取るとイイカンジになります。
アプリソースとコンテナdockerfileを1まとめのブランチに置いている前提で進めています。
#### ①開発環境
##### パイプラインA
- Source：CodeCommit

アクション名：source
変数名：source
出力アーティファクト：source
CodeCommitの発火したいブランチ名をターゲットにしてください。

- Build：CodeBuild

アクション名：build
変数名：build
入力アーティファクト：source
出力アーティファクト：build
codebuildで起動したコンテナを使って
pytestを実行したあと、dockerfileに```docker build```と```docker push```します。
ここでテストしてビルドが通る事を確認しておきましょう。

- Deploy：S3

アクション名：deploy
変数名：deploy
入力アーティファクト：**source**

S3へのデプロイソースをSourceアーティファクトにしているにはワケがあります。

- 商用環境パイプラインにアーティファクトを送るとき、開発で取得した構成やら何やらを変えたくない。
- 別にビルドしたりコンパイルしているワケではない。
- どうせDockerfileも送るんだし、素の状態で送っとけ
- **テストが通ったアーティファクトさえ送れるならヨシ！**

##### パイプラインB
- Source：ECR、S3

アクション名：source01
変数名：source01
出力アーティファクト：source01

アクション名：source02
変数名：source02
出力アーティファクト：source02

このsource02と呼んでいる箇所がイメージ定義ファイルになります。
SystemsManagerやCodeCommit内に置いても読み込むんちゃうかと思いましたが、
S3に置いてるパターンが多いのでS3に置いてます。
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/file-reference.html


- Deploy：ECS
アクション名：deploy
変数名deploy
入力アーティファクト：source01、source02

これでサービス登録されたECSクラスターにデプロイが可能となります。
サービス登録していない場合へのデプロイは、パイプラインAでECRにPushしたと思いますが
その時点で作業終了です。
ローリングデプロイしたい場合はサービス化しておく必要があるって感じですね。

#### ②本番環境
##### パイプライン
- Source01：S3

どうにかして開発パイプラインAでS3にデプロイした成果物を
本番S3バケットに置いてください。

- Build：CodeBuild

CodeBuildで入力アーティファクトに対して下記の処理を行ってください。

1. DockerfileをbuildしてECRにPUSHしてください。

- Source02：ECR、S3

開発環境と同じく、ECRとイメージ定義ファイルを指定してください。

- Deploy：ECS

ECSにデプロイして終了です。

# 反省点

時間が無くて、コンテナのブランチとAPPのブランチを分けちゃったのが結構面倒でした。[^1]
[^1]: 分けるメリットもあると思いますが、パイプライン追うのが面倒でした。
結局DockerImageBuildするときAPPファイルも組み込むんだし、まとめられるパイプラインはあるなぁ、と。[^2]
[^2]: ※そういう仕組みにしてるなら別ですけどね。起動時にCodeCommitから持ってくるみたいな。ただ起動時にCodeCommitから持ってくる分遅くないっすか？コンテナ重くなりませんか？という。

次作るときは下記気を付けたいです。

- CodeCommitでマージするときブランチ消させたくない。
- デフォルトブランチにPushすんな権限付けたい。
- イメージ定義ファイルをS3じゃなくて、パラメータストアとかリポジトリに格納する方法を調べる。
- CodeBuildは本当に色々やれそう

こうして書き起こすとクッソ面倒ですけど
CodeCommitからターゲットまで「えい！」でデプロイできるのは本当に強いので
インフラメンバーとアプリ共通メンバーに置かれましては
ぜひ導入をご検討ください。