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