Docker と Jenkins をマスターする: 堅牢な CI/CD パイプラインを効率的に構築

エンジニアや技術愛好家の皆さん、こんにちは! 私は、最新のソフトウェア配信のための私のお気に入りの戦略の1つである、DockerとJenkinsを組み合わせてCI/CDパイプラインを強化することを共有できることを嬉しく思います。 

シニア DevOps エンジニアおよび Docker キャプテンとしてのキャリアを通じて、この 2 つのツールがリリースを大幅に効率化し、環境関連の頭痛の種を減らし、チームがより迅速にリリースするために必要な自信を与えることができることがわかりました。

この投稿では、DockerとJenkinsとは何か、なぜそれらが完璧に組み合わされるのか、そして効率的なパイプラインを構築および保守する方法について説明します。 私の目標は、ワークフローを自動化するときに、あなたがくつろげるようにすることです。 さっそく見ていきましょう。

2400x1260 evergreen docker ブログ

継続的インテグレーションと継続的デリバリーの概要

継続的インテグレーション (CI) と継続的デリバリー (CD) は、現代の開発の重要な柱です。 これらの概念に慣れていない場合は、簡単に説明します。

  • 継続的インテグレーション (CI): 開発者は頻繁にコードを共有リポジトリにコミットし、自動化されたビルドとテストをトリガーします。 この方法により、競合を防ぎ、欠陥を早期に発見することができます。
  • 継続的デリバリー (CD): CIを導入することで、組織は自信を持ってリリースを自動化できます。 つまり、リリースサイクルが短縮され、予期せぬ事態が少なくなり、必要に応じて変更を迅速にロールバックできるようになったのです。

CI/CD を活用することで、チームのベロシティと品質を劇的に向上させることができます。 信頼性が高く、合理化されたパイプラインのメリットを一度体験すれば、後戻りすることはできません。

なぜCI/CDにDockerとJenkinsを組み合わせるのですか?

Docker を使用すると、アプリケーションをコンテナ化して、開発、テスト、運用全体で一貫した環境を作成できます。 一方、Jenkins は、コードのビルド、テスト、デプロイなどのタスクを自動化するのに役立ちます。 私は、Jenkinsを疲れを知らない「組立ラインの労働者」と考えるのが好きですが、Dockerはプロジェクトのライフサイクル全体で一貫性を確保するための同一の「コンテナ」を提供します。

これらのツールのブレンドが非常に強力である理由は次のとおりです。

  • 一貫性のある環境: Docker コンテナは、開発者のラップトップから本番環境までの均一性を保証します。 この一貫性により、エラーが減り、「私のマシンで動作する」という恐ろしい言い訳がなくなります。
  • 迅速なデプロイとロールバック: Docker イメージは軽量です。 変更の発送や差し戻しは一瞬で行うことができ、ダウンタイムを最小限に抑えることが重要な短納期のプロセスサイクルに最適です。
  • スケーラビリティ: 1テスト、000テストを並行して実行したり、マイクロサービスに取り組む複数のチームをサポートしたりする必要がありますか?大丈夫。 より多くのビルドエージェントが必要なときにはいつでも複数のDockerコンテナをスピンアップし、JenkinsパイプラインですべてをJenkinsにオーケストレーションさせます。

私のような DevOps ジャンキーにとって、JenkinsとDockerのこの相乗効果は夢の実現です。

Docker と Jenkins を使用した CI/CD パイプラインの設定

袖をまくり上げる前に、必要な必需品について説明しましょう。

  • Docker Desktop (または Docker サーバー環境) がインストールされ、実行されている。 Dockerは、さまざまなオペレーティングシステム で入手できます
  • Jenkins は Docker Hub からダウンロードされるか、マシンにインストールされます。最近では、非推奨のlibrary/jenkinsイメージではなく、jenkins/jenkins:lts(長期サポートイメージ)が必要になります。
  • Docker コマンドの適切な権限と、システム上の Docker イメージを管理する機能。
  • Jenkins パイプライン設定を保存できる GitHub または同様のコード リポジトリ (省略可能ですが、推奨)。

プロのヒント: 本番環境のセットアップを計画している場合は、 Kubernetes のようなコンテナ オーケストレーション プラットフォームを検討してください。 このアプローチにより、Jenkins のスケーリング、Jenkins の更新、およびより重いワークロードに対する追加の Docker サーバーの管理が簡素化されます。

Docker と Jenkins を使用した堅牢な CI/CD パイプラインの構築

環境を準備したら、最初の Jenkins-Docker パイプラインを作成します。 以下では、一般的なパイプラインの一般的な手順を説明します — スタックに合わせて自由に変更してください。

1。 必要なJenkinsプラグインをインストールする

Jenkinsは無数のプラグインを提供しているため、DockerでJenkinsを簡単に構成できるようにするいくつかのプラグインから始めましょう。

  • Docker パイプラインプラグイン
  • Docker
  • CloudBees Docker のビルドと公開

プラグインのインストール方法:

  1. 「Manage Jenkins」>「Manage Plugins」を開きます。
  2. [ 利用可能 ]タブをクリックして、上記のプラグインを検索します。
  3. それらをインストールします(必要に応じてJenkinsを再起動します)。

コード例(CLIによるプラグインのインストール):

# Install plugins using Jenkins CLI
java -jar jenkins-cli.jar -s http://<jenkins-server>:8080/ install-plugin docker-pipeline
java -jar jenkins-cli.jar -s http://<jenkins-server>:8080/ install-plugin docker
java -jar jenkins-cli.jar -s http://<jenkins-server>:8080/ install-plugin docker-build-publish

プロのヒント(高度なアプローチ): 完全な Infrastructure-as-Code セットアップを目指している場合は、 Jenkins Configuration as Code (JCasC) の使用を検討してください。 JCasC では、プラグイン、認証情報、パイプライン定義など、すべての Jenkins 設定を YAML ファイルで宣言できます。 つまり、Jenkins の設定全体がバージョン管理され、再現可能であるため、新しい Jenkins インスタンスを簡単にスピンアップしたり、複数の環境に一貫した設定を適用したりできます。 これは、Jenkins を大規模に管理しようとしている大規模なチームにとって特に便利です。

参考:

2。 Jenkins パイプラインを設定する

この手順では、パイプラインを定義します。 Jenkins の "パイプライン" ジョブでは、 Jenkinsfile (コード リポジトリに格納されている) を使用して、ステップ、ステージ、および環境要件を指定します。

Jenkinsfileの例:

pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://github.com/your-org/your-repo.git'
            }
        }
        stage('Build') {
            steps {
                script {
                    dockerImage = docker.build("your-org/your-app:${env.BUILD_NUMBER}")
                }
            }
        }
        stage('Test') {
            steps {
                sh 'docker run --rm your-org/your-app:${env.BUILD_NUMBER} ./run-tests.sh'
            }
        }
        stage('Push') {
            steps {
                script {
                    docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-credentials') {
                        dockerImage.push()
                    }
                }
            }
        }
    }
}

ここで何が起こっているのかを見てみましょう。

  1. チェックアウト: リポジトリをプルします。
  2. ビルド: ビルド番号をタグとしてビルドされた Docker イメージ (your-org/your-app) を作成します。
  3. テスト: 新しいコンテナ内でテストスイートを実行し、Docker コンテナがテスト実行ごとに一貫した環境を作成するようにします。
  4. プッシュ: テストに合格した場合、イメージを Docker レジストリ (Docker Hub など) にプッシュします。

参考: Jenkins パイプラインのドキュメント

3。 Jenkins を自動ビルド用に構成する

パイプラインが設定されたので、Jenkins でパイプラインを自動的に実行する必要があります。

  • Webhook トリガー: ソース管理 (GitHub など) を構成して、コードがプッシュされるたびに Webhook を送信するようにします。 Jenkins はすぐにビルドを開始します。
  • SCM のポーリング: Jenkins は、リポジトリで新しいコミットを定期的にチェックし、変更が検出された場合はビルドを開始します。

どのトリガー方法を選ぶべきですか?

  • Webhook トリガー は、ほぼリアルタイムのビルドが必要な場合に最適です。 リポジトリにプッシュするとすぐに、Jenkins に通知され、新しいビルドがほぼ即座に開始されます。 このアプローチは、Jenkins がリポジトリの更新を継続的にチェックする必要がないため、通常はより効率的です。 ただし、ソース管理システムとネットワーク環境が Webhook をサポートしている必要があります。
  • ポーリング SCM は、環境が受信 Webhook をサポートできない場合 (たとえば、企業のファイアウォールの内側にいる場合や、リポジトリが送信フック用に構成されていない場合) に役立ちます。 その場合、Jenkins は定義したスケジュール (5 分ごとなど) で新しいコミットを定期的にチェックするため、わずかな遅延や余分なオーバーヘッドが発生する可能性がありますが、ロックダウンされた環境でのセットアップが簡素化される可能性があります。

個人的な経験: Webhook トリガーは、すべてを可能な限りリアルタイムに近づけるため、私は Webhook トリガーが大好きです。 Webhook が不可能な場合はポーリングは正常に機能しますが、コードのプッシュとビルド開始の間にわずかな遅延が発生します。 また、ポーリング間隔が頻繁すぎると、追加のネットワークトラフィックが発生する可能性があります。

4。 Docker コンテナを使用したビルド、テスト、デプロイ

ここからが面白い部分です — ビルドからデプロイまでのサイクル全体を自動化することです。

  1. Docker イメージのビルド: コードをプルした後、Jenkins は docker.build を呼び出して新しいイメージを作成します。
  2. テストの実行: 自動または自動の受け入れテストは、そのイメージからスピンアップされたコンテナ内で実行され、一貫性が確保されます。
  3. レジストリへのプッシュ: テストに合格したと仮定すると、Jenkins はタグ付けされたイメージを Docker レジストリ (Docker Hub またはプライベートレジストリ) にプッシュします。
  4. デプロイ: 必要に応じて、Jenkins はイメージをリモート サーバーまたはコンテナー オーケストレーター (Kubernetes など) にデプロイできます。

この合理化されたアプローチにより、ビルド、テスト、デプロイのすべてのステップが 1 つのまとまったパイプラインに収まり、「そのステップはどこへ行ったのか」という謎を防ぐことができます。

5。 パイプラインの最適化と維持

パイプラインが稼働したら、すべてをスムーズに実行し続けるためのメンテナンスのヒントと機能強化をいくつか紹介します。

  • 画像をクリーンアップする: Docker イメージを定期的にクリーンアップすることで、スペースを再利用し、混乱を減らすことができます。
  • セキュリティ更新プログラム: Docker、Jenkins、およびプラグインのアップデートを常に把握します。 パッチを迅速に適用することで、CI/CD環境を脆弱性から保護することができます。
  • リソース監視: Jenkins ノードにビルドに十分なメモリ、CPU、およびディスク領域があることを確認します。 ノードが酷使されていると、パイプラインの速度が低下し、断続的な障害が発生する可能性があります。

プロのヒント: 大規模なプロジェクトでは、ビルドエージェントを Jenkins コントローラーから分離して、エフェメラル Docker コンテナ (Jenkins エージェントとも呼ばれます) で実行することを検討してください。 エージェントがダウンしたり古くなったりした場合は、新しいエージェントをすぐにスピンアップできるため、すべてのビルドでクリーンで一貫性のある環境を確保し、メインのJenkinsサーバーの負荷を軽減できます。

CI/CD に宣言型パイプラインを使用する理由

Jenkins は複数のパイプライン構文をサポートしていますが、 宣言型パイプライン は、その明確さとリソースに優しい設計で際立っています。 その理由は次のとおりです。

  • 簡略化された独自の構文: すべてが 1 つの pipeline { ... } ブロックにラップされているため、「スクリプトの無秩序な増加」が最小限に抑えられます。 これは、Groovyの詳細に深く掘り下げることなく、ベストプラクティスへの迅速な道を求めるチームに最適です。
  • リソース割り当ての容易化: パイプライン レベルまたは各ステージ内で agent を指定することで、重量の多いタスク (ビルド、テスト) を個別のワーカー ノードまたは Docker コンテナーにオフロードできます。 このアプローチは、メインの Jenkins コントローラーが過負荷になるのを防ぐのに役立ちます。
  • 並列化と行列のビルド: 複数のテストスイートを実行したり、さまざまな OS/ブラウザーの組み合わせをサポートしたりする必要がある場合、Declarative Pipelines を使用すると、並列ステージの定義やマトリックスビルドの設定が簡単になります。 この戦術は、マイクロサービスや、異なる環境を並行して必要とする大規模なテストスイートに非常に便利です。
  • ビルトイン「エスケープハッチ」: Groovyの高度な機能が必要ですか? scriptブロックにドロップするだけです。これにより、ニッチなケースのスクリプトパイプライン機能にアクセスしながら、ほとんどの場合、Declarativeの合理化された構造を楽しむことができます。
  • よりクリーンなパラメータ化: 実行するテストや使用するDockerイメージをユーザーに選択させたいですか? parametersディレクティブにより、パイプラインの柔軟性が向上します。1 つの Jenkinsfile で、単体テストと統合テストなど、複数のシナリオを重複することなく処理できます。

宣言型パイプラインの例

以下は、宣言型構文がリソース割り当てを簡素化し、Jenkins コントローラーを正常に保つ方法を示すサンプル パイプラインです。

例 1: 基本的な宣言型パイプライン

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                echo 'Building...'
            }
        }
        stage('Test') {
            steps {
                echo 'Testing...'
            }
        }
    }
}
  • 使用可能な任意の Jenkins エージェント (ワーカー) で実行されます。
  • 単純な順序で 2 つのステージを使用します。

例 2: リソース分離のためのステージ・レベルのエージェント

pipeline {
    agent none  // Avoid using a global agent at the pipeline level
    stages {
        stage('Build') {
            agent { docker 'maven:3.9.3-eclipse-temurin-17' }
            steps {
                sh 'mvn clean package'
            }
        }
        stage('Test') {
            agent { docker 'openjdk:17-jdk' }
            steps {
                sh 'java -jar target/my-app-tests.jar'
            }
        }
    }
}
  • 各ステージは独自のコンテナで実行され、1つのノードが圧倒されるのを防ぎます。
  • agent none 上部で、グローバルエージェントが不必要に割り当てられないようにします。

例 3: テスト・ステージの並列化

pipeline {
    agent none
    stages {
        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    agent { label 'linux-node' }
                    steps {
                        sh './run-unit-tests.sh'
                    }
                }
                stage('Integration Tests') {
                    agent { label 'linux-node' }
                    steps {
                        sh './run-integration-tests.sh'
                    }
                }
            }
        }
    }
}
  • テストを 2 つの並列ステージに分割します。
  • 各ステージは異なるノードまたはコンテナで実行できるため、フィードバックループが高速化されます。

例 4: パラメーター化されたパイプライン

pipeline {
    agent any

    parameters {
        choice(name: 'TEST_TYPE', choices: ['unit', 'integration', 'all'], description: 'Which test suite to run?')
    }

    stages {
        stage('Build') {
            steps {
                echo 'Building...'
            }
        }
        stage('Test') {
            when {
                expression { return params.TEST_TYPE == 'unit' || params.TEST_TYPE == 'all' }
            }
            steps {
                echo 'Running unit tests...'
            }
        }
        stage('Integration') {
            when {
                expression { return params.TEST_TYPE == 'integration' || params.TEST_TYPE == 'all' }
            }
            steps {
                echo 'Running integration tests...'
            }
        }
    }
}
  • 実行するテスト (単体、統合、またはその両方) を選択できます。
  • 選択したパラメーターに基づいて関連するステージのみを実行し、リソースを節約します。

例 5: マトリックスのビルド

pipeline {
    agent none

    stages {
        stage('Build and Test Matrix') {
            matrix {
                agent {
                    label "${PLATFORM}-docker"
                }
                axes {
                    axis {
                        name 'PLATFORM'
                        values 'linux', 'windows'
                    }
                    axis {
                        name 'BROWSER'
                        values 'chrome', 'firefox'
                    }
                }
                stages {
                    stage('Build') {
                        steps {
                            echo "Build on ${PLATFORM} with ${BROWSER}"
                        }
                    }
                    stage('Test') {
                        steps {
                            echo "Test on ${PLATFORM} with ${BROWSER}"
                        }
                    }
                }
            }
        }
    }
}
  • PLATFORM x BROWSERの行列を定義し、各組み合わせを並行して実行します。
  • パイプラインロジックを複製せずに複数のOS/ブラウザの組み合わせをテストするのに最適です。

その他のリソース:

宣言型パイプラインを使用すると、CI/CD セットアップの保守、スケーラビリティ、セキュリティを確保できます。 Dockerベースかラベルベースかにかかわらず、エージェントを適切に構成することで、ワークロードを複数のワーカーノードに分散し、リソースの競合を最小限に抑え、Jenkinsコントローラーを快適に動作させることができます。

Docker と Jenkins を使用した CI/CD のベスト プラクティス

セットアップを強化する準備はできましたか? ここでは、私が培った実証済みの習慣をいくつか紹介します。

  • Docker のレイヤー キャッシングを活用します。 Dockerfile を最適化して、安定した (変更頻度の低い) レイヤーが早期に表示されるようにします。 これにより、ビルド時間が大幅に短縮されます。
  • テストを並行して実行します。 Jenkins は、さまざまなサービスやマイクロサービスに対して複数のコンテナを実行できるため、それらを並べてテストできます。 宣言型パイプラインを使用すると、それぞれが独自のエージェントで並列ステージを簡単に定義できます。
  • セキュリティのシフトレフト: パイプラインの早い段階でセキュリティ チェックを統合します。 Docker Scoutのようなツールでは、イメージの脆弱性をスキャンでき、Jenkinsプラグインではコンプライアンスポリシーを適用できます。問題を発見するために、本番環境まで待たないでください。
  • リソース割り当ての最適化: Jenkins コンテナと Docker コンテナの CPU とメモリの制限を適切に構成して、リソースの占有を回避します。 Jenkins をスケーリングする場合は、効率を最大化するために、ビルドを複数のワーカー ノードまたはエフェメラル エージェントに分散します。
  • 構成管理: Jenkins ジョブ、パイプライン定義、およびプラグイン設定をソース管理に格納します。 Jenkins Configuration as Codeのようなツールは、バージョン管理と複数のDockerサーバー間でのセットアップの複製を簡素化します。

これらの戦略に加えて、十分な量の宣言型パイプラインを使用すれば、保守と進化が容易な、無駄のない高オクタン価の CI/CD パイプラインが得られます。

Docker と Jenkins パイプラインのトラブルシューティング

最高のシステムでさえ、時々暗礁に乗り上げます。 以下は、私が見た(そして克服した)いくつかのハードルです。

  • 環境の変動性の処理: Docker と Jenkins のバージョンを異なるノード間で同期します。 複数の Jenkins ノードが動作している場合は、Docker のバージョンを標準化して、ランダムなビルド失敗を回避します。
  • ビルドの失敗のトラブルシューティング: docker logs -f <container-id> を使用して、コンテナ内で何が起こったかを正確に確認します。多くの場合、ログには依存関係の欠落や環境変数の不適切な設定があります。
  • ネットワーキングの課題: コンテナが相互に通信する必要がある場合は、特に複数のホスト間で、Dockerネットワークまたはオーケストレーションプラットフォームを適切に構成してください。 詳細については、Docker のネットワークに関するドキュメントを参照し、トラブルシューティングのヒントについては、Jenkins の問題の診断ガイドを参照してください。

結論

Docker と Jenkins を組み合わせると、CI/CD に対する機敏で堅牢なアプローチが実現します。 Docker は一貫性のある環境をロックダウンし、超高速のロールアウトを実現する一方で、Jenkins はビルド、テスト、変更の本番環境へのプッシュなどの主要なタスクを自動化します。 この 2 つが調和していると、リリース サイクルが短縮され、統合の頭痛の種が減り、優れた機能の開発に集中する時間が増えることが期待できます。

また、パイプラインが健全であれば、チームはユーザーからのフィードバックに迅速に対応し、自信を持ってアップデートを展開できるという、ソフトウェアプロジェクトを成功させるための重要な要素です。 また、セキュリティに懸念がある場合は、アプリケーションを安全に保つためのツールやベストプラクティスがたくさんあります。

このガイドが、チームが気に入る高オクタン価の CI/CD パイプラインの構築 (および保守) に役立つことを願っています。 質問がある場合や手伝ってもらう必要がある場合は、 コミュニティフォーラムで気軽に連絡したり、 Slackで会話に参加したり、 GitHubの問題でチケットを開いたりしてください。 DockerやJenkinsの愛好家の中には、喜んで助けてくれる仲間がたくさんいます。

読んでくれてありがとう、そして幸せな建物!

さらに詳しく