White Box技術部

WEB開発のあれこれ(と何か)

CircleCIでDockerfileのあるプロダクトをテストする

既存のアプリケーションをコンテナ化したら、CircleCIで回していたテストがコケていたので、その修正の記録です。

CircleCIの設定を書く

CircleCIの設定をいじるのは久しぶりだったので、以下のようなことも調べつつやっていたのですが、結局ハマってだいぶ時間をとられてしまいました。

  • dockerキー配下のimageは、先頭のコンテナイメージが、stepsのコマンド実行に使われる
  • コンテナから別コンテナを呼ぶ際の名前を変えたければ、nameキーを使う
  • ポートフォワードの指定はconfig.ymlからできない
  • dockerhubのdockerはalpineだったので、ソフトウェアのインストールはapk add

テスト用イメージの作成(ハマったとこ)

何にハマっていたのかと言うと、大きくは2つで

  1. コンテナから他のコンテナに繋げない
  2. コンテナの状態を育てていけない

というところでした。

1. コンテナから他のコンテナに繋げない

こういうことをしたかった、という実行環境のイメージは以下のような感じでした。

f:id:seri_wb:20200126004427j:plain:w600

各種stepを実行するためのコンテナイメージ(docker)がCircleCI上で動作していて、 そのコンテナの中で、アプリケーション(python)用のコンテナがビルドされ、起動後にテストが実行される・・・みたいな。

つまり、テストアプリケーションのコンテナを、Docker in Dockerの状態にしたかったのです。

そして、アプリケーションのコンテナから参照するテスト用のコンテナ(MySQL、Azurite)は、実行用コンテナと同列で起動させれば、ローカルのDocker Composeと同じ状態にできるなって思ったのですが、 結論から言うとこれはできないそうです。

f:id:seri_wb:20200315195206j:plain:w600

そうとは知らず、色々試してはみたので、結果だけは残しておこうかなって・・・

privilegedオプションでできること・できないこと

まずは、stepsの実行イメージをdockerベースのコンテナにし、そのコンテナでビルドしたイメージをprivilegedオプションを付けて起動させて、やりたいことができるか確認しました。

  build:
    docker:
      - image: docker:git
      - image: arafato/azurite:latest
        name: azurite
...
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Build image
          command: docker build -t hoge/hoge-app .
      - run:
          name: Run image
          command: docker run -td --privileged --name hoge-app hoge/hoge-app bash

この状態で、それぞれのコンテナからできたことと、できなかったことは以下になりました。

dockerイメージのコンテナを実行コンテナ、その上でビルドしたイメージをアプリケーションとして書いています。

できた

  • 実行コンテナからAzuriteのイメージ名でcurl接続

できなかった

  • アプリケーションからAzuriteへのcurl接続(ホスト名解決できない)
  • 実行コンテナからlocalhostでAzuriteにcurl接続(ホスト名をazuriteにするとできる)

ちなみにAzuriteのホスト名解決を行うためには、dockerキーのvalue値でnameの指定も必要です(image名と同じだとしても)。

2. コンテナの状態を育てていけない

次に、アプリケーションのコンテナでテストを実行できるようにするには、 Dockerfileで実行される処理以外にも処理を実行する必要があったので、コンテナの状態を育てていく必要がありました。

そこで、docker run時にコマンドとしてpipenv sync --devを渡していたわけですが、これがなぜか実行されなかったため、 run時はDockerfile上のCMDを動作させないためにbashを渡し、別のステップでdocker execで実施するようにするしかありませんでした。

また、docker execでpipenv sync --devを実行したステップの後、pipenv run lintなどをexecに渡して実行すると

「mypyなんぞない」

と言われたので、、、execにコマンドを渡す際はbash -cを使って渡すようにしました。

      - run:
          name: Install dependencies
          command: docker exec hoge-app bash -c 'pipenv sync --dev'
      - run:
          name: Lint code
          command: docker exec hoge-app bash -c 'pipenv run lint 2>&1' | tee lint.log
      - run:
          name: Test hoge-app
          command: docker exec hoge-app bash -c 'pipenv run test 2>&1' | tee test.log

      - store_artifacts:
          path: lint.log
      - store_artifacts:
          path: test.log

とりあえずこうすると、Docker in DockerでもLintとTestの実行はできたのですが、名前解決がうまく行かないことなどから肝心のテストがコケるので、サポート問い合わせした結果、やりたいような構成はできないという回答をもらったわけです。

以下にも直接通信ができないと書いてあるので、そのとおりだと言う話でもあるのですが。。。

最終的なconfig.yml

というわけで、最終的には実行コンテナをDockerfileのベースイメージにし、Dockerfileに書いている処理をstepsに記載するという、普通のやり方で対応しました。

以下は最終的なconfig.ymlを適当に抜粋したものになります。

version: 2

jobs:
  build:
    docker:
      - image: circleci/python:3.6.6
        name: hoge-app
        environment:
          DB_HOST: test-db
      - image: circleci/mysql:5.7-ram
        name: test-db
        environment:
          MYSQL_USER: root
          MYSQL_ALLOW_EMPTY_PASSWORD: yes
          MYSQL_TCP_PORT: 3306
      - image: redis
        name: redis
      - image: arafato/azurite:latest
        name: azurite
    working_directory: ~/hoge-app
    environment:
      LANG: ja_JP.UTF-8
      LC_ALL: ja_JP.UTF-8
      LANGUAGE: ja_JP:ja
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Set locales
          command: sudo localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
      - run:
          name: Update submodule
          command: git submodule update --recursive --init
      - run:
          name: Install dependencies
          command: |
            sudo apt-get update && \
            sudo apt-get install -y curl build-essential \
            mecab mecab-ipadic libmecab-dev
      - run:
          name: Install dependencies
          command: pipenv sync --dev
      - run:
          name: Setup azurite filedata
          command: curl -v -X PUT -H 'x-ms-version:2017-11-09' http://azurite:10000/azureacount/filedata?restype=container

      - run:
          name: Lint code
          command: pipenv run lint 2>&1 | tee lint.log
      - run:
          name: Test hoge-app
          command: pipenv run test 2>&1 | tee test.log

      - store_artifacts:
          path: lint.log
      - store_artifacts:
          path: test.log