GolangのWebアプリケーションをECSにデプロイするCI環境をCodeCommit、CodeBuild、CodePipelineでつくる【cloudpack大阪ブログ】


cloudpack大阪の佐々木です。
内部で開発しているGolangのWebアプリをECSで稼働させるように、CI環境をつくってみました。
パブリックのgithubとかであれば、CircleCIとかでもう少しいい感じにできるとおもうのですが、ローカルのGitリポジトリで開発しているため、パブリックアクセスできないCodeCommitを使う形にしました。

# 概要

動作イメージは下記のような感じです。

![Kobito.HvgCcI.png](https://qiita-image-store.s3.amazonaws.com/0/3246/d41c87a7-f55c-052b-b798-acd86865ba1b.png "Kobito.HvgCcI.png")

1. CodeCommitにプッシュ
2. CodePipelineでコミットを検知し、CodeBuildを起動
3. CodeBuildでコンパイルし、実行ファイルをビルド、S3に保存
4. S3に保存後、ECSのサービスの既存タスクを停止
5. サービスが新しいタスクを起動
6. 新しいコンテナがS3から実行ファイルをダウンロードし、実行

# 環境
## アプリケーション
もとのアプリケーションのファイルは下記のような感じです。

```
.
├── app
│   ├── model
│   ├── util
│   └── view
├── bindata.go
├── glide.yaml
├── server.go
└── static
    └── js
```

## リージョン
CodeCommitが東京リージョン未対応のため、すべて`us-east-1`で作成します。

# 設定
## CodeCommitリポジトリの作成
### リポジトリの作成
リポジトリ名をgotestにします。
![Kobito.WZjQG3.png](https://qiita-image-store.s3.amazonaws.com/0/3246/e843ae4d-4aa1-c872-43a2-eb46d84b7144.png "Kobito.WZjQG3.png")

### CodeCommitリポジトリにアクセスするユーザを作成
IAMでユーザを作成します。
AccessKeyを使ったHTTPSでのアクセスも可能ですが、SSH接続でやってみます。
ユーザを作成したら、認証情報のタブを開いて、`AWS CodeCommitのSSHキー` の `SSH 公開キーのアップロード` をクリックし、SSH公開鍵を貼り付けます。
![Kobito.zYMfwr.png](https://qiita-image-store.s3.amazonaws.com/0/3246/3824bb8a-22ab-58bc-59c6-9faef0be53d4.png "Kobito.zYMfwr.png")

アップロードすると、`SSHキーID`が発行されます。
![Kobito.7LDjWt.png](https://qiita-image-store.s3.amazonaws.com/0/3246/e8d264df-24ac-00f1-90bf-bdd7484bbfe0.png "Kobito.7LDjWt.png")

SSHのconfigに情報を追加します。

```bash:~/.ssh/config
Host git-codecommit.*.amazonaws.com
    User `SSH キー ID`
    IdentityFile ~/.ssh/秘密鍵
```
コードをpushしておきます。

```bash:
$ git push origin master                                                          
Counting objects: 41, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (34/34), done.
Writing objects: 100% (41/41), 73.27 KiB | 0 bytes/s, done.
Total 41 (delta 3), reused 0 (delta 0)
To ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/gotest
 * [new branch]      master -> master
```

## Buildしたファイル保存用のS3バケットを作成

保存用S3バケットを作成します。

![Kobito.hjTh2t.png](https://qiita-image-store.s3.amazonaws.com/0/3246/6995c104-58b8-988e-dad3-cbe5a79ea88e.png "Kobito.hjTh2t.png")


## CodeBuildプロジェクトを作成

下記のようなプロジェクトを作成します。

![Kobito.KHdvNj.png](https://qiita-image-store.s3.amazonaws.com/0/3246/3f212c54-ee41-fe65-af03-6d8ace2c1091.png "Kobito.KHdvNj.png")

環境イメージはあらかじめ用意されているGolangのものを使用しました。
当初、自分でコンテナを作成し、ビルド＋Dockerコンテナ作成＋ECRにプッシュという感じでやりたかったのですが、独自コンテナでDocker on Docker はできない(下記参照)、DockerイメージにGolangをインストールするとビルドのたびに時間がかかりすぎるということで、独自コンテナはあきらめました。
https://forums.aws.amazon.com/thread.jspa?messageID=761184


ロールには自動で生成されるPolicyに、ECSのタスクをコントロールするために`AmazonEC2ContainerServiceFullAccess`を追加しています。


## CodeBuild用のファイルを作成
下記の2つのファイルを作成します。

```yaml:buildspec.yml
version: 0.1
 
phases:
  install:
    commands:
      - go get github.com/Masterminds/glide
      - go get -u github.com/jteeuwen/go-bindata/...    
  build:
    commands:
      - ./build.sh
  post_build:
    commands:
      - aws s3 cp /go/src/git.local/sasaki/gotest/server s3://gotest-pkg/server
      - aws ecs stop-task --task `aws ecs list-tasks --cluster $ECS_CLUSTER_NAME --service-name $ECS_SERVICE_NAME --region $AWS_DEFAULT_REGION --query taskArns --output text` --cluster $ECS_CLUSTER_NAME --region $AWS_DEFAULT_REGION
```

```bash:build.sh
mkdir -p $GOPATH/src/git.local/sasaki/gotest
cp -r app $GOPATH/src/git.local/sasaki/gotest
cp -r static $GOPATH/src/git.local/sasaki/gotest
cp server.go $GOPATH/src/git.local/sasaki/gotest
cp bindata.go $GOPATH/src/git.local/sasaki/gotest
cp glide.yaml $GOPATH/src/git.local/sasaki/gotest
cd $GOPATH/src/git.local/sasaki/gotest
glide up
go-bindata app/view
GOOS=linux GOARCH=amd64 go build server.go bindata.go
```

`buildspec.yml` がCodeBuildの設定ファイルになります。
CodeBuildeでGolang用のコンテナを起動しますので、goコマンドは使える状態になっています。
まずinstallフェーズでgolangの`glide`と`go-bindata`をインストールします。

次にbuildフェーズで`build.sh`を実行します。
開発時はローカルにあるGitリポジトリを使っているので、`glide up` でエラーになることを回避するために、ローカル環境と同じパスにソースをコピーしています。
そのあと、`go build` でコンパイルしています。

post_buildフェーズでは、S3に実行ファイルをアップロードし、ECSの現在のタスクを停止しています。
artifactでもアップロードはできるのですが、post_buildのECSタスク停止後にアップロードされ、タスクに新しいファイルが反映されないため、このようにしています。

## ECRにDockerイメージを作成
下記のDockerfileでDockerイメージを作成し、ECRにプッシュしておきます。

```bash:Dockerfile
FROM ubuntu:16.04
RUN apt-get -y update
RUN apt-get install -y python-pip
RUN yes | pip install --upgrade awscli
RUN apt-get remove -y python-pip
ADD start_gotest.sh /
ENTRYPOINT ./start_gotest.sh
```

```bash:start_gotest.sh
aws s3 cp s3://gotest-pkg/server ./
chmod +x server
./server
```

UbuntuにS3でビルドしたファイルをダウンロードし、実行するだけです。
Alpineでやると、 `./server` がどういうわけか、not foundになるので、断念・・・

## ECS設定
ECRに保存したイメージを常時稼働するように、タスク定義、サービスの設定します。

### CodePipeline

CodePipelineを作成します。

`ソースの場所` は作成したCodeCommitリポジトリを指定します。
![Kobito.H6ZYi8.png](https://qiita-image-store.s3.amazonaws.com/0/3246/c2d71c8e-987b-0ab1-8ad6-e14355aa00b9.png "Kobito.H6ZYi8.png")

`ビルド` は作成したCodeBuildを指定します。
![Kobito.xAGCCv.png](https://qiita-image-store.s3.amazonaws.com/0/3246/edfb33dc-44ad-67e1-abd1-9b4b2f2ac40d.png "Kobito.xAGCCv.png")

`デプロイ`はCodeDeploy等は使用しないので、`デプロイなし`を選択します。
![Kobito.ppkLhz.png](https://qiita-image-store.s3.amazonaws.com/0/3246/9eb53318-5414-6a92-25db-66b2e9a4042d.png "Kobito.ppkLhz.png")

## 実行
作成したリポジトリに、`buildspec.yml` と、`build.sh`を追加し、プッシュすると、CodePipelineが反応し、ECSのタスクが再起動されるまで自動実行されます。

![Kobito.NZkYFB.png](https://qiita-image-store.s3.amazonaws.com/0/3246/25d7ce65-b84a-3885-b3ee-5ae5d5ab7845.png "Kobito.NZkYFB.png")

## イケてないところ
- CodeBuildで使うコンテナが融通がきかない
	- 独自コンテナではDocker on Dockerができないとか
	- Docker + Go等 みたいなコンテナがないとか
	- 本来はビルドプロセスでコンテナイメージつくってECRにプッシュするまでやりたい
- Artifactが使えてない
	- そもそもあんまりよくわかってない・・・ 
- ECRに保存しているアプリケーションを動かすだけのコンテナのサイズがデカすぎ
    - S3からダウンロードするためにubuntuにpipインストールして、aws-cliをpipインストールしただけで、こんなに・・・

```
ubuntu              16.04               117 MB
gotest-image        latest              462 MB
```