この記事は リクルートライフスタイル 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年に提唱された概念で、以下のブログに詳細が記述されています。
- CoreOS Blog - Introducing Operators: Putting Operational Knowledge into Software
- CoreOS - Kubernetes Operators
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 Resource
と Custom 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として急速に発展していくのではないかと期待しています。