ブログ エンジニアリング CI 入門:ジョブを順序どおりに、並列に、または順不同で実行する方法
更新日:February 20, 2025
16分で読めます

CI 入門:ジョブを順序どおりに、並列に、または順不同で実行する方法

継続的インテグレーション (CI) 入門:CI は初めてですか?GitLab CI の使い方を学び、最初のCIパイプラインをGitLabでビルドしてみましょう。

cicd-cover

継続的インテグレーション (CI) (英語版) のことを何一つ知らず、ソフトウェア開発ライフサイクルに どうして CI が必要なのか (英語版) 分からない、と仮定しましょう。

いま、あるプロジェクトで作業をしていて、そこには2つのテキストファイルから成るコードがあるものとします。さらに、これらの2つのファイルには「Hello world」というフレーズが含まれている必要がある、という点に注意してください。

このフレーズが含まれていなければ、開発チームがその月のお給料を受け取れないことになるかもしれないくらい、重要なポイントです。

そこで、責任感のあるソフトウェアデベロッパーが、顧客にコードを納品する前に実行する、短いスクリプトを書きました。

以下のような非常に洗練されたコードです。

cat file1.txt file2.txt | grep -q "Hello world"

ここでの懸念事項はチームには10名のデベロッパーがいて、人的要因がコードの品質に大きな影響を及ぼす可能性があるという点です。

1週間前、新しくチームに入ったメンバーがこのスクリプトを実行し忘れ、3件のクライアントに機能しないビルドが納品されるという事態が発生しました。この事態の解決にあたり、幸いにもコードは既にGitLab にあり、ビルトインの CI があることが分かりました。さらに、あるカンファレンスで、テスト実行にCIを使うのが一般的ということを耳にしていました。

CI 内で最初のテストを実行する

ドキュメントによると、CI の実行に必要なのは .gitlab-ci.yml ファイル内に 以下の2 行のコードを追加することだけでした。

test:
  script: cat file1.txt file2.txt | grep -q 'Hello world'

コミットして...無事にビルドが成功しました。

CI内でビルドに成功

では、2 つ目のファイルの「world」という文言を「Africa」に置き換え、何が起こるか確認してみましょう。

CI内でビルドに失敗

ビルドは予想どおり失敗します。 ここで、自動化テストが完成しました。GitLab CI は、DevOps 環境内でソースコードのリポジトリに新しいコードをプッシュするたびにこのテストスクリプトを実行します。

注: 上記例では、file1.txt と file2.txt がGitLabランナーを実行するホストに存在すると仮定しています。

この例を実際に GitLabで実行するには、以下のようにファイルを作成するコードを実行した後、テストスクリプトを実行する必要があります。

test:
before_script:
      - echo "Hello " > | tr -d "\n" | > file1.txt
      - echo "world" > file2.txt
script: cat file1.txt file2.txt | grep -q 'Hello world'

なお、わかりやすくするために、この 2 つのファイルは既にホストに存在していると仮定し、以降の例では作成しないものとします。

CIビルド結果をダウンロード可能にする

次にすることは、顧客に納品するコードをパッケージ化することです。ソフトウェア開発プロセスのこの部分も自動化してしまいましょう。

まず、CI に別のジョブを定義する必要があります。このジョブは「package」という名前にしましょう。

test:
  script: cat file1.txt file2.txt | grep -q 'Hello world'

package:
  script: cat file1.txt file2.txt | gzip > package.gz

よって、今ここにはタブが 2 つあります。

2つのタブ ― 2つのジョブから生成

しかし、新たに作成されるファイルがダウンロードできるようにビルドの「アーティファクト」であることを指定し忘れてしまいました。。修正するには、artifacts セクションを追加します。

test:
  script: cat file1.txt file2.txt | grep -q 'Hello world'

package:
  script: cat file1.txt file2.txt | gzip > packaged.gz
  artifacts:
    paths:
    - packaged.gz

修正した結果を確認すると、アーティファクトが作成されダウンロードできるようになっています。

ダウンロードボタンのチェック

しかし、ここで修正が必要な新たな問題があります。2 つのジョブは現在は並列実行されていますが、テストに失敗した場合、アプリケーションをパッケージ化しないように変更をする必要があります。

ジョブを順次実行する

そこで「package」ジョブは、テストが成功した場合のみ実行するものとします。stages を指定し、ジョブの実行順序を定義しましょう。

stages:
  - test
  - package

test:
  stage: test
  script: cat file1.txt file2.txt | grep -q 'Hello world'

package:
  stage: package
  script: cat file1.txt file2.txt | gzip > packaged.gz
  artifacts:
    paths:
    - packaged.gz

上記のようになりました。

ちなみに、コンパイル (我々のケースでは2つのファイルを連結することを意味します) には時間がかかるため、2 回も実行したくはありません。コンパイルは別のステップとして定義しましょう。

stages:
  - compile
  - test
  - package

compile:
  stage: compile
  script: cat file1.txt file2.txt > compiled.txt
  artifacts:
    paths:
    - compiled.txt

test:
  stage: test
  script: cat compiled.txt | grep -q 'Hello world'

package:
  stage: package
  script: cat compiled.txt | gzip > packaged.gz
  artifacts:
    paths:
    - packaged.gz

それでは、アーティファクトを見てみましょう。

不必要なアーティファクト

この「コンパイル」ファイルを常にダウンロード可能にする必要はないようです。一時的なアーティファクトとして「20 分」で保存期間切れとなるよう、expire_in を設定します。

compile:
  stage: compile
  script: cat file1.txt file2.txt > compiled.txt
  artifacts:
    paths:
    - compiled.txt
    expire_in: 20 minutes

構成ファイルは見たところ問題なさそうです。

  • アプリケーションをコンパイル、テスト、パッケージ化するために、3 つの連続したステージを作成しました。

  • コンパイル済みアプリを次のステージに渡すと、コンパイルを 2回実行する必要がなくなります、(それにより実行が高速化されます)。

  • パッケージ化されたアプリケーションは、今後も使用できるようビルドアーティファクトとして保管します。

どのDockerイメージを使用するのか学ぶ

ここまでは順調です。しかし、CIビルドにはまだ時間がかかります。ログを見てみましょう。

ruby3.1

ここに注目してください。Ruby 3.1とあります。

なぜ Ruby が必要なのかといえば、GitLab.com が ビルド実行 (英語版) に Docker イメージを使用しており、デフォルトで (英語版) ruby:3.1 イメージを使用するからです。間違いなく、このイメージには必要のないパッケージが多数含まれています。Google で検索して調べたところ、alpine というイメージがあり、ほとんど空の Linux イメージであることが分かりました。

それでは、.gitlab-ci.ymlimage: alpine を追加して、このイメージを使用したいと明示的に指定しましょう。

うまくいきました。パイプラインの実行が3 分ほど短縮できたようです。

ビルド速度を短縮

パブリックイメージはたくさんあるようです。

それにより、我々の技術スタックに適したものを選ぶことができます。不必要なソフトウェアが含まれていないイメージを指定することで、ダウンロード時間が最短で済みます。

複雑なシナリオに対応する

さて、ここで新しいクライアントが、アプリを .gz ではなく、.iso イメージとしてパッケージ化してほしい、と希望しているとします。CI がすべての作業を行なってくれるため、コードにはジョブを 1 つ追加するだけです。ISO イメージは mkisofs コマンドを使用して作成できます。構成ファイルは次のようになります。

image: alpine

stages:
  - compile
  - test
  - package

# ... "compile" and "test" jobs are skipped here for the sake of compactness

pack-gz:
  stage: package
  script: cat compiled.txt | gzip > packaged.gz
  artifacts:
    paths:
    - packaged.gz

pack-iso:
  stage: package
  script:
  - mkisofs -o ./packaged.iso ./compiled.txt
  artifacts:
    paths:
    - packaged.iso

ジョブ名は同じにする必要はありません。ジョブ名が同じだと、ソフトウェア開発プロセスの同じステージでジョブを並列実行できません。そのため、ジョブやステージの名前が同じになるのは、偶然のことだと考えてください。

さて、ビルドは失敗しました。

mkisofs が欠落しているために失敗したビルド

mkisofsalpine イメージに含まれていないことが原因です。まずはこのパッケージをインストールする必要があります。

欠落しているソフトウェアやパッケージの対応

Alpine Linux Web サイト (英語版) によると、mkisofsxorrisoパッケージと cdrkit パッケージの一部です。次のコマンドを実行することでパッケージをインストールできます。

echo "ipv6" >> /etc/modules  # enable networking
apk update                   # update packages list
apk add xorriso              # install package

CI ではこれらは他のコマンドと何ら変わりません。script セクションで実行する必要があるコマンドの全リストは、このようになります。

script:
- echo "ipv6" >> /etc/modules
- apk update
- apk add xorriso
- mkisofs -o ./packaged.iso ./compiled.txt

構文的に正しくするため、パッケージのインストールに関連したコマンドは before_script 内に置きましょう。before_script を構成の最上位レベルで使うと、そのコマンドがすべてのジョブの前に実行されることに留意してください。今回は特定のジョブの前で実行させます。

DAG(有向非巡回グラフ):より高速で柔軟なパイプラインのために

ステージを定義して、テストに合格した場合にのみパッケージジョブを実行するようにしました。後のステージに定義されているジョブに対し、いくつかのジョブのステージ順序を並び替えて先に実行させたい場合はどうすればいいでしょうか。場合によっては、従来のステージ順序がパイプライン全体の実行時間を遅くしてしまう可能性があります。

テストステージに、実行に時間のかかる負荷の高いテストがいくつか含まれており、それらのテストが必ずしもパッケージジョブに関連していないとします。この場合、テストの完了を待たずにパッケージジョブを開始できれば、より効率的になります。それにはDAG (有向非巡回グラフ) が役立ちます。特定のジョブのステージ順序を変えるためには、ジョブの依存関係 (通常のステージの順序をスキップするもの) を定義します。

GitLab には、ジョブ間の依存関係を作成する特殊キーワード needs があります。これを使うことで、依存しているジョブが完了するとすぐにジョブを前倒しで実行できるようになります。

次の例では、テストジョブが完了するとすぐにパックジョブが実行を開始します。そのため、将来誰かがテストをテストステージに追加した場合に、新しいテストジョブの完了を待たずにパッケージジョブが実行を開始します。

pack-gz:
  stage: package
  script: cat compiled.txt | gzip > packaged.gz
  needs: ["test"]
  artifacts:
    paths:
    - packaged.gz

pack-iso:
  stage: package
  before_script:
  - echo "ipv6" >> /etc/modules
  - apk update
  - apk add xorriso
  script:
  - mkisofs -o ./packaged.iso ./compiled.txt
  needs: ["test"]
  artifacts:
    paths:
    - packaged.iso

.gitlab-ci.yml の最終バージョン:

image: alpine

stages:
  - compile
  - test
  - package

compile:
  stage: compile
  before_script:
      - echo "Hello  " | tr -d "\n" > file1.txt
      - echo "world" > file2.txt
  script: cat file1.txt file2.txt > compiled.txt
  artifacts:
    paths:
    - compiled.txt
    expire_in: 20 minutes

test:
  stage: test
  script: cat compiled.txt | grep -q 'Hello world'

pack-gz:
  stage: package
  script: cat compiled.txt | gzip > packaged.gz
  needs: ["test"]
  artifacts:
    paths:
    - packaged.gz

pack-iso:
  stage: package
  before_script:
  - echo "ipv6" >> /etc/modules
  - apk update
  - apk add xorriso
  script:
  - mkisofs -o ./packaged.iso ./compiled.txt
  needs: ["test"]
  artifacts:
    paths:
    - packaged.iso

パイプラインが作成できました!ステージは 3 つの連続したステージで、ジョブ pack-gzpack-iso が、package ステージ内で並列実行されています。

パイプラインのイラスト

高度なパイプラインの構築

ここからは、高度なパイプラインを構築する方法を説明します。

CIパイプラインに自動テストを実装

DevOps において、ソフトウェア開発戦略の重要なルールは、素晴らしいユーザーエクスペリエンスを備えた優れたアプリを作成する、というものです。ここでは、CI パイプラインにいくつかのテストを追加し、プロセス全体の早い段階でバグを検出、修正しましょう。この方法なら、問題が大きくなる前や新しいプロジェクトに移る前に問題を修正できます。

GitLabにはさまざまな テスト 用にすぐ使えるテンプレートがあり、これらを使用することで作業が簡単になります。必要な手順は、CI の構成にテンプレートを追加するだけです。

この例では、アクセシビリティテスト (英語版) を追加します。

stages:
  - accessibility

variables:
  a11y_urls: "https://about.gitlab.com https://www.example.com"

include:
  - template: "Verify/Accessibility.gitlab-ci.yml"

a11y_urls 変数をカスタム化し、Web ページの URL を挿入して、Pa11yコード品質 のテストを行います。

   include:
   - template: Jobs/Code-Quality.gitlab-ci.yml

GitLab を使うと、マージリクエストのウィジェットエリア内でテストレポートを簡単に確認できます。コードレビュー、パイプラインステータス、テスト結果を一か所にまとめることで、あらゆることがよりスムーズに効率よくできるようになります。

アクセシビリティレポート

アクセシビリティ・マージリクエスト・ウィジェット

MR 内のコード品質ウィジェット

コード品質マージリクエストウィジェット

マトリックスビルド

場合によっては、異なる構成、OS バージョン、プログラミング言語バージョンなどでアプリをテストする必要があります。そのような場合には、parallel:matrix (英語版) ビルドを使って、1 つのジョブ構成でさまざまな組み合わせでアプリケーションを並列にテストします。このブログでは、マトリックスキーワードを使用して、異なるバージョンの Python でコードのテストを行います。

python-req:
  image: python:$VERSION
  stage: lint
  script:
    - pip install -r requirements_dev.txt
    - chmod +x ./build_cpp.sh
    - ./build_cpp.sh
  parallel:
    matrix:
      - VERSION: ['3.8', '3.9', '3.10', '3.11']   # https://hub.docker.com/_/python

パイプライン実行中、このジョブは 4 通りの方法で並列実行されます。それぞれ以下のように異なる Python イメージを使用します。

実行中のマトリックスジョブ

ユニットテスト

ユニットテストとは

ユニットテストとは、ソフトウェアの個々のコンポーネントや機能が期待通りに機能するを確認する、対象を絞った単体テストです。ユニットテストは、ソフトウェア開発プロセスの早い段階でバグを検出して修正し、コードのそれぞれの部分が、独立した状態でも正しく機能することを確認するために必須です。

例: 計算機アプリを開発しているとします。加算関数のユニットテストでは、2 + 2 が 4 になるかどうかを確認します。このテストに合格すれば、加算関数が正しく機能していることが確認されます。

ユニットテストのベストプラクティス

テストに失敗すると、パイプラインは失敗し、ユーザーに通知が送られます。デベロッパーはジョブのログ (通常何千行もある) を確認し、どこでテストに失敗したのかを特定し、修正します。このチェックには時間がかかり、効率がよくありません。

そこで、ユニットテストレポート (英語版) を使うようにジョブを構成することができます。GitLab はマージリクエストとパイプラインの詳細ページ上にレポートを表示することができるため、ログ全体を確認しなくてもエラーをより簡単に素早く特定できます。

JUnitテストレポート

以下はJUnit テストレポートの例です。

パイプラインの JUnit テストレポート v13 10

統合テストおよびエンドツーエンドテスト戦略

通常の開発ルーチンに加え、統合テストとエンドツーエンドテスト専用に指定したパイプラインをセットアップすることが非常に重要です。これらのテストでは、マイクロサービス (英語版)、UI テスト、それ他のコンポーネントを含んだ、コードの異なる部位の連携がスムーズかどうかをチェックします。

これらのテストは 毎晩 (英語版) 実行されます。テスト結果を自動的に指定のスラックチャンネルに送るよう (英語版) に設定することもできます。そうすることで、デベロッパーが翌日出社したときに迅速に問題を確認できます。こうした機能はすべて、問題を早期に特定して修正することを優先すべく設計されています。

テスト環境

テストの中には、アプリをしっかりテストするために、テスト環境を構築しなければならない場合があります。GitLab CI/CD を使用するとテスト環境のデプロイが自動化でき、時間を大幅に節約できます。本ブログは主に CI に着目したものとなっているため、詳細には触れません。アプリのデプロイとリリースについては、GitLab ドキュメントのこのセクション (英語版) を参照してください。

CI パイプラインへのセキュリティスキャンの実装

CI パイプラインにセキュリティスキャンを実装する方法は次のとおりです。

SASTおよびDASTインテグレーション

コードの安全性を守ることは非常に重要です。最新の変更に脆弱性がある場合は、その内容を直ちに把握したいと考えます。したがって、パイプラインにセキュリティスキャンを追加するのが最適な対応といえるでしょう。セキュリティスキャンは、コミットごとにコードをチェックし、リスクがあれば警告してくれます。CI パイプラインに静的アプリケーションセキュリティテスト (SAST (英語版)) や動的アプリケーションセキュリティテスト (DAST (英語版)) などの各種スキャンを追加する方法を説明する以下のナビゲーションをご確認ください。

ナビゲーションを開始するには、以下の画像をクリックしてください。

スキャンの製品ツアー

さらに、AI を活用すれば、脆弱性をさらに深く掘り下げ、修正方法の提案が得られます。詳しくは以下のナビゲーションをご確認ください。

ナビゲーションを開始するには、以下の画像をクリックしてください。

製品ツアーでの脆弱性の説明

GitLab CI使い方まとめ

まだまだお伝えしたいことはありますが、ここで一度終わりにしましょう。説明に使用した例はすべて意図的に単純なものにしました。これは、慣れない技術スタックの説明が続くことで内容が頭に入らないといった状況を避け、GitLab CIのコンセプトを理解してもらうためです。では、ここまで学んできたことを次にまとめます。

  1. GitLab CI に作業をまかせるには、.gitlab-ci.yml 内で 1 つ以上のジョブ (英語版)を定義する必要があります。
  2. ジョブには名前を付ける必要がありますが、その際は適切な名前を付けるようにしてください。
  3. それぞれのジョブには、指定のキーワード で定義された、 GitLab CI 用の一連のルールと指示が含まれます。
  4. ジョブは、順序どおりに、並列に、または DAG (英語版) を使って順不同で実行できます。
  5. ジョブ間ではファイルを渡してビルドアーティファクトに保存し、インターフェイスからダウンロードできるようにすることも可能です。
  6. CI パイプラインに テストとセキュリティスキャン (英語版) を追加して、開発中のアプリの品質とセキュリティを確保します。

本記事で使用した用語およびキーワードの説明と、関連するドキュメントのリンクを下表にまとめました。

キーワードの説明とドキュメント

キーワード/用語 説明
.gitlab-ci.yml プロジェクトのビルド方法の定義をすべて含むファイル
script 実行するシェルスクリプトを定義する
before_script (全) ジョブの前に実行するコマンドを定義するために使用する
image 使用するDocker イメージを定義する
stages パイプラインのステージを定義する (デフォルトは test)
artifacts ビルドアーティファクトのリストを定義する
artifacts:expire_in 指定時間後にアップロードしたアーティファクトを削除するために使用する
needs ジョブ間の依存関係を定義するときに使用し、ジョブを特定の順序で実行することを可能にする
pipelines いくつかのステージ (バッチ) で実行されるビルドのグループを指す

※ドキュメントはすべて英語版です。

CI/CDについてさらに詳しく

よくある質問 (FAQ)

CIジョブを順次実行するか、または並列実行するか、どのように選択すればいいですか?

CI ジョブを順次実行するか、並列実行するかを選択する際は、ジョブの依存関係、リソースの利用可能性、実行時間、潜在的な干渉、テストスイートの構造、コスト面を考慮します。たとえば、デプロイジョブが始まるまでに終わらせなければならないビルドジョブがあったとします。その場合、これらのジョブを順次実行して、正しい実行順序を確かめます。一方、ユニットテストや統合テストなどのタスクは独立しており、それぞれの完了には依存しないため、並列実行できます。

GitLab CIの DAG(有向非巡回グラフ)とは何ですか?また、DAG はパイプラインの柔軟性をどのように向上させますか?

GitLab CI の有向非巡回グラフ (DAG) は、パイプラインステージの順序を並び替えます。DAG はジョブ間の依存関係を設定するため、前のステージにあるジョブが終わり次第、その後のステージのジョブが開始されます。これによりパイプライン全体の実行時間が短縮され、効率が向上し、一部のジョブは通常の順序より早く完了します。

GitLab の CI ジョブに最適なDockerイメージを選ぶ際に重要視しなければならないことは何ですか?

GitLab はジョブの実行の際に Docker イメージを使用します。デフォルトのイメージは ruby:3.1 です。ジョブの要件によって、最適なイメージを選ぶことがとても大切です。ジョブはまず指定された Docker イメージをダウンロードしますが、イメージに必要ではない追加パッケージが含まれていると、ダウンロードや実行に余分な時間がかかります。そのため、実行時に不必要な遅延が発生しないように、選択したイメージにはジョブに必要なパッケージだけが含まれていることを確認することが重要です。

次のステップ

ソフトウェア開発プラクティスで後れを取らないためにも、次のステップとしてCI/CDコンポーネントの標準化と再利用方法を理解することをお勧めします。GitLab CI/CD カタログ (英語版) もご確認ください。

継続的インテグレーションとデリバリー入門は、こちらをご覧ください。

監修:川瀬 洋平 @ykawase
(GitLab合同会社 カスタマーサクセス本部 シニアカスタマーサクセスマネージャー)

ご意見をお寄せください

このブログ記事を楽しんでいただけましたか?ご質問やフィードバックがあればお知らせください。GitLabコミュニティフォーラムで新しいトピックを作成して、ご意見をお聞かせください。 フィードバックをお寄せください

始めてみましょう

統合されたDevSecOpsプラットフォームによって、チームで何が実現できるかご確認ください。

無料トライアルを開始する

チームに最適なプランを見つけましょう

価格設定を見る

GitLabがチームにもたらすメリットをご覧ください

お問い合わせ