こんにちは、テクマトリックスの酒井です。

2023 年 9 月に、 Jenkins をベースとした CI/CD 製品である CloudBees CI に新しい高可用性構成/水平スケーリング機能が搭載されました。今回はローカルの Kubernetes クラスターに CloudBees CI をインストールして高可用性構成/水平スケーリング機能を試し、 Jenkins コントローラーの高可用性を確認してみます。

なぜ Jenkins コントローラーに高可用性が必要なのか

Jenkins のコントローラーは 1 つでたくさんのジョブを管理できますが、あまりにも多いと効率的に処理できなくなることがあります。そのようなコントローラーは過負荷のためにパフォーマンスが落ち、パイプラインの実行が長くなり、 UI の反応が遅くなり、クラッシュしてしまうこともあります。さらに、コントローラーで障害が発生したり再起動したりしてダウンタイムが発生すると、コントローラーで実行されているすべてのビルドと、コントローラーを利用しているすべてのチームが悪影響を受けてしまいます。

このような場合、 CloudBees CI の高可用性モードが提供する高可用性と水平スケーリングが課題の解決策となります。

CloudBees CI とは

CloudBees CI は、企業向けに設計された継続的インテグレーション(CI)のプラットフォームで、以下を提供します。

  • 大規模な DevOps
  • レジリエンスと高可用性
  • 簡単な管理
  • エンタープライズグレードのセキュリティ

このうち高可用性について、 2023 年 9 月に新しい高可用性構成/水平スケーリング機能がリリースされました。これによって次のような機能が提供されます。

  • コントローラーのフェイルオーバー:コントローラーが異常終了しても、パイプラインのビルドは別のレプリカで継続されます。
  • ローリング再起動:あるレプリカが再起動する間、他のレプリカは実行し続け、ユーザーはダウンタイムを経験しません。
  • 負荷分散:一つの論理的なコントローラーは複数のレプリカに負荷を分散し、レプリカを同期します。
  • 自動スケーリング:コントローラーの負荷に応じて自動的にレプリカ数を増減します。

このような機能により、 CI/CD のダウンタイムを削減または排除することや、高負荷なコントローラーを信頼でき安定したコントローラーに変換することができます。なおローリング再起動および自動スケーリングは、 CloudBees CI を Kubernetes にインストールする場合のみ利用可能です。

この記事では、 Kubernetes に CloudBees CI をインストールして、コントローラーのフェイルオーバーと自動スケーリングを試してみます。

構成

Windows 10 のローカル PC 上に Kubernetes クラスターを構築し、このクラスターに CloudBees CI をインストールします。本記事の内容をすべて試すためには、 Kubernetes クラスターは最低 5 以上の vCPU と 12GB 以上のメモリをポッドに提供できる必要があります。

CloudBees CI の高可用性構成を実行するためには複数のポッドから読み書き可能なファイルシステムが必要です。そのため Kubernetes クラスターに NFS サーバーのプロビジョナーをインストールします。

CloudBees CI には主要なコンポーネントが 3 種類あります。 1 つめはコントローラーと呼ばれ、 Jenkins のコントローラーと同等です。 2 つめはエージェントで、これも Jenkins のエージェントと同等です。 3 つめはオペレーションセンターと呼ばれ、ここで複数のコントローラーを一元管理します。事前の設定なしで CloudBees CI を Kubernetes にインストールするとオペレーションセンターのみが起動するため、続いてオペレーションセンターを操作して高可用性構成のコントローラーを作成する手順が必要です。コントローラーが作成できたら、このコントローラー上でジョブの作成や実行ができます。ジョブの実行に必要なエージェントについては、今回は Kubernetes のポッドとしてビルド実行の都度生成されるようにパイプラインで設定します。

準備

それでは構築していきます。最初に、ローカル PC で Hyper-V を有効化することと、環境構築に利用する Minikube, Kubernetes CLI, Helm などのツールをインストールします。この手順については以前の記事を参照してください。

Kubernetes クラスターの作成、および CloudBees CI のインストール

次に Minikube を利用して、 VM および Kubernetes クラスターを作成します。そして Helm チャートから CloudBees CI をインストールして、オペレーションセンターと高可用性構成のコントローラーを実行します。設定ファイルとスクリプトを利用して、 VM の作成から CloudBees CI のコントローラーの作成までをいっぺんに行います。

CloudBees CI の Helm チャートの設定

CloudBees CI の Helm チャートを利用して CloudBees CI をインストールするのですが、それに先立ってインストールのパラメーターを指定する values.yaml ファイルを作成します。ファイルの内容を以下に記します。

Persistence:
  StorageClass: nfs

OperationsCenter:
  # License:
  #   Evaluation:
  #     # 評価ライセンスを取得します。この機能を使用すると、使用許諾契約に同意したことになります。
  #     # https://www.cloudbees.com/r/subscription
  #     Enabled: true
  #     FirstName: "YOUR_FIRST_NAME"
  #     LastName: "YOUR_LAST_NAME"
  #     Email: "YOUR_EMAIL"
  #     Company: "YOUR_COMPANY_NAME"
  CasC:
    Enabled: true
  ExtraConfigMaps:
    - name: oc-casc-bundle
      data:
        bundle.yaml: |
          id: "bundle-1"
          version: "1"
          apiVersion: "1"
          description: "My CloudBees Configuration as Code (CasC) bundle"
          jcasc:
            - "jenkins.yaml"
          jcascMergeStrategy: "errorOnConflict"
          plugins:
            - "plugins.yaml"
          rbac:
            - "rbac.yaml"
          items:
            - "items.yaml"
        jenkins.yaml: |
          jenkins:
            securityRealm:
              # ユーザー情報にJenkinsのユーザーデータベースを使用します。
              # 通常はLDAPなど外部のサービスを使用することを推奨します。
              local:
                users:
                  - id: "butler"
                    password: "026d7c798b09524a2d2fb8b45634877c"
                    name: "Butler Admin"
                    properties:
                      - mailer:
                          emailAddress: "butler@ci.cluster.local"
            authorizationStrategy: "cloudBeesRoleBasedAccessControl"
          unclassified:
            cascAutoControllerProvisioning:
              provisionControllerOnCreation: true
              fireAndForget: true
              initialDelay: 15
              timeout: 600
              duration: 60
        plugins.yaml: |
          plugins:
            - id: "cloudbees-casc-client"
            - id: "cloudbees-casc-items-api"
            - id: "cloudbees-casc-items-commons"
            - id: "cloudbees-casc-items-server"
            - id: "configuration-as-code"
            - id: "operations-center-rbac"
        rbac.yaml: |
          removeStrategy:
            rbac: "SYNC"
          roles:
            - name: administer
              permissions:
                - hudson.model.Hudson.Administer
            - name: developer
              permissions:
                - hudson.model.Hudson.Read
                - hudson.model.Item.Read
                - hudson.model.Item.Create
                - hudson.model.Item.Configure
              filterable: "true" 
            - name: browser
              permissions:
                - hudson.model.Hudson.Read
                - hudson.model.Item.Read
              filterable: "true"
            - name: authenticated
              filterable: "true"
              permissions:
                - hudson.model.Hudson.Read
          groups:
            - name: Administrators
              roles:
                - name: administer
                  grantedAt: current 
              members:
                users:
                  - butler
            - name: Developers
              roles:  
                - name: developer
            - name: Browsers
              roles:
                - name: browser
        items.yaml: |
          removeStrategy:
            rbac: SYNC
            items: NONE
          items:
            - # 高可用性構成のコントローラー
              kind: managedController
              name: managed-controller-alpha
              displayName: managed-controller-alpha
              configuration:
              kubernetes:
                  domain: managed-controller-alpha
                  disk: 5
                  replication:
                  config:
                      # コントローラーのレプリカの数。
                      replicas: 2
                      # 自動スケーリングによるレプリカの最大数。 0 にすると自動スケーリングしません。
                      maxReplicas: 0
                      # コントローラーポッドの平均 CPU 使用率が設定値を超えるとスケールアップが開始されます。
                      targetCPUUtilizationPercentage: 80

values.yaml の 74行目以降はオペレーションセンターのコードによる設定です。 112-119 行目にコントローラーの高可用性構成の設定があります。コントローラーごとに、レプリカ数、自動スケーリングによるレプリカの最大数、自動スケーリングを開始するしきい値を設定できます。

また values.yaml の 5-13 行目は評価ライセンスの自動取得の設定です。 5-13 行目のコメントを外し、姓・名・メールアドレス・会社名を(英語で)入力すると、インストールと同時に自動的に評価ライセンスが取得・設定され、すぐに CloudBees CI の利用を開始できます。ただし、評価ライセンスを取得すると CloudBees の使用許諾契約に同意したことになりますのでご注意ください。 values.yaml の 5-13 行目をコメントのまま進めることも可能ですが、オペレーションセンターへのアクセス後に評価ライセンスの取得フォームに氏名などを入力する必要があります。この記事ではコメントを外した場合の手順のみを記載します。

Kubernetes クラスターの作成と CloudBees CI のインストール

続いて、Kubernetes クラスターを作成して、 CloudBees CI のオペレーションセンターと高可用性構成のコントローラーを起動します。以下の Powershell スクリプトを実行します。

# VM と Kubernetes クラスターを作成
minikube start --driver=hyperv --cpus=6 --memory=12g --disk-size=40gb `
  --addons ingress `
  --addons metrics-server `
  --docker-opt=bip=172.80.0.1/16 `
  --docker-opt=default-address-pool=base=172.90.0.0/16,size=24 `
  --docker-opt=dns="DNSサーバーのIPアドレス"

# アクセスモードが `ReadWriteMany` のボリュームをサポートするプロビジョナーをインストール
helm install --wait `
  --namespace nfs `
  --create-namespace `
  --set persistence.enabled=true `
  --set persistence.size=200Gi `
  --repo https://kubernetes-sigs.github.io/nfs-ganesha-server-and-external-provisioner/ `
  --version 1.8.0 `
  nfs nfs-server-provisioner

# CloudBees CI on modern cloud platforms をインストール
helm repo add cloudbees https://public-charts.artifacts.cloudbees.com/repository/public/
helm repo update cloudbees
kubectl create namespace cloudbees-core
kubectl config set-context $(kubectl config current-context) --namespace=cloudbees-core
helm upgrade --install cloudbees-core `
  cloudbees/cloudbees-core `
  --version 3.14783.0 `
  --namespace cloudbees-core `
  --values values.yaml `
  --set OperationsCenter.HostName="$(minikube ip).nip.io"

# オペレーションセンターの起動を待つ
Start-Sleep 5
kubectl rollout status sts cjoc --namespace cloudbees-core

# オペレーションセンターの URL を表示するため、 Helm リリースのステータスを表示
helm status cloudbees-core

スクリプトの実行が正常に終了すると、コンソール出力の最後の方に「2. Visit http://...」と出力されます。この URL をブラウザで開くと、 CloudBees CI のオペレーションセンターのログインページが表示されます。ユーザー名に butler 、パスワードに 026d7c798b09524a2d2fb8b45634877c と入力してログインすると、オペレーションセンターのダッシュボードが表示されます。

オペレーションセンターのダッシュボード

ダッシュボードには managed-controller-alpha というコントローラーが表示されています。このコントローラーの行の Pod status 列から、レプリカが 2 個中 1 個実行されていることが確認できます。レプリカが 1 個しか実行されていないのはコントローラーの初期設定が完了していないためで、初期設定が完了すると別のレプリカも起動します。

managed-controller-alpha をクリックしてコントローラーに移動すると、コントローラーの初期設定ページが表示されます。 Install suggested plugins を選択してプラグインをインストールし、続くページで Start using CloudBees CI Managed Controller ボタンをクリックすると、「 Jenkinsへようこそ!」と書かれたコントローラーのダッシュボードが表示されます。 CloudBees CI のコントローラーではありますが、 Jenkins のコントローラーと同じ使い方ができます。

コントローラーのダッシュボード

この時点でポッドを確認すると、 cjoc-0 というオペレーションセンターのポッドが 1 個と、 managed-controller-alpha- で始まるコントローラーのポッドが 2 個確認できます。

> kubectl get po
NAME                                       READY   STATUS    RESTARTS      AGE
cjoc-0                                     1/1     Running   0             27m
managed-controller-alpha-d6fbbc885-gzs9b   1/1     Running   0             24m
managed-controller-alpha-d6fbbc885-jqxtn   0/1     Running   4 (22s ago)   24m

フェイルオーバーさせてみる

ジョブの実行中にコントローラー(のレプリカ)が停止した際、実行中のジョブがどのようになるかを見ていきます。

まずはジョブを作成します。コントローラーのページの左側メニューの 新規ジョブ作成 から test-pipeline という名前のパイプラインジョブを作成します。続く設定ページで、パイプラインスクリプトに以下を入力して保存します。

pipeline {
    agent {
        kubernetes {
            yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: gcc
    image: gcc:13.2.0
    command:
    - cat
    tty: true
'''
            defaultContainer 'gcc'
        }
    }
    stages {
        stage('first') {
            steps {
                sh '''
                    make --version
                    sleep 30
                    gcc --version
                '''
            }
        }
    }
}

続いて左側メニューの ビルド実行 をクリックしてビルドを開始し、そのビルドのコンソール出力に移動します。コンソール出力の 2 行目から、このビルドは managed-controller-alpha-d6fbbc885-gzs9b ポッドによって管理されていることが分かります。

ビルドが開始された

それでは、このジョブの実行中にレプリカのポッドを削除してみます。コンソール出力に + sleep 30 まで出力されている時点、つまりスクリプトの実行中に、次のコマンドを実行してポッドを削除します。

> kubectl delete pod managed-controller-alpha-d6fbbc885-gzs9b
pod "managed-controller-alpha-d6fbbc885-gzs9b" deleted

ポッドの削除後も引き続きコンソール出力を見ていると、ビルドの管理がもう一つのポッド managed-controller-alpha-d6fbbc885-jqxtn に引き継がれる様子を確認できます。

ビルドが引き継がれる様子

ビルドを管理するレプリカが削除されてもビルドは継続され、無事完了しました!

自動スケーリングさせてみる

次にコントローラーの自動スケーリングを試してみます。自動スケーリングは、コントローラーの負荷が高くなると自動的にレプリカが増えて負荷を分散し、負荷が低くなるとレプリカが減る仕組みです。

まずは自動スケーリングを有効にします。パンくずリストの Jenkins > managed-controller-alpha の右の歯車アイコン > Configure の順に移動して managed-controller-alpha の設定ページを開きます( Jenkinsの管理 ページではありません)。そして High Availability までページを下にスクロールすると、高可用性構成の設定が見えます。

コントローラーの高可用性構成の設定

今回は自動スケーリングがわざと働くよう、 Maximum number of replicas に 4 を、 CPU threshold in percent に 20 を入力し、 Save ボタンをクリックします。そしてこの設定を反映するために、ページ左側の Stop 、 Start を順にクリックしてコントローラーを再起動します。コントローラーが起動する一方で Kubernetes クラスターでは HolizontalPodAutoscaler リソースが作成されます。

> kubectl get hpa
NAME                       REFERENCE                             TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
managed-controller-alpha   Deployment/managed-controller-alpha   <unknown>/20%   2         4         2          49s

Status に Connected と表示されたら、自動スケーリングを試してみましょう。 managed-controller-alpha コントローラーの test-pipeline ジョブに移動して、ページ左側の ビルド実行 を 10 ~ 15 回くらいクリックします。たくさんのビルドが開始されます。

ビルドがたくさん実行されている

HolizontalPodAutoscaler リソースをウォッチしていると、ターゲットが 20% を超えるとレプリカが増え、20% を下回ってしばらくするとレプリカが減る様子が確認できました。

> kubectl get hpa --watch
NAME                       REFERENCE                             TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
managed-controller-alpha   Deployment/managed-controller-alpha   6%/20%    2         4         2          13m
managed-controller-alpha   Deployment/managed-controller-alpha   53%/20%   2         4         2          14m
managed-controller-alpha   Deployment/managed-controller-alpha   53%/20%   2         4         4          15m
managed-controller-alpha   Deployment/managed-controller-alpha   37%/20%   2         4         4          15m
managed-controller-alpha   Deployment/managed-controller-alpha   8%/20%    2         4         4          16m
managed-controller-alpha   Deployment/managed-controller-alpha   30%/20%   2         4         4          17m
managed-controller-alpha   Deployment/managed-controller-alpha   26%/20%   2         4         4          18m
managed-controller-alpha   Deployment/managed-controller-alpha   17%/20%   2         4         4          19m
managed-controller-alpha   Deployment/managed-controller-alpha   17%/20%   2         4         4          20m
managed-controller-alpha   Deployment/managed-controller-alpha   10%/20%   2         4         4          20m
managed-controller-alpha   Deployment/managed-controller-alpha   15%/20%   2         4         4          21m
managed-controller-alpha   Deployment/managed-controller-alpha   3%/20%    2         4         4          22m
managed-controller-alpha   Deployment/managed-controller-alpha   2%/20%    2         4         4          23m
managed-controller-alpha   Deployment/managed-controller-alpha   2%/20%    2         4         4          24m
managed-controller-alpha   Deployment/managed-controller-alpha   2%/20%    2         4         3          24m
managed-controller-alpha   Deployment/managed-controller-alpha   2%/20%    2         4         3          26m
managed-controller-alpha   Deployment/managed-controller-alpha   2%/20%    2         4         3          27m
managed-controller-alpha   Deployment/managed-controller-alpha   4%/20%    2         4         2          27m
managed-controller-alpha   Deployment/managed-controller-alpha   1%/20%    2         4         2          28m

途中で別のターミナルからポッドを確認すると、 managed-controller-alpha- で始まるポッドが増えていました。

> kubectl get po
NAME                                        READY   STATUS        RESTARTS   AGE
cjoc-0                                      1/1     Running       0          55m
managed-controller-alpha-544b58fc68-b5ldh   1/1     Running       0          15m
managed-controller-alpha-544b58fc68-kdxlc   1/1     Running       0          15m
managed-controller-alpha-544b58fc68-lg9kc   0/1     Pending       0          50s
managed-controller-alpha-544b58fc68-pkqmk   0/1     Pending       0          50s
test-pipeline-11-24k8h-rzvf9-9jb5x          2/2     Running       0          79s
test-pipeline-12-48h8f-crb7v-mp0sh          2/2     Running       0          79s
test-pipeline-2-pgq3z-hlzwh-0wrlm           2/2     Terminating   0          78s
test-pipeline-8-dzt22-mxbz5-9ct1c           2/2     Running       0          79s

以上で、コントローラーの負荷に応じて自動的にスケールアップ・スケールダウンすることが確認できました!

まとめ

CloudBees CI を利用して Jenkins コントローラーのフェイルオーバーと自動スケーリングを体験しました。この記事の執筆時点では OSS の Jenkins にフェイルオーバーや自動スケーリングのような機能は搭載されておらず、結果として Jenkins が CI/CD における単一障害点となってしまいがちです。今回試した高可用性構成によって、この単一障害点が解消され、 CI/CD のダウンタイムがなくなると思いました。加えて、ダウンタイムがあることによる開発者の不満や、 CI/CD 環境をメンテナンスする労力も減らすことができるのではと感じました。

By tsakai

Jenkins関連のサービスやCloudBees製品を主に担当しています。 Certified CloudBees Jenkins Engineer (CCJE) および CloudBees CI DevOps Associate です。