AWS CodeBuildのコンテナから、RDSへ直接マイグレーションを実行する方法

## 概要
サービスを全部ECS + Fargateで行うサービスで、簡単かつCoolにマイグレーション実行する方法を記します。
具体的にはCodePipeline & CodeBuildを設定し、ビルド中にマイグレーションが走るように設定します。

### この記事が役に立ちそうな人

- ECS + Fargateでサービスを構築しようとしている人
- サービスにはRDSを用いているが、マイグレーションをどこから行おうか悩んでいる人

## 背景
マイグレーションをいかにCoolに行うかはサーバーサイドの至上命題と言っても過言ではありません。（？）

前述の通りECS + Fargateでサービスを構築すると、FargateにはSSHできないのは周知の事実です。
よってコンテナに入れないため、フレームワークの機能を用いたマイグレーションを実行できません。
そして、マイグレーションは必ずソースコードのデプロイ前に実行される必要があります。

以上のことから、ソースコードのデプロイを行うCodePipelineのフローでマイグレーションを行うフェーズを追加し、マイグレーション含めたアプリケーションのデプロイを管理できたらCoolだなと思った次第です。

[こちらの方法](https://qiita.com/hareku/items/73f7730c1adc01bbe5a0)で実はその例が示されているのですが、タスク定義をjsonで書く必要があり、ステージング環境などの環境ごと対応が手間のように見受けられました。

もっと簡単でセキュアな方法はないかと考えていたところ、[CodeBuildのドキュメント](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/vpc-support.html)にCodeBuildが特定VPC, サブネットで実行できると書いていました。

RDSにはプライベートサブネットからしかアクセスできないようにしていましたが、これならCodeBuildインスタンスに立ち上げたコンテナから、マイグレーションを直接実行できるのでは？　と思いついたわけです。

実際に
>プライベートサブネット上に分離された Amazon RDS データベース内のデータに対して、ビルドから統合テストを実行する。

とあるように、RDSへのアクセスが想定されているようです。

## 目的
- CodePipelineのフェーズ中にmigrationフェーズを追加し、ソースコードのデプロイに加えてマイグレーションも制御したい
- ECSタスク定義を書きたくない
- もちろんセキュリティは維持する
- フレームワークのマイグレーション機能を使いたい [^1]

## インフラ構成の説明
![CBMirationDesc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269942/c70cb58c-0cc7-a717-8291-1d158f842bc5.png)

これが全貌ではありませんが、プライベートサブネットについてはこの構成です。AZの冗長化は端折っています。
ECSのセキュリティグループとは別のSGを用意し、CodeBuildにはそれを設定しておきます。

セキュリティグループ3にはセキュリティグループ1のインバウンドトラフィックを許可しておきます。
セキュリティグループ１には特に設定は不要です。

## 具体的な設定

### CodeBuild
![vpcsettings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269942/07920016-ba78-6a8a-20ae-24b537d59248.png)

上記画像のようなVPCに関する設定があります。
ここにインフラ構成で記した通りの設定をしておきます。

あと環境変数`AWS_ENVIRONMENT`を設定しておきます。
これでどの環境のデータベースへ接続するかを、laravelのenvファイルの読み込みを切り替えることで選択できるようになります。
つまり後述のbuildspecは全環境で共通利用できます。

加えて使用するbuildspecは後述の`buildspec-migration.yml`を指定してください。

### buildspec
実例を示します。

``` yml:buildspec-migration.yml
version: 0.2
phases:
  install:
    runtime-versions:
      docker: 18
    commands:
      - echo Install started on `date`
      - cat /etc/issue
      - curl -L "https://github.com/docker/compose/releases/download/1.21.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose
      - docker-compose --version
      - docker -v
      - pwd && ls -l
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - sed -e 's/AWS_ENVIRONMENT/$AWS_ENVIRONMENT/g' docker-compose-migration.yml > docker-compose.yml
      - docker-compose build
      - docker-compose up -d
      - docker ps
      - docker exec laravel-api bash /project/build.sh
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Start migrate
      - docker exec laravel-api php project/artisan migrate
```
私はdocker-composeを用いてコンテナを立ち上げています。[^2]

`sed -e 's/AWS_ENVIRONMENT/$AWS_ENVIRONMENT/g' docker-compose-migration.yml`
これで環境ごとにdocker-compose.ymlを生成します。これでこのymlは全環境で使いまわせます。

`docker exec laravel-api bash /project/build.sh`
ここでは`composer install`を行うだけのシェルを実行しています。でないとmigrateが動かないので。

`docker exec laravel-api php project/artisan migrate`
ここでついにmigrationを実行しています。

### docker-compose
私はdocker-composeを用いているので、その設定も例を示します。

```yml:docker-compose-migration.yml
version: '3.3'

services:
  # web - api
  api:
    build: ./alpine-apache-php7
    container_name: laravel-api
    environment:
      - APP_ENV=AWS_ENVIRONMENT
    volumes:
      - "./project:/project"
    ports:
      - "18080:80"
```

`APP_ENV`には設定すればdevelopmentなどの任意のテキストを入れられるので、読み込む環境設定ファイルの制御ができます。

### CodePipeline
パイプラインの例を示します。

![cp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269942/60d11235-ff12-e2a8-b4a9-88803ac1b498.png)

上記のようにECRへのビルドフェーズと、マイグレーション含むデプロイフェーズは分けています。
こうすることでマイグレーションとデプロイが自動的に実行されないようになります。

## 参考文献
LaravelをElastic BeanstalkからFargateに移行しました
https://qiita.com/hareku/items/73f7730c1adc01bbe5a0

AWS公式CodeBuildのVPCサポートについてのドキュメント
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/vpc-support.html

[^1]: フレームワークはLaravelを使っています。多分他のフレームワーク、言語でも同じことができるはずです。


[^2]: どこかのタイミングからCodeBuildでdocker-composeを動かすとエラーを吐くようになりました。どうやら標準イメージを使うようになってから追加の設定が必要になったようです。このように別のソフトをインストールするときは、CodeBuildの環境設定において`Enable this flag if you want to build Docker images or want your builds to get elevated privileges.`にチェックを入れないとエラーとなります。注意してください。