この記事は CyberAgent Developers Advent Calendar 2018 の10日目の記事になります。 8日目は cstoku さんの BPFについて調べてみた でした。


みなさんこんにちは。2年ぶり2回目の CA Advent Calendar です。一昨年の僕は腰を痛めていたようですね。 去年からとある整体に通い始めたのですが、先生のゴッドハンドのおかげで腰の調子は最高です👍

さて、今年は Kubernetes Operator に関するネタを書いてみたいと思います。 自作の Operator を使って、プライベートクラウド利用時の運用タスク自動化ができないか PoC として試してみたことについて紹介します。

Kubernetes Operator is 何

ざっくりと Operator がどんなものかについて紹介します。

CoreOS
An Operator enables users to create, configure, and manage applications by extending the Kubernetes API. Discover what Operators can do for you with CoreOS.

Operator は Kubernetes 上でステートフルなアプリケーションをクラスタの利用者に代わって管理してくれるソフトウェアです。 データベースのようなステートフルなシステムでは、管理者は運用中に様々な作業を行う必要がでてきます。 例えば、クラスタにノードを追加したり、1ノードずつローリングアップデートをしたり、といった作業です。

こうした作業手順のやり方をソフトウェア (Operator) として実装し、人間の運用者に代わって実行してもらおうというわけです。 利用者は、ほしいシステムの状態を宣言的なマニフェストに書いて適用するだけです。 先程の例であれば、クラスタを追加したいときは「ノード数3台の etcd クラスタがほしい!」という内容の yaml を書いて apply すれば、あとは Operator が必要な手順を踏んでええ感じに作ってくれます。

Operator は Kubernetes の Custom Resource Definitions (CRDs) と、それを操作する Custom Controller によって構成されています。 CRDs を使うことで Kubernetes の API を拡張することができ、 Deployment や Pod のようなリソースタイプを自分で作ることができます。

この仕組みを使って独自のリソースをつくり、更にそのリソースを操作する Controller のソフトウェアを実装することで、 Operator を自作することができるようになります。

プライベートクラウド上の VM 操作に応用してみる

今回は、社内のプライベートクラウドの API を自作の Operator から操作し、仮想マシンのライブマイグレーション操作を自動化するということに挑戦してみました。

背景と課題

弊社のメディア事業部では、 DC やプライベートクラウド開発を管轄するチームと、会社で提供している各 Web サービスのインフラ層を横断的に見るサービスインフラチームとに別れています (私は後者のチーム所属)。

プライベートクラウドの IaaS 環境では、ライブマイグレーション用サービスが開放されています。 クラウド開発チームによって、 OpenStack の機能をラップした API と、クライアントとして CLI ツールが提供されており、利用者はこれを利用して仮想マシンを別のホストマシンに移すことができます。

マイグレーションの作業が発生するのは主にこんなとき:

  • ホストマシンの不調や故障時
    • NIC 片系の死やステータスランプ点灯など故障の予兆が見られれる場合
  • 同じホストに関連するインスタンスが同居している状態の解消
    • 例: 同じプロジェクトの同じロール、連携するサービス など
現状のライブマイグレーション実行方式の概要
現状のライブマイグレーション実行方式の概要

このマイグレーションの作業について、以下のような課題があります:

  • 移行先ホストの決定が作業者にとって “難しい”
    • 例1: 移行先のコンピュートリソースの空き状況を確認する
    • 例2: 同居することになる他の仮想マシンが何か調べる
  • ホスト障害の場合の運用負荷が大きい
    • 複数の仮想マシンが一度にマイグレーション対象となる

特に、万が一問題が起きた際にもサービスのユーザ影響が出ないインスタンス (開発環境など) に関しては、積極的に自動化しオペレーション負荷を下げていきたいという声が上がっていました。

この条件付きのライブマイグレーションを Operator の仕組みで実現する migrate-operator というものを試しに作ってみました。

“仮想マシンのあるべき配置のされ方” を定義し、その状態を Custom Controller で維持してもらう、というアイディアです。

migrate-operator の構成

migrate-operatorの構成概要
migrate-operatorの構成概要

migrate-operator は2種類の Custom Resource と、それらに対応する Custom Controller によって構成されています。

  • LiveMigrationPolicy Resource
    • マイグレーションの条件と操作対象の仮想マシンのリストを持つ
    • マニフェストの情報を元に VierualMachine リソースを作成する (ReplicaSet と Pod のようなイメージ)
  • VirtualMachine Resource
    • 操作対象の仮想マシンを表す
  • LiveMigrationPolicy Controller
    • IaaS の Metadata API を定期的にアクセス
    • マイグレーションが必要な場合は該当する VierualMachine リソースにフラグを立てる
  • VirtualMachine Controller
    • マイグレーションが必要な場合は Job を作成しマイグレーションを開始する
    • 実行中の Job の管理し、実行結果を VierualMachine リソースに反映する

マイグレーション処理自体は、コントローラの実装としてではなく Job によって行われる仕組みにしてみました。 Job が元々持っているリトライの仕組みや、実行時のログを個別に確認できるようにする狙いがあります。 Job によって作られる Pod では、現在既に使われているマイグレーション用の CLI ツールをコンテナイメージ化たものを立ち上げています。

IaaS の Metadata API からの状態取得は、各 VirtualMachine にやらせても良いかなと思ったのですが、作られたリソース数とともに API のコール数が増加するのが嫌だったのでやめました。

今は LiveMigrationPolicy にその役割を持たせてしまっていますが、意味的にこいつの役割では無いような… と思っています。 もしこの Operator が正式に使われるのであれば、この辺りの設計は見直したいです。 (VirtualMachineSet のような複数の VM を束ねる中間リソースを作って、そいつの役割にする?)

実装

migrate-operator は Kubebuilder を使って開発しました。 Kubebuilder は Kubernetes API を拡張するための Framework で、付属のコマンドを叩くと Custom Resource Definitions や、それに対応する Custom Controller の boilerplate を生成してくれます。

Kubernetes の Controller は、クラスタ上のリソースを監視し、リソースの create/update/delete などのイベントに応じて Reconcile と呼ばれる処理を行います。 この処理の中で、マニフェストで指定した Spec と現在のリソースとの差分を無くす動作を行います。

今回の実装では、外部 API のポーリング処理をどう実装するについて悩みました。 Kubebuilder で生成される Go のコードでは、リソースに関するイベントを拾って Reconcile function が実行されるように作り込まれています。 IaaS の Metadata API から情報を定期的に取得するために、イベントの有無に関わらず定期的に Reconcile されるようにしたかったのですが、どのように実装したらよいのかわかりませんでした。

Kubernetes Slack を眺めていたところ、定期的に何か処理を実行させる方法として Reconcile function の結果を返す際に Requeue の条件を指定するやり方 が紹介されているのを見つけました。

// pkg/controller/livemigrationpolicy/livemigrationpolicy_controller.go
func (r *ReconcileLiveMigrationPolicy) Reconcile(request reconcile.Request) (reconcile.Result, error) {

    //   ...
    // ReconcileLiveMigrationPolicy の reconcile 処理
    //   ...
  
    return reconcile.Result{RequeueAfter: 60 * time.Second}, nil
}

Reconcile() function が返す reconsile.ResultRequeueAfter を設定することで、目的の動作をさせることができました。

今回はこのやり方で IaaS の Metadata API を定期的に呼び出し、 VirtualMachine リソースの Status 更新に利用しました。 他に良いやり方があれば是非教えてください…! 🙇

migrate-operator を動かしてみる

実際に migrate-operator を使ってみた様子をご紹介します。

まず、 Operator の利用者は LiveMigrationPolicy を作成します。 以下のようにマニフェストを作成し、ライブマイグレーション時の条件と操作の対象となる仮想マシンを記述します。

# live-migration-policy.yaml
apiVersion: cyberagent.co.jp/v1beta1
kind: LiveMigrationPolicy
metadata:
  name: infra-dev
spec:
  region: region01   # Private Cloud のリージョン名
  tenant: infra-dev  # プロジェクト名 (OpenStack のテナント名)
  
  # マイグレーション時の条件
  # (ここでは一部省略しています)
  option:
    allowSameTenant: true  # 同じホストマシンに同じテナントの VM の同居を許可するかどうか
    isolationKeywords:     # 同居を避けたいホスト名/テナント名
      - test-k8s
      - prd-hogehoge-mysql

  # 操作対象の仮想マシン名
  virtualMachines:
    - name: test-k8s01
    - name: test-k8s02
    - name: test-k8s03     # わざと実際には存在しない VM も書いてみる

kubectl でこのマニフェストを apply します。

% kubectl apply -f live-migration-policy.yaml
livemigrationpolicy.cyberagent.co.jp "infra-dev" created

% kubectl get livemigrationpolicy
NAME            AGE
infra-dev       12s

LiveMigrationPolicy リソースが作成されました。同時に、管理対象の仮想マシンを表す VirtualMachine リソースも作成されます。

% kubectl get virtualmachines \
  -o "custom-columns=NAME:.metadata.name,HOST:.status.host,MIGRATION:.status.migrationRequired,PHASE:.status.phase"
NAME          HOST      MIGRATION   PHASE
test-k8s01    host018   false       Ready
test-k8s02    host033   false       Ready
test-k8s03              false       VMNotFound

LiveMigrationPolicy コントローラでは IaaS のMetadata API を定期的にコールし、実際のインスタンスの状態を VirtualMachine リソースの Status に反映させています。 test-k8s{01,2} は現在のホストマシン名が表示され、インスタンスが存在しない test-k8s03 は VMNotFound となっていますね👀

このとき、取得した Status の内容をもとに LiveMigrationPolicy で定義した条件を満たしているかを確認しています。 条件を満たさない場合はマイグレーションが必要と判断し、対象の VirtualMachine リソースにフラグを立てます。

それでは、作成済みの LiveMigrationPolicy のマイグレーション条件を変更し、意図的にマイグレーションが必要な状態に変更してみます。 test-k8s01 インスタンスと同じホストに載っていた VM の名前を、同居禁止条件のキーワードとして Policy に書き込み apply しました。

% vim live-migration-policy.yaml
% kubectl apply -f live-migration-policy.yaml
livemigrationpolicy.cyberagent.co.jp "infra-dev" configured

以下は VirtualMachine リソースを watch し、マイグレーション中の Status の遷移を表示させたものです。

% kubectl get virtualmachines \
  -o "custom-columns=NAME:.metadata.name,HOST:.status.host,MIGRATION:.status.migrationRequired,PHASE:.status.phase" -w
NAME          HOST      MIGRATION   PHASE
test-k8s01   host018   false       Ready
test-k8s01   host018   true      Ready
test-k8s01   host018   true      MigrationJobCreated
test-k8s01   host018   true      Migrating
test-k8s01   host091   false       Ready  

フラグが変化したあと、マイグレーションが開始され、最終的にホストマシンが変わった様子がわかりますね👀

実際のマイグレーション処理は VirtualMachine コントローラによって作られた Job によって行われます。

% kubectl get job
NAME                DESIRED   SUCCESSFUL   AGE
test-k8s01-c9mkv    1         0            10s

% kubectl get pod
NAME                                    READY     STATUS              RESTARTS   AGE
test-k8s01-c9mkv-nh6b9                  0/1       ContainerCreating   0          9s

VirtualMachine コントローラ側では Job の作成と管理のみを行う仕組みです。 成功/失敗の判定や、失敗時のリトライ処理は、 Kubernetes の Job が元々持っている仕組みをそのまま使っています。

さいごに

Kubernetes クラスタ外の仮想マシン操作を行う Operator を Kubebuilder を使って実装してみたことについて紹介しました。 IaaS の API から取得できる情報を元に、マイグレーションを自動実行することができました。

Operator 化により、以下のメリットが期待できそうです:

  • この仕組みのための独自の実行環境を構築/運用する必要がない
    • 今後 DC で提供される予定の KaaS 上に載せることができる
  • ホスト選定時の前提知識が Operator により隠蔽される
    • 利用者はマイグレーションの条件と対象 VM をマニフェスト化し apply するだけ
  • k8s へのコンテナのデプロイフローと同じ方法で利用可能
    • 既存のデプロイフロー (CI/CD) の仕組みが流用できそう

冬の自由研究としてお試しで作ってみた段階なので、このアイディアで課題解決できそうか/利用者にとって使いやすいかは検討が必要そうです。 所属チームやプライベートクラウドチームと一緒に検討していきたいと思います。

ソフトウェアによる解決だけでなく、運用フローの見直しといった、そもそもの仕事を直す/無くす動きもセットで進めていきたいですね。(やっていき💪)

その他の感想:

  • (上の方でも書いたが) Custom Resource/Controller の設計に満足していない
    • Operator デザインパターン的な情報があれば知りたい
  • Operator Framework も使ってみたい
    • controller の SDK だけでなく、作った Operator のカタログ化や利用量のレポーティングもできるらしい
    • マネージドサービスが作れる!
  • フレームワークを使わず一度スクラッチで何か作ってみたい
    • k8s の controller そのものについて理解を深めるため

参考サイト

ThirdPartyResource を使った Kubernetes as a Service の実装
Kubernetes Meetup Tokyo #3で発表した ThirdPartyResource を使った Kubernetes as a Service の実装 の資料です。

最後まで読んでいただきありがとうございました!

CyberAgent Developers Advent Calendar 2018 はまだまだ続きます!

明日も是非ご覧くださいー!!

CyberAgent Developers Advent Calendar 2018