これは TECHSCORE Advent Calendar 2018 の14日目の記事です。
GitLab CIにてコンテナ化したアプリケーションのCIを行った中で得た知見のまとめです。GitLab CIのYAMLは書いたことがあって、Dockerを使ったアプリケーションのCIを始めたい人を読者として想定しています。GitLab CI環境でのDockerの使用準備は、公式ドキュメントや記事が簡単に見つかるのでそちらをご参考に。
GitLab CIでのDocker使用開始手順について
公式ドキュメント: https://docs.gitlab.com/ee/ci/docker/using_docker_build.html
Qiita: https://qiita.com/1000k/items/64b0cc8acdd964112b00
環境変数の設定
CI環境においてGitやDockerイメージのレジストリにアクセスするため、あるいは成果物をプッシュするために、外部サービスのアカウント情報やシークレットトークンなどを使う必要が出てきます。そういった情報の使用方法の一つとして環境変数があります。
1 2 |
docker login -u ${DOCKER_USER} -p ${DOCKER_PW} docker pull *** |
環境変数の設定画面はプロジェクトの左にあるバーを「Settings」、「CI / CD」とたどることで現れます。
与えられたユーザ権限によっては「Settings」が表示されていないこともあります。その場合は権限を持つ人に追加を依頼しましょう。
入力欄の横にある”Protected”は、保護された特定のブランチかタグでのみ値を使えるようにするオプションです。下記のドキュメントが参考になります。
https://docs.gitlab.com/ee/ci/variables/#protected-variables
ステージ間での成果物の持ち越し
GitLab CIではbuild, test, deployというステージがあります。ステージが変わるたびに環境が作り直されるので、そのままではビルドしたファイルは消えてしまいます。ビルドしたDockerイメージも同じく消えます。ビルド済みのイメージを、ステージ間をまたいで使うために、
- ビルドを行う環境でDockerイメージをファイル化してartifactsに入れる
- 使いたいステージで使用設定をして引っ張り出す
ということを行う必要があります。
まず、Dockerイメージは下記のコマンドでファイル化できます。
1 |
docker save webapp > webapp.tar |
ディレクトリdocker_imagesを作成し、そこに上記のコマンドでファイル化したDockerイメージを保存するという流れを考えます。このディレクトリdocker_imagesをartifactsとして指定し、のちに続くステージで使用します。
.gitlab-ci.ymlで設定を行います。[stage_name].artifacts.pathsに、ディレクトリdocker_imagesを加えます。
1 2 3 4 5 6 7 8 9 |
build_image: stage: build script: - docker-compose build - mkdir docker_images - docker save webapp > docker_images/webapp.tar artifacts: paths: - docker_images |
別のステージでオプションdependenciesにDockerイメージのビルドと保存を行ったステージbuild_imageを加えます。こうすることでartifactsとして指定していたものにアクセスできるようになります。artifactsにアクセスして、保存しておいたDockerイメージを展開します。これでビルド済みのイメージがdockerコマンドで使えるようになります。
1 2 3 4 5 6 |
test_container: stage: test dependencies: - build_image script: - docker load < docker_images/webapp.tar |
ホストからコンテナをcURLで叩いてみる(Docker in Dockerの場合)
CI環境のコンテナでDockerを使う一つの手段としてDocker in Dockerがあります。Docker in Dockerの使用設定を行った上で.gitlab-ci.ymlのオプションservicesにdocker:dindを加えると、コンテナ内でもdockerコマンドを使えるようになります。ただしこれはローカルで動いているDockerを使うわけではないので、下記のようなことをやってもレスポンスは返ってきません。
1 2 |
docker run -d -p 8080:80 nginx:alpine curl http://127.0.0.1:8080/ |
ポートバインディングしたコンテナへのアクセスは"docker"という名前を使えば解決してくれます。
1 2 |
docker run -d -p 8080:80 nginx:alpine curl http://docker:8080/ |
コンテナ内のアプリケーション起動を待ってから処理を開始する
"docker run"や"docker-compose up"などのコマンドを実行するとコンテナが立ち上がります。Webアプリケーションとデータベースを入れたdocker-compose.ymlを書き、並行してコンテナを立ち上げたとして、このまますぐにE2Eテストを行おうとすると高確率で失敗します。コンテナを立ち上げても、直後に内部でWebアプリケーションやデータベースが起動するには多少時間がかかりますので、それを待たなければなりません。
Docker Composeでコンテナに設定できるオプションにdepends_onというものがありますが、これはコンテナの立ち上げ順の制御に使えても、コンテナ間の通信ができる状態になっているかを制御してくれるものではありません。
内部のアプリケーションが準備完了になるのを期待して適当な時間でsleepをかける、自分でwait.shを書くなどの方法もありますが、Webアプリケーションやデータベースならdockerizeを使うと楽に済みます。これを使えば、一行挟むだけでターゲットがlisten状態になるのを待ってから処理を進められるようになります。
1 2 3 |
docker-compose up -d # コンテナ起動 dockerize -wait http://docker:3306 -timeout 30s # ポート3306にバインドしたMySQLの起動を待つ例 curl http://docker:8080/ |
https://github.com/jwilder/dockerize
GItLabのContainer Registryを使う。ビルドしたDockerイメージのバージョン管理も行う
- GitLabのコンテナレジストリを使うために"docker login"を行う
- Dockerイメージをビルド
- Dockerイメージをプッシュ
以上がここで行うことの流れです。
まずGitLabのコンテナレジストリへのプッシュ権限を得ておくため、"docker login"から始めます。これさえ済めばプッシュはいつもの"docker push"で行えます。
https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#authenticating-to-the-container-registry
上記のリンクにあるように、認証を通過する方法は数通りあります。その中でスペシャルユーザ"gitlab-ci-token"を使ったやり方が特別な設定も不要で楽です。以下のように書けば環境変数はセットされており、コンテナレジストリへプッシュできるユーザとして認証が済みます。
1 |
docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY |
コンテナレジストリへ保存する場合のDockerイメージの名前は下記の構成でなければなりません。
適当なところに適当な名前でプッシュするというわけにはいきません。ですが"[image]"以下はパスを切ることが可能となっており、複数のDockerイメージをプロジェクト内に保存できるようになっています。
上記の名前の構成は、プロジェクトのパスによっては長くなりかねません。書くのつらい… そのために設定済み環境変数"CI_REGISTRY_IMAGE"を使います。下記のようにビルドコマンドが書きかえられます。
1 |
docker build -t "${CI_REGISTRY_IMAGE}" . |
ここまで来たらもう一歩、Dockerイメージにつけるタグを決めます。先日Container Daysに参加してきましが、タグを"latest"で固定してしまうのはやめておいたほうがいいと何度か聞きました。適切なタグを付けて、Dockerイメージもバージョン管理対象にしてしまいましょう。
タグの内容は設定済み環境変数から持ってくると楽です。下記がGitLab CI中で使える環境変数の一覧リンクです。
GitLab - Predefined variables (Environment variables)
CI実行のたびにインクリメントされる値で探すと、"CI_PIPELINE_IID"が適していると思います。プロジェクトでのCI実行ごとにインクリメントされていく値です。"CI_PIPELINE_ID"という似た名前の値もありますが、これは同じGitLab内で使っているCIすべてでインクリメントされていく値なので番号が大きくなりやすく、また飛びやすいのでやめておきます。
1 |
docker build -t "${CI_REGISTRY_IMAGE}:${CI_PIPELINE_IID}" . |
ここでのまとめとして、コンテナレジストリへのプッシュを行うコマンドは下記のようになりました。環境変数を使っているのでプロジェクトごとに内容は自動で切り替わり、汎用性が高いです。
1 2 3 |
docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY} docker build -t "${CI_REGISTRY_IMAGE}:${CI_PIPELINE_IID}" . docker push "${CI_REGISTRY_IMAGE}:${CI_PIPELINE_IID}" |
Dockerレジストリのちょっと細かいところ
パブリックなDockerイメージのレジストリとしてDocker Hubがあります。これはただイメージのプッシュ、プルを行うためのサーバではなく、APIも持っており、イメージ情報を取得できるようになっています。
https://docs.docker.com/registry/spec/api/
今回使っているGitLab CIもこれまでのとおり、Dockerイメージをプッシュ、プルをするためのレジストリを持っています。APIも持っていますが、Dockerレジストリとは異なる点がありました。
つい最近、あるDockerイメージのバージョン一覧を取得したくて調べたのですが、Docker Hubは上にあるAPIのとおりでした。
1 |
https://hub.docker.com/v2/repositories/${IMAGE_NAME}/tags/ |
GitLab CIではAPIのドキュメントが見つからず、そんなものは存在しないのかともがいていたらGitLabのIssuesの中で見つけられました。
https://gitlab.com/gitlab-org/gitlab-ce/issues/40826
1 |
https://gitlab.com/$PROJECT_PATH/registry/repository/$REGISTRY_ID/tags?format=json |
なんとなく似ているぐらいのパス構成です。割愛しますが返ってくるJSONもちょっと似ている…ぐらいの別物でした。
DockerレジストリのAPIを使ってやりたいことがある場合、GitLabのコンテナレジストリを使う場合は処理を分ける必要がでてきそうです。
デプロイ用のテンプレートファイルを用意して環境変数の値を埋め込む
CIでDockerイメージをレジストリにプッシュします。たとえば本番がKubernetesの場合、このDockerイメージを使用したDeploymentのYAMLが欲しくなります。イメージ名に環境変数を使いましたが、同じ環境変数をYAMLファイルに埋め込んで保存したくなります。
コマンド"envsubst"を使うのが王道ではないでしょうか。イメージnginx:alpineではデフォルトで使えるようになっています。コマンド"envsubst"はパッケージ"gettext"を入れれば使えるようになります。AlpineLinuxなら下記のように使います。
1 2 |
apk add gettext envsubst '$FOO,$BAR' < [source file] > [dest file] |
たとえばKubernetesのPodの使用イメージを下記のように書いた_deployment.ymlを用意しておきます。
1 |
image: ${CI_REGISTRY_IMAGE}:${CI_PIPELINE_IID} |
環境変数を埋め込んだYAMLを出力します。
1 |
envsubst '$CI_REGISTRY_IMAGE,$CI_PIPELINE_IID' < _deployment.yml > deployment.yml |
あとはこれをartifactsに入れておくなどすればすぐに使うことができ、デプロイまでの手間が減ります。
細かいことですが、テンプレートとなるファイルは_pod.ymlなどのように頭にアンダーバーをつけるなどして、拡張子を変えないでおくのがオススメです。pod.yml.templateというふうに末尾にテンプレートとして示すものを加えると、テキストエディタでのファイル形式の認識に影響が出てきます。
まとめ
社内でGitLab CIが使える状態になっていましたが、いざCIを構築していくと、簡単な内容でも調べなければならないことがけっこう出てきました。この記事が同じGitLab CIを使う人の時間の消費を防ぎ、さくっとCIを構築する役に立てば嬉しいです。