GitHub の operator-framework/getting-started を、ステップ・バイ・ステップで試してみるが、Create a Memcached CRで、書いてある通りに動かない。 なぜ、オペレーターが GitHubのREADME.md通りに動作しないか理由が解らない。(2019年8月15日現在)
具体的には、カスタム・リソースを適用した後に、kubectl get deployment を実行して、デプロイメント・コントローラーの管理下で、複数のポッドが起動していることを確認している。しかし実際に動かして試すと、ポッドが一個起動するだけで、カスタム・リソースのspec.Size の値を変更しても、ポッドの数が変われない。そこで原因を追求して、動かない理由が解ったので、メモとして残しておく。
これは Qiita あまり良く知られていない Kubernetes Operator とは?の続きであり、以前の記事は「オペレーターとな何に?」という質問に答えるように書いた。一方、この記事では、さらに一歩進めて、オペレーターのプログラム開発にせまる内容である。
Operator SDKのインストール
オペレーターの開発のために、Operator SDK[1] があり、その中のコマンド operator-sdk を利用する[2],[3]。
GO言語の開発環境
オペレーターの開発にはGo言語が必要なので、別途インストールして、環境要件[2]に適合するようにしておく。
Homebrew でインストールした場合、v1.12.9がインストールされるが、v1.12.8にレベルダウンする方法[4]を書き残しておいたので必要に応じて参照いただきたい。
$ go version
go version go1.12.8 darwin/amd64
$ go env GOPATH
/Users/maho/go
$ export GOPATH=$(go env GOPATH)
$ cd $GOPATH
Operator-SDKの導入
macOSの場合は、brew install を利用すると時間の節約ができ便利である[2]。 バージョンは v0.10.0である。バージョン番号から Operator SDK の開発は、まだまだ、道半ばと言った印象を受ける。
$ operator-sdk version
operator-sdk version: v0.10.0, commit: ff80b17737a6a0aade663e4827e8af3ab5a21170
オペレータの足場(scaffold)作成
operator-sdk new <オペレータ名> コマンドを利用することで、コンテナのGo言語ソースコードとビルド用ファイル、K8sのマニフェストと、必要なディレクトリが作成される。
次の実行例では、takara-operatorの足場となるコードを生成したところである。このコマンドによって、オペレータの本体 cmd/manager/main.go が作成された。コードの内容も良く確認しておくと良い。
$ operator-sdk new takara-operator
INFO[0000] Creating new Go operator 'takara-operator'.
INFO[0000] Created go.mod
INFO[0000] Created tools.go
INFO[0000] Created cmd/manager/main.go
INFO[0000] Created build/Dockerfile
INFO[0000] Created build/bin/entrypoint
INFO[0000] Created build/bin/user_setup
INFO[0000] Created deploy/service_account.yaml
INFO[0000] Created deploy/role.yaml
INFO[0000] Created deploy/role_binding.yaml
INFO[0000] Created deploy/operator.yaml
INFO[0000] Created pkg/apis/apis.go
INFO[0000] Created pkg/controller/controller.go
INFO[0000] Created version/version.go
INFO[0000] Created .gitignore
INFO[0000] Validating project
go: finding github.com/operator-framework/operator-sdk master
INFO[0005] Project validation successful.
INFO[0005] Project creation complete.
オペレーターのコード cmd/manager/main.go が何をしているのか、箇条書きのメモを残しておく。
func main()の概要を以下の列挙する。コードの頭から順番に初期化処理が流れ、最後にマネージャーを起動して、イベント待ちに入る。この後は、コントローラーが中心に動作することになる。
- フラグの読み取り
- デフォルトロガーの設定
- バージョンのプリント
- K8s環境の名前空間名の取得
- apiserverと説臆するためkubeconfig取得
- 空のコンテキスト生成
- オペレータのポッドが一個とするために、ポッドのリーダーにする
- コントローラーを作成するために、新しいマネジャーを作る
- 全てのリソースをスキーマに追加する
- 全てのコントローラーをマネージャーに追加する
- カスタムリソースのメトリックスを提供できるようにセットアップする
- マネージャーを開始する
検証では、オペレーターに対して、コントローラーやリソースも一つしか作っていないが、この作りからすると、オペレーターは、複数のコントローラーを同時並行で動かせることになる。例えば、Cluster AutoScale Controller と Horizontal Pod Scaller を組み合わせて、一つのカスタムコントローラーにすることもできると考えられる。そうすれば、それぞれを設定するのではなく。一つのカスタム・オートスケーラーで、ポッド数の増減とノード数の増減を制御できることになる。
APIsの追加
ここではカスタム・リソースを作成して、K8s APIとしてapiserverをアクセスできるようにするための準備を行う。YAMLのマニフェストのヘッダーにセットする項目が、ここで決まる。
apiVersion: maho.takara.org/v1alpha1 == --api-version
kind: Takara == --kind
<以下省略>
コマンドは、オペーレーターのコードが収められるディレクトリのルートで実行する。 このコマンドで、pkg/apisフォルダの下にファイルが生成され、カスタム・リソース・デフィニッションとカスタム・リソースが deploy/crds/ に作成されたことがわかる。
$ cd takara-operator/
$ operator-sdk add api --api-version=maho.takara.org/v1alpha1 --kind=Takara
INFO[0000] Generating api version maho.takara.org/v1alpha1 for kind Takara.
INFO[0000] Created pkg/apis/maho/group.go
INFO[0001] Created pkg/apis/maho/v1alpha1/takara_types.go
INFO[0001] Created pkg/apis/addtoscheme_maho_v1alpha1.go
INFO[0001] Created pkg/apis/maho/v1alpha1/register.go
INFO[0001] Created pkg/apis/maho/v1alpha1/doc.go
INFO[0001] Created deploy/crds/maho_v1alpha1_takara_cr.yaml
INFO[0008] Created deploy/crds/maho_v1alpha1_takara_crd.yaml
INFO[0008] Running deepcopy code-generation for Custom Resource group versions: [maho:[v1alpha1], ]
INFO[0014] Code-generation complete.
INFO[0014] Running OpenAPI code-generation for Custom Resource group versions: [maho:[v1alpha1], ]
INFO[0027] Created deploy/crds/maho_v1alpha1_takara_crd.yaml
INFO[0027] Code-generation complete.
INFO[0027] API generation complete.
ここで生成されたコードのうちで、pkg/apis/maho/v1alpha1/takara_types.go に specとstatusのAPIのパラメータ項目をセットする部分があり、コントローラーの実装と並行して、API項目の追加が必要になる。 ここでAPIsに変更を加えたら、次のコマンドを実行するようにとの注意書きがあったので、その通りに実行しておく。これによって、Util関数や補助コードが生成されるらしい。 将来、APIパラメータ項目を修正したら、毎回、次のコマンドを実行して、変更を反映させること。
$ operator-sdk generate k8s
INFO[0000] Running deepcopy code-generation for Custom Resource group versions: [maho:[v1alpha1], ]
INFO[0006] Code-generation complete.
コンローラーの追加
APIsのコードが生成された後に、本体となるコントローラーを作成する。実行時に指定するオプションの--api-vesrion と --kind が一致していなければならない。 pkg/controller の下にコントローラーの本体となるコードが生成される。 これから takara_controller.go のカスタマイズが重要になる。
$ operator-sdk add controller --api-version=maho.takara.org/v1alpha1 --kind=Takara
INFO[0000] Generating controller version maho.takara.org/v1alpha1 for kind Takara.
INFO[0000] Created pkg/controller/takara/takara_controller.go
INFO[0000] Created pkg/controller/add_takara.go
INFO[0000] Controller generation complete.
コントローラーのビルド
Macのローカル環境で、「operator-sdk build」により、オペレータのコンテナをビルドして、プッシュできるようにタグを付与してく。 コンテナのレジストリは、どこでも良いので、アカウントを持っている DockerHubとした。ここでビルドするオペレーターは、operator-sdk だけで生成されたコードで、カスタマイズ部分を含んでいないので、何もできないオペレータである。
$ operator-sdk build docker.io/maho/takara-operator:v0.0.1
INFO[0028] Building OCI image docker.io/maho/takara-operator:v0.0.1
Sending build context to Docker daemon 45.29MB
Step 1/7 : FROM registry.access.redhat.com/ubi7/ubi-minimal:latest
---> f5abf81c362e
Step 2/7 : ENV OPERATOR=/usr/local/bin/takara-operator USER_UID=1001 USER_NAME=takara-operator
---> Using cache
---> 9230ee0cb5dc
Step 3/7 : COPY build/_output/bin/takara-operator ${OPERATOR}
---> 764ee389108f
Step 4/7 : COPY build/bin /usr/local/bin
---> bad204a241c0
Step 5/7 : RUN /usr/local/bin/user_setup
---> Running in 32f5a6d4612d
+ mkdir -p /root
+ chown 1001:0 /root
+ chmod ug+rwx /root
+ chmod g+rw /etc/passwd
+ rm /usr/local/bin/user_setup
Removing intermediate container 32f5a6d4612d
---> 05f5a398c120
Step 6/7 : ENTRYPOINT ["/usr/local/bin/entrypoint"]
---> Running in ab37d8257689
Removing intermediate container ab37d8257689
---> 8951daa19d4c
Step 7/7 : USER ${USER_UID}
---> Running in e9cb749be9e1
Removing intermediate container e9cb749be9e1
---> 122cf9949a2f
Successfully built 122cf9949a2f
Successfully tagged maho/takara-operator:v0.0.1
INFO[0037] Operator build complete.
Dockerレジストリへプッシュ
ビルドして出来たイメージを、レジストリへ登録しておく。
$ docker push docker.io/maho/takara-operator:v0.0.1
The push refers to repository [docker.io/maho/takara-operator]
d9086086704a: Pushed
d46b339737f0: Pushed
f48f156fe734: Pushed
2705f79af6db: Layer already exists
ce459cc53899: Layer already exists
v0.0.1: digest: sha256:7a8d3feac8d51910dc4407c878d53960280b00655b3fc8607fb8510f7f7d557b size: 1363
Custom Resource Definition をデプロイすることで、APIをつうじてオペレーターへアクセスできるようになる。
Custom Resource Definition (CRD)は、operator-sdk によって deploy/crds の下に生成されているので、そのまま利用する。「kubectl get crd」で結果を確認しておく。
$ kubectl apply -f deploy/crds/maho_v1alpha1_takara_crd.yaml
customresourcedefinition.apiextensions.k8s.io/takaras.maho.takara.org created
$ kubectl get crd
NAME CREATED AT
<中略>
takaras.maho.takara.org 2019-08-17T14:08:53Z
オペレータをローカルで動作させる
オペレーターは、K8sクラウド上で動かすものであるが、開発時はローカル環境で起動して、apiserverに接続して、デバッグをローカル環境上で実施できる。次の実行画面は、ローカルで起動して、起動初期のメッセージが一通り表示されたところである。
$ operator-sdk up local --namespace=default
INFO[0000] Running the operator locally.
INFO[0000] Using namespace default.
{"level":"info","ts":1566050832.627087,"logger":"cmd","msg":"Go Version: go1.12.8"}
{"level":"info","ts":1566050832.627469,"logger":"cmd","msg":"Go OS/Arch: darwin/amd64"}
{"level":"info","ts":1566050832.6274781,"logger":"cmd","msg":"Version of operator-sdk: v0.10.0"}
{"level":"info","ts":1566050832.62928,"logger":"leader","msg":"Trying to become the leader."}
{"level":"info","ts":1566050832.629302,"logger":"leader","msg":"Skipping leader election; not running in a cluster."}
{"level":"info","ts":1566050833.838594,"logger":"cmd","msg":"Registering Components."}
{"level":"info","ts":1566050833.838762,"logger":"kubebuilder.controller","msg":"Starting EventSource","controller":"takara-controller","source":"kind source: /, Kind="}
{"level":"info","ts":1566050833.838927,"logger":"kubebuilder.controller","msg":"Starting EventSource","controller":"takara-controller","source":"kind source: /, Kind="}
{"level":"info","ts":1566050833.839081,"logger":"cmd","msg":"Could not generate and serve custom resource metrics","error":"operator run mode forced to local"}
{"level":"info","ts":1566050834.335118,"logger":"metrics","msg":"Skipping metrics Service creation; not running in a cluster."}
{"level":"info","ts":1566050834.383544,"logger":"cmd","msg":"Could not create ServiceMonitor object","error":"no ServiceMonitor registered with the API"}
{"level":"info","ts":1566050834.383578,"logger":"cmd","msg":"Install prometheus-operator in your cluster to create ServiceMonitor objects","error":"no ServiceMonitor registered with the API"}
{"level":"info","ts":1566050834.383592,"logger":"cmd","msg":"Starting the Cmd."}
{"level":"info","ts":1566050834.488001,"logger":"kubebuilder.controller","msg":"Starting Controller","controller":"takara-controller"}
{"level":"info","ts":1566050834.592572,"logger":"kubebuilder.controller","msg":"Starting workers","controller":"takara-controller","worker count":1}
カスタムリソースをデプロイする
オペレーターのスタンバイが出来たので、もう一つターミナルを開いて、カスタム・リソースを適用して、オペレーターへ指示を送ってみる。
apiVersion: maho.takara.org/v1alpha1
kind: Takara
metadata:
name: example-takara
spec:
# Add fields here
size: 3
カスタムに作ったリソース、APIとコントローラーで、ポッドが起動した。
しかし、上記では size: 3 と設定されているにも関わらず、下記の実行結果では、ポッドは一つしか起動していない。
$ kubectl apply -f crds/maho_v1alpha1_takara_cr.yaml
takara.maho.takara.org/example-takara created
$ kubectl get po
NAME READY STATUS RESTARTS AGE
example-takara-pod 1/1 Running 0 4s
takara-operator-7b48f487c8-ghzzs 1/1 Running 0 5m10s
ポッドが1個しか挙がらない原因調査
次のコードは、operator-sdk によって生成されたコントローラーのコードである。この中で、Podが起動させているか否かを確認して、ポッドを起動しているが、Sizeつまり、ポッドの数を見ていないことがわかる。つまり、カスタム・リソースのマニフェストのAPIを受け取っていないのである。
85 func (r *ReconcileTakara) Reconcile(request reconcile.Request) (reconcile.Result, error) {
86 reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
87 reqLogger.Info("Reconciling Takara")
88
89 // Fetch the Takara instance
90 instance := &mahov1alpha1.Takara{}
91 err := r.client.Get(context.TODO(), request.NamespacedName, instance)
92 if err != nil {
93 if errors.IsNotFound(err) {
94 // Request object not found, could have been deleted after reconcile request.
95 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
96 // Return and don't requeue
97 return reconcile.Result{}, nil
98 }
99 // Error reading the object - requeue the request.
100 return reconcile.Result{}, err
101 }
102
103 // Define a new Pod object
104 pod := newPodForCR(instance)
105
106 // Set Takara instance as the owner and controller
107 if err := controllerutil.SetControllerReference(instance, pod, r.scheme); err != nil {
108 return reconcile.Result{}, err
109 }
110
111 // Check if this Pod already exists
112 found := &corev1.Pod{}
113 err = r.client.Get(context.TODO(), types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, found)
114 if err != nil && errors.IsNotFound(err) {
115 reqLogger.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
116 err = r.client.Create(context.TODO(), pod)
117 if err != nil {
118 return reconcile.Result{}, err
119 }
120
121 // Pod created successfully - don't requeue
122 return reconcile.Result{}, nil
123 } else if err != nil {
124 return reconcile.Result{}, err
125 }
126
127 // Pod already exists - don't requeue
128 reqLogger.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name)
129 return reconcile.Result{}, nil
130 }
オペレータのカスタマイズ
operator-sdkで自動生成されるコードは、ポッドを1つ立ち上げるだけのシンプルなコードなので、目的の動作を実行するためのコントローラーに完成させなければならない。複数のポッドの起動を制御する必要があるので、Bareなポッドを起動するのではなく、Deploymentコントローラの制御下で起動するべきである。
オペレータの修正箇所: cmd/manager/main.go
ここでは、主要な箇所の説明にとどめ、補助的なコードなどを含めた全体は、https://github.com/takara9/takara-operator を参照して欲しい。読者自身でトレースする場合にはGitHubからクローンすると良い。次コードの コントローラーの Reconcile すなわち 調整機能の一部に変更を加え、Deploymentコントローラーを起動するようにする。次のコードの107行目から修正を加える。既存のPodを対象としたコードは削除して、Deploymentコントローラーを起動すること、instance の Spec.Size をチェックして、値が違っていれば、Replicas レプリカ数に、値をセットする。 Go言語もC言語と同じで、ポインタやアドレスを意識したコーディングが必要なので注意が必要あである。
102 // Error reading the object - requeue the request.
103 return reconcile.Result{}, err
104 }
105
106
107 // deploymentの存在をチェックして、既に存在していれば生成しない
108 found := &appsv1.Deployment{}
109 err = r.client.Get(context.TODO(), types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, found)
110 if err != nil && errors.IsNotFound(err) {
111 // 新deploymentの定義
112 dep := r.deploymentForTakara(instance)
113 reqLogger.Info("Creating a new Deployment.", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
114 err = r.client.Create(context.TODO(), dep)
115 if err != nil {
116 reqLogger.Error(err, "Failed to create new Deployment.", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
117 return reconcile.Result{}, err
118 }
119 // Deployment生成成功
120 return reconcile.Result{Requeue: true}, nil
121 } else if err != nil {
122 reqLogger.Error(err, "Failed to get Deployment.")
123 return reconcile.Result{}, err
124 }
125
126
127 // デプロイメントのサイズをspecと同じになるように調整する
128 size := instance.Spec.Size
129 if *found.Spec.Replicas != size {
130 found.Spec.Replicas = &size
131 err = r.client.Update(context.TODO(), found)
132 if err != nil {
133 reqLogger.Error(err, "Failed to update Deployment.", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
134 return reconcile.Result{}, err
135 }
136 // Specを更新
137 return reconcile.Result{Requeue: true}, nil
138 }
<中略>
165 return reconcile.Result{}, nil
166 }
上記の部分が、コアになる修正箇所であるが、関連して、DeploymentコントローラーのAPIを操作する部分、ラベルを取得するなど、追加が必要である。
APIの修正箇所: pkg/apis/maho/v1alpha1/takara_types.go
APIのコードには、2箇所の変更がある。これで、スペックとして受け取る部分と、ステータスとして
- type TakaraSpecにメンバーを追加: Sizeを追加して、ポッド数を変更可能にする
- type TakaraStatusにメンバーを追加: Nodes配列を追加して、ノードのリストを取得可能にする
10 // TakaraSpec defines the desired state of Takara
11 // +k8s:openapi-gen=true
12 type TakaraSpec struct {
13 // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
14 // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
15 // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html
16 Size int32 `json:"size"` <<--- ここ
17 }
18
19 // TakaraStatus defines the observed state of Takara
20 // +k8s:openapi-gen=true
21 type TakaraStatus struct {
22 // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
23 // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
24 // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html
25 Nodes []string `json:"nodes"` <<--- ここ
26 }
このコードの修正により、Nodesにポッドのリストの表示されるようになる。
$ kubectl get takaras.maho.takara.org
NAME AGE
example-takara 26h
$ kubectl describe takaras.maho.takara.org example-takara
Name: example-takara
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"maho.takara.org/v1alpha1","kind":"Takara","metadata":{"annotations":{},"name":"example-takara","namespace":"default"},"spec...
API Version: maho.takara.org/v1alpha1
Kind: Takara
Metadata:
Creation Timestamp: 2019-08-18T02:05:38Z
Generation: 1
Resource Version: 347217
Self Link: /apis/maho.takara.org/v1alpha1/namespaces/default/takaras/example-takara
UID: ac99732e-c15c-11e9-8089-e2eef895ad20
Spec:
Size: 3
Status:
Nodes: <-- 前述のAPIsのステータスの追加などによって、表示されるようになる。
example-takara-96695d664-5g869
example-takara-96695d664-5snrz
example-takara-96695d664-9g7gt
コントローラーの修正箇所: pkg/controller/takara/takara_controller.go
上記はポイントになる場所だけだったので、以下で、その修正点を賀状書きにする。
- func newPodForCR を削除: 単独でポッドを起動することを辞める
- func deploymentForTakara を追加: デプロイメントを通じてポッドを起動する
- func labelsForTakara を追加: コントローラとポッドを繋ぐラベルをリスト
- func getPodNamesを追加: ポッド名のリストを返す
- import にモジュール追加: 詳細はリスト参照
- func Reconcile を修正: Deploymentからポッドを起動、ポッド数をスペックに合わせて調整他
takaraオペレータを再デプロイして、コンテナをレジストリへ登録
修正を行ったあと、オペレーターのコンテナを再ビルドして、レジストリへアップロードする。
$ operator-sdk generate k8s
$ operator-sdk build docker.io/maho/takara-operator:v0.0.3
INFO[0005] Building OCI image docker.io/maho/takara-operator:v0.0.3
<中略>
$ docker push docker.io/maho/takara-operator:v0.0.3
以下、オペレーターのマニフェストのimage を置き換えて、kubectl で apply する。
14 spec:
15 serviceAccountName: takara-operator
16 containers:
17 - name: takara-operator
18 # Replace this with the built image name
19 image: docker.io/maho/takara-operator:v0.0.3 <-- sedなどで修正
20 command:
オペレーターの実行に必要な RBACも設定して、オペレーターをデプロイ
$ kubectl apply -f role.yaml
role.rbac.authorization.k8s.io/takara-operator configured
$ kubectl apply -f role_binding.yaml
rolebinding.rbac.authorization.k8s.io/takara-operator unchanged
$ kubectl apply -f service_account.yaml
serviceaccount/takara-operator unchanged
$ kubectl apply -f operator.yaml
deployment.apps/takara-operator created
カスタムリソースのデプロイ
operator-sdkに作られたカスタム・リソースのマニフェストを適用する。
$ kubectl apply -f maho_v1alpha1_takara_cr.yaml
takara.maho.takara.org/example-takara created
オペレーターによって、デプロイメントがデプロイされ、ポッドが起動されたことが確認できます。
もちろん、Sizeを変更すれば、応じてポッド数が変わります。
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
example-takara 3/3 3 3 20s
takara-operator 1/1 1 1 27h
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
example-takara-96695d664-djww8 1/1 Running 0 9s
example-takara-96695d664-k7j94 1/1 Running 0 9s
example-takara-96695d664-zw2ps 1/1 Running 0 9s
takara-operator-7499958656-n6npt 1/1 Running 0 27h
カスタム・リソース・デフィニッションも表示できます。
$ kubectl get crd takaras.maho.takara.org
NAME CREATED AT
takaras.maho.takara.org 2019-08-17T14:08:53Z
まとめ
Go言語のプログラムで、インポートしているライブラリ名に、sigs.k8s.io/controller-runtime/pkg/manager などが見うけられ、まだまだ、これからという印象を受ける。 いくつかの Operator SDK のサンプルコードでは、operator-sdkで生成したコードで、Deploymentコントローラーが動作しているように書かれているが、実施には手順が抜けているだけで、Go言語の K8sクライアントライブラリを利用してコードを書かないと、いけないことが解った。
オペレーターから呼び出されるコントローラーのコードの書き方には作法があるようだ。その作法について調べておかないといけないことを忘れないようにしないと.
参考資料
[1] Operator SDK、https://github.com/operator-framework/operator-sdk
[2] Install Operator SDK、https://github.com/operator-framework/operator-sdk/blob/master/doc/user/install-operator-sdk.md
[3] CLI Guide、https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md
[4] Go言語のバージョンを切り替える方法 macOS HomeBrew、https://qiita.com/MahoTakara/items/5095ac721aa054cf1cf8
[5] User Guide、https://github.com/operator-framework/operator-sdk/blob/master/doc/user-guide.md
[6] Memcahcedオペレーター、https://github.com/operator-framework/operator-sdk/tree/master/example/memcached-operator/memcached_controller.go.tmpl