GitLab Pipelineを使ってテストを自動化する
これまではテスト駆動によるコーディングの進め方といった趣の内容でしたが、ここからようやく今回の本題であるCIの話に移ります。作成したPingPong APIを含んだサーバサイドアプリケーションのDockerコンテナイメージのビルド、レジストリへの保存やテストといったCIパイプラインを実装していきましょう。
サーバアプリケーションのDockerfile作成
ここまでで作成したPingPong APIを含んだサーバアプリケーションをコンテナイメージ化するために、Dockerfileを作成します。ここでは、DockerfileのDSL(Domain Specifig Language)については解説しません。これらの詳細について知りたい場合は、Docker公式のDockerfile referenceや、Docker実践ガイド第二版(古賀正純著、インプレス刊)などの書籍を参照してください。
さて、それでは作成したサーバサイドアプリケーションのDockerfile(server/Dockerfile)を下に示します(リスト18)。
リスト19:リスト18:server/Dockerfile
07 | RUN mkdir -p /home/django/.local/bin |
08 | # PATHにユーザ固有のpip install先を含める |
09 | ENV PATH=$PATH:/home/django/.local/bin |
10 | COPY --chown=django:django requirements.txt ./ |
11 | # requirementsをユーザ領域にインストール |
12 | RUN pip install --user -r requirements.txt |
13 | # アプリケーション本体をコンテナ内にデプロイ |
14 | COPY --chown=django:django ./sampleapp /home/django/sampleapp |
15 | WORKDIR /home/django/sampleapp |
16 | # コンテナ外部からもアプリケーションにアクセスできるように0.0.0.0:8000を指定 |
17 | CMD ["python", "manage.py" , "runserver", "0.0.0.0:8000"] |
補足として効率的にビルドを行うためのDockerfile記述の基本的な考え方について解説します。その基本的な考え方は、可能な限りイメージのキャッシュが効くようにするということです。詳細な説明は省きますが、変更されることが少ないものほどDockerfileの頭の方に記述すると覚えておくと良いでしょう。
今回の場合、それを端的に表しているのが、下記に抜粋している部分です(コード19)。
リスト20:リスト19:server/Dockerfileの抜粋(パッケージ依存関係のインストールまで)
07 | RUN mkdir -p /home/django/.local/bin |
08 | # PATHにユーザ固有のpip install先を含める |
09 | COPY --chown=django:django requirements.txt ./ |
10 | # requirementsをユーザ領域にインストール |
11 | RUN pip install --user -r requirements.txt |
EXPOSEで外部公開するポートの指定、RUNでアプリケーションの実行ユーザであるdjangoユーザの作成とUSERによるdjangoユーザへのコンテナ内プロセスの(デフォルトの)実行ユーザの切り替えなどを行っています。さらに、COPYでrequirements.txtをコピーして、今回のアプリケーションの動作に必要なモジュールを、コンテナイメージ内にコードをデプロイする前にインストールしています。
これまで説明していなかったrequirements.txtですが、以下のリストのように単純にインストールするPythonのパッケージ(とそのバージョン)を記載したものです(コード20)。
リスト21:リスト20:server/requirements.txt
2 | djangorestframework==3.9.1 |
リスト19にも示しているように、最も変更頻度が高いであろうアプリケーションコードのデプロイを、Dockerfileのなるべく後段に持っていくことで、コンテナイメージのキャッシュを可能な限り活かしつつ開発を進めることができます(ただし、今回はGitLabのCI Pipelineのアーキテクチャの都合上、イメージキャッシュが効きにくくなっています。そのため、ローカルの開発端末で試行錯誤する際の効率の向上にとどまる点はご理解ください)。
効率的なイメージのビルドを実現するための方法をさらに掘り下げたい方は、DockerCon EU 2018 - Dockerfile Best Practicesや、Better Docker Imageなどを参照してください。
さて、ビルドが正常にできるか、アプリケーションが正しく立ち上がるかを確認するためにイメージをビルドしてみましょう(リスト21)。
リスト22:リスト21:サーバアプリケーションのコンテナイメージのビルド
01 | $ docker build -t test `pwd` |
02 | Sending build context to Docker daemon 47.1kB |
03 | Step 1/11 : FROM python:3.6-alpine |
06 | Step 11/11 : CMD ["python", "manage.py" , "runserver", "0.0.0.0:8000"] |
07 | ---> Running in 42e30aef24ea |
08 | Removing intermediate container 42e30aef24ea |
10 | Successfully built f4447a7efa2b |
11 | Successfully tagged test:latest |
Successfully...の記述から無事Dockerイメージのビルドが成功したと判断できます。手元でイメージを起動して動作確認してみましょう(リスト22)。
リスト23:リスト22:ビルドしたサーバアプリケーションの動作確認
1 | $ docker run -d -p 8000:8000 --name test test |
レスポンスを見る限り、期待したとおりのイメージをビルドできているようです。
GitLabのCI/CD Pipeline定義の作成(.gitlab.ymlの記述)
今回はGitLab上でCIを行うので、GitLab CI/CD Pipelineを利用します。GitLab CI/CD Pipelineを利用する際はリポジトリのルートディレクトリに.gitlab-ci.yml※3ファイルを作成して、その中にパイプライン定義を記述します。ここまでで作成したPingPong APIを含んだサーバサイドアプリケーションのコンテナイメージをビルドした後に、テストを実行するパイプラインを作成していきましょう(リスト23)。
リスト24:リスト23:サーバアプリケーションのビルドとテストを実装したパイプライン(.gitlab-ci.yml)
11 | - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com |
13 | - docker build -t registry.gitlab.com/fufuhu/ti_rancher_k8s_sampleapp/todo/server:latest server |
14 | - docker push registry.gitlab.com/fufuhu/ti_rancher_k8s_sampleapp/todo/server:latest |
20 | image: registry.gitlab.com/fufuhu/ti_rancher_k8s_sampleapp/todo/server:latest |
※3:ファイル名は.gitlab-ci.ymlにしておいてください.gitlab-ci.yamlでは動作しません
Dockerfileなどと同様に、このパイプラインについても個別のシンタックスに関する解説はいたしません。
個別のシンタックスの詳細についてはGitLab CI/CD Pipelineの公式ドキュメント、または、GitLab実践ガイド(北山晋吾著、インプレス刊)を参照してください。
パイプラインの全体的な構成としては、
- パイプラインを2つのステージ(buildとtest)に分割
- buildステージのsampleapp_image_buildジョブでサーバサイドアプリケーションのイメージをビルドしてGitLab Container Registryに保存
- testステージのsampleapp_testジョブでサーバサイドアプリケーションのイメージをテスト
としています。
またDockerイメージのリポジトリ名(コード23の例では、registry.gitlab.com/fufuhu/ti_rancher_k8s_sampleapp/todo/server)のregistry.gitlab.com/fufuhu/ti_rancher_k8s_sampleappの部分はGitLabに作成したプロジェクト名に依存するので、その点は注意してください。
では、作成した各種ファイルをリモートリポジトリにプッシュしましょう。ファイルの変更をプッシュすれば、.gitlab-ci.ymlに定義したとおりにパイプラインが実行されるはずです。
では、当該リポジトリを格納しているプロジェクトにアクセスしてパイプラインの状態を確認してみましょう。GitLab.comの当該プロジェクトにアクセスし、左側メニューのCI/CDの下にあるPipelinesを選択します(図10)。
図10:左側メニューのCI/CDの下にあるPipelinesを選択する
画面が遷移すると、実行中または実行待ち、実行済みのパイプラインの一覧が表示されます(図11)。
図11:パイプラインの一覧
図11のPipelineカラムの数字(図11では#46399869)をクリックして、パイプラインの実行結果の詳細(図12)を見てみましょう。
図12:パイプラインの実行結果詳細
BuildステージとTestステージに分割されたパイプラインがグラフィカルに表示されていることがわかります。では個別のジョブについて見るために、画面上に表示されている個々のジョブの枠をクリックすると、ジョブの詳細を確認できます(図13、14)。
図13:sampleapp_image_buildジョブの実行結果
図14:sampleapp_testジョブの実行結果
図13、14の表示内容からわかるとおり、ジョブの中で記述したスクリプトのコマンドの実行結果の標準出力および標準エラー出力の内容が表示されます。ジョブの成否は、ジョブ内部で実行されている個々のコマンドの終了コードで判断しています。
それでは、サーバサイドの機能の実装が終わったので、マージリクエストを作成しましょう。
プロジェクトの左側メニューからMerge Requestsを選択し、マージリクエストの管理ページへ遷移します。今回の場合は先ほどプッシュしたコミットのマージリクエストを作成するためのボタン(図15右側のCreate merge requestボタン)があるので、これをクリックします。
図15:マージリクエストの作成
マージリクエストの名前、サマリについての記述を行い、Submit merge requestボタンをクリックしてマージリクエストを作成します(図16)。
図16:マージリクエストのサブミット
マージリクエストを作成すると、パイプラインの実行結果もマージリクエストの中に表示されます(図17:画像中Pipeline #46399869 passed for ~~の部分)。
図17:マージリクエストの例
図17からわかるとおり、CIパイプラインが正常に終わってることがマージリクエストの画面上にも表示されます。実装コードに対するテストコードが適切に記述されていることが確認でき、テストをすべてパスできている場合はマージしても良いでしょう。
最後に動作確認を進めていきましょう。図18にあるとおり、CIパイプラインが実行された結果、Dockerコンテナイメージが生成され、GitLab Container Registryに保管されています。
図18:作成されたDockerコンテナイメージ
念のための動作確認
作成したDockerイメージをローカルに持ってきて、念のための動作確認をしてみましょう。イメージの取得手順はリスト24に挙げたとおりです。docker login時に尋ねられるユーザ名とパスワードは、GitLab.comのユーザ名とパスワードになります。
リスト25:リスト24:作成したDockerイメージの取得
01 | $ docker login registry.gitlab.com |
02 | $ docker pull registry.gitlab.com/fufuhu/ti_rancher_k8s_sampleapp/todo/server:latest |
03 | latest: Pulling from fufuhu/ti_rancher_k8s_sampleapp/todo/server |
04 | 8e402f1a9c57: Already exists |
05 | cda9ba2397ef: Already exists |
06 | 7a817918d4d5: Already exists |
07 | e64fbda5eefb: Already exists |
08 | 6d027cc21c42: Already exists |
09 | 77c0f5455a39: Pull complete |
10 | 7130100d2837: Pull complete |
11 | a00052bb47ab: Pull complete |
12 | 504f2626383e: Pull complete |
13 | d823da105d66: Pull complete |
14 | Digest: sha256:6630bc23bf1ced5e1f643d4feafeaa1d60979914fe1ff8e5849c08f2483b9c06 |
15 | Status: Downloaded newer image for registry.gitlab.com/fufuhu/ti_rancher_k8s_sampleapp/todo/server:latest |
イメージの取得が完了したら、起動してPingPong APIを叩いてみましょう(コード25)。
リスト26:リスト25:取得したサーバアプリケーションイメージが正常に動作しているようす
1 | $ docker run -d -p 8000:8000 --name test registry.gitlab.com/fufuhu/ti_rancher_k8s_sampleapp/todo/server:latest |
2 | latest: Pulling from fufuhu/ti_rancher_k8s_sampleapp/todo/server |
curlコマンドの実行結果から、期待したとおりの動作をしていることがわかります。
まとめ
今回はPythonを使ったサンプルアプリケーションの実装とテストの記述、GitLab CI/CDを使ったCIの実行について解説しました。
今回実装した機能は非常に単純なものなので、意味のないもののように思えますが、コード規模が大きくなるほど効いてきます。例えばコードのリファクタリングを安全に行えたり、コードの変更による影響範囲の可視化などに役立つはずです。
今回はあくまでも実際にコンテナベースのCIの触りまでの部分の流れを解説したのみなので、テストについて入門から知りたい場合は、知識ゼロから学ぶソフトウェアテスト【改訂版】(高橋寿一著、翔泳社)を、テスト駆動開発についてコードのリファクタリング方法なども含めて理解したい場合はテスト駆動開発(KentBeck著、和田卓人訳、オーム社)などを参照してみると良いでしょう。
次回はRancherに戻って、Rancherのカスタムカタログリポジトリの実装、および今回作成しているサンプルアプリケーションをデプロイするためのカタログアプリ用のチャート作成について解説します。