GitLab CI/CD を試しに使ってみる

社内でのソースコード管理に利用している GitLab では、CI/CD を支援するための GitLab CI/CD というツールが利用可能です。DevOps を実践するための第一歩として、 GitLab CI/CD を使ってみました。

そもそも、CI/CD って?

  • 下記の資料が参考になるかと思います。
  • GitLab のサイトでは以下のように説明されています:
    • CI(継続的インテグレーション)とは?
      • コードを共有リポジトリに統合して、それぞれの変更を可能な限り早く (通常は1日に数回) 自動的にビルド/テストする取り組みです。
      • CI を行うメリットは、以下の3点です:
        • 可能な限り迅速にエラーを検出できる:開発者の頭の中で問題を修正
        • 統合に関する問題を削減できる:問題が小さいほど、処理が容易
        • 複雑な問題の回避できる:チームの開発速度を向上させ、自信を深めることができます。
    • CD(継続的デリバリ、継続的デプロイメント)とは?
      • 継続的デリバリ:変更を自動的にプッシュすることによって、ソフトウェアをいつでも本番環境にリリースできるようにすることです。
      • 継続的デプロイメント:さらに進んで、変更を本番環境に自動的にプッシュします。
      • CD を行うメリットは、以下の3点です:
        • すべての変更がリリース可能であることを確認できる:完了する前に、導入を含むすべてをテストする
        • 各リリースのリスクを低減する:リリースを「ボーリング」にする
        • より頻繁に価値を提供できる:信頼性の高い導入は、より多くのリリースを意味する
        • 緊密な顧客フィードバックループが可能になる:変更に対する迅速かつ頻繁な顧客フィードバック
  • 開発と運用が一体となって進む DevOps において、CI/CD を効率的に行えるかどうかは重要なポイントとなります。
  • 今回使用した GitLab CI/CD は CI/CD を効率的に行えるよう補助してくれるツールとなります。

GitLab CI/CD と GitLab Runner の概要

f:id:linkode-okazaki:20200114182253p:plain

GitLab CI/CD

  • GitLab CI/CD は、GitLab に付属している CI/CD を支援するツールです。
  • ビルドなどの各種ジョブを実行するのではなく、ジョブの結果を確認・管理するのに利用します。

GitLab Runner

  • GitLab Runner は、定期的に GitLab のリポジトリを確認し、ジョブの実行命令があった場合にビルドやテスト、デプロイのジョブを実行します。
  • GitLab Runner は Go 言語で実装されており、LinuxMac、Window sといった様々なプラットフォーム上で動作させることができます。
  • GitLab Runner には大きく次の 2 種類があります。
    • Shared Runners:プロジェクトをまたいで利用できる Runner です。プロジェクト毎にいちいち Runner の環境を構築しなくても良くなります。また、Runner がアイドリング状態で遊び続け、リソースが無駄になることを抑止できます。
    • Specific Runners:特定のプロジェクトに紐付けて専用利用するタイプの Runner です。自分のプロジェクトのビルドを実行する際に、他のプロジェクトのビルドが完了するまで待たされることがありません。また、Runner にプロジェクト固有の設定を追加でき、別ユーザーと隔離された環境で自分のプロジェクトのジョブを実行できるため、セキュリティレベルを担保しやすいのがメリットです。

executer

  • ジョブの実行方式である executer として、以下の7つが用意されています:

    1. Shell
    2. Docker
    3. Docker Machine and Docker Machine SSH (autoscaling)
    4. Parallels
    5. VirtualBox
    6. SSH
    7. Kubernetes
  • 実行したいジョブの内容に応じて、好きな executer を選択することが可能です。例えば以下のような感じです:

    • Shell:GitLab Runner がインストールされたホスト上でシェルベーズでジョブを実行
    • Docker:指定した Docker イメージを基にコンテナを生成し、その中でジョブを実行
  • executer によって使える機能が異なります。
  • GitLab.com では、デフォルトで Docker の executer が用意されています。
  • 使える機能が豊富で、ビルド環境をクリーンに保つことができる Docker か Kubernetes の利用が推奨されています。

パイプライン

f:id:linkode-okazaki:20200114182211p:plain

  • GitLab Runner では、パイプライン方式でジョブを実行します。
  • パイプラインの構成要素は以下の 2 つです:
    • ジョブ:実行対象を定義します(コードのコンパイル、テストの実行、など)。
      • 上記画像の楕円 1 つ 1 つがジョブの単位です。
    • ステージ:いつ、どのように実行するかを定義します。
      • 上記画像の「Build」「Test」「Staging」「Production」がステージです。
      • この場合、Build の次は Test、その次は Staging、最後に Production といった具合で進んで行きます。
      • 通常、ステージ内のジョブがすべて成功した場合のみ、次のステージに進みます。
  • 同じステージ内の複数のジョブは同時に並行して実行されます。
    • 上記画像の場合、test1 のジョブと test2 のジョブが並行で実行されます。

.gitlab-ci.yml

  • ジョブの実行内容や実行条件、ステージの実行順などの設定を記述するファイルです。
  • このファイルがリポジトリのルートディレクトリにあった場合に、Runner がジョブを実行してくれます。

GitLab.com 上の GitLab Runner を使って CI/CD

  • では、実際に使用してみます。
  • 今回は、GitLab Runner の環境を自分で構築するのではなく、予め GitLab.com 上に用意された Runner を使ってみます。
    • excuter は Docker になります。
  • 本格的なプロジェクトで運用する前に、GitLab CI/CD をどう使えば良いかを理解するための手順です。

.gitlab-ci.yml の編集画面を表示する

  • GitLab 上で空の新規プロジェクトを作成します。
  • 作成し終わったら、プロジェクトトップ画面にある「Set up CI/CD」を選択します。
    • すると、.gitlab-ci.yml の編集画面に移動します。

f:id:linkode-okazaki:20200114182248p:plain

.gitlab-ci.yml を書いてみる

  • 編集画面で、以下の内容を書き込み、「Commit changes」を押し、.gitlab-ci.yml をコミットします。
stages:
  - build
  - test

build:
  stage: build
  script:
    - echo "this is building"
    - hostname
    - mkdir builds
    - touch builds/data.txt
    - echo "true" > builds/data.txt
  artifacts:
    paths:
      - builds/

test:
  stage: test
  script:
    - echo "this is testing"
    - hostname
    - test -f builds/data.txt
    - grep "true" builds/data.txt
  • 上記ファイルの内容は次のことを表しています:

    • stages:パイプラインのステージを表します。
      • 書かれているステージを上から順番に実行します。
      • 今回は、buildtest の 2 つのステージを定義しており、build が正常終了したら、test に進みます。
    • buildtest:それぞれジョブを表します。
      • 今回、ステージ名と同じにしてしまっているのでややこしいですが…
      • 以下は、ジョブ名として利用できない予約語です。これらをジョブ名にしたい場合は、クォートで囲います。
        • image
        • services
        • stages
        • types
        • before_script
        • after_script
        • variables
        • cache
      • ジョブの下には、ジョブの挙動を設定するパラメータを記述します。ここで使われているパラメータの設定は
        • stage:ジョブがどのステージに属するのかを設定します。
        • script:Runner で実行するシェルスクリプトを設定します。配列形式で記述します。
        • artifacts:ジョブが成功したときに、ジョブの成果物として紐付けるファイルやディレクトリを指定します。ここで指定したファイルやディレクトリは、以降のジョブで利用したり、ダウンロードすることが可能です。
  • コミット完了後、左サイドバーの「CI/CD」を選択すると、パイプラインが作成されているのがわかります。

f:id:linkode-okazaki:20200114182257p:plain

  • ここでパイプライン名(#xxxxxxxxx)をクリックすると、パイプラインの状況がわかります。
    • 赤い四角がステージを、青い四角がジョブを表しています。
    • ジョブの状態は丸いアイコンで示されています。

f:id:linkode-okazaki:20200114182951p:plain

  • 各ジョブ名をクリックすると、ジョブの実行状況がわかります。
    • 下の画像は、「test」のジョブを開いたときです。
    • 最後に「Job succeeded」と表示されていれば成功です。

f:id:linkode-okazaki:20200114182234p:plain

ステージを追加して生成物を確認してみる

  • 先ほどの .gitlab-ci.yml を編集します。リポジトリのルートから .gitlab-ci.yml を選択し、「Edit」のボタンを押して編集画面に移動できます。
  • 下記の通りステージの追加を行ってみます。
stages:
  - build
  - test
# ----- ここから追加行 -----
  - package
# ----- ここまで追加行 -----

build:
  stage: build
  script:
    - echo "this is building"
    - hostname
    - mkdir builds
    - touch builds/data.txt
    - echo "true" > builds/data.txt
  artifacts:
    paths:
      - builds/

test:
  stage: test
  script:
    - echo "this is testing"
    - hostname
    - test -f builds/data.txt
    - grep "true" builds/data.txt

# ----- ここから追加行 -----
package:
  stage: package
  script: cat builds/data.txt | gzip > packaged.gz
  artifacts:
    paths:
      - packaged.gz
# ----- ここまで追加行 -----
  • 編集が終わったら、先程と同様にコミットします。パイプラインが更新され、ジョブが走ります。

f:id:linkode-okazaki:20200114182230p:plain

  • 左サイドバーから「CI/CD」->「Pipelines」から、さっきのパイプラインの生成物がダウンロードできます。
    • ここでは、Build、Package のそれぞれのステージの成果物がダウンロードできるのがわかります。

f:id:linkode-okazaki:20200114182222p:plain

  • GitLab.com を利用している場合は、成果物は削除されません。
  • 上記の場合、Build ステージでの成果物は特に利用することがないので、一定の保存期間が過ぎたら自動的に削除するようにしておいたほうが良いでしょう。その場合、artifacts:expire_in パラメータで設定します。
    • 以下、20 分で保存期間を 20 分に設定した例です。
build:
  stage: build
  script:
    - echo "this is building"
    - hostname
    - mkdir builds
    - touch builds/data.txt
    - echo "true" > builds/data.txt
  artifacts:
# ----- ここから追加行 -----  
    expire_in: 20 min
# ----- ここまで追加行 -----
    paths:
      - builds/

ジョブを失敗させてみる

  • gitlab-ci.yml を以下のように変更し、test ステージのジョブを失敗させてみます。
stages:
  - build
  - test
  - package

build:
  stage: build
  script:
    - echo "this is building"
    - hostname
    - mkdir builds
    - touch builds/data.txt
# ----- ここから変更した行 -----
    - echo "false" > builds/data.txt
# ----- ここまで変更した行 -----
  artifacts:
    expire_in: 20 min
    paths:
      - builds/

test:
  stage: test
  script:
    - echo "this is testing"
    - hostname
    - test -f builds/data.txt
    - grep "true" builds/data.txt

package:
  stage: package
  script: cat builds/data.txt | gzip > packaged.gz
  artifacts:
    paths:
      - packaged.gz
  • コミット後、パイプラインを確認すると、Test ステージでジョブが失敗していることがわかります。また、後続の Package ステージは実行されずにスキップされています。

f:id:linkode-okazaki:20200114182226p:plain

使用する Docker イメージを変更する

  • 各ジョブのコンソールを確認すると、Ruby の Docker イメージが Pull されていることがわかります。
    • GitLab.com のイメージを指定しなかった場合のデフォルトでは、Ruby の Docker イメージが利用されます。

f:id:linkode-okazaki:20200114182239p:plain

  • 利用する Docker イメージを変更するには image パラメータを使います。
    • ファイルの冒頭に書くと、各ジョブで共通して利用するイメージを、ジョブ内で image パラメータを指定すると、そのジョブで利用するイメージを個別に指定できます。
# ----- ここから追加した行 -----
image: alpine
# ----- ここまで追加した行 -----

stages:
  - build
  - test
  - package

build:
  stage: build
  script:
    - echo "this is building"
    - hostname
    - mkdir builds
    - touch builds/data.txt
# ----- テストが成功するように再度変更しておきます -----
    - echo "true" > builds/data.txt
  artifacts:
    expire_in: 20 min
    paths:
      - builds/

test:
  stage: test
  script:
    - echo "this is testing"
    - hostname
    - test -f builds/data.txt
    - grep "true" builds/data.txt

package:
  stage: package
  script: cat builds/data.txt | gzip > packaged.gz
  artifacts:
    paths:
      - packaged.gz

f:id:linkode-okazaki:20200114183228p:plain

  • 適切な Docker イメージを選択することで、ジョブの実行時間を短縮したり、不要な処理を書かずに済ませることができます。

もう少し複雑なパイプラインの設定をする

  • Package で 2 種類のアーカイブファイルを作成するパイプラインの設定をしてみます。
  • mkisofs コマンドを利用すると、ISO ファイルが作成できます。
mkisofs -o ./packaged.iso builds/compiled.txt
  • しかし、このコマンドは素の Alpine Linux 上にはコマンドが含まれていないため、そのまま script パラメータに指定して実行してもジョブは失敗します。
  • よって、以下のコマンドをジョブの起動前に完了しておく必要があります。
echo "ipv6" >> /etc/modules  # ネットワークを有効化
apk update                   # パッケージリストを更新
apk add xorriso              # # パッケージをインストール
  • ジョブの起動前に実行させたいコマンドは before_script パラメータで指定します。
    • 全ジョブの起動前に実行させたいコマンドはグローバルレベルで、ジョブごとに個別で指定する場合は、ジョブの下に書きます。
    • ジョブレベルの内容は全体で書かれたものを上書きします。
  • 以下、設定例です。
image: alpine

stages:
  - build
  - test
  - package

build:
  stage: build
  script:
    - echo "this is building"
    - hostname
    - mkdir builds
    - touch builds/data.txt
    - echo "true" > builds/data.txt
  artifacts:
    expire_in: 20 min
    paths:
      - builds/

test:
  stage: test
  script:
    - echo "this is testing"
    - hostname
    - test -f builds/data.txt
    - grep "true" builds/data.txt

# ----- ここから追加した行 -----
pack-gz:
  stage: package
  script: cat builds/data.txt | gzip > packaged.gz
  artifacts:
    paths:
    - packaged.gz
 
pack-iso:
  stage: package
  before_script:
  - echo "ipv6" >> /etc/modules
  - apk update
  - apk add xorriso
  script:
  - mkisofs -o ./packaged.iso builds/data.txt
  artifacts:
    paths:
    - packaged.iso
# ----- ここまで追加した行 -----
  • コミットしてパイプラインを確認します。Package ステージが並行で動くようになっています。

f:id:linkode-okazaki:20200114182219p:plain

  • ジョブが完了すると、gz ファイルも iso ファイルもダウンロードできるようになっています。

f:id:linkode-okazaki:20200114182244p:plain

まとめ

  • GitLab CI/CD を用いてビルドやテストを自動化させることができます。
  • ここでは、あまり複雑でない設定で動かすことに留めましたが、ジョブの設定については様々なパラメータが用意されているため、それらを駆使すればより複雑な設定が可能です。
    • master ブランチに push されたときのみ実行する、フォーク元に push されたときのみ実行する、など。
  • Runner としては、GitLab.com でデフォルトで利用されている Docker を用いましたが、他の Runner で実行する場合も試してみたいと思います。

参考資料