コンテンツにスキップ

GitLab CI@CIツール

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. GitLab CIの仕組み

アーキテクチャ

GitLab Runnerは、GitLabリポジトリのgitlab-ci.ymlファイルをHTTPSで参照し、定義されたパイプラインを実行する。

gitlab-ci_architecture


GitLab Runner

▼ GitLab Runnerとは

GitLab CIのgitlab-ci.ymlファイルで定義されたパイプラインを実行する。


パイプライン構成

記入中...


リリースノート

▼ パイプラインバッジ

![pipeline](https://gitlab.com/foo-project/foo-repository/badges/main/pipeline.svg)

▼ 最新バージョンタグバッジ

![release](https://gitlab.com/foo-project/foo-repository/badges/-/badges/release.svg)


02. セットアップ

インストール

repository/
├── .gitlab-ci.yml


他のプライベートリポジトリへのアクセス

他のプライベートリポジトリにアクセスするためには、GitLab CIで、Gitの認証情報をセットアップする必要がある。

go_mod:
  stage: build
  image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/golang:${GO_VERSION}
  before_script:
    # 他のプライベートリポジトリからモジュールをプルするために、認証情報をセットアップする
    - echo "machine foo.gitlab.com" > ~/.netrc
    - echo "login ${GIT_USER}" >> ~/.netrc
    - echo "password ${GIT_TOKEN}" >> ~/.netrc
  script:
    # 他のプライベートリポジトリのモジュールをインポートする
    - go mod tidy


03. API

パイプライン実行

他のリポジトリのパイプラインを発火する。

例えば、GitOps時にアプリリポジトリがKubernetesリポジトリのパイプラインを実行する場合に役立つ。

trigger_upstream_pipeline:
  image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/alpine/git
  before_script:
    - apk update && apk --no-cache add curl curl-dev
  script:
    - |
      curl -X POST "https://gitlab.com/api/v4/projects/<プロジェクトID>/trigger/pipeline"
      -F token=<トークン>
      -F ref=<ブランチ>
      -F variables[<変数名>]=<値>


04. Global

予約変数

CI_COMMIT_BRANCH

現在のブランチ名が割り当てられている。

featureブランチの名前によらずにCIを実行する条件を定義できる。

foo_job:
  stage: build
  script:
    - echo foo
  rules:
    # featureブランチ (develop、main、以外) のみで実行する
    - if: $CI_PIPELINE_SOURCE == 'push' && $CI_COMMIT_BRANCH$CI_COMMIT_BRANCH != 'develop' && $CI_COMMIT_BRANCH != 'main'

CI_COMMIT_TAG

現在のタグ名が割り当てられている。

条件文と組み合わせれば、タグの作成時にパイプラインを発火させられる。

CI_PIPELINE_SOURCE

現在のパイプラインを発火させたイベント名 (MR作成/更新イベント、手動パイプライン実行イベント) が割り当てられている。

タグの付与時にパイプラインを発火させる場合、CI_COMMIT_TAG変数を使用する。

foo_job:
  stage: build
  script:
    - echo foo
  rules:
    # MRを作成/更新したタイミングで発火する
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
説明
merge_request_event マージリクエストの作成時を表す。
push プッシュ時を表す。
web 画面からの手動実行時を表す。

CI_PROJECT_DIR

GitLabの実行環境のルートディレクトリが割り当てられている。

GitLabは、ルートディレクトリにGitLabリポジトリをクローンする。

GIT_SUBMODULE_STRATEGY

デフォルトですと、GitLab CIがサブモジュールを無視して処理してしまうため、これを無視しないようにする。

foo_job:
  variables:
    GIT_SUBMODULE_STRATEGY: "recursive"


include

▼ includeとは

参照する別リポジトリとCIテンプレートファイルを設定する。

GitLab CIのJobの設定ファイルを、中央集中的なリポジトリで一括管理しておき、これを他のリポジトリからリモート参照できるようになる。

ポリレポ構成規約と相性がよい。

▼ 親リポジトリ側のCIテンプレート

子リポジトリでは、Jobを定義する。

GitLab CIでは、定義したJobは自動的に実行される。

一方で. (ドット) をつけることで、使用を明示的に宣言しない限り実行できない『隠しJob』として定義できる。

また、子リポジトリで上書きできる変数を親リポジトリに設定しておく。

# foo-job.yaml

# 変数のデフォルト値を設定しておく
variables:
  PATH: "default"

.foo_job:
  stage: build
  script:
    - cat "${PATH}"/foo.txt
# bar-job.yaml

# 変数のデフォルト値を設定しておく
variables:
  PATH: "default"

.bar_job:
  stage: build
  script:
    - cat "${PATH}"/bar.txt
# baz-job.yaml

# 変数のデフォルト値を設定しておく
variables:
  PATH: "default"

.baz_job:
  stage: build
  script:
    - cat "${PATH}"/baz.txt

▼ 子リポジトリ側のリモートコール

子リポジトリでは、親リポジトリのCIテンプレート上の隠しJobをコールする。

include:
  # GitLab CIのテンプレートを管理するリポジトリ
  - project: project/ci-template-repository-1
    ref: main
    file:
      - foo-job.yml
      - bar-job.yml
      - baz-job.yml
  - project: project/ci-template-repository-2
    ref: main
    file:
      - qux-job.yml

foo_1_job:
  # 親リポジトリで定義した隠しJobをコールする
  extends: .foo_job
  stage: build
  script:
    - cat "${PATH}"/foo.txt
  # 親リポジトリの変数を上書きする
  variables:
    PATH: "path_1"

foo_2_job:
  # 親リポジトリで定義した隠しJobをコールする
  extends: .foo_job
  stage: build
  script:
    - cat "${PATH}"/foo.txt
  # 親リポジトリの変数を上書きする
  variables:
    PATH: "path_2"

bar_job:
  extends: .bar_job
  stage: build
  needs:
    - foo1_job
    - foo2_job
  script:
    - cat "${PATH}"/bar.txt

baz_job:
  extends: .baz_job
  stage: build
  script:
    - cat "${PATH}"/baz.txt

▼ ヒアドキュメントを使用したファイルの配布

ヒアドキュメントを使用して、CIの実行コンテナでファイルを動的に作成し、これを配布する。

artifactsキーを使用して、後続のJobでも設定ファイルを使用できるようにしている。

variables:
  GITLAB_COMMENT_VERSION: "6.0.1"

# github-commentを準備する
.install_github_comment:
  stage: build
  image: alpine/git:latest
  script:
    # github-commentをインストールする
    - |
      apk add --upgrade curl tar jq
      LATEST_DOWNLOAD_URL=$(curl -sL https://api.github.com/repos/suzuki-shunsuke/github-comment/releases/latest | jq -r ".assets[].browser_download_url" | grep linux_amd64)
      curl -sL -O "${LATEST_DOWNLOAD_URL}"
      tar zxvf *.tar.gz
    - ./github-comment --version
    # CIの実行環境で各リポジトリに配布するgithub-comment.yamlファイルを作成する
    - |
      cat << 'EOF' > github-comment.yaml
      # https://suzuki-shunsuke.github.io/github-comment/getting-started
      ---
      exec:
        # 静的解析以外の処理のためのテンプレート
        # -kオプションで何も指定しない場合、defaultテンプレートになる
        default:
          - when: "true"
            template: |

              ## `{{ .Vars.TestName }}`

              | 項目 | 内容 |
              |-----|--------------------|
              | コマンド | `{{ .JoinCommand }}` |
              | 説明 | {{ .Vars.Description }} |
              | 実行Job | {{ template "link" . }} |

              ## 詳細

              <details>
              <summary>クリックで開く</summary>

              ```bash
              $ {{ .JoinCommand }}

              {{ .CombinedOutput | AvoidHTMLEscape }}
              ```

              </details>

        # 静的解析のためのテンプレート
        test:
          - when: "true"
            template: |

              ## `{{ .Vars.TestName }}`

              | 項目 | 内容 |
              |-----|--------------------|
              | 静的解析 | `{{ .JoinCommand }}` |
              | 説明 | {{ .Vars.Description }} |
              | 成否 | {{ template "status" . }} |
              | 実行Job | {{ template "link" . }} |

              ## 詳細

              <details>
              <summary>クリックで開く</summary>

              ```bash
              $ {{ .JoinCommand }}

              {{ .CombinedOutput | AvoidHTMLEscape }}
              ```

              </details>

      EOF
      cat github-comment.yaml
  artifacts:
    paths:
      - ./github-comment
      # github-commentの設定ファイルを配布する
      - github-comment.yaml

variables (Jobレベルでも設定可)

▼ variablesとは

Job内で使用する変数を設定する。

値をダブルクオートかシングルクオートで囲わないと、.gitlab-ci.ymlファイル自体で予期せぬ構文エラーになる。

variables:
  BAR: "bar"
  BAZ: "baz"
  QUX: "qux"

▼ ファイルの切り分け

可能であれば、variablesキーをvariables.ymlファイルとして切り分け、これを読み込むようにする。

可読性が高くなる。

# variables.ymlファイル
# variablesで空文字を設定する
variables:
  FOO: ""
include:
  - local: .gitlab-ci/variables.yml

foo_job:
  stage: build
  script:
    # ダブルクオートがない
    - echo ${FOO}

▼ 空文字の出力

空文字を設定したい場合、variablesキーに設定しても空文字として出力されない。

# variablesで空文字を設定する
variables:
  FOO: ""

foo_job:
  stage: build
  script:
    # ダブルクオートがない
    - echo ${FOO}
# variablesを定義しない

foo_job:
  stage: build
  script:
    # ダブルクオートがある
    - echo "${FOO}"

▼ リスト

変数でリストを定義できる。

これを使用して、単一のJob内でforを実行できる。

foo_job:
  variables:
    LIST: foo1 foo2 foo3
  script:
    - |
      for VALUES in $LIST
        do
          echo ${VALUES}
        done


workflow

▼ workflowとは

GitLab CI自体の発火を制御する。

▼ if

GitLab CIが発火する条件を設定する。

# ブランチ名に応じて、CIで使用する実行コンテナ名を切り替える
# main、develop、MR作成/変更、の順に条件を検証する。
workflow:
  rules:
    # mainブランチにて、任意の方法でパイプラインを実行した場合
    - if: $CI_COMMIT_REF_NAME == 'main'
      variables:
        ENV: "prd"
    # developブランチにて、任意の方法でパイプラインを実行した場合
    - if: $CI_COMMIT_REF_NAME == 'develop'
      variables:
        ENV: "stg"
    # MRにて、任意の方法でパイプラインを実行した場合
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
      variables:
        ENV: "tes"
    # 上記以外で、webから手動でパイプラインを実行した場合
    - if: $CI_PIPELINE_SOURCE == 'web'
      variables:
        ENV: "tes"

setup-manifest:
  stage: build
  image: alpine/helm:latest
  # ブランチ名に応じて、valuesファイルを切り替える
  script:
    - helm lint . -f "${VALUES_FILE_PATH}" -f "${SECRETS_FILE_PATH}"
    - helm template . -f "${VALUES_FILE_PATH}" -f "${SECRETS_FILE_PATH}" > manifest.yaml
    - cat manifest.yaml

▼ changes

workflow:
  rules:
    - changes: foo/**/*


05. Job

allow_failure

0以外のすべての終了コードの場合

stages:
  - build

# terraform fmt
fmt:
  # サービスコンテナ
  services:
    - docker:dind
  image: hashicorp/terraform:1.4.6
  stage: build
  script:
    - terraform fmt -check -recursive
  # 0以外の全ての終了コードの場合のみ終了する
  # インデントを揃えるべき場所がある場合に、Jobを失敗させる
  allow_failure: "true"
  rules:
    # MRを作成/更新したタイミングで発火する
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
foo_job:
  stage: build
  script:
    # 『echo foo-1』が失敗しても、終了コードを1にしてJobを中断させない
    - echo foo-1 || true
    - echo foo-2
  # 0以外の全ての終了コードの場合のみ終了する
  allow_failure: "true"

0以外の特定の終了コードの場合

foo_job:
  stage: build
  script:
    - echo foo
  # 特定の終了コードの場合のみ終了する
  allow_failure:
    exit_codes:
      - 1
      - 3


artifacts

▼ artifactsとは

GitLabでは、以前のステージのJobのファイルを後続のJobにデフォルトで継承できる (GitLabのバージョンが古いとこの機能がない場合がある) 。

しかし、needsでJob間に依存関係を定義している場合、artifactsを使用しても、needsで指定しているJob以外のファイルを継承できない。

needsで指定したJobのartifactsのみを継承できる。

stages:
  - build
  - deploy

# ビルドステージ
foo_job:
  stage: build
  script:
    - echo foo
  artifacts:
    paths:
      - foo

bar_job:
  stage: build
  script:
    - echo bar
  artifacts:
    paths:
      - bar

# デプロイステージ
baz_job:
  stage: deploy
  # foo_jobのartifactsは継承できるが、bar_jobのartifactsは継承できない
  needs:
    - foo_job
  script:
    - echo baz

qux_job:
  stage: deploy
  # foo_jobとbar_jobの両方のartifactsを継承できる
  needs:
    - foo_job
    - bar_job
  script:
    - echo qux

▼ artifactsが不要な場合

GitLabでは、以前のステージのJobのファイルを後続のJobにデフォルトで継承できる。

そのため、artifactsは不要である。

# ビルドステージ
foo_job:
  stage: build
  script:
    - echo foo

# デプロイステージ
bar_job:
  stage: deploy
  script:
    # buildステージのファイルを使用する
    ...


before_script

▼ before_scriptとは

記入中...

▼ 共通化

before_scriptキーを隠しJobとして定義することで、共通のスクリプトとして使用できる。

# GitLabの他のリポジトリからモジュールをプルするために、認証情報をセットアップする
.setup_git:
  before_script:
    - echo "machine foo.gitlab.com" > ~/.netrc
    - echo "login ${GIT_USER}" >> ~/.netrc
    - echo "password ${GIT_TOKEN}" >> ~/.netrc

go_mod:
  stage: build
  image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/golang:${GO_VERSION}
  extends:
    - .setup_git
  script:
    # 本モジュールはgo buildする必要はないため、go modのみを実行する
    - go mod tidy


cache

▼ cacheとは

指定したディレクトリのキャッシュを作成する。

もしそのディレクトリに変化がなければ、前回のパイプラインのディレクトリを再利用する。

これにより、CIの時間を短縮できる。

bar_job:
  stage: build
  cache:
    # キャッシュの名前を設定する
    key: $CI_COMMIT_REF_SLUG
    # キャッシュとして保管するディレクトリを設定する
    paths:
      - ./node_module

▼ policy

キャッシュ作成のルールを設定する。

bar_job:
  stage: build
  cache:
    paths:
      - ./node_module
    # キャッシュのダウンロードのみを実行する
    policy: pull
bar_job:
  stage: build
  cache:
    paths:
      - ./node_module
    # キャッシュのアップロードのみを実行する
    policy: push


dependencies

▼ dependencies

通常、dependenciesを指定せずにartifactsを使用した場合に、全てのJobとファイルを継承する。

dependenciesを設定すれば、artifactsが設定された特定のJobを指定し、そのJobのみをファイルを継承する。

stages:
  - build
  - deploy

foo_job:
  stage: build
  script:
    - echo foo
  artifacts:
    paths:
      - foo

bar_job:
  stage: build
  script:
    - echo bar
  artifacts:
    paths:
      - bar

baz_job:
  stage: deploy
  script:
    - echo baz
  # foo_jobのアーティファクトのみを継承する
  dependencies:
    - foo_job


image

▼ imageとは

Jobの実行コンテナを設定する。

foo_job:
  stage: build
  image:
    name: alpine:1.0.0
    entrypoint: ["sh"]
  script:
    - echo foo

CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX

CIでは、コンテナイメージのプルの頻度が高まるため、イメージレジストリ (例:DockerHub) によってはプル数の制限にひっかかってしまう。

イメージレジストリのパスのプレフィクスとしてCI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIXをつけると、コンテナイメージがGitLabのDependency Proxyを経由するようになる。

Dependency Proxyはコンテナイメージをキャッシュするため、毎回DockerHubにコンテナをプルしなくなり、プル数の制限にひっかかりにくくなる。

foo_job:
  stage: build
  image:
    name: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/alpine:1.0.0
    entrypoint: ["sh"]
  script:
    - echo foo


needs

▼ needsとは

Job間の依存関係を設定する。

同じステージ内に複数のJobがある場合に役立つ。

stages:
  - build

foo_job:
  stage: build
  script:
    - echo foo

# fooの後に、baz_jobと並行実行する
bar_job:
  stage: build
  needs:
    - foo_job
  script:
    - echo bar

# fooの後に、bar_jobと並行実行する
baz_job:
  stage: build
  needs:
    - foo_job
  script:
    - echo baz


parallel

▼ parallelとは

同じJobを複数並列実行する。

foo_job:
  stage: build
  parallel:
    matrix:
      - ENV:
          - foo1
          - foo2
          - foo3
  # foo1、foo2、foo3、を出力する異なるJobを並列実行する
  script:
    - echo ${ENV}

▼ アーティファクト依存関係

並列実行した各Jobに対して、アーティファクトの依存関係を設定できる。

stages:
  - build
  - deploy

foo_job:
  stage: build
  parallel:
    matrix:
      - ENV:
          - foo1
          - foo2
          - foo3
  # foo1、foo2、foo3、を出力する異なるJobを並列実行する
  script:
    - echo ${ENV}

baz_job:
  stage: deploy
  script:
    - echo baz
  # foo_jobのfoo1のアーティファクトのみを継承する
  dependencies:
    - "foo_job: [foo1]"

▼ Jobの依存関係

並列実行した各Jobに対して、Jobの依存関係を設定できる。

stages:
  - build
  - deploy

foo_job:
  stage: build
  parallel:
    # foo1、foo2、foo3、を出力する異なるJobを並列実行する
    matrix:
      - ENV:
          - foo1
          - foo2
          - foo3
  script:
    - echo ${ENV}

baz_job:
  stage: deploy
  script:
    - echo baz
  needs:
    - job: foo_job
      parallel:
        matrix:
          # foo_jobのfoo2に依存する
          - ENV: foo2


rules

▼ rulesとは

Jobの発火条件を設定する。

複数の条件 (複数のifキー、ifキーとchangesキーの組み合わせ) を並べた場合、上から順番に条件を検証していくため、OR条件になる。

▼ if

条件に合致した場合のみ、Jobを発火する。

ブランチ名やタグを使用した発火を定義できる。

イベントの種類が設定されたCI_PIPELINE_SOURCE変数を使用できる。

check_tag:
  # OR条件
  rules:
    # mainブランチのみ
    - if: $CI_COMMIT_BRANCH == 'main'
      variables:
        TAG_NAME: "main"

    # hotfixから始まるブランチのみ
    - if: $CI_COMMIT_BRANCH =~ /^hotfix.*$/
      variables:
        TAG_NAME: "hotfix-${CI_COMMIT_SHA}"

    # 任意の名前のタグがついている場合のみ
    - if: $CI_COMMIT_TAG
      variables:
        TAG_NAME: "$CI_COMMIT_TAG"

▼ changes

プッシュ時に、指定したファイルやディレクトリで差分があれば、Jobを発火する。

gemerate_template:
  script:
    - helm template . -f foo-values.yaml -f foo-secrets.yaml > foo.yaml
  rules:
    # webイベント (パイプライン実行ボタン) の場合
    - if: $CI_PIPELINE_SOURCE == "web"
    # ファイルやディレクトリ内に差分があった場合
    - changes:
        - template/**/*
        - foo-values.yaml


services

▼ services

gitlab_service-container

JobのCIの実行コンテナとは別のサービスコンテナを作成する。

両方のコンテナで使用するイメージのバーションは揃えるようにする。

foo_job:
  # CIの実行環境
  image: docker:19.03.0
  # サービスコンテナ
  services:
    - name: docker:19.03.0-dind

▼ 複数のコンテナを同時に起動するため

Jobでアプリコンテナを動かし、DBコンテナを別に起動しておく場合、もう一つコンテナが必要になる。

これを回避するために使用する。

▼ TLSの無効化

GitLab CI上でDocker in Dockerを使用する場合、実行コンテナとサービスコンテナでセットアップが必要である。

variables:
  # ドライバーの設定
  DOCKER_DRIVER: "overlay2"
  # ホストの設定
  DOCKER_HOST: "tcp://docker:2375"
  # TLSの無効化
  DOCKER_TLS_CERTDIR: ""

foo_job:
  # CIの実行環境
  image: docker:19.03.0
  # サービスコンテナ
  services:
    - name: docker:19.03.0-dind
      # TLSの無効化
      command: ["--tls=false"]


stage

▼ stage

Jobが所属するステージを設定する。

より前のステージ内のJobが全て成功しない限り、後続のJobを開始しない。

同じステージに所属するJobは、並行的に実行される。

stages:
  - build
  - test
  - deploy

# ----------
# build
# ----------
foo_job:
  stage: build
  script:
    - echo foo

bar_job:
  stage: build
  script:
    - echo bar

# ----------
# test
# ----------
baz_job:
  stage: test

# ----------
# deploy
# ----------
qux_job:
  stage: deploy


script

▼ scriptとは

Jobで実行する処理を設定する。

foo_job:
  stage: build
  script:
    - echo "Hello World"


trigger

Jobが発火した場合に、特定のアクションを実施する。

*例*

モノレポでGitLabCIを採用している場合に、 親の.gitlab-ci.ymlファイルでディレクトリ配下の変更を検知し、子の.gitlab-ci.ymlファイルを読み込む (include) ようにする。

# 親の.gitlab-ci.yml
stage:
  - build

foo:
  stage: build
  rules:
    - if: $CI_COMMIT_BRANCH
      changes:
        - foo/*
        - foo/**/*
  trigger:
    include: foo/.gitlab-ci.yml
    strategy: depend

bar:
  stage: build
  rules:
    # ディレクトリ配下の変更を検知する
    - if: $CI_COMMIT_BRANCH
      changes:
        - foo/*
        - foo/**/*
  trigger:
    # 各ディレクトリ配下に置いた子の.gitlab-ci.ymlファイルを読み込む
    include: foo/.gitlab-ci.yml
    strategy: depend


when

▼ whenとは

Jobを実行する条件を設定する。

▼ always

ワークフローのうちで、前のJobのステータスに関係なく、必ずJobを実行する

bar_job:
  stage: build
  script:
    - echo bar
  when: always

▼ manual

ワークフローのうちで、手動の場合にのみJobを実行する。

foo_job:
  stage: build
  script:
    - echo foo
  when: manual

▼ never

特定の条件の場合に、Jobを実行しない。

なお、when: neverのみの定義は意味がない。

baz_job:
  stage: build
  script:
    - echo bar
  # BAZ変数がfalseの場合は実行しない (never)
  rules:
    - if: $BAZ == 'false'
      when: never