🕌

GitLabでCIを実行する方法を調べてみた

に公開

今回はGitLabを利用してどのようにCIを実行するかについて試してみました。私自身今までGitHub Actionsしか使ってこなかったので、今回試してみました。

GitLabとは

GitLabはGitLab社が展開しているGitを利用するためのバージョン管理システムのサービスになります。DevOpsだけでなくDevSecOpsのワークフローを意識したサービスとなっています。

以下が公式ページとなっております。

https://about.gitlab.com/ja-jp/

CIとは?

CIとは継続的インテグレーション、英語にするとContinuous Integrationの頭文字をとってCIとなります。CIはソフトウェア開発において、頻繁に変更されるコードを統合して、それに対してコードフォーマットチェックやテスト、ビルドなどを自動で行うためのプロセスとなります。トリガーは様々ですが、CIを用いることでコードの品質の担保ができ、特にアジャイル開発のようにクイックに開発サイクルを回す場合は特に重要です。

GitLabでCIを動かしてみる

CIの実装方法

GitLabでCIを実装する場合はGitHub Actionsと同様にYAMLファイルを作成します。パイプラインを定義するには.gitlab-ci.ymlというファイル名で定義します。

それではまずはテストとしてHello Worldと表示されるパイプラインを実装してみます。

.github-ci.yml
Hello-World:
  stage: test
  script:
    - echo "Hello World"

これをpushするとパイプラインが実行され、以下のようなログとなりました。パイプライン終盤をみるとHello Worldと表示あsれていることがわかります。

Pythonコードを実装してテストを実行させてみる

次は簡単なPythonコードを作成して、pytestでテストをさせてみます。

まず環境構築にはuvを利用しました。コマンドは以下になります。

uv init -p 3.12
uv add pytest

次に関数を定義します。func.pyというファイルで四則演算を実装しました。

func.py
def add(x: int, y: int) -> int:
    return x + y


def sub(x: int, y: int) -> int:
    return x - y


def mul(x: int, y: int) -> int:
    return x * y


def div(x: int, y: int) -> float:
    if y == 0:
        raise ZeroDivisionError

    return float(x) / y

そしてtest.pyにテストコードを実装します。

test_func.py
from func import add, sub, mul, div
import pytest


def test_add():
    assert add(1, 1) == 2
    assert add(-1, 1) == 0
    assert add(0, 0) == 0


def test_sub():
    assert sub(1, 1) == 0
    assert sub(-1, 1) == -2
    assert sub(0, 0) == 0


def test_mul():
    assert mul(1, 1) == 1
    assert mul(-1, 1) == -1
    assert mul(0, 0) == 0


def test_div():
    assert div(1, 1) == 1
    assert div(-1, 1) == -1
    assert div(1, 2) == 0.5


def test_div_zero_div():
    with pytest.raises(ZeroDivisionError):
        div(1, 0)

それではCIパイプラインを実装しましょう。今回はuvの公式にて提供されているイメージを利用して作成してみます。公式ページは以下になるので、参照してください。

https://docs.astral.sh/uv/guides/integration/gitlab/

.github-ci.yml
variables:
  UV_VERSION: "0.5"
  PYTHON_VERSION: "3.12"
  BASE_LAYER: bookworm-slim
  # GitLab CI creates a separate mountpoint for the build directory,
  # so we need to copy instead of using hard links.
  UV_LINK_MODE: copy

exceute-pytest:
  stage: test
  image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
  script:
    - uv sync
    - uv run pytest

こちらを実行すると以下のようなログになります。結果を見ると、uvの設定も適切になされtest_func.pyに定義されたテスト全てが成功していることが確認できました。

ステージの利用

GitLabのCIではステージを定義することで、実行順を制御することができます。最も基本的な方法はstagesを利用し方法です。ここでは例としてジョブAとジョブBを連続して実行させてみます。

.github-ci.yml
stages:
  - stage_a
  - stage_b

job_a:
  stage: stage_a
  script: 
    - echo "Message from stage a"

job_b:
  stage: stage_b
  script: 
    - echo "Message from stage b"

このワークフローを実行してみます。パイプラインを見ると、以下のようにjob_aが完了した後にjob_bが実行されていることがわかります。

これを応用すると、例えば一つのステージ内で複数のジョブがあるようなフローも作成できます。

.github-ci.yml
stages:
  - stage_a
  - stage_b
  - stage_c

job_a:
  stage: stage_a
  script: 
    - echo "Message from stage a"

job_b_1:
  stage: stage_b
  script: 
    - echo "Message from stage b"

job_b_2:
  stage: stage_b
  script: 
    - echo "Message from stage b"

job_b_3:
  stage: stage_b
  script: 
    - echo "Message from stage b"

job_c:
  stage: stage_c
  script: 
    - echo "Message from stage c"

実行すると、ステージAが完了した後にステージBの3つのジョブが完了してからステージCが実行されることが確認できました。

ちなみに、ステージ内でエラーを発生させると次のステージには進めないようになります。試しに、先ほどのjob_b_3を以下のように書き換えてみます。

job_b_3:
  stage: stage_b
  script:
    - exit 1

実行すると以下のようにjob_b_3が失敗したことでjob_cが実行されていないことがわかります。

GitHub Actionsと比較して

今回利用している範囲ではGitHub ActionsでCIを構築するのと大きな差は感じませんでした。実行の依存関係も定義できますし、ワークフローも事前定義されたものが提供されているので、CIの作成には困ることはないかなと思います。また、YAMLで定義するという点も同じなので独自の実装方式をキャッチアップする必要も少なく導入ハードルは低いと思います。

まとめ

今回はGitLabでCIを実装してみました。とても使い勝手が良く、CIを簡単に実装できました。私自身GitHub Actionsでの実装経験はありましたが、GitLabでも同様に実装でき、とても使い勝手がよかったです。次はCDの実装であったり、より複雑なCIも実装してみたいと思います。

Discussion