開発用データ入り MySQL Docker イメージを作成する仕組みを AWS CodeBuild で構築したよ

流れをぶった切ってしまってすみません! Speee Advent Calendar 2017 の 20 日目の記事です。

19 日目: スポンサー活動を本気でやってみた1年でした

概要

開発用のデータは、Web アプリケーションを作る上で必要不可欠です。しかし、手動で Jenkins から日時更新のダンプデータをローカルに落としてきて MySQL にインポートするのはダサいです。はじめから開発データが入った MySQL Docker イメージを配布しておけば、手動でダンプデータをインポートする必要がなく便利です。そこで AWS CodeBuild で開発データ入り MySQL イメージを作る仕組みを構築しました。運用はまだできていませんが、CodeBuild を使う上でハマったことがいくつかあったので書こうと思います。

きっかけ

ある日、開発環境の MySQL コンテナのデータがぶっ壊れました。データを残しつつ復旧するのが面倒だったので、MySQL コンテナを再ビルド、開発データをインポートし復旧させました。復旧に数十分かかったのですが、仮に開発データが Docker Volume ではなく、本体コンテナのイメージレイヤーに含まれていれば、データが壊れたとしても、コンテナを再起動することで破損したファイルを持つイメージレイヤーが消え、容易に復旧できるのではと思ったのです。

実際、それほど頻繁にデータは壊れませんが、はじめから開発データが入った MySQL イメージを配布しておけば、手動でダンプデータをインポートする必要がなくなり便利になるはずです。

仕組み

以下の図で説明します。まず、Jenkins が本番環境のマスク済みデータ(開発データ)を S3 バケットにアップロードします。Lambda は、S3 バケットへのアップロードを検知し、CodeBuild をキックします。それを受け CodeBuild は、最新の開発データを S3 から取得し、Docker コンテナ上で動作する MySQL に開発データをインポートします。インポート後、Docker コンテナの状態をイメージとして保存、ECR に push します。ECR に push されたイメージは、docker-compose 等から利用できます。

f:id:nyamadori:20171222010826p:plain

CodeBuild

以下がこの仕組みで作った CodeBuild の YAML ファイルです。この YAML ファイルをビルドスペックといい、buildspec.yml というファイル名をつけます。CircleCI のような感じの設定ファイルでビルド手順を記述できますが、いくつかハマリポイントがありました。環境変数は、AWS コンソール画面から与えています。

version: 0.2 # ハマリポイント 1

phases:
  pre_build:
    commands:
      - $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email) # ハマリポイント 2
      - aws s3 cp s3://${DUMP_BUCKET}/${DUMP_PATH} .
      - docker build -t mysql_without_volume docker/mysql_without_volume
      - docker run -d --name mysql_with_seed -e 'MYSQL_ALLOW_EMPTY_PASSWORD=yes' -p 3306:3306 mysql_without_volume
      - sudo apt update && sudo apt-get -y install mysql-client
  build:
    commands:
      - DOCKER_IMAGE_TAG=$(date '+%Y%m%d')
      - DOCKER_REPO="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}"
      - echo $DOCKER_REPO
      - echo $DOCKER_IMAGE_TAG
      - |
          mysql -u root -h 127.0.0.1 <<-SQL
            create database ${DUMP_DATABASE} CHARACTER SET utf8mb4;
          SQL
      - gzip -dc $(basename $DUMP_PATH) | mysql -u root -h 127.0.0.1 -f ${DUMP_DATABASE}
      - docker commit mysql_with_seed "${DOCKER_REPO}:latest"
      - docker commit mysql_with_seed "${DOCKER_REPO}:${DOCKER_IMAGE_TAG}"
  post_build:
    commands:
      - docker push "${DOCKER_REPO}:latest"
      - docker push "${DOCKER_REPO}:${DOCKER_IMAGE_TAG}"

MySQL Docker イメージ

この仕組みは、開発用データと一緒に MySQL イメージを配布できるようにするため、Docker 公式の MySQL 5.6 イメージ から Volume を取り除いた独自のイメージを使っています。といっても、公式 Dockerfile から VOLUME の記述をコメントアウト しただけです。これで DB のデータをコンテナのイメージレイヤーに含めることができます。

成果物

github.com

ハマりどころ

環境変数

ビルドスペックのバージョン 0.1 は、1 コマンドごとにシェルのインスタンスが独立していて、カレントディレクトリや環境変数の状態を共有することができません。 例えば、以下のようなビルドスペックにおいて、最初に定義した環境変数 DOCKER_IMAGE_TAG DOCKER_REPO の値を、後ろの echo している行で取得することができません。

version: 0.1

phases:
  build:
    commands:
      - DOCKER_IMAGE_TAG=$(date '+%Y%m%d')
      - DOCKER_REPO="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}"
      - echo $DOCKER_REPO       # => 何も表示されない
      - echo $DOCKER_IMAGE_TAG  # => 何も表示されない

対策は、バージョン 0.2 のビルドスペックを使うことです。バージョン 0.2 から前のコマンドで定義した環境変数を、後ろのコマンドに引き継いでくれます。phase が分かれていても問題ありません。 どうしてもバージョン 0.1 を使う必要がある場合は、シェルスクリプトとして切り出すか、環境変数の定義と参照を 1 コマンドにまとめることになると思います。

docs.aws.amazon.com

CodeBuild のサンプル解説ページで想定している Docker バージョンが古く、ECR ログインがコマンドオプションでこける

今回の仕組みを作るにあたり、以下の AWS 公式が提供しているサンプルを参考にしましたが、ECR のログインコマンドでコケてしまいました。

docs.aws.amazon.com

原因としては、Docker 17.06 以降 docker login コマンドから --email オプションが削除されたものの、aws ecr get-login コマンドが使う docker login コマンドに --email オプションが含まれていて、コマンドエラーになったようです。対策は、以下のように aws ecr get-login コマンドに ----no-include-email を付与することです。これで正常に動作するはずです。

- $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)

docs.docker.com github.com

感想

今回、初めて CodeBuild を触りましたが、一言で表すと CircleCI のような感じです。AWS のサービスなので、すでに利用している EC2 や S3 等と密に連携できたり、サーバレスでリソースを気にせずジョブを実行できるし、CircleCI に置くのが憚られるセンシティブなデータを扱ったり、多数のビルドジョブによるテスト実行だったりに使えそうだと思いました。

21日目は、「朝起きれない問題に本気で向き合う目覚ましサービスを作った」です!

kohtaro24.hatenablog.jp