GitLab CI


 ページ内目次

🏗️ CI/CDパイプライン構築入門:実践プロジェクトで学ぶ自動化の第一歩


こんにちは!ソフトウェア開発の世界に足を踏み入れた皆さん。今回は、GitLab CI/CDを使い、実際のプロジェクトで**継続的インテグレーション(CI)**のパイプラインを構築する方法を学んでいきます。

🚀 継続的インテグレーション(CI)とは?

CIとは、複数の開発者がコードを変更するたびに、その変更を頻繁に**統合(インテグレート)**し、自動でビルドやテストを行う開発手法です。

  • なぜCIが必要なの?

    • 開発者がバラバラに作業すると、後でコードを統合しようとしたときに、多くの問題(コンフリクト)が発生し、解決に時間がかかってしまいます。

    • CIは、これらの問題を早期に発見し、修正を容易にするために不可欠です。

このセクションでは、Node.jsとReactで構築されたシンプルなウェブアプリケーションを例に、CIパイプラインをゼロから構築していきます。

👩‍💻 まずはローカルで開発フローを理解する

GitLabでパイプラインを自動化する前に、手動でどのような手順が必要なのかを理解することが超重要です。自動化は、手動での作業を置き換えるものだからです。

Node.jsのプロジェクトでは、一般的に以下の3つのステップが必要です。

  1. 依存関係のインストール

    • npm installコマンドで、プロジェクトに必要な外部ライブラリ(パッケージ)をインストールします。

    • これらの依存関係はpackage.jsonファイルに記述されており、node_modulesというディレクトリにインストールされます。

  2. プロジェクトのビルド

    • npm run buildコマンドで、開発用のコードを本番環境で効率的に動くように最適化されたファイル(buildディレクトリに生成される)に変換します。

  3. テスト

    • プロジェクトが正しく動作するか確認します。

    • 私たちはnpm install -g serveserve buildというコマンドを使い、ローカルで簡単なウェブサーバーを立ち上げて動作確認しました。

これらの手動ステップを、GitLab CI/CDを使って自動化していきます。


🐳 CIパイプラインでDockerイメージを使う理由

GitLabのパイプラインでこれらのコマンドを実行しようとすると、node not foundのようなエラーに直面することがあります。これは、デフォルトで使われるDockerイメージ(例:Alpine)には、Node.jsやnpmがインストールされていないためです。

ここで登場するのがDockerです。Dockerを使うと、必要なツールがすべて揃った**「実行環境の箱」**を簡単に用意できます。

  • Dockerイメージの選び方

    • 公式イメージを選ぶ:Docker Hubでnodephppythonなどの公式イメージを探しましょう。公式イメージは信頼性とセキュリティが高いです。

    • 軽量イメージを選ぶ:Dockerイメージには、alpineslimといった軽量版があります。これらは余分なものが含まれていないため、ダウンロードが速く、パイプラインの実行時間を短縮できます。

      • 例:node:22(386MB)とnode:22-alpine(52MB)では、後者の方が圧倒的に軽量です。

  • パイプラインでの記述

    • .gitlab-ci.ymlファイルで、image: node:22-alpineのように指定するだけで、GitLabランナーがDocker Hubから自動でイメージをダウンロードし、その環境でジョブを実行してくれます。


🔨 ビルドステージを構築しよう

それでは、先ほどローカルで確認した手順をCIパイプラインに落とし込んでいきましょう。

  1. ステージとジョブの定義

    • まず、buildというステージを定義します。

    • 次に、build_websiteというジョブを作成し、このステージに割り当てます。

  2. スクリプトの記述

    • スクリプトブロックに、Node.jsのバージョン確認コマンドと、依存関係のインストール、そしてビルドコマンドを記述します。

    • 依存関係のインストールには、CI環境向けの**npm ci**コマンドを使うのがベストプラクティスです。これは、package-lock.jsonに書かれた正確なバージョンをインストールするため、再現性が保証されます。

stages:
  - build

build_website:
  stage: build
  image: node:22-alpine
  script:
    - node --version
    - npm --version
    - npm ci  # CI環境向けの依存関係インストールコマンド
    - npm run build
  1. ジョブの実行と確認

    • この設定をコミットすると、GitLabが自動でパイプラインを実行します。

    • ログを確認すると、node_modulesディレクトリがインストールされ、buildディレクトリが生成されていることがわかります。

このようにして、手動で行っていた依存関係のインストールとプロジェクトのビルドを自動化する、最初のCIパイプラインが完成しました!


📦 GitLab CI/CDの成果物を保存しよう!アーティファクトとDockerコンテナの仕組み


こんにちは!前回までの内容で、CIパイプラインの構築とデバッグの基本を学びましたね。今回は、ジョブが生成した**「成果物(アーティファクト)」を保存する方法と、パイプラインの裏側で動いているDockerコンテナ**の仕組みについて、さらに深く掘り下げていきます。


📝 課題:ビルドディレクトリをアーティファクトとして保存する

ビルドジョブが完了した後、その結果として生成された**buildディレクトリ**のファイルを、GitLabのジョブページから閲覧できるようにしてみましょう。

  • ヒント.gitlab-ci.ymlファイルで、artifactsというキーワードを使います。

解答編:アーティファクトの設定方法

build_websiteジョブに、scriptブロックと同じ階層(インデント)でartifactsブロックを追加します。

build_website:
  stage: build
  image: node:22-alpine
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - build/ # これがアーティファクトとして保存されるディレクトリ

この変更をコミットしてパイプラインを実行すると、ジョブが成功した後に右側に**「Job artifacts」**というセクションが表示されます。ここからbrowseをクリックすると、buildディレクトリとその中身を確認できます。

💡ポイント💡

  • artifacts:paths:ここに保存したいファイルやディレクトリのパスをリスト形式で指定します。

  • /(スラッシュ):ディレクトリを指定する場合は、最後にスラッシュを付けることで、それがディレクトリであることを明示できます。


🐳 GitLabとDockerコンテナの舞台裏

私たちは何も意識せずにコマンドを書いていますが、GitLabのパイプラインの裏側では、すべてのジョブで以下のことが自動的に行われています。

  1. パイプラインのトリガー:コードがプッシュされると、GitLabサーバーがパイプラインを起動します。

  2. ランナーの選定:サーバーは、ジョブを実行するのに適したGitLabランナーを探します。

  3. Dockerコンテナの起動:ランナーは、.gitlab-ci.ymlで指定されたDockerイメージ(例:node:22-alpine)を使って、新しいDockerコンテナを起動します。

  4. コードのクローン:ランナーはGitリポジトリからソースコードをクローンし、コンテナ内の一時的な作業スペースに配置します。

  5. スクリプトの実行scriptブロックに書かれたコマンドが、このコンテナ内で順番に実行されます。

  6. アーティファクトの保存:コマンドが正常に終了すると、artifactsで指定された成果物がGitLabサーバーにアップロードされ、保存されます。

  7. コンテナの終了:ジョブが完了すると、そのために起動したDockerコンテナは完全に破棄されます。

💡重要なポイント💡

  • ジョブは独立している:すべてのジョブは、それぞれ新しいDockerコンテナで実行されます。つまり、前のジョブで作成したファイルは、次のジョブには引き継がれません

  • なぜ再利用しないの?

    • 一見非効率に見えますが、こうすることでジョブ間の衝突を防ぎ、常にクリーンで一貫した環境で実行できます。

    • また、複数のジョブを異なるランナーで同時に並行実行できるため、パイプライン全体の時間を短縮できます。

まとめ:アーティファクトとDockerコンテナの関係

ジョブは、毎回新しく用意されたDockerコンテナという「作業場」でコードをビルド・テストします。この作業場はジョブが終わると消えてしまいますが、そこで作られた成果物は**「アーティファクト」**としてGitLabサーバーに保存され、次のジョブや後で使うために活用されます。

この仕組みを理解しておけば、なぜジョブ間でファイルが引き継がれないのか、なぜアーティファクトの定義が重要なのかが明確になります。これからも、この知識を活かして、様々なパイプラインを構築していきましょう!


🧪 GitLab CI/CDで賢くテスト!ユニットテストとJUnitレポートを使いこなそう


こんにちは!前回、GitLab CI/CDでビルドの成果物(アーティファクト)を保存する方法を学びましたね。今回は、その成果物を自動でテストする仕組みと、テスト結果を分かりやすく表示する方法について掘り下げていきます。


🔍 課題:アーティファクトをテストするジョブを作ろう

ビルドジョブで生成されたbuildディレクトリの中に、ウェブサイトの入り口であるindex.htmlファイルがあるかを確認するテストを、新しいジョブとして作成してみましょう。

  • ヒント:ビルドジョブで作成されたアーティファクトは、次のジョブで自動的にダウンロードされます。

解答編:テストジョブの作成方法

.gitlab-ci.ymlファイルに、新しいステージとジョブを追加します。

stages:
  - build
  - test # 新しいステージを追加

build_website:
  stage: build
  image: node:22-alpine
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - build/

test_artifact: # 新しいジョブ
  stage: test
  image: alpine # このジョブはNode.jsを必要としないため、軽量なAlpineを使う
  script:
    - test -f build/index.html # ファイルが存在するかをテスト

この設定でパイプラインを実行すると、buildジョブの成功後、test_artifactジョブが自動で実行されます。test -f build/index.htmlコマンドが正常に終了するため、ジョブは成功と表示されます。

💡ここがポイント💡

  • test_artifactジョブは、build_websiteジョブのアーティファクトを自動でダウンロードしています。

  • もしbuildジョブでアーティファクトを定義していなかったら、test_artifactジョブはbuild/index.htmlを見つけられず、失敗してしまいます。


📝 ユニットテストを導入する

テストには、ファイルが存在するかどうかをチェックするような簡単なものだけでなく、コードのロジックが正しいかを検証する**「ユニットテスト」**があります。

  • ユニットテストとは?:プログラムの最小単位(関数やメソッドなど)が、単独で正しく動作するかを確認するテストです。

  • なぜ重要?:実行が非常に速く、問題の場所を正確に特定できるため、バグを早期に発見できます。

私たちのプロジェクトでは、npm testコマンドでユニットテストが実行できます。これをパイプラインに追加してみましょう。

ユニットテストジョブの作成

test_artifactジョブと同様に、testステージにunit_testsという新しいジョブを追加します。

# ... (build_website, test_artifact ジョブの後に追加)

unit_tests:
  stage: test
  image: node:22-alpine # npm testにはNode.jsが必要
  script:
    - npm ci # テスト実行に必要な依存関係をインストール
    - npm test

💡ポイント💡

  • npm testを実行する前に、npm ciで依存関係をインストールする必要があります。

  • ジョブを並列実行するtest_artifactジョブとunit_testsジョブを同じtestステージに割り当てることで、これらが同時に実行され、パイプライン全体の時間を短縮できます。


📊 テスト結果を分かりやすく表示する:JUnitレポート

テストの数が増えると、ログをいちいち確認するのは大変です。そこで役立つのがJUnitレポートです。これはテスト結果をまとめた特別なファイルで、GitLabがこれを読み込んで、テスト結果を分かりやすく表示してくれます。

JUnitレポートの公開方法

unit_testsジョブに、artifacts:reports:junitという特別な設定を追加します。

# ... (unit_tests ジョブ)

unit_tests:
  stage: test
  image: node:22-alpine
  script:
    - npm ci
    - npm test
  artifacts:
    reports:
      junit: reports/junit.xml # JUnitレポートのパスを指定

この設定でパイプラインを実行すると、GitLabのパイプラインページに**「Tests」**という新しいタブが表示されます。ここをクリックすると、どのテストが成功し、どれが失敗したのかを、ログよりもずっと見やすく確認できます。

🚨 テストが失敗したときに学ぶ

テストは、失敗したときにこそ価値があります。意図的にコードにミスを加えて、テストがどのように失敗するかを観察してみましょう。

  • ビルドジョブの失敗buildディレクトリの名前を意図的に変更してみましょう。

    • buildディレクトリが生成されないため、test_artifactジョブが失敗します。

    • しかし、buildジョブ自体は成功したと表示されます。これは、アーティファクトが見つからないことはジョブの失敗要因ではないためです。

  • ユニットテストの失敗:ウェブサイトのロゴ画像を削除してみましょう。

    • ロゴの存在をチェックするユニットテストが失敗し、unit_testsジョブが失敗します。

    • JUnitレポートのおかげで、どのテストが、なぜ失敗したのかを一目で確認できます。

DevOpsの世界では、テストは煙探知機のようなものです。定期的にテストを動かし、失敗を恐れずに原因を突き止める練習をすることが、高品質なソフトウェアを迅速に開発するために最も重要なスキルになります。


🛡️ メインブランチを守れ!GitLabのマージリクエストとブランチワークフロー


こんにちは!CI/CDの基本を学んだところで、今回はより実践的な開発手法、ブランチワークフローマージリクエストについて解説します。これにより、チーム開発におけるコードの安全性を飛躍的に高めることができます。

🚨 なぜメインブランチを保護する必要があるのか?

もし開発者が直接メインブランチにコードをプッシュすると、以下のようなリスクが発生します。

  • バグの混入:テストが不十分なコードが混ざると、プロジェクトが動かなくなる可能性があります。

  • 生産性の停止:メインブランチが壊れると、他の開発者は誰も作業を進められなくなります。これはまるで、工場の生産ラインが止まってしまうようなものです。

これを防ぐには、テストされていないコードをメインブランチに入れないことが唯一の解決策です。


🌳 Gitのブランチワークフローを理解する

私たちは、**「Git Feature Branch Workflow」**という手法を採用します。これは非常にシンプルで効果的なワークフローです。

  1. 新しいブランチを作成:新しい機能開発、バグ修正、または些細な変更でも、必ずメインブランチから新しいブランチを作成します。

    • 例:feature/improved-docsのような命名規則を使います。

  2. 変更をプッシュ:変更内容をこの新しいブランチにプッシュします。

  3. パイプラインの実行:ブランチにプッシュすると、GitLabが自動的にCIパイプラインを実行し、コードのビルドとテストを検証します。

  4. マージリクエストの作成:パイプラインが成功したら、変更をメインブランチに統合するための**マージリクエスト(MR)**を作成します。

  5. レビューと統合:チームメンバーがコードをレビューし、問題がなければ変更がメインブランチにマージされます。

この流れにより、メインブランチは常に安定した状態が保たれ、もしブランチで問題が発生しても、他の開発者に影響はありません。


📝 マージリクエストの設定

マージリクエストを本格的に使う前に、GitLabでいくつかの設定を行う必要があります。

  1. マージリクエストの設定

    • Settings > General > Merge requests

    • マージ方法Fast-forward mergeを選択し、コミット履歴をきれいに保ちます。

    • パイプラインチェックPipeline must succeedを有効にし、パイプラインが失敗している場合はマージできないようにします。

    • スレッドの解決All discussions must be resolvedを有効にし、レビューでのコメントがすべて解決されるまでマージできないようにします。

  2. ブランチの保護

    • Settings > Repository > Protected branches

    • mainブランチの設定で、Allowed to pushAllowed to mergeを適切な設定に変更します。

    • これにより、メインブランチへの直接プッシュが禁止され、マージリクエスト経由でしか変更を統合できなくなります。


🚀 実際のマージリクエストの流れ

設定が完了したら、実際にマージリクエストを作成してみましょう。

  1. 新しいブランチで作業:Web IDEでコードに変更を加え、commit時にCreate a new branchを選択します。

  2. プッシュとパイプライン:新しいブランチにコミットがプッシュされると、すぐにCIパイプラインが実行されます。

  3. マージリクエストの作成:GitLabの画面に表示されるプロンプトからCreate merge requestをクリックします。

    • タイトルと説明:マージリクエストの目的を明確に伝える、分かりやすいタイトルと説明を付けます。

    • マージオプション:マージ後にソースブランチを自動で削除するオプションを選択し、不要なブランチが残らないようにします。

  4. コードレビューChangesタブで、他の開発者が変更内容を確認し、コメントを残します。

    • コメントはスレッドとして残り、解決されるまでマージできません。

  5. マージ:パイプラインが成功し、すべてのレビューが完了したら、Mergeをクリックして変更をメインブランチに統合します。

💡最後のチェック:マージ後のパイプライン💡 マージが完了すると、GitLabはメインブランチに対しても自動的にパイプラインを再実行します。これは、統合後に問題が発生していないことを最終的に確認するための、非常に重要なステップです。



🧹 コードをきれいに保つ!GitLabのリンターとパイプラインの最適化


こんにちは!コードレビューやマージリクエストの運用を学びましたが、今回はさらに一歩進んで、コード品質を自動的にチェックしてくれるリンターと、パイプラインの効率的な運用方法について解説します。


📝 リンターとは?

リンターは、コード内のスタイルに関する問題や、些細なバグの可能性を自動で検出してくれるツールです。

  • :使われていない変数を定義してしまったり、インデントがずれていたりする小さな問題を指摘してくれます。

私たちは、ESLintというツールを使って、このプロジェクトのコード品質をチェックします。このプロジェクトはすでに設定が完了しているので、GitLabにジョブを追加するだけで簡単に連携できます。

リンターのジョブをパイプラインに追加しよう

マージリクエストの流れで学んだように、メインブランチから新しいブランチを作成し、以下の設定を.gitlab-ci.ymlファイルに追加します。

lint_code_quality:
  stage: test
  image: node:22-alpine
  script:
    - npm ci
    - npm run lint # プロジェクトに設定済みのlintコマンドを実行
  artifacts:
    reports:
      codequality: gl-codequality-report.json # GitLabが解釈できる形式でレポートを生成
  • npm run lintpackage.jsonに定義されているリンターコマンドを実行します。

  • artifacts:reports:codequality:この設定により、GitLabはリンターが生成したレポートを自動的に解析し、マージリクエストに結果を表示してくれます。

この変更をコミットしてマージリクエストを作成すると、パイプラインが実行され、**「Code Quality」**というセクションにリンターの実行結果が表示されます。

意図的にエラーを起こしてみる

リンターが正しく機能しているか確認するために、使われていない変数をコードに追加してみましょう。

  • 結果:パイプラインは失敗し、マージリクエストのページでCode Qualityセクションにエラーが報告されます。

  • マージリクエストのブロック:リンターのジョブが失敗したため、マージリクエストは自動的にマージできなくなります。

これにより、品質の低いコードがメインブランチにマージされるのを未然に防ぐことができます。


🏃‍♂️ パイプラインを最適化する2つの原則

パイプラインの実行時間は、開発のスピードに直結します。以下の2つの原則を考慮することで、より効率的なパイプラインを構築できます。

  1. 依存関係を理解する

    • ジョブの順序buildジョブが完了しないとtest_artifactジョブは実行できません。このように、ジョブ間の依存関係を正確に把握し、ステージを適切に順序付ける必要があります。

    • 並列実行の活用:依存関係のないジョブ(例:unit_testslint_code_quality)は同じステージに配置することで、並列で実行でき、パイプライン全体の時間を短縮できます。

  2. 早く失敗する(Fail Fast)

    • 短時間で終わるジョブを先に:パイプラインが失敗する可能性が高い、短時間で終わるジョブ(例:リンター)は、パイプラインの早い段階で実行しましょう。

    • これにより、無駄な時間をかけずにすぐにフィードバックを得られ、修正に取り掛かることができます。

⚙️ パイプラインジョブの管理方法

開発中に一時的に特定のジョブを無効にしたい場合があります。そんな時は、ジョブ名の前に.(ドット)を付けることで、そのジョブを一時的に無効化できます。

# このジョブは無効化され、パイプラインで実行されません
.test_artifact:
  stage: test
  image: alpine
  script:
    - test -f build/index.html
  • 活用シーン:特定のジョブを開発中だが、他のジョブは動かしたい場合などに便利です。

これらの知識を活用することで、コードの品質を維持しつつ、効率的でスピーディーな開発プロセスを実現できます。