JavaアプリケーションのKubernetesクラウド移行とJenkinsによるCI/CDパイプラインの構築

オンプレミス環境で稼働していた既存システムをクラウド環境へ移行し、Jenkinsを用いた自動デプロイメント環境を構築する機会は少なくありません。従来のSSHによるファイル転送や手動デプロイから脱却し、コンテナオーケストレーションとCI/CDパイプラインを導入するプロセスは、インフラ側の知見が深まる重要なステップです。本記事では、Alibaba Cloud環境(ACK, ACR)を例に、Java開発者が直面するであろうKubernetesとJenkinsを用いた自動化デプロイの実装手法と、押さえておくべき技術的要点を解説します。

クラウドネイティブ基盤の主要コンポーネント

コンテナレジストリとクラスタ管理

ACR (Alibaba Cloud Container Registry) は、Dockerイメージを保存・管理するプライベートリポジトリサービスです。セキュリティとガバナンスの観点から、社内アプリケーションのイメージをDocker Hubなどのパブリックレジストリに公開することは避け、このようなプライベートレジストリを利用します。ビルドしたイメージをここにプッシュし、実行環境であるクラスタがここからプルします。

ACK (Alibaba Cloud Container Service for Kubernetes) は、マネージドなKubernetesクラスターです。コントロールプレーンの管理やクラスタの構築をクラウドプロバイダーに委ねることで、開発者はアプリケーションのデプロイと運用に集中できます。Kubernetesの標準的なリソース概念を理解することが、ACKの利用には不可欠です。

名前空間 (Namespace)

クラスター内のリソースを論理的に分割するための仕組みです。開発環境、検証環境、本番環境など、用途ごとにリソースを隔離し、管理の複雑さを低減させます。

ステートレスとステートフル

マイクロサービスアーキテクチャにおいて、多くのアプリケーションはステートレス(状態を持たない)として設計されます。これにより、Podの再起動やスケーリングが容易になります。一方、データベースなどのように永続的なデータを必要とするものはステートフルとして扱われ、永続ボリュームとの連携が必須となります。

Kubernetesにおけるネットワーキング

サービス (Service) の種類

Podの集合に対するネットワークアクセスを提供する抽象化レイヤーです。

  • ClusterIP: クラスター内部の仮想ネットワークでのみアクセス可能なIPを割り当てます。外部からの直接アクセスは不可能です。
  • NodePort: 各ノードの特定ポートをサービスにマッピングします。ノードのIPアドレスとポート番号を使用してアクセスできますが、セキュリティやネットワーク構成に依存します。
  • LoadBalancer: クラウドプロバイダーのロードバランサーを自動的にプロビジョニングし、外部からのトラフィックをサービスに転送します。最も簡便な公開方法の一つです。

イングレス (Ingress)

単一のIPアドレス(ロードバランサー)を使用して、ドメイン名やパスに基づいてトラフィックを異なるServiceにルーティングするためのルールを定義します。Nginxなどのリバースプロキシとして機能します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-gateway-ingress
  namespace: backend-services
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /service-a
        pathType: Prefix
        backend:
          service:
            name: service-a
            port:
              number: 8080

設定管理とシークレット

ConfigMap

設定データをキーと値のペアとして保存するために使用します。`application.properties` や `application.yml` などのコンフィグファイルをコンテナイメージから切り離して管理できます。ただし、動的な設定管理が必要な場合はNacosなどの設定センターと連携させることも一般的です。

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: backend-services
data:
  application.properties: |
    server.port=8080
    spring.datasource.url=jdbc:mysql://mysql-service:3306/db

Secret (シークレット)

パスワード、OAuthトークン、SSHキーなどの機密情報を扱います。ACRなどのプライベートレジストリからイメージをプルするための認証情報(`dockerconfigjson`タイプ)を格納するのによく使用されます。

apiVersion: v1
data:
  .dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJwYXNzd29yZDEyMyIsImF1dGgiOiJYVjBjTWpBbE0wWTJZVEUwIn19fQ==
kind: Secret
metadata:
  name: registry-credentials
  namespace: backend-services
type: kubernetes.io/dockerconfigjson

永続ストレージの管理

Kubernetesでは、Dockerのボリュームマウントとは異なり、ストレージリソースを動的にプロビジョニングし、宣言的に管理する必要があります。これはJavaでクラスを定義し、インスタンス化するプロセスに似ています。

StorageClass

ストレージの「クラス」を定義します。使用するストレージの種類(SSDやHDDなど)やプロビジョナーを指定します。クラウドプロバイダーが提供するデフォルトのStorageClassを利用するのが一般的です。

provisioner: diskplugin.csi.alibabacloud.com
parameters:
  type: cloud_essd
reclaimPolicy: Retain

PersistentVolume (PV)

ストレージリソースそのものを表す「インスタンス」です。容量やアクセスモード、実際のストレージパスなどを定義します。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-app-logs
spec:
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: alicloud-disk-ssd
  hostPath:
    path: /data/app/logs

PersistentVolumeClaim (PVC)

PodがPVを利用するための「宣言」です。必要な容量やアクセスモードを要求し、システムが適切なPVとバインドします。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-app-logs
  namespace: backend-services
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 20Gi
  storageClassName: alicloud-disk-ssd

実行環境の準備と踏み台サーバー

クラウド環境へのデプロイを行うためには、安全なネットワーク構成が不可欠です。通常、クラスタやレジストリへの直接アクセスは制限されており、踏み台サーバー(Bastion Host)を経由して操作を行います。

踏み台サーバーには以下の環境を構築します。

  • Docker & Kubectl: イメージのビルド、プッシュ、およびクラスタ操作のためのコマンドラインツール。
  • Jenkins: CI/CDパイプラインを実行するためのオートメーションツール。

ネットワーク設定では、踏み台サーバーからGitLab、ACR、ACK、および必要に応じてMavenリポジトリへのアクセスが許可されている必要があります。ファイアウォールやセキュリティグループの設定を事前に確認し、疎通を確保しておきます。

Jenkinsのデプロイと設定

Jenkins自体もDockerコンテナとしてデプロイし、Docker-in-Docker (DinD) 機能を利用してホストのDockerソケットをマウントすることで、Jenkinsから直接イメージをビルド・プッシュできるように構成します。

version: '3.8'

services:
  jenkins-controller:
    image: jenkins/jenkins:2.440.1-lts
    container_name: jenkins-primary
    restart: unless-stopped
    privileged: true
    user: root
    ports:
      - "9000:8080"
      - "50000:50000"
    environment:
      - DOCKER_TLS_CERTDIR=/certs
      - JENKINS_OPTS=--httpPort=8080
    volumes:
      - ./jenkins_data:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/local/bin/docker:/usr/local/bin/docker
      - /usr/local/bin/kubectl:/usr/local/bin/kubectl
      - ./maven_repo:/root/.m2
    networks:
      - cicd-network

networks:
  cicd-network:
    driver: bridge

認証情報の管理

Jenkinsの「Manage Credentials」セクションにて、Gitリポジトリへのアクセス用認証情報、ACRへのログイン用ユーザー名/パスワード、およびKubernetesのkubeconfigファイル(Secret File形式)を登録します。これらのIDはJenkinsパイプライン内で参照されます。

Jenkinsパイプラインの実装

実際のデプロイフローはJenkins Pipeline(Jenkinsfile)としてコード化します。以下に、コードのチェックアウトからMavenビルド、Dockerイメージ化、プッシュ、Kubernetesへのデプロイまでを行うサンプルを示します。

pipeline {
    agent any

    tools {
        maven 'Maven-3.8.6'
    }

    environment {
        // 環境変数の定義
        REGISTRY_ENDPOINT = 'registry.example.com'
        PROJECT_NAMESPACE = 'production'
        APP_NAME = 'order-service'
        
        // 認証情報ID
        GIT_CREDS = 'gitlab-ssh-key'
        REGISTRY_CREDS = 'acr-login-user'
        KUBE_CONFIG_CREDS = 'kubeconfig-prod'
        
        // Git設定
        GIT_REPO_URL = 'git@gitlab.com:group/order-service.git'
        GIT_BRANCH = 'main'
        
        // イメージタグ(コミットハッシュなどを使用)
        IMAGE_TAG = "${env.BUILD_NUMBER}"
        FULL_IMAGE = "${REGISTRY_ENDPOINT}/${PROJECT_NAMESPACE}/${APP_NAME}:${IMAGE_TAG}"
    }

    stages {
        stage('ソースコードの取得') {
            steps {
                git branch: GIT_BRANCH, 
                    url: GIT_REPO_URL, 
                    credentialsId: GIT_CREDS
            }
        }

        stage('Mavenビルド') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }
        
        stage('Dockerイメージのビルド') {
            steps {
                script {
                    docker.build("${FULL_IMAGE}", "--build-arg JAR_FILE=target/*.jar .")
                }
            }
        }

        stage('イメージのプッシュ') {
            steps {
                script {
                    docker.withRegistry("https://${REGISTRY_ENDPOINT}", REGISTRY_CREDS) {
                        docker.image("${FULL_IMAGE}").push()
                    }
                }
            }
        }

        stage('Kubernetesへのデプロイ') {
            steps {
                script {
                    // マニフェストファイル内のイメージタグを置換
                    sh "sed -i 's|IMAGE_NAME|${FULL_IMAGE}|g' k8s-deployment.yaml"
                    
                    withCredentials([file(credentialsId: KUBE_CONFIG_CREDS, variable: 'KUBECONFIG')]) {
                        sh "kubectl --kubeconfig=${KUBECONFIG} apply -f k8s-deployment.yaml"
                        
                        // ロールアウトの確認
                        sh "kubectl --kubeconfig=${KUBECONFIG} rollout status deployment/${APP_NAME} -n ${PROJECT_NAMESPACE}"
                    }
                }
            }
        }
    }

    post {
        success {
            echo "デプロイが成功しました: ${FULL_IMAGE}"
        }
        failure {
            echo "デプロイに失敗しました。"
        }
        always {
            cleanWs()
        }
    }
}

このパイプラインでは、JavaアプリケーションをJarファイルにビルドした後、Dockerイメージを作成し、プライベートレジストリへプッシュします。最後に、`kubectl`コマンドを使用してKubernetesクラスター上のリソースを更新し、新しいバージョンのアプリケーションをデプロイします。

タグ: Kubernetes Jenkins Docker Java CI/CD

6月1日 11:15 投稿