GitLab CI/CD 実践[基本編]:パイプライン作成の基本

はじめに

前回の記事では、GitLabのコンテナレジストリについて2回にわたり解説しました。リポジトリに加えてレジストリを利用することで、ソースコードとコンテナイメージを一元管理できる点を確認しました。

今回からは、いよいよGitLabのCI/CD機能に焦点を当てます。CI/CDは、開発からデプロイまでの流れを自動化するための基本となる仕組みです。特にGitLabでは、リポジトリ・レジストリ・Runnerを組み合わせることで、シンプルながら強力なパイプラインを構築できます。

本記事では基本編として、ビルド、push、デプロイといったCI/CDにおける基本的な操作をジョブ単位で検証します。まずはシンプルなジョブを一つずつ動かし、パイプラインの仕組みに慣れることを目的とします。次回はこの内容を発展させ、複数のステージを組み合わせたマルチステージパイプラインを解説します。

ゴール

  • GitLab Runnerを利用して簡単なCI/CDパイプラインを実行できるようにする
  • ビルド→push→デプロイの一連の流れを体験する
  • パイプラインの仕組みを理解する

前提条件

本記事では、あらかじめ以下の環境が構築されていることを前提とします。環境の詳細な構築手順については、環境構築編の記事をご参照ください。

  • GitLab(Self-Managed版)
    • Omnibusパッケージを利用してLinuxサーバーにインストールしたGitLabを利用します
    • 無償版であるCommunity Editionを利用します
  • Gitlab Runner
    • OpenShiftクラスター上にデプロイ済みのRunnerを利用して、ジョブを実行します
  • OpenShiftクラスター
    • 閉域環境(インターネット非接続環境)に構築されています
  • GitLab Container Registry
    • コンテナイメージのpush / pullに利用します

これらの環境を利用して、GitLab CI/CDパイプラインを実行し、ビルドからデプロイまでの流れを検証できます。また、本記事では以下のようにテスト用のプロジェクトとブランチを用意して検証します。

テスト用プロジェクトの作成

  • 任意の名称の動作確認用プロジェクトを新規作成します。例:test-project
  • このプロジェクト配下で以降のジョブ検証を行います。

ブランチ構成

  • 本記事では、各ジョブを個別のブランチで検証します。
  • 基本の流れはbuild→push→deployですが、deployについては2つの方法(マニフェスト、Helm)をそれぞれ検証する構成にしています。実際の運用ではHelmを利用するケースが多いですが、まずはマニフェスト適用から試すとより理解が深まります。
    • single-job-build
      • 目的:Dockerイメージのビルド
      • 主要ファイル:.gitlab-ci.yml、Dockerfile
    • single-job-push
      • 目的:既存イメージのタグ名リネーム&レジストリへのpush
      • 主要ファイル:.gitlab-ci.yml
    • single-job-deploy-manifest
      • 目的:oc applyでマニフェストを適用
      • 主要ファイル:.gitlab-ci.yml、deploy.yaml
      • ※deployの検証パターン1
    • single-job-deploy-helm
      • 目的:Helmチャートを利用したデプロイ
      • 主要ファイル:.gitlab-ci.yml、templates/ディレクトリ、values.yaml、Chart.yaml
      • ※deployの検証パターン2

各ブランチのファイル配置

single-job-build

test-project/
├─ .gitlab-ci.yml
├─ Dockerfile
└─ README.md

single-job-push

test-project/
├─ .gitlab-ci.yml
└─ README.md

single-job-deploy-manifest

test-project/
├─ .gitlab-ci.yml
├─ deploy.yaml
└─ README.md

single-job-deploy-helm

test-project/
├── Chart.yaml
├── templates
│   └── deployment.yaml
├── values.yaml
└─ README.md

用語説明

本記事で紹介するパイプラインを理解するために、CI/CDに関連する基本用語を簡単におさらいしておきます。

  • Pipeline(パイプライン)
    • GitLab CI/CDにおける一連の自動化処理の流れを指します。コードのビルド、テスト、デプロイといった複数の処理を順番にまとめたものです。
  • Stage(ステージ)
    • パイプラインを構成する処理のグループです。たとえば、「build」「test」「deploy」といった大まかな段階をステージとして定義します。ステージは順番に実行されます。
  • Job(ジョブ)
    • 各ステージの中で実行される具体的な処理の単位です。ビルドのジョブ、デプロイのジョブといった形で定義されます。ジョブは.gitlab-ci.ymlに記述します。
  • Runner(ランナー)
    • ジョブを実際に実行する役割を担うコンポーネントです。GitLab本体とは別に用意され、指定した環境(Docker、Kubernetes、VMなど)でジョブを動かします。本記事ではOpenShift上にデプロイしたRunnerを利用します。

これらの用語を理解しておくことで、以降のサンプルコードや解説がスムーズに読み進められます。

サンプルコード全体像

今回の検証では、以下の基本的な処理をそれぞれ独立したジョブとして実行し、CI/CDの流れを確認します。

  • Build:Dockerイメージのビルド
  • Push:GitLab Container Registryへのイメージpush
  • Deploy(マニフェスト):Kubernetesマニフェストを適用してデプロイ
  • Deploy(Helm):Helmチャートを利用してデプロイ

まずは全体像をイメージしやすいよう、シンプルな.gitlab-ci.ymlの例を示します。

stages:
  - build
  - push
  - deploy
build_job:
  stage: build
  script:
    - echo "Build Docker image"
push_job:
  stage: push
  script:
    - echo "Push image to GitLab Registry"
deploy_job:
  stage: deploy
  script:
    - echo "Deploy application"

上記は最小限の例であり、実際には各ジョブに詳細な処理(docker build / push、kubectl apply、helm installなど)を記述します。以降のセクションでは、各ジョブを個別に取り上げ、実際のコード例とともに動作を確認していきます。

ジョブ別解説

ここからは、実際に.gitlab-ci.yml にジョブを定義し、CI/CDの流れを確認していきます。
基本の流れはBuild → Push → Deployです。まずはビルドとpushを行い、その後のデプロイ方法として「マニフェスト適用」と「Helm」の2種類を検証します。

Buildジョブ

最初に実行するのはBuildジョブです。ソースコードからDockerイメージを作成し、次のステップで利用できる状態にします。

以下の例では、docker buildを実行してnginxのイメージを作成し、GitLabのレジストリに登録できるようにリポジトリ名をtestに変更したタグ(test:v1.0)を付与しています。

なお、本記事の環境ではOpenShiftクラスターはインターネットと接続できない閉域環境に配置しているため、ベースイメージとなるnginxのイメージはあらかじめGitLabのプロジェクト内のレジストリに格納してあります。

最小のnginxベースのサンプルです。

Dockerfile

FROM registry.gitlab.local.example.com/root/test-project/nginx:v1.0

ここでは Docker-in-Docker(dind)でビルドする例を示します。GitLab既定のレジストリ変数(CI_REGISTRY / CI_REGISTRY_IMAGE / CI_REGISTRY_USER / CI_REGISTRY_PASSWORD)を利用します。

このジョブを実行することで、アプリケーションのDockerイメージが生成されます。

.gitlab-ci.yml

stages:
  - build
build_job:
  stage: build
  tags:
    - devsecops-runner
  image: registry.gitlab.local.example.com/root/registry-project/docker:28.3.3
  services:
    - name: registry.gitlab.local.example.com/root/registry-project/docker:28.3.3-dind
      alias: docker
      entrypoint: ["/bin/sh","-lc"]
      command:
        - >
          cp /etc/gitlab-runner/certs/gitlab.local.example.com.crt /usr/local/share/ca-certificates/ca.crt &&
          update-ca-certificates &&
          exec dockerd-entrypoint.sh
          --tls=false
          --host=unix:///var/run/docker.sock
          --host=tcp://0.0.0.0:2375
  variables:
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ""
  before_script:
    - cp /etc/gitlab-runner/certs/gitlab.local.example.com.crt /usr/local/share/ca-certificates/ca.crt
    - update-ca-certificates || true
  script:
    - |
      echo "Login to $CI_REGISTRY"
      docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
      docker build . --tag="${CI_REGISTRY_IMAGE}/test:v1.0"

上記のサンプルコードをGitLabのプロジェクトにpushすると、pushをトリガーとしてパイプラインが起動します。

プロジェクト画面のサイドメニューから[ビルド] > [パイプライン]を選択して移動し、パイプラインの実行結果を確認します。

パイプラインの実行に成功したことを確認できました。

ジョブのログを見ると、.gitlab-ci.yml内で指定した通り、イメージタグのリネームが行われています。

Pushジョブ

次に実行するのはPushジョブです。このジョブでは、あらかじめ検証用プロジェクトのレジストリにpushしておいた既存のnginx:v1.0イメージのリポジトリ名をtest:v1.0に付け替えて、GitLab Container Registryへpushします。

stages:
  - push
push_only:
  stage: push
  tags: [devsecops-runner]
  image: registry.gitlab.local.example.com/root/registry-project/docker:28.3.3
  services:
    - name: registry.gitlab.local.example.com/root/registry-project/docker:28.3.3-dind
      alias: docker
      entrypoint: ["/bin/sh","-lc"]
      command:
        - >
          cp /etc/gitlab-runner/certs/gitlab.local.example.com.crt /usr/local/share/ca-certificates/ca.crt &&
          update-ca-certificates &&
          exec dockerd-entrypoint.sh
          --tls=false
          --host=unix:///var/run/docker.sock
          --host=tcp://0.0.0.0:2375
  variables:
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ""
    SOURCE_IMAGE: registry.gitlab.local.example.com/root/test-project/nginx:v1.0
  before_script:
    - cp /etc/gitlab-runner/certs/gitlab.local.example.com.crt /usr/local/share/ca-certificates/ca.crt
    - update-ca-certificates || true
  script: |
    echo "Login to $CI_REGISTRY"
    docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
    echo "Pull source image and push re-tagged"
    docker pull "$SOURCE_IMAGE"
    docker tag "$SOURCE_IMAGE" "${CI_REGISTRY_IMAGE}/test:v1.0"
    docker push "${CI_REGISTRY_IMAGE}/test:v1.0"

このジョブでレジストリにpushしたイメージは、後続のデプロイジョブで利用できるようになります。

上記のサンプルコードをGitLabのプロジェクトにpushすると、pushをトリガーとしてパイプラインが起動します。

プロジェクト画面のサイドメニューから[ビルド] > [パイプライン]を選択して移動し、パイプラインの実行結果を確認します。

パイプラインは正常に実行されていることを確認できました。

プロジェクト画面のサイドメニューから[デプロイ] > [コンテナレジストリ]を選択して移動し、レジストリにpushしたイメージが格納されていることを確認します。

ジョブ内で指定した通り、test:v1.0というタグのイメージが保存されていることを確認できました。

Deployジョブ(マニフェスト)

ここからはデプロイの検証です。まずはKubernetesのマニフェストを直接oc applyで適用する方法を試します。
※本記事の環境ではコンテナ基盤としてOpenShiftを利用しているため、クラスター操作のCLIツールとしてocを利用しています。Kubernetes環境を利用している場合は、imageでkubectlを利用可能なイメージを指定し、scriptのデプロイコマンドはkubectl applyを使用してください。

まずはocコマンドを実行できるコンテナイメージをローカルでビルドして、GitLabのレジストリにpushします。

$ podman login registry.redhat.io
$ podman pull registry.redhat.io/openshift4/ose-cli-rhel9:v4.18

$ podman login registry.gitlab.local.example.com
$ podman tag registry.redhat.io/openshift4/ose-cli-rhel9:v4.18 \
  registry.gitlab.local.example.com/root/test-project/oc:4.18
$ podman push registry.gitlab.local.example.com/root/test-project/oc:4.18

デプロイテスト用のマニフェストファイルです。このファイルを作成したデプロイテスト用のブランチにpushしておきます。

deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: gitlab-runner-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      serviceAccountName: gitlab-runner-sa
      containers:
        - name: nginx
          image: registry.gitlab.local.example.com/root/test-project/test:v1.0
          imagePullPolicy: IfNotPresent
.gitlab-ci.yml
stages:
  - deploy
deploy_manifest:
  stage: deploy
  tags: [devsecops-runner]
  image: registry.gitlab.local.example.com/root/test-project/oc:4.18
  before_script:
    - oc whoami
  script: |
    set -euo pipefail
    oc apply -f deploy.yaml

このジョブを実行すると、deploy.yamlに記載されたリソースがOpenShiftクラスター上に作成されます。

上記のサンプルコードをGitLabのプロジェクトにpushすると、pushをトリガーとしてパイプラインが起動します。

プロジェクト画面のサイドメニューから[ビルド] > [パイプライン]を選択して移動し、パイプラインの実行結果を確認します。

ジョブの実行が成功していることを確認できました。

続いて、クラスターにPodがデプロイされていることを確認します。

deploy.yamlで指定した通り、myappという名前のPodが起動していることを確認できました。

$ oc get pod -n gitlab-runner-test -l app=myapp

NAME                    READY   STATUS    RESTARTS   AGE
myapp-7d45468b7-24mwn   1/1     Running   0          69m

Deployジョブ(Helm)

最後にHelmを使ったデプロイ方法です。Helmを利用することで、複数のマニフェストをまとめて管理することができ、環境ごとの設定差分を柔軟に扱うことができます。

今回の検証では、以下のテスト用のHelmチャートを利用します。

先ほどと同様に、プロジェクト上にHelmテスト用のブランチを作成してHelmチャートを作成したブランチにテスト用のチャートをpushしておきます。

Podが参照するイメージは、Pushジョブでpushしたイメージを利用します。

Chart.yaml

apiVersion: v2
name: single-job-deploy-helm-chart
description: A Helm chart for a single deployment job
type: application
version: 0.1.0
appVersion: "1.0"

templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: single-job-helm-deploy
  labels:
    app: helm-test-app
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: helm-test-app
  template:
    metadata:
      labels:
        app: helm-test-app
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end}}
      containers:
        - name: nginx
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}

values.yaml

replicaCount: 1
image:
  repository: "registry.gitlab.local.example.com/root/test-project/test"
  pullPolicy: IfNotPresent
  tag: "v1.0"
imagePullSecrets:
  - name: gitlab-registry-secret

Helmチャートをデプロイできるようにするため、Helm CLIが利用できるジョブPod用のイメージをプロジェクト内のレジストリに格納します。

$ podman pull docker.io/alpine/helm:3
$ podman tag docker.io/alpine/helm:3 \
  registry.gitlab.local.example.com/root/test-project/helm:3
$ podman push registry.gitlab.local.example.com/root/test-project/helm:3

.gitlab-ci.yml

stages: [deploy]

deploy_helm:
  stage: deploy
  tags: [devsecops-runner]
  image: registry.gitlab.local.example.com/root/test-project/helm:3
  variables:
    RELEASE_NAME: mynginx
    NAMESPACE: gitlab-runner-test
  before_script:
    - |
      if [ -n "${KUBECONFIG_DATA:-}" ]; then
        mkdir -p ~/.kube
        echo "$KUBECONFIG_DATA" | base64 -d > ~/.kube/config
        export KUBECONFIG="$HOME/.kube/config"
      fi
    - helm version
  script: |
    set -euo pipefail
    helm upgrade --install "$RELEASE_NAME" ./chart \
      --namespace "$NAMESPACE" \
      -f ./values.yaml \
      --wait --timeout 180s
    helm -n "$NAMESPACE" status "$RELEASE_NAME"

Helmを利用したデプロイでは、再実行時もupgrade –installにより差分更新されるため、継続的デプロイ(CD)の仕組みとして有効です。

上記のサンプルコードをGitLabのプロジェクトにpushすると、pushをトリガーとしてパイプラインが起動します。

プロジェクト画面のサイドメニューから[ビルド] > [パイプライン]を選択して移動し、パイプラインの実行結果を確認します。

ジョブの実行に成功していることを確認できました。

続いて、クラスターにPodがデプロイされていることを確認します。

templates/deployment.yamlで指定した通り、helm-test-appという名前のPodが起動していることを確認できました。

$ oc get pod -n gitlab-runner-test -l app=helm-test-app

NAME                                      READY   STATUS    RESTARTS   AGE
single-job-helm-deploy-6bdbc56bbf-58nv8   1/1     Running   0          68m

まとめ

本記事では、GitLab CI/CDの基本的な使い方を確認するために、ビルド→push→デプロイという一連の流れをジョブ単位で検証しました。

  • BuildジョブでDockerイメージを作成
  • PushジョブでGitLabコンテナレジストリへ登録
  • DeployジョブでOpenShiftクラスターにデプロイ(マニフェスト / Helm)

ジョブを分けて検証することで、CI/CDの各ステップを個別に把握でき、トラブル発生時の切り分けや理解の定着に役立ちます。これは、後に複数のステージを組み合わせたパイプラインを設計する際の基盤となります。

次回は、今回学んだ各ジョブを組み合わせてマルチステージパイプラインを構築したり、パイプラインの実行管理をする方法について解説します。これにより、より実践的なCI/CDパイプラインを設計できるようになります。

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です