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

概要

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

  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にします。

CodeCommitリポジトリにアクセスするユーザを作成

IAMでユーザを作成します。
AccessKeyを使ったHTTPSでのアクセスも可能ですが、SSH接続でやってみます。
ユーザを作成したら、認証情報のタブを開いて、AWS CodeCommitのSSHキーSSH 公開キーのアップロード をクリックし、SSH公開鍵を貼り付けます。

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

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

~/.ssh/config

Host git-codecommit.*.amazonaws.com
    User `SSH キー ID`
    IdentityFile ~/.ssh/秘密鍵

コードをpushしておきます。

$ 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バケットを作成します。

CodeBuildプロジェクトを作成

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

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

https://forums.aws.amazon.com/thread.jspa?messageID=761184

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

CodeBuild用のファイルを作成

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

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

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のglidego-bindataをインストールします。

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

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

ECRにDockerイメージを作成

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

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

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リポジトリを指定します。

ビルド は作成したCodeBuildを指定します。

デプロイはCodeDeploy等は使用しないので、デプロイなしを選択します。

実行

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

イケてないところ

  • 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

元記事はこちら

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