CircleCI+Code4兄弟でFargateで動くGoプログラムをデプロイする

# はじめに

Go言語で書かれたプログラムをFargateでサービス実行していて、そこにCI/CDを設定する
手順の紹介記事です。
構成は以下の通り

* GitHub
* CircleCI
* CodePipeline(CodeCommit/CodeBuild/CodeDeploy)
* ECS(Fargate)

CircleCIを経由しないフローでもいいですが、CircleCI 2.1での新しい書き方を試したかった
のもあり、こんな構成にしました。 参考：[`CircleCI2.1` Qiita検索結果](https://qiita.com/search?q=CircleCI+2.1&sort=rel)
なお、手順の紹介に重点を置いたので、各種設定ファイルについては概要説明に止めます。

# 前提

## 環境

* QA/Production環境それぞれ別のAWSアカウントで利用
* 東京リージョン(ap-northeast-1)を利用
* FargateでサービスとしてGoプログラムを実行している
* CirceCIのconfig.ymlはversion2.1を使用
    * executors/commands/parametersを使いたいので
* Golangのバージョンは1.13~
    * Go Modulesを使っているので

## デプロイフロー

GitHubでは３種類のブランチを使います。
それを、以下のように動作するよう設定します。

* masterブランチ
    * developブランチのマージ先
    * PRマージされるとProduction環境のCodeCommitへPushされる
    * CodeCommitへのPushをトリガにCodePipelineによりECSへデプロイされる
* developブランチ
    * 開発用ブランチのマージ先
    * 開発を始める場合はここから新しい開発用ブランチを作成
    * PRマージされるとQA環境のCodeCommitへPushされる
    * CodeCommitへのPushをトリガにCodePipelineによりECSへデプロイされる
* 開発用ブランチ(ここではfeature_hogeブランチ)
    * GitHubにPushされるとLint/Testが実行される

なので、マージの流れは、 feature_hoge -> develop -> master の順

### イメージ図
![スクリーンショット 2019-10-23 19.18.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/5e7e7957-eafb-a000-2860-e8cd0c740830.png)


## 今回利用するGoプログラム

[sample-go](https://github.com/miyaz/sample-go)
プログラムの内容は適当（個人的な検証プログラム）です。
ECSにサービス登録して実行する前提なので、デーモンとして動く実装であればどんなコードでもOK。

### ファイル構成

```terminal:sample-go
├── .circleci
│   └── config.yml   # CircleCI設定ファイル
├── Dockerfile       # Goプログラム実行用コンテナ作るやつ（マルチステージビルド！！）
├── Makefile         # Goプログラムのビルドに使う
├── bin              # リポジトリには含まれません
│   └── sample-go    # make bin/sample-go を実行して生成されるバイナリ
├── buildspec.yml    # CodeBuildが実行するコマンドを記述したファイル
├── main.go
├── main_test.go
├── go.mod
└── go.sum
```

CI設定に関わる下記ファイルの概要説明

* .circleci/config.yml
    * GitHubへのPushをトリガに動くCircleCIワークフローの設定ファイル
* buildspec.yml
    * CodeBuildが実行するコマンドなどを指定したファイル
    * docker build してイメージをECRへPushするという内容
* Dockerfile
    * Goプログラムのビルド、及び実行バイナリを格納したコンテナイメージを作成するDockerfile
    * buildspec.yml内で指定されている

# 手順

## GitHub - CircleCI 連携設定

### https://circleci.com/ にアクセス

まだGitHub - CircleCI 連携設定していない場合は下記手順を実施

#### GitHubアカウントでログイン
下記ボタンをクリック
<img width="300" alt="スクリーンショット 2019-10-21 15.30.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/4de345c5-7352-6a22-b9b2-90f3fdbf614a.png">

#### CircleCIからGitHubへのアクセスを許可

下記のような確認画面が表示されるので画面下の `Authorize circleci`ボタンをクリック

<img width="300" alt="スクリーンショット 2019-10-21 15.30.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/aa9eb226-ff8e-d262-f66a-71f1370e13a8.png">

### CIをセットアップするリポジトリを指定

https://circleci.com/add-projects/gh/{GitHubアカウント名} にアクセス

<img width="700" alt="スクリーンショット 2019-10-21 15.35.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/c253773e-e6f7-9e93-74d3-f2b5e3065bab.png">
対象リポジトリの`Set Up Project`ボタンを押す

Set Up Project 画面からビルドを一度実行する  

* Operating System -> Linux  
* Language -> Go  
を選択し、Next Stepsセクションの `Start building` をクリック  
<img width="800" alt="スクリーンショット 2019-10-21 15.58.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/68bacb51-dfcc-cfe1-9054-db13413d2e81.png">

初回はエラーになるがこのまま進めます

### CI(Lint/Test)動作確認

#### `feature_hoge`ブランチを作成してPush

```terminal
git checkout -b feature_hoge
git push origin feature_hoge
```

https://circleci.com/gh/{GitHubアカウント名}
にアクセスして上記のPushをトリガに最新Job(一番上)が動いていることを確認します。

<img width="1187" alt="スクリーンショット 2019-10-23 10.59.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/dff19071-6d30-0e2b-7fbb-0decb78a4e61.png">

SUCCESSであれば、Lint/Testにパスしたということになります。
もし、FAILEDであれば、対象のJobをクリックして、Lint/Test結果を確認し、必要な修正を行ってください。

以降の設定は、QA/Productionの両方のAWSアカウントで実施する必要があります。
**QA用AWSアカウントの手順のみ記載しますが、Productionにも同様に設定する必要があります**

## Fargate設定

### ECRにリポジトリを作成してコンテナイメージをPush

```terminal
# awsクレデンシャルのプロファイルを指定
export AWS_PROFILE={プロファイル名}

# ECRにリポジトリを作成
aws ecr create-repository --region ap-northeast-1 --repository-name sample-go

# コンテナイメージをECRにPush
ACCOUNT_ID=$(aws sts get-caller-identity | jq -r .Account)
$(aws ecr get-login --no-include-email --region ap-northeast-1)
docker build -t sample-go .
docker tag sample-go:latest $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/sample-go:latest
docker push $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/sample-go:latest
```

### Fargate設定（詳細割愛）

参考記事：[AWS ECRとECSの入門(Fargate編)](https://casualdevelopers.com/tech-tips/how-to-deploy-dockerized-react-application-to-aws-with-ecr-and-ecs-fargate/)

ここでは下記を作成した前提で進めます

* クラスタ名：sample-go
* サービス名：sample-go
* タスク定義名：sample-go
    * コンテナ定義のイメージURL：123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/sample-go:latest

## CodeCommit設定

CodePipelineのトリガとなる、CodeCommitの設定を行います。
CI設定時に、事前にCodeCommitにブランチを作成する必要があるのでそのセットアップも行います。

### CodeCommitにPushするためのIAMユーザ作成

#### IAMユーザ作成

ユーザ名：codecommit-pusher(任意)
アクセスの種類：`プログラムによるアクセス`にチェック
アタッチポリシー：AWSCodeCommitPowerUser
※ここでは権限広めですが、適切に絞ってくださいー

後は、デフォルト値で進めてユーザ作成まで実行

#### CodeCommitのSSHキーを作成

注）ここで発行するSSHキーは、CodeCommitブランチ作成、及びCircleCIのSSH Permissionsでも使用します

ローカル端末でSSHキーペアを作成します
(後述するCircleCIへの鍵アップロードではPEM形式にしか対応していないので `-m pem`を忘れずに！）

```terminal
cd ~/.ssh/
ssh-keygen -t rsa -b 2048 -f codecommit-pusher-key -m pem
ls codecommit-pusher-key*
→ codecommit-pusher-key, codecommit-pusher-key.pubが存在することを確認
```
（鍵ファイル名を変えて、QA/Production用に別々に発行してください）

IAMユーザ画面の`認証情報`タブを開き、`SSHパブリックキーのアップロード`ボタンをクリックし
codecommit-pusher-key.pubの内容を貼り付けてアップロードする
<img width="733" alt="スクリーンショット 2019-10-23 15.16.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/d3fcba56-6bc1-a28c-6cf6-f81a647f5429.png">

表示されたSSHキーIDを控えておいてください

### CodeCommitリポジトリ作成

* AWSマネジメントコンソールからCodeCommit画面を開き、`リポジトリを作成`ボタンをクリック
* リポジトリ名に `sample-go` を入力して `作成`ボタンをクリック

#### developブランチを作成する

GitHubとCodeCommitでコミット履歴が同じ状態である必要があるので、git remoteにCodeCommitリポジトリを追加
する形でブランチを追加します。

```terminal
# CodeCommitにgitでアクセスできるようにssh_config設定
IAM_SSH_KEY_ID={SSHキーIDを指定}
echo -e "Host git-codecommit.*.amazonaws.com\n  User ${IAM_SSH_KEY_ID}\n  StrictHostKeyChecking no\n  UserKnownHostsFile=/dev/null\n  IdentityFile ~/.ssh/codecommit-pusher-key" >> ${HOME}/.ssh/config

# githubからcloneしたsample-goローカルリポジトリへ移動
cd sample-go
# CodeCommitのリポジトリをremote追加
git remote add codecommit ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/sample-go
# developブランチをPush
git checkout develop
git pull codecommit develop
git push codecommit develop
git remote remove codecommit
```
（Productionの場合はmasterブランチに読み替えてください）

### CodeCommitにPushするためのCircleCI設定

.circleci/config.ymlを見るとわかりますが、今回QA/ProductionでAWSアカウントが別れている関係で
CircleCIからPushするCodeCommitは２アカウント分となるため、SSHキー＆秘密鍵が２つ必要になります。
今回は、環境ごとにdeploy_qaとdeploy_prodの２つのジョブに分けて、**IAM_SSH_KEY_ID_QA / IAM_SSH_KEY_ID**という
環境変数で使用するSSHキーを使い分ける形にしました。

```yaml:.circleci/config.ymlの該当箇所抜粋
  deploy_qa:
　　　〜略〜
    steps:
      - checkout
      - run:
          name: deploy for qa
          command: |
            echo -e "Host git-codecommit.*.amazonaws.com\n  User ${IAM_SSH_KEY_ID_QA}\n  StrictHostKeyChecking no\n  UserKnownHostsFile=/dev/null" >> ${HOME}/.ssh/config
            git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/sample-go develop

  deploy_prod:
　　　〜略〜
    steps:
      - checkout
      - run:
          name: deploy for production
          command: |
            echo -e "Host git-codecommit.*.amazonaws.com\n  User ${IAM_SSH_KEY_ID}\n  StrictHostKeyChecking no\n  UserKnownHostsFile=/dev/null" >> ${HOME}/.ssh/config
            git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/sample-go master
```

#### CircleCIの環境変数追加

`Project Settings`画面（https://circleci.com/gh/{GitHubアカウント名}/sample-go/edit） にアクセス
`Environment Variables`をクリック
`Add Variable`ボタンをクリック
IAM_SSH_KEY_ID/IAM_SSH_KEY_ID_QAという環境変数として、QA/Production環境で発行したSSHキーIDを追加します

<img width="645" alt="スクリーンショット 2019-10-23 16.39.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/549acae5-766d-31e1-10cf-c6b51d78e45d.png">

#### CircleCIのSSH鍵追加

`Project Settings`画面（https://circleci.com/gh/{GitHubアカウント名}/sample-go/edit） にアクセス
`SSH Permissions`をクリック
`Add SSH Key`ボタンをクリック
`Private Key`欄に codecommit-pusher-keyの内容を貼り付けます（Hostnameは空欄でOK）
`Add SSH Key`ボタンをクリック

QA/Production環境用に発行した２つのキーを登録します

<img width="622" alt="スクリーンショット 2019-10-23 17.24.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/4c251525-ded1-6225-5d28-6b0fb0658c46.png">

## コードパイプライン設定

### コードパイプライン作成

AWSマネジメントコンソールからコードパイプライン画面を開き、`パイプラインを作成する`ボタンをクリック

#### パイプラインの設定を選択する

パイプライン名を入力して、`次に`をクリック
ロール名は自動的に入力されます（手入力は不要です）
<img width="782" alt="スクリーンショット 2019-10-23 14.13.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/b66297be-a478-0952-52dd-5f947cddeee8.png">

#### ソースステージを追加する

ソースプロバイダーに`AWS CodeCommit`を選択
リポジトリ名に`sample-go`を指定し、ブランチ名に`develop`を指定して、`次に`をクリック

<img width="784" alt="スクリーンショット 2019-10-23 15.35.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/0be1c66a-18b1-b4a9-9a9b-93af12d14703.png">

#### ビルドステージを追加する

プロバイダー：AWS CodeBuild
リージョン：アジアパシフィック（東京）
を選択し、`プロジェクトを作成する` ボタンをクリック

#### ビルドプロジェクトを作成する

プロジェクト名：sample-go
環境イメージ：マネージド型イメージ
オペレーティングシステム：Ubuntu
ランタイム：Standard
イメージ：aws/codebuild/standard:2.0
特権付与：チェックOn
サービスロール：新しいサービスロール
ロール名：codebuild-sample-go-service-role
※あとで権限変更するため、ロール名を控えておくこと

<追加設定>を開いて環境変数として下記入力

| 名前 | 値 | 入力 |
|------|-----|------|
| AWS_DEFAULT_REGION | ap-northeast-1 | プレーンテキスト |
| AWS_ACCOUNT_ID     | 123456789012   | プレーンテキスト |

以降、Buildspec、ログ設定はデフォルトのまま `CodePipelineに進む`をクリック
`ビルドステージを追加する`画面に戻るので、`次に`ボタンをクリック

<img width="600" alt="スクリーンショット 2019-10-23 15.56.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/f4fe3240-649c-2324-a538-41951aa17a1d.png">
<img width="600" alt="スクリーンショット 2019-10-23 15.57.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/91f614cd-30f4-c62a-0dcc-f5fe22c78be3.png">
**この画像には、特権付与にチェック入っていませんが必要です m(_ _)m**
<img width="600" alt="スクリーンショット 2019-10-23 15.57.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/f8f5f9ec-e2d0-3252-9857-37f977ae3717.png">
<img width="600" alt="スクリーンショット 2019-10-23 15.57.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/e3d3ab71-ac2d-f736-3fa6-c97530639139.png">
<img width="600" alt="スクリーンショット 2019-10-23 15.58.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/f0d4a0c9-f90d-f3a2-e514-ed9d7eae917f.png">

#### デプロイステージを追加する

デプロイプロバイダーに`Amazon ECS`を選択
リージョン：アジアパシフィック（東京）
クラスター名：sample-go
サービス名：sample-go
`次に`ボタンをクリック

<img width="400" alt="スクリーンショット 2019-10-23 16.10.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/36db9a3d-3084-9cac-261b-19891eb62914.png">

確認画面が表示されるので、`パイプラインを作成する`ボタンをクリック
パイプラインが作成されると、最初のパイプライン処理が実行されます。
まだ設定完了ではないのでエラーになりますが、気にせずに。

## ECRアクセス権限付与

CodeBuildがビルドしたイメージをECRにPushするための権限を付与します。

### IAMロール[codebuild-sample-go-service-role]に権限追加

※デフォルトでは、codebuild-{プロジェクト名}-service-roleというロール名になります
IAMコンソールで、上記ロールを開きます
`アクセス権限`タブを開きます
`AmazonEC2ContainerRegistryPowerUser`ポリシーをアタッチします
※ここでは権限広めですが、適切に絞ってくださいー

<img width="420" alt="スクリーンショット 2019-10-23 16.21.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/e5ee7e53-acee-4ef1-c1da-3daf166d25fc.png">

## CI/CD動作確認

feature_hogeブランチをPushした際に、golangci-lintやgo testの結果がNGであれば、その旨GitHubのプルリク上
で表示されます。CircleCIのジョブ画面でエラー詳細が確認できます。
developブランチにMergeした時には、自動的にQA環境のECS(Fargate)のサービスが更新（デプロイ）されます。
また、手順省略しましたが、Production環境用の設定も行えば、masterにMergeした時に、自動的にProductionの
ECS(Fargate)のサービスが更新（デプロイ）されるようになります。

# まとめ

ビルドが早い、マルチステージビルドでコンテナイメージがコンパクトにできる、ワンバイナリで動く、ということで
もともとGoとコンテナ実行の相性はいいですが、これにCI/CDができるとさらにデリバリスピードが上がるので最高ですねー。
さらに、サーバの存在を意識しなくてよいECS(Fargate)へのデプロイも、Code4兄弟が面倒をみてくれるので、
開発者だけでなく、SREにもメリットのあるかなりよい構成なので、今後もこの構成推しでいこうと思っています。

あと、今回は長々とコンソールを操作する手順を紹介しましたが、本来こういう設定はCloudFormationでテンプレートを
作って自動でセットアップするのがいいと思います（実際に、弊社でもテンプレート化してそれを使って構築しています）
ですが、コンソールで操作してみるとAWS/GitHub/CircleCIの連携部分の理解が進むので、一度やってみるのを
オススメします！