kops を使った Kubernetes のクラスタ操作を CI 化できないか試してみました。

  • cluster spec を git で管理したい
    • (S3 にもバージョニング機能はあるが) git を使いたい
    • 誰がいつどのような変更を行ったか記録を残しておきたい
  • GitHub 上の操作でクラスタに変更を加えたい
    • 作業用インスタンスを使って手でコマンドを打っていたが面倒
    • 都度の手作業でオペミスが発生する事を避けたい

まだプロダクション環境で動かしているわけではないです。 こんな感じで実現できるんじゃないかなと、検証環境で試して上手くいったのでまとめてみます。

環境

  • AWS
  • kops 1.9.0
  • Jenkins 2.107.3

リポジトリ構成

repository
  ├── Jenkinsfile
  ├── README.md
  └── cluster-spec
      └── test.k8s.example.com.yml

cluster spec は kops get -o yaml でファイルに吐き出し、 kops-states ディレクトリの下にクラスタごとの YAML ファイルを置くことにしました。

kops get cluster $NAME -o yaml > $NAME.yaml
kops get instancegroups --name $NAME-o yaml >> $NAME.yaml

Jenkinsfile

Jenkinsfile の全体像はこんな感じ。

feature branch では dry-run として --yes オプション無しの kops update/rolling-update が実行されるようにし、 master branch では --yes オプションを付け、実際にクラスタに対する変更が行われるように pipeline を組んでいます。

pipeline {
  agent any
  environment {
    SPEC_S3_BUCKET          = 'k8s-kops'
    SPEC_S3_BUCKET_FOR_TEST = 'k8s-kops-for-test'
    K8S_CLUSTER_NAME        = 'test.k8s.example.com'
  }
  stages {
    stage('Test') {
      steps {
        echo 'Testing...'
        dir ('./cluster-spec') {
          sh "kops replace -f ${K8S_CLUSTER_NAME}.yml --state s3://${SPEC_S3_BUCKET_FOR_TEST} -v 10"
          sh "kops export kubecfg ${K8S_CLUSTER_NAME} --state s3://${SPEC_S3_BUCKET}"

          sh "kops update cluster ${K8S_CLUSTER_NAME} --state s3://${SPEC_S3_BUCKET_FOR_TEST}"
        }
      }
    }
    stage('Deploy') {
      when {
        branch 'master'
      }
      steps {
        echo 'Deploying...'
        dir ('./cluster-spec') {
          sh "kops replace -f ${K8S_CLUSTER_NAME}.yml --state s3://${SPEC_S3_BUCKET} -v 10"
          sh "kops export kubecfg ${K8S_CLUSTER_NAME} --state s3://${SPEC_S3_BUCKET}"

          sh "kops update cluster ${K8S_CLUSTER_NAME} --state s3://${SPEC_S3_BUCKET} --yes"
          sh "kops rolling-update cluster ${K8S_CLUSTER_NAME} --state s3://${SPEC_S3_BUCKET}"
        }
      }
    }
  }
}

各ビルドステップでは kops replace コマンドを使って git リポジトリ上の YAML ファイルを S3 にアップロードしています。 テスト (dry-run) 時には、テスト用の S3 バケットにアップロードするようにしています。実際のクラスタの状態と S3 バケット上の spec や state が一致している状態は保ちたかったので、テスト用バケットを別途作って使い分けています。

kops replace で S3 上にアップロードされたファイルは、自動的に各設定ごとに分離されて置かれます。

  • s3://BUCKET-NAME/CLUSTER-NAME/config
  • s3://BUCKET-NAME/CLUSTER-NAME/instancegroup/master-ap-northeast-1a
  • s3://BUCKET-NAME/CLUSTER-NAME/instancegroup/master-ap-northeast-1c
  • s3://BUCKET-NAME/CLUSTER-NAME/instancegroup/master-ap-northeast-1d
  • s3://BUCKET-NAME/CLUSTER-NAME/instancegroup/nodes

今回は cluster spec は1つの YAML ファイルとして管理していますが、個別の設定ごとにファイルを分けて replace することも可能です。このあたりはリポジトリの構成とスクリプトの組み方次第で何とでもなりそうです。

クラスタの rolling-update は、node の再作成が行われ実行に長い時間がかかるため pipeline 内では実行していません。--yes オプション無しで kops rolling-update を実行し、 rolling-update が必要であるかどうかだけ確認できるようにしています。実行が必要な場合は別の Jenkins ジョブで rolling-update のみを実行できるようにしています。

実行例

実際にクラスタに変更を加える例を紹介します。rolling-update が必要になるケースを見ることができるように、k8s のバージョンを変更させて試してみます。

1. cluster spec の変更

cluster spec の YAML ファイルを編集し、変更を GitHub に push し Pull Request を作成します。

Jenkins 上でビルドが走り、変更内容に基づきテストとして kops update cluster (dry-run) が実行されます。 k8s のバージョン番号を変更したため、関連する箇所で diff が出ているのが確認できます。

2. 変更を merge して cluster に反映する

Pull Request を merge し、 master branch に変更を取り込みます。 Jenkins 上でビルドが走り kops update cluster --yes が実行され、クラスタに実際に変更が加えられます。

ビルドステップの最後で --yes オプション無しの kops rolling-update cluster を実行しています。node のステータスを確認し、 NeedsUpdate となっているかどうかを確認します。

rolling-update が必要ない場合 (ex: node に割り当てる IAM Role の変更など) は、これで変更完了です。

3. rolling-update が必要な場合は実行する

今回は rolling-update は別ジョブで実行する形式にしました。パラメータ付きビルドにし、デフォルトでは dry-run として動作するようにジョブを組んでいます。

こちらは Jenkinsfile は使用せず、フリースタイルのビルドで以下のようなシェルスクリプトを実行しています。

#!/bin/bash -xe
STATE_S3_BUCKET=“k8s-kops”

kops export kubecfg $K8S_CLUSTER_NAME --state s3://${STATE_S3_BUCKET}
if [ “x${DRY_RUN}” == “xtrue” ] ; then
 kops rolling-update cluster ${K8S_CLUSTER_NAME} --state s3://${STATE_S3_BUCKET}
else
 kops rolling-update cluster ${K8S_CLUSTER_NAME} --state s3://${STATE_S3_BUCKET} --yes
fi

まとめ

kops の cluster spec を git 管理下におき、 CI (Jenkins) を使って k8s クラスタの操作を行うことを試してみました。

今回は pipeline 内では kops コマンドを実行しているだけですが、クラスタの E2E テストも組み込むなどの応用例も考えられそうです。