Edited at

Kubernetes Operator

この記事は リクルートライフスタイル Advent Calendar 2018 の18日目の記事です。


はじめに

CETチーム 兼 新規サービス開発を担当している @shotat です。

先週シアトルで KubeCon + CloudNativeCon North America 2018 (以下KubeCon) が開催されました。自分も現地シアトルへ向かい、KubeConに参加してきました。なお、KubeConはキューブ(kjúːb)コンと発音するようです。

KubeConの規模は年々増加傾向にあり、今回は8000人以上のattendeeがいたようで、Kubernetes Communityの盛り上がりを肌で感じることができました。

KubeConではService meshやMachine Learning等様々なテーマが語られていたのですが、中でも CRD(CustomResourceDefinition)・Custom Controller・Operator 等、KubernetesのExtensibilityについての言及が非常に多く、キーノートや様々なセッションで繰り返し登場していたのが印象的でした。

本記事では、 上述の Kubernetes Operator についての概要について簡単にまとめ、実際に動かして見ます。


Kubernetes Operator の概要


Operator とは

Kubernetes OperatorはCoreOSによって2016年に提唱された概念で、以下のブログに詳細が記述されています。

Operatorを一言でまとめると、 "アプリケーション特化型のKubernetes Controller" です。

複雑になりがちなStatefulアプリケーションの運用(scaling, backup, failover, upgrade 等)を自動化することがOperatorの役割です。


何故 Operator が必要なのか

StatelessなアプリケーションであればKubernetesのDeployments等である程度十分に実用レベルの運用が可能といえます。

しかし、Statefulなアプリケーションはより複雑な運用タスクが必要であり、アプリケーション毎に最適な運用方法は異なります。

Statefulなアプリケーションを正しくスケーリング・アップデートするにはアプリケーション固有の運用知見が必要になります。

こういったアプリケーション固有の運用を人間の手で行うのは非常に骨が折れるため、運用知見をコード化してKubernetes上の仕組みに乗せてしまおう!という思想がOperatorに繋がります。


仕組み


Kubernetes Control Loop

前提としてまずKubernetesの Control Loop について簡単に復習しておきます。詳細は Kubernetesの公式ドキュメント をご参照下さい。

Kubernetes内ではDesired Stateと実際のCurrent State(Actual State, Observed State表記もあり)を一致・更新させる処理のループを回しており、これをControl Loopと呼びます。

またDesired StateとCurrent Stateの乖離を埋める働きをReconciliationと呼びます。

Desired Stateは kubectl apply ... コマンド等でKubernetesユーザがAPI経由で直接登録・更新することができ、実際にどうやってCurrent StateをDesired Stateに近づけていくかはKubernetesユーザはほとんど意識しなくて良いインタフェースになっています。

つまり、ユーザはKubernetesに対して宣言的に What を伝えるだけで良く、 How の部分(Reconciliation Logic)はKubernetes内部に隠蔽されています。


Operator

Operatorは Custom ResourceCostom Controller から成り立ちます。

Custom ResourceはKubernetes上に登録可能な独自リソースであり、上述の What 部に相当します。

Custom Resourceを作成するには CRD (CustomResourceDefinition) という機能を利用します。

CRDに独自のリソース(Kind: MySQL等)とそのスペックを定義してKubernetes上に登録することで、Custom Resourceの利用が可能になります。

一方Custom Controllerは上述の How に相当し、どのようにCurrent StateとDesired Stateを一致させるか(Reconciliation Logic)を担います。Operatorの文脈ではCustom ResourceをCustom Controllerが扱う対象とします。

つまり、Custom Resourceに定義されたDesired Stateを実現するのがCustom Controllerであり、これらを一括りにOperatorと呼びます。


Operator を動かしてみる

理解を深めるために実際にOperatorを利用し、動作を観察してみます。

KubeConのキーノートではUberが M3DB Operator について紹介していたので、今回はそちらを使ってみようと思います。

なお、M3DBはUberが開発している時系列データベースです。

また、以下のGitHub Repositoryに他にもいくつかのOperatorが公開されています。


GKE クラスタの作成

https://github.com/m3db/m3db-operator のREADMEに記述されている以下の制約に従ってGKEクラスタを立てます。


  • Kubernetes 1.10 and 1.11

  • Kubernetes clusters with nodes in at least 3 zones

$ gcloud container clusters create m3db-example \

--zone us-central1-a \
--node-locations us-central1-a,us-central1-b,us-central1-c

クラスタが構築できたらkubectlを先程構築したクラスタに向けましょう。

$ gcloud container clusters get-credentials m3db-example --zone us-central1-a --project <PROJECT_NAME>

Fetching cluster endpoint and auth data.
kubeconfig entry generated for m3db-example.

GKEのドキュメント にも記載されているように、RoleBindingを設定が必要です。

$ kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=<name@domain.com>

clusterrolebinding.rbac.authorization.k8s.io/cluster-admin-binding created


Operatorのデプロイ

ドキュメントに従い、以下のコマンドでM3DB Operatorをデプロイします。

$ kubectl apply -f https://raw.githubusercontent.com/m3db/m3db-operator/v0.1.1/bundle.yaml

bundle.yaml の中身を覗いてみます。すると、m3db-operatorは quay.io/m3db/m3db-operator:v0.1.1 というImageを使ったStatefulSetであるということが分かります。

また、必ずしもStatefulSetを使う必要はなく、他のOperatorの例を見ると普通のDeploymentだったりします。

# ...中略

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: m3db-operator
namespace: default
spec:
serviceName: m3db-operator
replicas: 1
selector:
matchLabels:
name: m3db-operator
template:
metadata:
labels:
name: m3db-operator
spec:
containers:
- name: m3db-operator
image: quay.io/m3db/m3db-operator:v0.1.1
command:
- m3db-operator
imagePullPolicy: Always
env:
- name: ENVIRONMENT
value: production
serviceAccount: m3db-operator

この時点でOperatorのログを確認してみると、CustomResourceDefinitionが生成されていることが分かります。

$ kubectl logs pod/m3db-operator-0 | grep CRD

{"level":"error","ts":1544814329.1473405,"caller":"k8sops/crd.go:67","msg":"could not get CRD","error":"customresourcedefinitions.apiextensions.k8s.io \"m3dbclusters.operator.m3db.io\" not found","stacktrace":"github.com/m3db/m3db-operator/pkg/k8sops.(*k8sops).GetCRD\n\t/go/src/github.com/m3db/m3db-operator/pkg/k8sops/crd.go:67\ngithub.com/m3db/m3db-operator/pkg/k8sops.(*k8sops).CreateCRD\n\t/go/src/github.com/m3db/m3db-operator/pkg/k8sops/crd.go:75\ngithub.com/m3db/m3db-operator/pkg/controller.(*Controller).Init\n\t/go/src/github.com/m3db/m3db-operator/pkg/controller/controller.go:223\nmain.main\n\t/go/src/github.com/m3db/m3db-operator/cmd/m3db-operator/main.go:187\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:198"}
{"level":"info","ts":1544814329.6673043,"caller":"k8sops/crd.go:113","msg":"CRD created"}

kubectlコマンドでCRDが生成されていることも確認してみます。

$ kubectl get crd

NAME CREATED AT
m3dbclusters.operator.m3db.io 2018-12-14T19:05:29Z

$ kubectl describe crd m3dbclusters.operator.m3db.io

Name: m3dbclusters.operator.m3db.io
Namespace:
Labels: <none>
Annotations: <none>
API Version: apiextensions.k8s.io/v1beta1
Kind: CustomResourceDefinition
Metadata:
Creation Timestamp: 2018-12-14T19:05:29Z
Generation: 1
Resource Version: 3077
Self Link: /apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/m3dbclusters.operator.m3db.io
UID: 38d89036-ffd3-11e8-9a7e-42010a800140
Spec:
Group: operator.m3db.io
Names:
Kind: M3DBCluster
List Kind: M3DBClusterList
Plural: m3dbclusters
Singular: m3dbcluster
Scope: Namespaced
Version: v1alpha1
Status:
Accepted Names:
Kind: M3DBCluster
List Kind: M3DBClusterList
Plural: m3dbclusters
Singular: m3dbcluster
Conditions:
Last Transition Time: 2018-12-14T19:05:29Z
Message: no conflicts found
Reason: NoConflicts
Status: True
Type: NamesAccepted
Last Transition Time: 2018-12-14T19:05:29Z
Message: the initial names have been accepted
Reason: InitialNamesAccepted
Status: True
Type: Established
Events: <none>

このCustomResourceDefinitionでは M3DBCluster というCustom Resourceが定義されていることが分かります。

M3DB Operator(Controller)はこの M3DBCluster リソースを扱うことになります。


M3DB クラスタの構築

M3DBのクラスタを構築してみます。

M3DBはクラスタトポロジやメタデータを etcd に保存するため、まずはetcdを構築します。

$ kubectl apply -f https://raw.githubusercontent.com/m3db/m3db-operator/v0.1.1/example/etcd/etcd-basic.yaml

CustomResourceDefinitionに従い、M3DBCluster リソースを作成してみましょう。

以下のファイルを m3db-cluster.yaml として保存し、applyします。

apiVersion: operator.m3db.io/v1alpha1

kind: M3DBCluster
metadata:
name: simple-cluster
spec:
image: quay.io/m3db/m3dbnode:latest
replicationFactor: 3
numberOfShards: 256
isolationGroups:
- name: us-central1-a
numInstances: 1
- name: us-central1-b
numInstances: 1
- name: us-central1-c
numInstances: 1
podIdentityConfig:
sources:
- PodUID
namespaces:
- name: metrics-10s:2d
preset: 10s:2d

$ kubectl apply -f m3db-cluster.yaml

m3dbcluster.operator.m3db.io/simple-cluster created

上記のmanifestをapplyして少し待つとKubernetes上にM3DBが構築されます。

$ kubectl get pods

NAME READY STATUS RESTARTS AGE
etcd-0 1/1 Running 0 ...
etcd-1 1/1 Running 0 ...
etcd-2 1/1 Running 0 ...
m3db-operator-0 1/1 Running 0 ...
simple-cluster-rep0-0 1/1 Running 0 ...
simple-cluster-rep1-0 1/1 Running 0 ...
simple-cluster-rep2-0 1/1 Running 0 ...

$ kubectl get m3dbcluster

NAME CREATED AT
simple-cluster ...

無事Operatorを使ってクラスタを立てることができました。


Operator を作成する

今まで、OperatorがCustom ControllerとCustom Resourceによって動作することを確認してきましたが、実際にはどうやってOperatorを作成すれば良いのでしょうか。Custom ControllerにはKubernetes APIを利用してReconciliation Logicを記述する必要がありますが、フルスクラッチで処理を書くのは相当厳しいと思います。


Operator Framework

そこで、Operator Frameworkの利用が可能です。

中でもOperator SDKを使うことで、Kubernetes APIの詳細を知らずともCRDの生成やReconsilication Logicの実装に集中できるようになります(Golangがサポートされています)。

Operator Frameworkを利用することでKubernetes APIを詳細に理解せずともReconcile ロジックの実装に集中できるようになります。

(実際にOperator Frameworkで何かOperatorを作る...というところまでやりたかったのですが、それはまた別の機会に。)


まとめ

本記事ではKubernetes Operatorを紹介しました。

今後はメジャーなStatefulアプリケーション運用のベストプラクティスを吸収したOperatorがOSSとして急速に発展していくのではないかと期待しています。