第3回:.gitlab-ci.yml の基本とビルド・テスト
前回は、ローカル環境でコードの品質を担保するpre-commit フックの導入について解説しました。もし前回の記事をまだご覧になっていない方は、以下のリンクからぜひご確認ください。
GitLab で実現する実践CI/CD パイプライン構築ガイド第2回:ローカル環境での品質チェック:pre-commit フック
今回は、いよいよCI/CD パイプラインの心臓部である.gitlab-ci.yml ファイルに焦点を当てます。このファイルは、第一回の全体像で示したパイプラインの設計図です。
本記事では、このYAML ファイルの基本的な書き方を学び、まずはビルドと単体テストを自動化する方法を具体的に解説していきます。これにより、コードがリモートリポジトリへプッシュやマージされるたびに、自動テストを実行する仕組みが完成します。
.gitlab-ci.yml とは
.gitlab-ci.yml は、GitLab CI/CD のパイプラインを定義するための設定ファイルで、「リポジトリのコードに変更があったら、この手順で処理を進めてほしい」というGitLab への指示書となります。このファイルはYAML 形式にて記述し、Git リポジトリのルートディレクトリに配置します。
.gitlab-ci.yml の基本構造
.gitlab-ci.yml は大きく分けて二つの重要な要素で構成されます。
- ステージ(stages) パイプラインを構成する一連のステップを定義します。各ステージは順番に実行され、前のステージが成功した場合にのみ、次のステージが始まります。
-
ジョブ(job) 各ステージで実行する具体的なタスクを定義します。ジョブは、一意な名前(例:build_job)とscript キーワードが最低限必要ですが、stage 、image などのキーワードは、パイプラインの意図を明確にするため記述が推奨されています。
- script: ジョブで実行するシェルコマンドを記述します。
- stage: このジョブがどのステージに属するかを指定します。省略された場合は、デフォルトでtest ステージに割り当てられます。
- image:ジョブを実行するDocker コンテナイメージを指定します。ここに必要なツールがインストールされ、この隔離された環境の上でscript に書かれたコマンドが実行されます。
Pythonプロジェクトのサンプル
ここで利用するサンプルコードは以下の通りです。
ファイル構成
├── main.py
├── tests/
│ └── test_main.py
├── .pre-commit-config.yaml
├── .gitlab-ci.yml
└── requirements.txt
main.py
このファイルは第2回でも利用したアプリケーションの本体で、シンプルな挨拶メッセージを返します。
def get_message():
"""Returns a simple greeting message."""
return "Hello, World!"
def main():
"""Main function to print the greeting."""
print(get_message())
if __name__ == "__main__":
main()
test_main.py
このファイルは、main.py のget_message() 関数が期待通りの文字列を返すかを検証する単体テストです。pytest を使って実行します。
from main import get_message
def test_get_message_returns_correct_string():
"""Tests if get_message() returns the expected string."""
assert get_message() == "Hello, World!"
requirements.txt
このファイルには、プロジェクトに必要な依存ライブラリを記述します。今回のテスト実行にはpytest が必要になります。
pytest==7.4.0
pytest のバージョンを7.4.0と明記していますが、これを省略するとCI パイプラインを実行するたびにpytest の最新版がインストールされます。これは一見便利に思えますが、以下のようなリスクを伴うため、バージョンを固定してビルド環境の安全性を確保することをお勧めします。
- 予期せぬビルド失敗: 将来、pytest の新しいバージョンで、互換性を損なう変更が導入された場合、突然CI パイプラインが失敗するかもしれません。
- 再現性の喪失: 開発者のローカル環境とCI 環境とで、動作するバージョンが異なるため、ローカルでは成功するのにCI では失敗するといった、再現性のないバグが発生する原因となるかもしれません。
Python プロジェクトのビルドとテスト
それでは、用意したPython プロジェクトを使って、GitLab Runner 上で自動的にビルドと単体テストを実行するジョブを記述してみます。
Python はコンパイルを伴わないため、厳密な意味での「ビルド」は行われません。しかし、CI/CD の文脈では、依存ライブラリのインストールや実行環境の準備といった、コードをテスト・実行できる状態にする一連のプロセスを広義の「ビルド」と呼びます。
.gitlab-ci.yml
プロジェクトのビルドを行い単体テストを実施するジョブを記載します。
stages:
- build_and_test
# パイプライン内で一意なジョブ名
run_unit_tests:
# このジョブが実行されるステージ
stage: build_and_test
# Python 3.9環境のDockerイメージを指定
image: python:3.9-slim
# ジョブで実行するコマンド
script:
- echo "Installing dependencies..."
- pip install -r requirements.txt
- echo "Running unit tests..."
# テストを実行する前に、Pythonのパスにカレントディレクトリを追加
- export PYTHONPATH=.
- pytest
このジョブでは、コードをリポジトリからコピーするという明示的なコマンドはありませんが、これはGitLab Runner が自動で行ってくれるためです。CI/CD ジョブが開始されると、GitLab Runner はまずGit リポジトリのコードを自動でクローンし、ジョブの実行環境に配置します。そのため、script に書かれたコマンドは、すでにクローンされたコードに対して実行されます。
また、requirements.txt にpytest が含まれているため、pip install -r requirements.txt コマンドだけでテスト実行に必要なライブラリはすべてインストールされます。
補足:Runner の実行タイミング
このrun_unit_tests ジョブには、実行タイミングを限定する設定(only やrules)が記述されていません。これはGitLab のデフォルトの挙動に従うことを意味します。
GitLab CI/CD は、デフォルトで以下のイベントをトリガーにパイプラインを実行します。
- push イベント: リポジトリにコミットをプッシュしたとき。
- merge request イベント: 新しいマージリクエストを作成したときや、既存のマージリクエストに新しいコミットをプッシュしたとき。
このように、明示的に設定しない場合でも、開発者がコードを変更してプッシュするたびに自動でパイプラインが走り、即座にフィードバックが得られるようになっています。
GitLab パイプラインの実行
これまでに作成したファイルをGitLab サーバーにプッシュして、実際にパイプラインを動かしてみます。
Git コマンドを利用して、Bash ターミナル上でファイルをステージングエリアに追加し、コミットします。
$ git add .
$ git commit -m "add .gitlab-ci.yml and source code"
次に、リモートリポジトリにプッシュします。このコマンドでローカルのmain ブランチとリモートリポジトリのmain ブランチを紐づけます。次回からは git push だけでプッシュできるようになります。
$ git push -u origin main
このコマンドを実行すると、GitLab の認証が開始されるので自分の認証情報を入力してください。プッシュが完了すると、.gitlab-ci.yml ファイルが自動的に読み込まれ、CI パイプラインが実行されます。
GitLab パイプラインの実行結果の確認
GitLab のWeb ページで実行結果を確認してみます。該当のプロジェクトに入り、左のメニューから「Build -> Pipelines」 を選択します。最新のパイプラインが一番上に表示されます。
Status が Passed と表示されていればテストが成功したことを示します。もし失敗した場合は、赤い背景に Failed と表示されます。
ジョブをクリックすれば、詳細なログを確認してエラーの原因を特定できます。
上の画像にある2行目のパイプライン(失敗時のもの)を例に、エラーの原因を調べるためにジョブのログを表示してみます。ここには、テストを実行する際にmain関数が見つからないというエラーが表示されています。これは、テストを実行している環境が、テスト対象のmain.py ファイルを見つけられないことが原因です。
このエラーを解決するために、.gitlab-ci.yml のscript に export PYTHONPATH=. を追加しています。
これで、コードをプッシュするだけで自動的にテストが実行される、CI パイプラインの基本部分が完成しました。
まとめと次回予告
今回は、CI/CD の心臓部である**.gitlab-ci.yml** の基本構造について、Python プロジェクトのビルドと単体テストをGitLab CI 上で自動化する方法を解説しました。これで、コードがリモートリポジトリへプッシュされるたびに、自動で品質が確認される仕組みが完成しました。
次回は、CI/CDパイプラインをより堅牢にするためのステップ、静的解析と脆弱性診断について解説します。クリーンビルドされたコンテナイメージをコンテナレジストリに格納し、セキュリティと品質を確保するプロセスを自動化します。