CodeCommitからCodeBuild、イメージをECRにPushするまで入門してみた

# はじめに

CodePipelineを利用してCommitの検知からBuild,Deployまでを自動的に行うようにするために、各サービスの特徴を把握しておこうってことで、

- CodeCommitへの必要ファイル群の生成
- CodeBuildでコンテナイメージの生成とECRへのPush
- PushされたコンテナイメージのECS(Fargate)への展開

までをやってみる記事です。  
今回はNginxの公式イメージを元に、適当なコンフィグを叩き込んだイメージをビルドして、ECSへデプロイするところを目指します。

間違っているところがあったら教えて下さい。

# 準備編

- ECRへのレポジトリ作成
  - 名前をつけて作成するだけ。ないとPush出来ないのでビルドで怒られます。

# CodeCommit編

## CodeCommitとは

AWSが提供するバージョン管理サービスです。ドキュメントやソースコードなどをクラウド上に保存出来ます。  
Gitの標準機能を備えているため、Gitコマンドで管理可能です。IAMによる権限管理も可能です。

Githubに比べるとプロジェクトやらIssuesやらといった機能はなく、タグ・ブランチ・PRがあるくらいですね。

## レポジトリの作成

AWSコンソールから名前つけてぽちっと作成してください。

## 権限の付与

IAMから、アクセスを許可するためのユーザを作成します。  
流石にルートユーザのアクセスキーは恐ろしいのでちゃんと専用にユーザを作りましょう。

作成したユーザには `AWSCodeCommitFullAccess` のポリシーを付与しておけばOKです。  
FullだとRepositoryの削除権限も、どのレポジトリにもアクセス件がも付いてるので適宜制限したり減らしたりして下さい。

IAMユーザの設定で、認証情報タブの下の方にある「AWS CodeCommitnoSSHキー」からSSHキーがアップロード出来るので、コードをCommitする環境で鍵を生成してアップロードしておきましょう。

SSHキーIDをコピーして、サーバ側でSSH Configを設定します。

``` ~/.ssh/config
Host git-codecommit.*.amazonaws.com
 User <SSH Key ID>
 IdentityFile ~/.ssh/codecommit_rsa
```

これで生成したユーザ権限でSSHアクセスされるようになります。  
テスト接続してみましょう。

```
$ ssh -T <SSH Key ID>@git-codecommit.ap-northeast-1.amazonaws.com
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.
```

SuccessfullyってでてるのでOKでしょう。  
お手元のPCやサーバなどで該当のレポジトリをCloneしましょう。

```
git clone ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/<RepoName>
```

## コードの準備

Clone出来たら中に入れるファイルを用意していきます。  
最低限必要なのは、

- Dockerイメージを作るので、`Dockerfile`
- CodeBuildのビルド内容を記載した`buildspec.yml`
- テーマとして挙げたNginxのコンフィグなど

まずはDockerfileとbuildspec.ymlから。  
Dcokerfileはレポジトリ内にあるnginxのコンフィグ類をコンテナ内にコピーしたあと、nginxを起動するスクリプトを実行する仕組みになってます。DBへの接続情報はコンテナイメージに含めず、起動時にentrypointスクリプト内でどこかからpullするなどして取得する感じを想定してます。

P.S. [nginx公式のDockerfile](https://github.com/nginxinc/docker-nginx)とかもあるのでご参考まで。

```Dockerfile.
FROM nginx

# Get updates and wget
RUN apt-get -y update && apt-get -y install wget

# Create Dirs and copy files.
RUN mkdir -p /var/log/{nginx,app} && \
    mkdir -p /var/cache/nginx

RUN mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.orig

COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY ./nginx/conf.d /etc/nginx/conf.d

# Create index.html for localhsot.
RUN echo "localhost.conf" > /usr/share/nginx/html/index.html

# Copy entrypoint script and define it.
COPY ./nginx/entrypoint.sh /bin/
RUN ["chmod", "+x", "/bin/entrypoint.sh"]
ENTRYPOINT ["/bin/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
```

buildspecはCodeBuildがDockerイメージをビルドするための手順書みたいなものです。各コマンドがビルド環境内で実行されていきます。  
最後に記載されているアーティファクトのJSONはECSへのデプロイ時に利用されるファイルです。

`<container-definition>` と書いてあるところにはECSでサービス登録する際に利用するコンテナ名を入れて下さい。  
また、各行に環境変数を利用しているため、CodeBuildからビルドする際には環境変数の定義が必要になります。（詳しくは後述）

```buildspec.yml
version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...          
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - echo docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG      
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      - printf '[{"name":"<container-definition>","imageUri":"%s"}]' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > artifacts.json

artifacts:
  files: artifacts.json
```

Nginxのコンフィグ周りは適当に編集していただければと思いますが、このサンプルだと以下のようなディレクトリ構造になっています。  
service.conf内にサービス用のserverディレクティブなど書いていきます。

```
ROOT
 |
 +-- Dockerfile
 +-- buildspec.yml
 +-- nginx/
      |
      +-- entrypoint.sh
      +-- nginx.conf
      +-- conf.d/
          |
          +-- service.conf
```

一通りファイルが出来たらCodeCommitにPushしましょう。

# CodeBuild編

CodeCommitのレポジトリに上のファイル群がCommitされたら、それらを用いてビルドしていきます。  
CodeBuildはプロジェクトという単位でビルド設定をしていきます。  
今の所は検索機能とかもないので、ステージごとや機能ごとに分割して作成することになるので徐々にごちゃごちゃしそうな予感。

さてビルドプロジェクトを作成しましょう。

## ソースの指定

まずはビルドの対象です。  
今回は先程作成したCodeCommitのレポジトリを指定します。

## ビルド環境の指定

次にビルド方法を定義します。  
今回はDockerイメージを生成することをメインとしているのでDockerを選択します。

- 環境イメージ
  - AWS CodeBuildによって管理されたイメージの使用
- OS
  - Ubuntu
- ランタイム
  - Docker (versionは適当に最新とかで)
- ビルド仕様
  - ソースコードのルートディレクトリのbuildspec.ymlを使用
- buildspec名
  - buildspec.yml

## サービスロール

ビルドを行う際に利用されるサービスロールです。  
ビルドの中で行う内容によって適切に権限付与してあげないと、ビルドに成功しなくなります。

ここでは

- CodeCommitからのPull
- S3へのアーティファクトの設置（CodePipelineでの実行時に必要）
- ECRへのコンテナイメージのプッシュ

のような権限が必要です。  
一旦新規作成を選択して、あとで書き換える感じでもOKです。ただしかなり権限が荒いのでもうちょっと絞ったほうが良い気もします。

手動で追加したのは最上部のECR部分ですね。あとは自動生成されたものです。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ecr:BatchCheckLayerAvailability",
                "ecr:CompleteLayerUpload",
                "ecr:GetAuthorizationToken",
                "ecr:InitiateLayerUpload",
                "ecr:PutImage",
                "ecr:UploadLayerPart"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Effect": "Allow",
            "Resource": [
                "arn:aws:logs:ap-northeast-1:NNNNNNNNNNNN:log-group:/aws/codebuild/NginxBuilder",
                "arn:aws:logs:ap-northeast-1:NNNNNNNNNNNN:log-group:/aws/codebuild/NginxBuilder:*"
            ],
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ]
        },
        {
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::codepipeline-ap-northeast-1-*"
            ],
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:GetObjectVersion"
            ]
        },
        {
            "Effect": "Allow",
            "Resource": [
                "arn:aws:codecommit:ap-northeast-1:NNNNNNNNNNNN:NginxImage"
            ],
            "Action": [
                "codecommit:GitPull"
            ]
        }
    ]
}
```

## 詳細設定

タイムアウトとかコンピューティングタイプとかは適切に設定して下さい。重要なのは環境変数です。  
前述したようにbuildspec.yml内の各コマンド内で利用する環境変数が必要ですのでそれぞれ設定して下さい。

- AWS_ACCOUNT_ID
  - アカウントID
- AWS_DEFAULT_REGION
  - デフォルトでどのリージョンを利用するか
- IMAGE_REPO_NAME
  - Push先のECRのレポジトリ名
- IMAGE_TAG
  - コンテナイメージにつけるタグ

これで完了です。

# ビルドしよう

ビルドプロジェクトを保存したら、ビルドを実行しましょう。  
CodeCommitレポジトリのどのブランチを利用するのか選択して下さい。  
他の設定は先程行ったものが利用されるので、ビルドの度に変更したい情報などがなければ弄らなくてOKです。

ビルドのためにIMAGE_TAGを用いてバージョン番号をカウントアップしていくような作りならここで数字をあげていくのが良いでしょう。

## ビルドの様子

ビルドにおける各ステップの進捗が表示されます。  
実際にInstallフェーズ移行のログは下部に表示されるので、ビルドが失敗したときはログから原因を突き止めて解消しましょう。

当初よく引っかかったのは以下のようなところです。

- docker buildの失敗（可能ならローカルでやったほうが確実）
- 環境変数の設定ミス
- ECRにPushする権限が設定されていない

表示されるエラーなどは基本的にコマンドの実行結果に当たるので、CodeBuildだから特殊な何かはさほどない印象です。

# ビルドが完了したら

成功後は、ECRを見てレポジトリにイメージがPushされたか確認しましょう。  
（そしてPushしたイメージがちゃんとDocker上で起動するか動かしてみましょう。


## 終わりに

CodePipelineを利用する前に、まずはCodeCommitとCodeBuildを利用してみました。  
次の記事ではこのイメージを利用してECSクラスタの用意とコンテナの生成を行ってみます。