以下の記事でOperatorを作成する方法がわかりましたので、NamespaceをプロビジョニングするOperatorがあったら、マルチテナントでクラスター運用するのが楽になるかなぁと思って初めてみました。
Operator SDK をインストールしてOperatorを作成する(1/2)
Operator SDK をインストールしてOperatorを作成する(2/2)
- はじめに
- Operatorを作成する
- プロジェクトを作成する
- CRDを作成する
- Controllerを追加する
- Operatorをビルドする
- Operatorをデプロイする
- Operatorを確認する
はじめに
本記事では、サイジングされたNamespaceを作成するOperatorを作成します。サイズはLarge/Medium/Smallを用意し、以下のようにQuotaでサイズを制限します。
- Large・・・CPU Core 300m, Memory 300Mi
- Medium・・・CPU Core 200m, Memory 200Mi
- Small・・・CPU Core 100m, Memory 100Mi
これだけだとyamlを用意しておいた方が早いですが、今後拡張していくので一旦これだけの機能を備えます。
Operatorを作成する
Operator SDKを使ってgolanでOperatorを作成します。
プロジェクトを作成する
Operator SDKのプロジェクトを作成します。
$ operator-sdk new nspro-operator
CRDを作成する
CR用のAPIを追加します。
$ cd nspro-operator $ operator-sdk add api --api-version=nspro.example.com/v1alpha1 --kind=Nspro
pkg/apis/nspro/v1alpha1/nspro_types.goが作成されるので、CRで設定できる設定項目を定義します。今回は、NsproSpecにNsSize string `json:”nssize”`を加えて、それぞれのサイズが指定できるようにします。
~~省略~~ type NsproSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html NsSize string `json:"nssize"` } ~~省略~~
変更を加えたら、以下のコマンドで必要なコードを再生成します。
$ operator-sdk generate k8s INFO[0006] Running deepcopy code-generation for Custom Resource group versions: [nspro:[v1alpha1], ] INFO[0011] Code-generation complete.
Controllerを追加する
以下のコマンドを実行して、Controllerのテンプレートを作成します。作成が完了すると、pkg/controller/nspro/nspro_controller.go が作成されます。
$ operator-sdk add controller --api-version nspro.example.com/v1alpha1 --kind Nspro
nspro_controllerを以下に示します。
package nspro import ( "context" nsprov1alpha1 "operator/nspro-operator/pkg/apis/nspro/v1alpha1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" resource "k8s.io/apimachinery/pkg/api/resource" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/source" ) var log = logf.Log.WithName("controller_nspro") /** * USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller * business logic. Delete these comments after modifying this file.* */ // Add creates a new Nspro Controller and adds it to the Manager. The Manager will set fields on the Controller // and Start it when the Manager is Started. func Add(mgr manager.Manager) error { return add(mgr, newReconciler(mgr)) } // newReconciler returns a new reconcile.Reconciler func newReconciler(mgr manager.Manager) reconcile.Reconciler { return &ReconcileNspro{client: mgr.GetClient(), scheme: mgr.GetScheme()} } // add adds a new Controller to mgr with r as the reconcile.Reconciler func add(mgr manager.Manager, r reconcile.Reconciler) error { // Create a new controller c, err := controller.New("nspro-controller", mgr, controller.Options{Reconciler: r}) if err != nil { return err } // Watch for changes to primary resource Nspro err = c.Watch(&source.Kind{Type: &nsprov1alpha1.Nspro{}}, &handler.EnqueueRequestForObject{}) if err != nil { return err } // TODO(user): Modify this to be the types you create that are owned by the primary resource // Watch for changes to secondary resource Pods and requeue the owner Nspro err = c.Watch(&source.Kind{Type: &corev1.Namespace{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &nsprov1alpha1.Nspro{}, }) if err != nil { return err } return nil } // blank assignment to verify that ReconcileNspro implements reconcile.Reconciler var _ reconcile.Reconciler = &ReconcileNspro{} // ReconcileNspro reconciles a Nspro object type ReconcileNspro struct { // This client, initialized using mgr.Client() above, is a split client // that reads objects from the cache and writes to the apiserver client client.Client scheme *runtime.Scheme } // Reconcile reads that state of the cluster for a Nspro object and makes changes based on the state read // and what is in the Nspro.Spec // TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates // a Pod as an example // Note: // The Controller will requeue the Request to be processed again if the returned error is non-nil or // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. func (r *ReconcileNspro) Reconcile(request reconcile.Request) (reconcile.Result, error) { reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) reqLogger.Info("Reconciling Nspro") // Fetch the Nspro instance instance := &nsprov1alpha1.Nspro{} err := r.client.Get(context.TODO(), request.NamespacedName, instance) if err != nil { if errors.IsNotFound(err) { // Request object not found, could have been deleted after reconcile request. // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. // Return and don't requeue return reconcile.Result{}, nil } // Error reading the object - requeue the request. return reconcile.Result{}, err } // Define a new Namespace object ns := newNsproForCR(instance) // Set Nspro instance as the owner and controller if err := controllerutil.SetControllerReference(instance, ns, r.scheme); err != nil { return reconcile.Result{}, err } err = r.client.Create(context.TODO(), ns) quota := r.newQuotaForNS(instance) err = r.client.Create(context.TODO(), quota) return reconcile.Result{}, nil } func newNsproForCR(cr *nsprov1alpha1.Nspro) *corev1.Namespace { nssize := cr.Spec.NsSize labels := map[string]string{ "size": nssize, } return &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, Labels: labels, }, } } func (r *ReconcileNspro) newQuotaForNS(cr *nsprov1alpha1.Nspro) *corev1.ResourceQuota { nsname := cr.Name size_map := map[string]int64{ "large": 3, "medium": 2, "small": 1 } size := size_map[cr.Spec.NsSize] cpuCores := resource.NewMilliQuantity(100*size, resource.DecimalSI) memorySize := resource.NewQuantity(1*1024*1024*size, resource.BinarySI) quota := &corev1.ResourceQuota{ ObjectMeta: metav1.ObjectMeta{ Name: nsname + "-quota", Namespace: cr.Name, }, Spec: corev1.ResourceQuotaSpec{ Hard: corev1.ResourceList{ "limits.cpu": *cpuCores, "limits.memory": *memorySize, }, }, } controllerutil.SetControllerReference(cr, quota, r.scheme) return quota }
作成した部分は、newNsproForCR/newQuotaForNSとそれらの呼び出しです。
newNsproForCRがNamespaceを定義していて、nssizeで指定されたサイズをラベルに貼ってNamespaceを作成する単純な関数です。
newQuotaForNSは、nssizeで指定されたサイズに応じてlimitのサイズを定義します。各サイズに応じて数字をかけています。limits.cpu/limits.memoryは、Quantityを指定する必要があり、package resourceが参考になりました。
Operatorをビルドする
作成したOperatorのコンテナイメージをビルドします。
$ go mod vendor $ operator-sdk build 192.168.64.2:32000/nspro-operator:v1
以下のコマンドでレジストリに登録します。今回はMicroK8s上のコンテナレジストリ上に登録するので、[WorkerNodeのIPアドレス]:[NodePort]/[イメージ名]:[タグ名]のように登録しておきます。
$ docker push 192.168.64.2:32000/nspro-operator:v1
Operatorをデプロイする
Operatorをデプロイします。Operatorの前にCRDを作成しないとapiVersionが利用できないので、CRDから作成します。
$ kubectl create -f deploy/crds/nspro_v1alpha1_nspro_crd.yaml customresourcedefinition.apiextensions.k8s.io/nspros.nspro.example.com created
続いてdeploy/配下にあるyamlを作成します。operator.yamlだけイメージ名を変更する必要がありました。
$ kubectl delete -f deploy/operator.yaml deployment.apps "nspro-operator" deleted $ kubectl create -f deploy/operator.yaml -n nspro-operator deployment.apps/nspro-operator created $ kubectl create -f deploy/service_account.yaml -n nspro-operator serviceaccount/nspro-operator created $ kubectl create -f deploy/role.yaml -n nspro-operator role.rbac.authorization.k8s.io/nspro-operator created $ kubectl create -f deploy/role_binding.yaml -n nspro-operator rolebinding.rbac.authorization.k8s.io/nspro-operator created
OperatorのPodがデプロイされます。
$ kubectl get pods -n nspro-operator NAME READY STATUS RESTARTS AGE nspro-operator-585b76656d-jt4nm 1/1 Running 0 25s
Operatorを確認する
デプロイしたOperatorが正常に動くか確認します。
CRとして、nssizeがsmallのsmall-nsとnssizeがlargeのlarge-nsを作成します。それぞれ$ kubectl create -f [ファイル名] -n [Operatorのnamespace]
でリソースを作成します。
apiVersion: nspro.example.com/v1alpha1 kind: Nspro metadata: name: small-ns spec: nssize: small
apiVersion: nspro.example.com/v1alpha1 kind: Nspro metadata: name: large-ns spec: nssize: large
ラベルで正しく分かれていることが分かります。
$ kubectl get ns small-ns -L size NAME STATUS AGE SIZE small-ns Active 19s small $ kubectl get ns large-ns -L size NAME STATUS AGE SIZE large-ns Active 7s large
それぞれのサイズに合ったQuotaが定義されていて、使用量が制限できていることがわかります。
$ kubectl describe quota small-ns-quota -n small-ns Name: small-ns-quota Namespace: small-ns Resource Used Hard -------- ---- ---- limits.cpu 0 100m limits.memory 0 1Mi $ kubectl describe quota large-ns -n large-ns Name: large-ns-quota Namespace: large-ns Resource Used Hard -------- ---- ---- limits.cpu 0 300m limits.memory 0 3Mi
以上です!半角スペースに気づかなかったりして大分PDに時間を取られてしまいました。。。
コメント
コメントは停止中です。