GitLabでDockerを使ったアプリケーションのCIを行うTips

 これは 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イメージのレジストリにアクセスするため、あるいは成果物をプッシュするために、外部サービスのアカウント情報やシークレットトークンなどを使う必要が出てきます。そういった情報の使用方法の一つとして環境変数があります。

環境変数の設定画面はプロジェクトの左にあるバーを「Settings」、「CI / CD」とたどることで現れます。

 与えられたユーザ権限によっては「Settings」が表示されていないこともあります。その場合は権限を持つ人に追加を依頼しましょう。

 入力欄の横にある”Protected”は、保護された特定のブランチかタグでのみ値を使えるようにするオプションです。下記のドキュメントが参考になります。
https://docs.gitlab.com/ee/ci/variables/#protected-variables

 

ステージ間での成果物の持ち越し

 GitLab CIではbuild, test, deployというステージがあります。ステージが変わるたびに環境が作り直されるので、そのままではビルドしたファイルは消えてしまいます。ビルドしたDockerイメージも同じく消えます。ビルド済みのイメージを、ステージ間をまたいで使うために、

  • ビルドを行う環境でDockerイメージをファイル化してartifactsに入れる
  • 使いたいステージで使用設定をして引っ張り出す

ということを行う必要があります。

 まず、Dockerイメージは下記のコマンドでファイル化できます。

 ディレクトリdocker_imagesを作成し、そこに上記のコマンドでファイル化したDockerイメージを保存するという流れを考えます。このディレクトリdocker_imagesをartifactsとして指定し、のちに続くステージで使用します。

 .gitlab-ci.ymlで設定を行います。[stage_name].artifacts.pathsに、ディレクトリdocker_imagesを加えます。

 別のステージでオプションdependenciesにDockerイメージのビルドと保存を行ったステージbuild_imageを加えます。こうすることでartifactsとして指定していたものにアクセスできるようになります。artifactsにアクセスして、保存しておいたDockerイメージを展開します。これでビルド済みのイメージがdockerコマンドで使えるようになります。

ホストからコンテナをcURLで叩いてみる(Docker in Dockerの場合)

 CI環境のコンテナでDockerを使う一つの手段としてDocker in Dockerがあります。Docker in Dockerの使用設定を行った上で.gitlab-ci.ymlのオプションservicesにdocker:dindを加えると、コンテナ内でもdockerコマンドを使えるようになります。ただしこれはローカルで動いているDockerを使うわけではないので、下記のようなことをやってもレスポンスは返ってきません。

 ポートバインディングしたコンテナへのアクセスは"docker"という名前を使えば解決してくれます。

コンテナ内のアプリケーション起動を待ってから処理を開始する

 "docker run"や"docker-compose up"などのコマンドを実行するとコンテナが立ち上がります。Webアプリケーションとデータベースを入れたdocker-compose.ymlを書き、並行してコンテナを立ち上げたとして、このまますぐにE2Eテストを行おうとすると高確率で失敗します。コンテナを立ち上げても、直後に内部でWebアプリケーションやデータベースが起動するには多少時間がかかりますので、それを待たなければなりません。
 Docker Composeでコンテナに設定できるオプションにdepends_onというものがありますが、これはコンテナの立ち上げ順の制御に使えても、コンテナ間の通信ができる状態になっているかを制御してくれるものではありません。
 内部のアプリケーションが準備完了になるのを期待して適当な時間でsleepをかける、自分でwait.shを書くなどの方法もありますが、Webアプリケーションやデータベースならdockerizeを使うと楽に済みます。これを使えば、一行挟むだけでターゲットがlisten状態になるのを待ってから処理を進められるようになります。

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"を使ったやり方が特別な設定も不要で楽です。以下のように書けば環境変数はセットされており、コンテナレジストリへプッシュできるユーザとして認証が済みます。

 コンテナレジストリへ保存する場合のDockerイメージの名前は下記の構成でなければなりません。

"[registry URL]/[namespace]/[project]/[image]"

 適当なところに適当な名前でプッシュするというわけにはいきません。ですが"[image]"以下はパスを切ることが可能となっており、複数のDockerイメージをプロジェクト内に保存できるようになっています。
 上記の名前の構成は、プロジェクトのパスによっては長くなりかねません。書くのつらい… そのために設定済み環境変数"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すべてでインクリメントされていく値なので番号が大きくなりやすく、また飛びやすいのでやめておきます。

 

 ここでのまとめとして、コンテナレジストリへのプッシュを行うコマンドは下記のようになりました。環境変数を使っているのでプロジェクトごとに内容は自動で切り替わり、汎用性が高いです。

Dockerレジストリのちょっと細かいところ

 パブリックなDockerイメージのレジストリとしてDocker Hubがあります。これはただイメージのプッシュ、プルを行うためのサーバではなく、APIも持っており、イメージ情報を取得できるようになっています。

https://docs.docker.com/registry/spec/api/

 今回使っているGitLab CIもこれまでのとおり、Dockerイメージをプッシュ、プルをするためのレジストリを持っています。APIも持っていますが、Dockerレジストリとは異なる点がありました。

 つい最近、あるDockerイメージのバージョン一覧を取得したくて調べたのですが、Docker Hubは上にあるAPIのとおりでした。

 

 GitLab CIではAPIのドキュメントが見つからず、そんなものは存在しないのかともがいていたらGitLabのIssuesの中で見つけられました。

https://gitlab.com/gitlab-org/gitlab-ce/issues/40826

 なんとなく似ているぐらいのパス構成です。割愛しますが返ってくるJSONもちょっと似ている…ぐらいの別物でした。

 DockerレジストリのAPIを使ってやりたいことがある場合、GitLabのコンテナレジストリを使う場合は処理を分ける必要がでてきそうです。

デプロイ用のテンプレートファイルを用意して環境変数の値を埋め込む

 CIでDockerイメージをレジストリにプッシュします。たとえば本番がKubernetesの場合、このDockerイメージを使用したDeploymentのYAMLが欲しくなります。イメージ名に環境変数を使いましたが、同じ環境変数をYAMLファイルに埋め込んで保存したくなります。
 コマンド"envsubst"を使うのが王道ではないでしょうか。イメージnginx:alpineではデフォルトで使えるようになっています。コマンド"envsubst"はパッケージ"gettext"を入れれば使えるようになります。AlpineLinuxなら下記のように使います。

 たとえばKubernetesのPodの使用イメージを下記のように書いた_deployment.ymlを用意しておきます。

 環境変数を埋め込んだYAMLを出力します。

 あとはこれをartifactsに入れておくなどすればすぐに使うことができ、デプロイまでの手間が減ります。

 細かいことですが、テンプレートとなるファイルは_pod.ymlなどのように頭にアンダーバーをつけるなどして、拡張子を変えないでおくのがオススメです。pod.yml.templateというふうに末尾にテンプレートとして示すものを加えると、テキストエディタでのファイル形式の認識に影響が出てきます。

まとめ

 社内でGitLab CIが使える状態になっていましたが、いざCIを構築していくと、簡単な内容でも調べなければならないことがけっこう出てきました。この記事が同じGitLab CIを使う人の時間の消費を防ぎ、さくっとCIを構築する役に立てば嬉しいです。

Both comments and trackbacks are currently closed.