Dockerデプロイの自動化:VPS向けJenkins実践ガイド

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

サーバーへのSSHログインはやめよう

VPSにSSHで入り、コードをプルして、手動でDockerイメージを再ビルドする……そんな作業に夜遅くまで時間を費やした経験はありませんか? 本来2分で終わるはずの作業に20分もかけるのは非効率です。手動デプロイは単なる時間の浪費だけでなく、設定の乖離(Configuration Drift)やヒューマンエラーの温床となります。CI/CDモデルに移行することで、このストレスフルな作業を、予測可能でわずか2分で完了するバックグラウンドタスクへと変貌させることができます。

なぜJenkinsなのか? GitHub Actionsも人気ですが、Jenkinsはビルド環境を完全に自前で制御できるという利点があります。これは、低コストなVPSを利用していて、すべてを1つの環境にまとめたい場合に大きな強みとなります。私の経験上、このセットアップを導入することで、デプロイ関連のダウンタイムを90%近く削減できました。「自分の環境では動いたのに」という変数を完全に排除できるからです。

Docker経由でJenkinsをセットアップする

JenkinsをDockerコンテナ内で実行することで、ホストシステムをクリーンに保つことができます。これにより、VPSが競合する Javaのバージョンや依存関係の墓場になるのを防げます。Jenkinsにイメージをビルドさせるために、「Docker-out-of-Docker」(DooD)戦略を採用します。これは、ホスト側のDockerソケットを直接Jenkinsコンテナにマウントする方法です。

まず、VPS上にdocker-compose.ymlファイルを作成します:

version: '3.8'
services:
  jenkins:
    image: jenkins/jenkins:lts-jdk17
    container_name: jenkins
    privileged: true
    user: root
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - jenkins_home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/bin/docker

volumes:
  jenkins_home:

docker-compose up -dでサービスを起動します。/var/run/docker.sockを共有することで、JenkinsコンテナがホストのDockerエンジンを操作できるようになります。これは非常に効率的で軽量な方法です。コンテナが起動したら、docker logs jenkinsコマンドで初期管理者パスワードを取得し、ポート8080にアクセスしてセットアップを完了させましょう。

必須プラグイン

最初のパイプラインを作成する前に、Jenkinsの管理 > Pluginsに移動し、以下の3つの主要プラグインをインストールしてください:

  • Docker Pipeline:スクリプト内でDockerコマンドを使用可能にします。
  • SSH Agent:リモートデプロイ用のSSHキーを安全に管理します。
  • Git Pipeline:リポジトリのクローンやウェブフックを処理します。

Jenkinsfile:パイプラインの頭脳

Jenkinsfileは自動化の核心です。このファイルをGitリポジトリに保存することで、デプロイロジックをアプリケーションコードと同様にバージョン管理できるようになります。可読性が高く、エラーハンドリング機能が組み込まれているDeclarative Pipeline構文の使用をお勧めします。

以下は、本番環境でも使えるテンプレートです。イメージをビルドし、レジストリにプッシュして、本番環境のVPSにデプロイします。

pipeline {
    agent any

    environment {
        DOCKER_REGISTRY = "DockerHubのユーザー名"
        APP_NAME = "マイ・アプリ名"
        IMAGE_TAG = "${env.BUILD_NUMBER}"
        DOCKER_CREDENTIALS_ID = 'docker-hub-creds'
    }

    stages {
        stage('ソースのクローン') {
            steps {
                checkout scm
            }
        }

        stage('イメージのビルド') {
            steps {
                script {
                    appImage = docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG}")
                }
            }
        }

        stage('レジストリへのプッシュ') {
            steps {
                script {
                    docker.withRegistry('', DOCKER_CREDENTIALS_ID) {
                        appImage.push()
                        appImage.push("latest")
                    }
                }
            }
        }

        stage('VPSへのデプロイ') {
            steps {
                sshagent(['vps-ssh-key']) {
                    sh '''
                        ssh -o StrictHostKeyChecking=no user@your-vps-ip "
                            docker pull ${DOCKER_REGISTRY}/${APP_NAME}:latest && \
                            docker stop ${APP_NAME} || true && \
                            docker rm ${APP_NAME} || true && \
                            docker run -d --name ${APP_NAME} -p 80:3000 ${DOCKER_REGISTRY}/${APP_NAME}:latest
                        "
                    '''
                }
            }
        }
    }

    post {
        always {
            sh "docker rmi ${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG} || true"
        }
    }
}

安全な認証情報管理

パスワードのハードコーディングは、DevOpsにおける大罪です。Jenkinsの認証情報管理(Jenkinsの管理 > Credentials)を使用して、機密情報を隠しましょうdocker-hub-credsをレジストリのログイン情報に、vps-ssh-keyを秘密鍵にマッピングします。これにより、Jenkinsfileをチームで安全に共有できるようになります。

デプロイロジックのブラッシュアップ

デプロイステージでは、docker stopに続いてdocker rmを使用しています。これは、新しいコンテナバージョンのためにパスを確保する最もシンプルな方法です。複数のサービスを管理している場合は、代わりにdocker-compose up -d --pull alwaysの使用を検討してください。ネットワークリンクや環境変数をよりスマートに処理できます。

プロのヒント:イメージには常にJenkinsのBUILD_NUMBERでタグを付けましょう。新しいリリースでサイトが壊れた場合でも、数秒で正常に動作していたバージョンにロールバックできます。latestタグだけに頼っていると、ロールバックが当てずっぽうの作業になってしまいます。

メンテナンスとモニタリング

自動化には監視が不可欠です。Jenkinsの「コンソール出力」を監視して、リアルタイムのフィードバックを確認しましょう。もし「Push」ステージでビルドが止まった場合は、認証情報の期限切れが考えられます。「Deploy」で失敗した場合は、VPSのファイアウォールがJenkinsコンテナのIPアドレスからのSSHトラフィックを許可しているか確認してください。

VPSをクリーンに保つ

Dockerビルドは急速にディスク容量を消費します。ビルドのたびに新しいレイヤーが作成され、ドライブに蓄積されます。私はJenkinsfilepostブロックを含め、レジストリにプッシュした後にローカルイメージを削除するようにしています。また、ホスト側で週に一度、古いデータをクリーンアップするcronジョブを実行することをお勧めします:

# 24時間以上前のイメージをクリーンアップ
docker image prune -af --filter "until=24h"

最終ヘルスチェック

最終ヘルスチェックとして、curlを使用してアプリケーションのヘルスエンドポイントを叩くステージを追加しましょう。30秒以内に200 OKが返されない場合は、パイプラインを失敗させてアラートを飛ばすようにします。これにより、壊れたコンテナを誤ってユーザーに公開してしまうのを防げます。

JenkinsとDockerをマスターすることは、開発者にとって不可欠なスキルです。ロジックをバージョン管理されたJenkinsfileに移行することで、ドキュメント化され、再現可能で、完全に手放しのデプロイプロセスを構築できます。

Share: