Gitlab(nginx経由)+Jenkinsと連携する場合のCodeBuildの設定方法

#前提
本稿では、Amazon ECR、GitLab、Jenkins側でそれぞれCodeBuildと連携するための設定やアクセストークンの払い出しなどが全て完了していることを前提としている。

# CodeBuildとは

AWSで提供されているビルドサービスである。
詳細は[公式のドキュメント](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/welcome.html)を参照すること。

CodeBuildでできることは、大きく分けて以下の2つである。

* ソースコードのビルドを、CodeBuildから起動するDockerコンテナ内部で実行する。
* ソースコードのビルドに自前で用意したDockerコンテナを使用したい場合に、作成したDockerfileをビルドして生成したDockerイメージをAmazon ECRに登録する。

# なぜCodeBuildを使用するのか
CodeBuildを使用する利点は、主に以下の3点である。

* Jenkins Slaveを使用する場合と違い、ビルドが待ち状態になることが無い。
 * Jenkins Slaveはサーバの台数に限りがあるが、CodeBuildではビルドごとにコンテナを起動して実行するため、他のビルドの完了を待つ必要が無い。  

* ビルド環境のメンテナンスが不要である。
 * 毎回使い切りのコンテナの中でビルドを行うため、ビルド環境をメンテナンスする必要が無い。

* Jenkins Slaveを使用するよりも費用を安く抑えられる。
 * Jenkins Slaveを使用する場合、Slaveとして構築したEC2を常に起動しておく必要があるため、ビルドを実行していない時間にも常に費用が発生する。  
 しかし、CodeBuildではEC2が不要であり、なお且つビルドが実行されている間のみの従量課金であるため、費用を抑えることができる。

# CodeBuildの設定
## ビルドに使用できるコンテナについて
CodeBuildでは、ビルドを行う際にコンテナを構築し、その中で処理を行う。
コンテナの構築に使用できるDockerイメージの種類は、大きく分けて以下の2つである。

* デフォルトで提供されているDockerイメージ
* ユーザが独自に作成してAmazon ECRに登録したDockerイメージ

デフォルトで用意されているDockerイメージについては、[こちら](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-available.html)を参照すること。

また、独自にDockerイメージを作成する必要があるのは、例えばDBやキューなどのリソースを構築したコンテナを各ビルド専用の使い捨て環境として占有できるようにしたい場合である。
その場合は、独自に作成したDockerfileをCodeBuildでビルドしてAmazon ECRに登録しておく必要がある。
そのための設定方法についても併せて説明する。

## buildspec.yml
### ファイルの記載内容
ビルド中に実行する処理は、buildspec.ymlに定義しておく必要がある。
独自イメージをビルドする場合と、ソースコードをビルドする場合のサンプルをそれぞれ以下に示す。
詳細は[公式のドキュメント](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-spec-ref.html)を参照すること。

#### 独自イメージのビルド用

```
version: 0.2  <1>

phases:
  install:  <2>
    runtime-versions:
      docker: 18
    commands:
      - nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:XXXX --storage-driver=overlay&
      - timeout 15 sh -c "until docker info; do echo .; sleep 1; done"
  pre_build:  <3>
    commands:
      - $(aws ecr get-login --no-include-email)
      - docker pull ${REPOSITORY_URI}:latest || true
  build:  <4>
    commands:
      - docker build -t sample-build --cache-from ${REPOSITORY_URI}:latest .
      - docker tag sample-build:latest ${REPOSITORY_URI}:latest
  post_build:  <5>
    commands:
      - docker push ${REPOSITORY_URI}:latest
```
<1> AWSが提供しているビルド仕様のバージョンを記載する。
前述の公式のドキュメントを参照し、必ず提供されている中で最新のバージョンとすること。
<2> インストールフェーズで実行する処理を記載する。
ここでは、ランタイムバージョンの定義とDockerデーモンの起動を行っている。
<3> ビルド前に実行する処理を記載する。
ここでは、Amazon ECRへのログインとDockerイメージのプルを行っている。
ここでプルしているのは、前回作成したイメージを取得してビルド時のキャッシュとして使用するため。
`${REPOSITORY_URI}`の値については、後述するビルドプロジェクトの環境変数に設定する必要がある。
<4> ビルドフェーズで実行する処理を記載する。
ここでは、独自イメージのビルドとタグ付けを行っている。
`--cache-from ${REPOSITORY_URI}:latest`を付与していることにより、上記のビルド前処理で取得したイメージをキャッシュとして使用することができ、処理時間を短縮できる。
<5> ビルド後に実行する処理を記載する。
ここでは、独自イメージをAmazon ECRにプッシュしている。

#### ソースコードのビルド用

```
version: 0.2  <1>

phases:
  install:  <2>
    commands:
      - chmod +x./gradlew
      - touch gradle.properties
      - |
        cat <<EOL >> gradle.properties
        sample.datasource.password=${DB_PASSWORD}
        org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m
        EOL
  pre_build:  <3>
    commands:
      - sh /var/tmp/service.sh
  build:  <4>
    commands:
      - |
        if [ -n "${BRANCH_NAME%%release-*}" ]; then
          ./gradlew :sample-project:db:flywayClean :sample-project:db:flywayMigrate
          ./gradlew check jacocoTestReport sonarqube
        fi
      - ./gradlew assemble
  post_build:  <5>
    commands:
      - |
        if [ -z "${BRANCH_NAME%%release-*}" ]; then
          ./gradlew createPackage
          aws s3 cp src/sample/build/distributions/sample-project.tar.gz "{資材を登録するS3のブランチとパス}/${BRANCH_NAME#release-}/sample-project.tar.gz"
cache:  <6>
  paths:
    - /root/.gradle/caches/**/*
    - /root/.gradle/wrapper/**/*

```
<1> AWSが提供しているビルド仕様のバージョンを記載する。
前述の公式のドキュメントを参照し、必ず提供されている中で最新のバージョンとすること。
<2> インストールフェーズで実行する処理を記載する。
このサンプルでは、gradlewコマンドへの権限付与とgradle.propertiesの作成を行っている。
`${DB_PASSWORD}`の値は、後述するビルドプロジェクトに設定する環境変数、またはJenkinsからCodeBuildに連携する環境変数から取得する。
<3> ビルド前に実行する処理を記載する。
このサンプルでは、コンテナ内に配置したシェルを起動している。
<4> ビルドフェーズで実行する処理を記載する。
このサンプルでは、テストやビルドを行っている。
ブランチ名を判定し、リリースブランチとそれ以外のブランチで処理を分けている。
<5> ビルド後に実行する処理を記載する。
このサンプルでは、資材のパッケージングとS3への格納を行っている。
<6> キャッシュを取得しておくディレクトリがある場合は、パスを記載する。

### buildspec.ymlの格納場所
必ずソースディレクトリのroot配下に格納すること。
ディレクトリ構成のサンプルは、以下の通り。

```
<root>/
   ├─ modules
   │     ├─ XXXXX
   │     │    ├─ src
   │     │    │   ├─ main
   │     │    │   │   └─ :
   │     │    │   └─ test
   │     │    │       └─ :
   │     │    ├─ formatter
   │     │    └─ build.gradle
   │     ├─ YYYYY
   │     ├─   :
   │     └─   :
   ├─ gradle
   │    └─ wrapper
   │         ├─ :
   │         └─ :
   ├─ build.gradle
   ├─ buildspec.yml
   ├─ 　　　:
   └─ 　　　:
```

## ビルドプロジェクト
AWSマネジメントコンソールにログインし、CodeBuildの画面からビルドプロジェクトを作成する。
ここでは、主に以下の設定を行う。

* プロジェクト名の設定
* ソースの取得元
* 環境
 * 使用するDockerイメージ
 * 使用するサービスロール
 * VPC/サブネット
 * コンテナを起動する際のメモリ容量
 * 環境変数
* アーティファクト
 * キャッシュの取得先
* ログ設定

### ビルドパターン別の設定サンプル
CodeBuildでのビルドには以下の2つのパターンがある。

* ビルド環境として使用する自前Dockerイメージのビルド
* ソースコードのビルド

どちらのビルドを行うかによって、ビルドプロジェクトの設定内容が変わる。

#### 独自に作成したDockerイメージのビルド
前述の通り、ソースコードのビルドに使用するDockerイメージを自前で用意する場合は、作成したDockerfileをCodeBuildでビルドしてAmazon ECRに登録しておく必要がある。
その場合のビルドプロジェクトの設定サンプルを以下に示す。

1. プロジェクトの設定
![build-project-docker-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/03bbf8d6-c3b2-0b86-863a-ecf141ffbd67.png)
【必須項目】
プロジェクト名：任意のプロジェクト名を入力する。

1. 送信元
![build-project-docker-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/04bd7204-14b9-b7d8-9437-12bfec3e9b6a.png)
【必須項目】
ソースプロバイダ：ソースを管理しているサービスを選択する。（図は、Gitlabを使用する場合のサンプル）
リポジトリのURL：ソースを管理しているリポジトリのURLを入力する。（図は、Gitlabを使用する場合のサンプル）
Gitサブモジュール：Gitでソースを管理していて、Gitサブモジュールの機能を利用している場合はチェックを入れる。利用していない場合は不要。

1. 環境
![build-project-docker-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/2ce2dbb5-9fcd-c4b5-78cb-e7dc60aabb44.png)
【必須項目】
環境イメージ：`マネージド型イメージ`を選択する。
オペレーティングシステム：条件に合ったものを選択する。
ランタイム：`Standard`を選択する。
イメージ：最新のものを選択する。
イメージのバージョン：最新のものを選択する。
特権付与：チェックを入れる。
サービスロール/ロール名：既に作成済みのサービスロールを選択し、`AWS Codebuildにこのサービスロールの編集を許可し、このビルドプロジェクトでの使用を可能にする`にチェックを入れる。

1. 環境(追加設定)
![build-project-docker-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/d79deee2-a521-72a1-ef8c-0e803b11835f.png)
【必須項目】 
VPC：対象のVPCを選択する。 
サブネット：対象のサブネットを選択する。 
セキュリティグループ：対象のセキュリティグループを選択する。 
`VPCの確認`を押下し、上記のキャプチャのように`VPCはインターネットに接続されています。`と出力されればOK。 
![build-project-docker-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/e4428ba1-ec0b-56c3-5554-d22305875fa2.png)
【必須項目】 
コンピューティング：必要なスペックを選択する。 
環境変数：必要な設定値があれば、ここに定義する。 
先述の`${REPOSITORY_URI}`もここに設定しておく。

1. Buildspec
![build-project-docker-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/82a9d003-8e40-3d07-a1b1-b65209352379.png)
【必須項目】
ビルド仕様：`buildspecファイルを使用する`にチェックを入れる。

1. アーティファクト
デフォルト設定のままでOK。

1. ログ
デフォルト設定のままでOK。


#### ソースコードのビルド
ソースコードをビルドする場合の設定サンプルを以下に示す。

1. プロジェクトの設定
![build-project-source-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/1a48691f-a34f-c50d-b7e4-24bd821f438b.png)
【必須項目】
プロジェクト名：任意のプロジェクト名を入力する。

1. 送信元
![build-project-source-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/af49b964-2a91-e139-96e7-fdc0275f0942.png)
【必須項目】
ソースプロバイダ：ソースを管理しているサービスを選択する。（キャプチャは、GitLabを使用する場合のサンプル）
リポジトリのURL：ソースを管理しているリポジトリのURLを入力する。（キャプチャは、GitLabを使用する場合のサンプル）
Gitサブモジュール：Gitでソースを管理していて、Gitサブモジュールの機能を利用している場合はチェックを入れる。利用していない場合は不要。

1. 環境
 1. ビルド環境として、デフォルトで提供されているDockerイメージを使用する場合
![build-project-source-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/e51179f0-1c8e-147c-1772-b9a4f9e50a0d.png) 
【必須項目】  
環境イメージ：`マネージド型イメージ`を選択する。  
オペレーティングシステム：条件に合ったものを選択する。  
ランタイム：`Standard`を選択する。  
イメージ：最新のものを選択する。  
イメージのバージョン：最新のものを選択する。  
サービスロール/ロール名：既に作成済みのサービスロールを選択し、`AWS Codebuildにこのサービスロールの編集を許可し、このビルドプロジェクトでの使用を可能にする`にチェックを入れる。

 1. ビルド環境として、自前で用意したDockerイメージを使用する場合  
![build-project-source-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/5b389b1c-cb57-821b-170b-f07036030967.png)
【必須項目】  
環境イメージ：`カスタムイメージ`を選択する。  
オペレーティングシステム：`Linux`を選択する。  
イメージレジストリ：`Amazon ECR`を選択する。  
ECRアカウント：`自分のアカウント`にチェックを入れる。

1. 環境(追加設定)
![build-project-source-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/f7293c3a-2bf2-f696-8f9b-2c75ae6004a3.png)
【必須項目】
VPC：対象のVPCを選択する。
サブネット：対象のサブネットを選択する。
セキュリティグループ：対象のセキュリティグループを選択する。
`VPCの確認`を押下し、上記のキャプチャのように`VPCはインターネットに接続されています。`と出力されればOK。
![build-project-source-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/5f764e97-5f83-a7a4-c7e8-36d00ac8a7b3.png)
【必須項目】 
コンピューティング：必要なスペックを選択する。 
環境変数：必要な設定値があれば、ここに定義する。 

1. Buildspec
![build-project-source-07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/a38d3423-da02-e730-94a7-adba5beec9c9.png)
【必須項目】
ビルド仕様：`buildspecファイルを使用する`にチェックを入れる。

1. アーティファクト
![build-project-source-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367074/cffca382-5630-0d87-e41a-8f3737e3d4e8.png)
【必須項目】
暗号化キー：AWS KMSのカスタマーマスターキーを入力する。
キャッシュタイプ：Amazon S3を選択する。
キャッシュバケット：キャッシュを保存する任意のバケット名を選択する。
キャッシュパスのプレフィックス：選択したキャッシュバケット内の、任意のパスを入力する。

1. ログ
デフォルト設定のままでOK。

## 前段にNginxを構築したGitLabとの連携
ソースを管理するツールとしてGitLabを使用し、さらに前段にNginxを構築している場合、CodeBuildからGitLabへのアクセス時のリクエストに付いているAuthorizationヘッダの値を書き換える必要がある。
そのためのnginx.confの記載方法を以下に示す。

```
server {
  server_name gitlab.REVERSE_PROXY_DOMAIN_NAME
              gitlab.REVERSE_PROXY_INTERNAL_DOMAIN_NAME;
  location / {
    proxy_pass http://gitlab;
    set $authorization_value $http_authorization;
    if ($http_authorization = "Basic XXXXXXX") {  <1>
        set $authorization_value "Basic YYYYYYY";  <2>
    }
    proxy_set_header Authorization $authorization_value;
                                   ：
                                   ：
  }
}
```
<1> `XXXXXXX`には、`{GitLabで払い出したアクセストークン}:x-oauth-basic`という形式をBase64エンコードした文字列を設定する。
<2> `YYYYYYY`には、`{GitLabで払い出したアクセストークン}`のみをBase64エンコードした文字列を設定する。

Authorizationヘッダには、元々`{GitLabで払い出したアクセストークン}:x-oauth-basic`という形式をBase64エンコードした文字列が設定されている。
しかし、`:x-oauth-basic`の部分が余計であり、この部分が付いていることによって認証に失敗する。
そのため、Authorizationヘッダの値を`{GitLabで払い出したアクセストークン}`のみをBase64エンコードした文字列で上書きする必要がある。


## Jenkinsとの連携
JenkinsとCodeBuildを連携する方法を以下に示す。

### Jenkins側の設定
Jenkinsに、`AWS CodeBuild プラグイン`をインストールする必要がある。
詳細は、[公式のドキュメント](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/jenkins-plugin.html)を参照すること.


### JenkinsfileにおけるCodeBuild定義
JenkinsのビルドジョブからCodeBuildを呼び出すには、連携するための設定をJenkinsfileに記載する必要がある。
設定のサンプルを以下に示す。

```
stages {
    stage('codebuild') {
        steps {
            awsCodeBuild(
                credentialsType: 'keys',
                projectName: '{Gitに登録しているプロジェクト名}',
                region: '{利用しているAWSのリージョン}',
                sourceControlType: 'project',
                sourceVersion: env.BRANCH_NAME,
                envVariables: "[{BRANCH_NAME,${env.BRANCH_NAME}}]"  <1>
            )
        }
    }
}
```
<1> JenkinsからCodeBuildに渡す環境変数を定義する。
上記のサンプルの通り、最低限`BRANCH_NAME`は必ず定義すること。

### Jenkinsfileの格納場所
必ずソースディレクトリのroot配下に格納すること。
ディレクトリ構成のサンプルは、以下の通り。

```
<root>/
   ├─ modules
   │     ├─ XXXXX
   │     │    ├─ src
   │     │    │   ├─ main
   │     │    │   │   └─ :
   │     │    │   └─ test
   │     │    │       └─ :
   │     │    ├─ formatter
   │     │    └─ build.gradle
   │     ├─ YYYYY
   │     ├─   :
   │     └─   :
   ├─ gradle
   │    └─ wrapper
   │         ├─ :
   │         └─ :
   ├─ build.gradle
   ├─ buildspec.yml
   ├─ Jenkinsfile
   ├─ 　　　:
   └─ 　　　:
```