この記事はミクシィグループ advent calendar 2019の12月25日分にあたります。
TL;DR
- CircleCI remote-docker上でvolumeが使えないので、コンテナの内と外でファイルを共用できず、つらい
- シェルコマンドで解決する
- machine executor ubuntuを用いて解決する
CircleCI上でDockerとファイルをうまくやりとりする
先日登壇発表したNuxt.js SSR E2Eテストの実装のこぼれ話です。
スライドの内容をかいつまむと、CI上でうまくDockerを走らせてスクリーンショットテストを実行するというものですが、実装中に頭を悩ませたのがCircleCIでDocker Volumeが使えないという問題でした。そのため、ヘッドレスブラウザで生成した画像をうまく外部にアップロードできないトラブルが発生していました。この記事ではその現象をうまく切り抜けるためのテクニックを紹介します。
CircleCI 上でvolumeが使えない
さっそく具体例を見ていきます。
/blog
├── Dockerfile
├── asset
│ ├── a.txt
│ └── b.txt
└── docker-compose.yml
というフォルダがあったとしましょう。
このとき、docker-compose.ymlとDockerfileは次のように設定されています:
version: '3.7'
services:
blog:
build: .
tty: true
volumes:
- ./asset:/asset-in-docker
command: ls /asset-in-docker
FROM node:10-slim
Docker volumeとしてasset
ディレクトリをマウントし、その内部をls
するだけのシンプルな構造と仮定します。
ローカルで実行すると:
$ docker-compose up
Starting blog_blog_1 ... done
Attaching to blog_blog_1
blog_1 | a.txt b.txt
blog_blog_1 exited with code 0
確かにa.txt
および b.txt
があるのが見えます。
このまますんなりいってくれればよいのですが……。
CIで実行すると:
jobs:
ls_asset:
parallelism: 1
docker:
- image: circleci/node:12.13
working_directory: ~/repo
steps:
- checkout
- setup_remote_docker
- run:
name: docker-compose up
command: docker-compose up
working_directory: ~/repo/blog
docker-compose up を走らせると、
[circleci]$ docker-compose up
Starting blog_blog_1 ... done
Attaching to blog_blog_1
blog_blog_1 exited with code 0
a.txt
とb.txt
を見つけられませんでした。
原因特定
CircleCIに直接ssh接続してみると、Dockerコンテナ自体がasset
ディレクトリをマウントできていないことが分かります。ここでこの情報をもとに調べてみると、
Running Docker Commands - CircleCI
という公式ドキュメントが引っかかります。
It is not possible to mount a volume from your job space into a container in Remote Docker (and vice versa). You may use the docker cp command to transfer files between these two environments.
曰く、remote docker環境ではボリュームをマウントできません。代わりにdocker cp
コマンドを使って二つの環境間でファイルを交換してくださいとあります。そもそもremote dockerとはdockerを安全にビルドするための隔絶された空間なので、二環境間でディレクトリのマウントができないのには納得が行きます。
シェルコマンドで解決する
COPY asset asset-in-docker
$ mkdir dir-in-ci
$ docker-compose restart blog && docker cp $(docker-compose ps -q blog):/repo/blog/asset ./dir-in-ci/
$ ls ./dir-in-ci
コンテナのビルド時にassetを含めるようにします。
その後ドキュメントの指示通りにdocker cp
コマンドを使い、ファイルをコンテナから取り出します。ここでもう一つちょっとした落とし穴があり、少なくとも2019年12月20日現在、ドキュメントに書いてあるサンプルコマンドでは失敗するはずです。docker cp
を使うためにはそのコンテナが起動状態になければならないからです。ここではrestart
を用いてコンテナを起動し、docker-compose経由でコンテナidを取得してdocker cp
します。各自の環境に合わせてうまく書き換えてください。
machine executor ubuntuを用いて解決する
しかしこれでは完全な解決になったとはいえません。本来この仕事ではdocker volumeを使いたかったはず。CIのためにCOPY
を使い、ローカル実行時にvolumeが使えない。帳尻を合わせるようにローカル専用コマンドを作るのでは何のためにdockerコンテナを書いたのか分からなくなってしまいます。どこでも同じように・誰もが見通し良くコンテナを使えるようになっていてほしいですね。
ここは是が非でもdocker volumeを貫き通したいと思い調べ続けていたところ、次の短いページを発見しました!
How can I mount volumes to docker containers? - CircleCI
It's not possible to use volume mounting with the docker executor, but using the machine executor it's possible to mount local directories to your running Docker containers.
machine executorすなわち実行マシンの設定を変えればいい!
ということでubuntu-1604:201903-01
を使ってみます。
jobs:
ls_asset:
parallelism: 1
machine:
image: ubuntu-1604:201903-01
volumeを使用するため、Dockerfileとdocker-compose.ymlも最初の状態に戻しておきます。
すると...
[circleci]$ docker-compose up
Starting blog_blog_1 ... done
Attaching to blog_blog_1
blog_1 | a.txt b.txt
blog_blog_1 exited with code 0
確かにローカルと同じように動作させることができました!
ただし公式ドキュメント曰く、machine-executorは新しいコンテナビルドの方式しかサポートしないないこと、今後のアップデートによっては課金要素になり追加料金を請求されるかもしれないという二点に注意が必要です。