投稿者「yosk222」のアーカイブ

MicroK8sへのユーザー追加と権限付与でハマった話

MicroK8s上にユーザーを作成して権限を付与するときにハマった内容を共有します。MicroK8sのインストールは、MicroK8sをMacOSにインストールしてみるで紹介していますので、よろしければご参照ください。

  • はじめに
  • ユーザーの作成
  • コンテキストの作成
  • RBAC機能の有効化
  • 権限の付与

はじめに


本記事では、MicroK8sに以下の実装を行う上で発生した事象を紹介します。

  • MicroK8sクラスターに、system:authenticatedとdeveloperグループに所属するdev-user01ユーザーを作成する
  • developerグループにdefaultネームスペースの権限を付与する

ユーザーの作成


「system:authenticated」と「developer」グループに所属する「dev-user01」ユーザーを作成します。

Kubernetesでユーザーを作成する場合は何通りかあり、公式ドキュメントに説明があります。その中で、MicroK8sのデフォルトユーザー(admin)は「Static Password File」を使って作成していますので、今回も同じ方法を使います。

ユーザー情報が記載されているbasic_auth.csvは/var/snap/microk8s/current/credentials/配下にあります。中身を確認すると、[Password], [User], [Uid], "[Group1], [Group2], ..."というフォーマットでadminユーザーの情報が保管されています。同じフォーマットで、以下のようにdev-user01ユーザーを入力します。

$ cat /var/snap/microk8s/current/credentials/basic_auth.csv 
[adminのPassword],admin,admin,"system:masters"
[dev-user01のPassword],dev-user01,dev-user01,"system:authenticated,developer"

ファイル更新後は、以下のコマンドでMicroK8sを再起動します。

$ microk8s.stop
$ microk8s.start

以上で、ユーザーの作成が完了しました。

コンテキストの作成


上記で作成したユーザーがクラスターにログインするためのコンテキストを作成します。~/.kube/configファイルを以下の通り編集し、dev-user01でクラスターにアクセスするmicrok8s-developerコンテキストを作成します。

apiVersion: v1
clusters:
- cluster:
    insecure-skip-tls-verify: true
    server: https://127.0.0.1:16443
  name: microk8s-cluster
contexts:
- context:
    cluster: microk8s-cluster
    user: admin
  name: microk8s
- context:
    cluster: microk8s-cluster
    user: dev-user01
  name: microk8s-developer
current-context: microk8s-developer
kind: Config
preferences: {}
users:
- name: admin
  user:
    password: [adminのPassword]
    username: admin
- name: dev-user01
  user:
    password: [dev-user01のPassword]
    username: dev-user01

~/.kube/configはkubectlコマンドをmultipass-vmにインストールしてる場合のみになります。microk8s.kubectlコマンドを利用したい場合は、/var/snap/microk8s/current/credentials/client.configにコンテキストとユーザーを追加します。

コンテキストを切り替えてコマンドを実行してみると、コマンドが実行できるようになったのですが、なぜか最初から権限が付与されています。まだ権限を付与していないので、本来であればError from server (Forbidden)というエラーが返ってくるはずなのですが。。。

$ kc config use-context microk8s-developer 
Switched to context "microk8s-developer".
$ kc get pods
No resources found in default namespace.
$ kc auth can-i get pods -n default
yes

作成方法が違うのかなと思い、以下のURLで紹介されているX509 Client Certsを使った作成方法を試しましたが、結果は同じでした。。以下のURLはものすごく綺麗にまとまってくださっていて、Error from server (Forbidden)のエラーが出るところまで記載されていたので、やはりこの挙動はおかしいと確信しました。

RBAC機能の有効化


これはMicroK8sの仕様かなと思い、ドキュメントを確認したところ以下の記載がありました。

By default all authenticated requests are authorized as the api-server runs with --authorization-mode=AlwaysAllow. Turning on RBAC is done through microk8s enable rbac.

ああ、本当に自分ってセンスがないなと感じた瞬間でした。。

簡単に翻訳すると、「MicroK8sはデフォルトで全ての権限が付与されることになっているので、RBAC機能を使いたい場合は有効化してください。」とあります。ということで、以下のコマンドを使ってRBAC機能を有効化します。

$ microk8s.status | grep rbac
rbac: disabled
$ microk8s.enable rbac
Enabling RBAC
Reconfiguring apiserver
RBAC is enabled
$ microk8s.status | grep rbac
rbac: enabled

もう一度コマンドを実行すると期待していたError from server (Forbidden)が出ました。

$ kc config use-context microk8s-developer
Switched to context "microk8s-developer".
$ kc get pods
Error from server (Forbidden): pods is forbidden: User "dev-user01" cannot list resource "pods" in API group "" in the namespace "default"

権限の付与


developerグループにdefautネームスペースの権限を付与します。

権限を付与する場合は、RoleとRoleBindingを作成する必要があります。defaultネームスペースでの操作権限(クラスター操作は除く)を持ったdefault-roleと、それをdeveloperグループに紐づけるdefault-role-for-developerを作成します。それぞれのyamlを以下に示します。

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: default-role
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: default-role-for-developer
subjects:
- kind: Group
  name: developer
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: default-role
  apiGroup: rbac.authorization.k8s.io

以下のコマンドでdefault-roleとdefault-role-for-developerを作成します。(作成するときはadminユーザーに切り替えています。)

$ kc create -f role.yaml 
role.rbac.authorization.k8s.io/default-role created
rolebinding.rbac.authorization.k8s.io/default-role-for-developer created

再度確認すると、Error from server (Forbidden)が解消されました。

$ kc config use-context microk8s-developer 
Switched to context "microk8s-developer".
$ kc get pods
No resources found in default namespace.
$ kc auth can-i get pods -n default
yes

他のネームスペースには権限を与えていないので、以下のようにエラーが表示されました。期待通りです。

$ kc get pods -n kube-system
Error from server (Forbidden): pods is forbidden: User "dev-user01" cannot list resource "pods" in API group "" in the namespace "kube-system"
$ kc auth can-i get pods -n kube-system
no

以上です。ドキュメントを読むのは大切なんだということを改めて感じました。。

NamespaceをプロビジョニングするOperatorを作ってみる(NetworkPolicy編)

NamespaceをプロビジョニングするOperatorがあったら、マルチテナントでクラスター運用するのが楽になるかなぁと思って作成してみています。本日はNetworkPolicyのプロビジョニング機能を作成します。

  • はじめに
  •  Operatorを作成する
    • CRDを作成する
    • Controllerを追加する
  • Operatorをビルドする
  • Operatorをデプロイする
  • Operatorを確認する

はじめに


以下の記事からNamespaceをプロビジョニングするOperatorを作成しています。以下の記事では、リクエストサイズに合わせてResourceQuotaを作成する機能を作成しました。

NamespaceをプロビジョニングするOperatorを作ってみる(サイズ編)

今回は、上記で作成したOperatorにNetworkPolicyを作成する機能を追加します。アプリケーション毎にNetworkPolicy要件は変わりますが、ログ管理やメトリクス管理などの管理系Podからの通信は許可することが多いです。従って、以下の通信要件を持ったNetworkPolicyをプロビジョニングするOperatorを作成します。

  • Ingress・・・カスタムリソースのmanagedの値に応じて、通信要件を変更する。
    • managed: true・・・ラベルが「app=infra」のNamespace内にあるPod(管理系Pod)からの通信を許可する。それ以外は拒否。
    • managed: false・・・全て許可。
  • Egress・・・全て許可。

Operatorを作成する


CRDを作成する


CRで設定できる項目を定義します。pkg/apis/nspro/v1alpha1/nspro_types.goのNsproSpecにManaged bool `json:”managed”`を加えて、運用管理対象か指定できるようにします。

~~省略~~
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"`
    Managed bool `json:"managed"`
}
~~省略~~

変更を加えたら、以下のコマンドで必要なコードを再生成します。

$ operator-sdk generate k8s
INFO[0006] Running deepcopy code-generation for Custom Resource group versions: [nspro:[v1alpha1], ]
INFO[0011] Code-generation complete.

Controllerを追加する


ControllerのReconcile関数にはnewNetworkPolicyForNSを呼び出して、リソースを作成する処理を記述します。

func (r *ReconcileNspro) Reconcile(request reconcile.Request) (reconcile.Result, error) {
    ~~省略~~
    np := r.newNetworkPolicyForNS(instance)
    err = r.client.Create(context.TODO(), np)
    ~~省略~~
}

newNetworkPolicyforNSは以下の通りです。作成方法は他のリソースと同じですが、NetworkPolicyのAPIはvender/k8s.io/api/networking/v1/type.goに記述されています。

func (r *ReconcileNspro) newNetworkPolicyForNS(cr *nsprov1alpha1.Nspro) *networkingv1.NetworkPolicy {
  nsname  := cr.Name
  namespace_labels := map[string]string{}
  if ( cr.Spec.Managed ) {
    namespace_labels["app"] = "infra"
  }
  np := &networkingv1.NetworkPolicy{
    ObjectMeta: metav1.ObjectMeta{
      Name:      nsname + "-np",
      Namespace: nsname,
    },
    Spec: networkingv1.NetworkPolicySpec{
      Ingress: []networkingv1.NetworkPolicyIngressRule{
        {
          From: []networkingv1.NetworkPolicyPeer{
            {
              NamespaceSelector: &metav1.LabelSelector{
                MatchLabels: namespace_labels,
              },
            },
          },
        },
      },
    },
  }
  controllerutil.SetControllerReference(cr, np, r.scheme)
  return np
}

Operatorをビルドする


作成したOperatorのコンテナイメージをビルドします。

$ 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を確認する


デプロイしたOperatorが正常に動くか確認します。

largeサイズの運用管理対象とするNamespaceを作成するカスタムリソースを作成します。以下のyamlを$ kubectl create -f [ファイル名] -n [Operatorのnamespace]で作成します。

apiVersion: nspro.example.com/v1alpha1
kind: Nspro
metadata:
  name: np
spec:
  nssize: large
  managed: true

カスタムリソース作成後、Namespaceが確認できました。

$ kubectl get ns np --show-labels
NAME   STATUS   AGE   LABELS
np     Active   7s    managed=true,size=large

想定しているNetworkPolicyが作成されていることも確認できました。

$ kubectl describe networkpolicy np-np -n np
Name:         np-np
Namespace:    np
Created on:   2020-06-28 15:26:29 +0900 JST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    To Port: <any> (traffic allowed to all ports)
    From:
      NamespaceSelector: app=infra
  Allowing egress traffic:
    <none> (Selected pods are isolated for egress connectivity)
  Policy Types: Ingress

以上です。実際の挙動確認しようと思いましたが、network-pluginが入ってないと機能しないので断念しました。

OpenShiftのImageStreamを登録する方法

久しぶりに「コピーしておくと便利なコマンド」を書きます。今日はOpenShiftのImageStreamで外部レジストリを登録する方法をご紹介します。

ImageStreamについては、以下の公式ドキュメントをご参照ください。

https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.4/html/images/managing-image-streams

1. 外部レジストリにログインする


認証ファイルを生成するため、docker loginコマンドを使って対象の外部レジストリに一度ログインします。

$ docker login [外部レジストリURL]
→ユーザー名とパスワードを入力する

2. Secretを作成する


pullするときに、外部レジストリにアクセスするための認証情報をSecretで作成します。

$ oc create secret generic [Secret名] --from-file .dockerconfigjson=[認証情報のjsonファイルパス] --type kubernetes.io/dockerconfigjson

「認証情報のjsonファイルパス」は、OS毎に異なります。MacOSの場合は、~/.docker/config.jsonです。

3. SecretをServiceAccountにリンクする


手順2で作成したSecretがImageStreamに紐づいていないと、pullすることができないので、ServiceAccountにリンクします。

$ oc secrets link default [手順2で作成したSecret名]

S2Iビルダーイメージを利用する場合は、builderのServiceAccountにリンクします。

$ oc secrets link builder [手順2で作成したSecret名]

4. ImageStreamを登録する


以下のコマンドでImageStreamを登録します。

$ oc import-image [ImageStream名] --from [外部レジストリのURL] --confirm

 

以上です。

NamespaceをプロビジョニングするOperatorを作ってみる(サイズ編)

以下の記事で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に時間を取られてしまいました。。。

Operator SDK をインストールしてOperatorを作成する(2/2)

Operator SDKをMacOSにインストールしてgolangでOperatorを作成します。以下の記事の続きでは、Operator SDKのインストールとソースコードの作成を行いました。本記事ではOperatorのビルドとデプロイを実施します。

Operator SDK をインストールしてOperatorを作成する(1/2)

  • Operatorのビルド
  • Operatorのデプロイ
    • CRDの作成
    • Opearatorのデプロイ
    • CRの作成
    • CRの更新

Operatorのビルド


以下のコマンドでOperatorのビルドを実施します。「Operator build complete. 」が出力されれば完了です。

$ go mod vendor
$ operator-sdk build 192.168.64.2:32000/hello-operator:v1
INFO[0008] Building OCI image 192.168.64.2:32000/hello-operator:v1 
Sending build context to Docker daemon  118.1MB
Step 1/7 : FROM registry.access.redhat.com/ubi7/ubi-minimal:latest
 ---> 7d397bf59678
Step 2/7 : ENV OPERATOR=/usr/local/bin/hello-operator     USER_UID=1001     USER_NAME=hello-operator
 ---> Using cache
 ---> d4043ef43b60
Step 3/7 : COPY build/_output/bin/hello-operator ${OPERATOR}
 ---> a40f16af6556
Step 4/7 : COPY build/bin /usr/local/bin
 ---> e758ef7bd8d0
Step 5/7 : RUN  /usr/local/bin/user_setup
 ---> Running in fc181e29564c
+ 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 fc181e29564c
 ---> 8b5e7541c344
Step 6/7 : ENTRYPOINT ["/usr/local/bin/entrypoint"]
 ---> Running in 903d3d9e95d9
Removing intermediate container 903d3d9e95d9
 ---> b4fff90c4f99
Step 7/7 : USER ${USER_UID}
 ---> Running in 8333de254bce
Removing intermediate container 8333de254bce
 ---> 6a813292199d
Successfully built 6a813292199d
Successfully tagged 192.168.64.2:32000/hello-operator:v1
INFO[0022] Operator build complete.

イメージ名は、[multipass-vmのIPアドレス]:[Docker RegistryのNodePort]/[イメージ名]:[タグ]にしています。今回はMicroK8s上のDocker Registryコンテナにpushするので、multipass-vmのIPアドレスとしています。

以下のコマンドでMicroK8s上のDocker Registryにpushしておきます。

$ docker push 192.168.64.2:32000/hello-operator:v1
The push refers to repository [192.168.64.2:32000/hello-operator]
afb9afe8923c: Pushed 
f507dc8f12b6: Pushed 
570d657b8592: Pushed 
da37c371002e: Layer already exists 
e0a4651703ad: Layer already exists 
v1: digest: sha256:ff4b946376cf992ca5fd83208eb0547eb9dc782f7331633f8712f03b8f86aad4 size: 1363

Operatorのデプロイ


上記でビルドしたOperatorをデプロイします。

CRDの作成


kind: Helloが作成できるように、deploy/crds配下に作成された以下のyamlを使ってCRDを作成します。

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: hellos.hello.example.com
spec:
  group: hello.example.com
  names:
    kind: Hello
    listKind: HelloList
    plural: hellos
    singular: hello
  scope: Namespaced
  subresources:
    status: {}
  validation:
    openAPIV3Schema:
      properties:
        apiVersion:
          type: string
        kind:
          type: string
        metadata:
          type: object
        spec:
          type: object
        status:
          type: object
  version: v1alpha1
  versions:
  - name: v1alpha1
    served: true
    storage: true

以下のコマンドで作成します。

$ kubectl create -f deploy/crds/hello_v1alpha1_hello_crd.yaml -n hello-operator

Operatorのデプロイ


Operatorをデプロイします。Operator SDKでプロジェクトを作成すると、deploy配下にデプロイ用のyamlが作成されます。deploy/operator.yamlのイメージ名を変更して、$ kubectl create -f [ファイル名]でデプロイします。

$ kubectl create -f deploy/operator.yaml -n hello-operator
deployment.apps/hello-operator created
$ kubectl create -f deploy/role.yaml -n hello-operator
role.rbac.authorization.k8s.io/hello-operator created
$ kubectl create -f deploy/role_binding.yaml -n hello-operator
rolebinding.rbac.authorization.k8s.io/hello-operator created
$ kubectl create -f deploy/service_account.yaml -n hello-operator
serviceaccount/hello-operator created

OperatorのステータスがRunningになれば大丈夫です。

$ kubectl get pods -n hello-operator -w
NAME                              READY   STATUS    RESTARTS   AGE
hello-operator-64f7df787d-5tq8f   1/1     Running   0          103s

CRの作成


deploy/crds配下に作成されたyamlを以下の通り編集し、CRを作成します。最初はhelloconfigにhelloaを指定してhelloaアプリケーションを作成します。

apiVersion: hello.example.com/v1alpha1
kind: Hello
metadata:
  name: example-hello
spec:
  # Add fields here
  size: 1
  helloconfig: helloa

以下のコマンドで作成すると、自動でhelloaアプリケーションが作成されます。

$ kubectl create -f hello_v1alpha1_hello_cr.yaml -n hello-operator 
hello.hello.example.com/example-hello created
$ kubectl get pods -n hello-operator -o wide
NAME                              READY   STATUS    RESTARTS   AGE    IP          NODE          NOMINATED NODE   READINESS GATES
example-hello-pod                 1/1     Running   0          16s    10.1.2.19   microk8s-vm   <none>           <none>
hello-operator-64f7df787d-5tq8f   1/1     Running   0          113s   10.1.2.18   microk8s-vm   <none>           <none>

httpリクエストをすると、Hello Aと表示されます。

$ curl 10.1.2.19:8080
Hello A

CRの更新


helloconfigの値をhelloaからhellobに変更して、hellobアプリケーションをデプロイします。以下のコマンドを使ってhelloconfigの値を変更します。

$ kubectl patch hello.hello.example.com/example-hello -n hello-operator --type merge -p '{"spec": {"helloconfig": "hellob"}}'
hello.hello.example.com/example-hello patched

自動でPodが更新されるかと思いましたが、イメージ名の変更なので自動再デプロイはされませんでした。従って、手動でPodを削除して更新します。(Podを削除してもCRは削除されないので、自動で再デプロイされます。)

$ kubectl delete pod example-hello-pod -n hello-operator
pod "example-hello-pod" deleted
$ kubectl get pods -n hello-operator -o wide
NAME                              READY   STATUS    RESTARTS   AGE   IP          NODE          NOMINATED NODE   READINESS GATES
example-hello-pod                 1/1     Running   0          12s   10.1.2.20   microk8s-vm   <none>           <none>
hello-operator-64f7df787d-5tq8f   1/1     Running   0          14m   10.1.2.18   microk8s-vm   <none>           <none>

httpリクエストすると、example-hello-podがhellobアプリケーションであることがわかります。

$ curl 10.1.2.20:8080
Hello B

以上です。まずはOperator SDKの使い方がわかって良かったです。

Operator SDK をインストールしてOperatorを作成する(1/2)

Operator SDKをMacOSにインストールしてgolangでOperatorを作成します。長くなってしまうので、2回に分けます。

  • Operator SDKのインストール
  • Operatorを作成する
    • プロジェクトを作成する
    • CRDを作成する
    • Controllerを追加する

Operator SDKのインストール


MacOSにインストールする方法は、以下の二通りあります。今回はGitHub releaseからのインストールを実施します。インストール方法は、こちらを参考にしています。

  • GitHub releaseからのインストール
  • brewコマンドからのインストール

以下のコマンドでOperator SDKコマンドをダウンロードします。

$ RELEASE_VERSION=v0.8.0
$ curl -OJL https://github.com/operator-framework/operator-sdk/releases/download/${RELEASE_VERSION}/operator-sdk-${RELEASE_VERSION}-x86_64-apple-darwin
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   652  100   652    0     0    860      0 --:--:-- --:--:-- --:--:--   859
100 68.0M  100 68.0M    0     0   997k      0  0:01:09  0:01:09 --:--:-- 1196k
curl: Saved to filename 'operator-sdk-v0.8.0-x86_64-apple-darwin'

Operator SDKのコマンドを/usr/local/bin配下にコピーします。

$ chmod +x operator-sdk-${RELEASE_VERSION}-x86_64-apple-darwin
$ sudo cp operator-sdk-${RELEASE_VERSION}-x86_64-apple-darwin /usr/local/bin/operator-sdk

これでOperator SDKコマンドが利用できるようになりました。

$ operator-sdk version
operator-sdk version: v0.8.0, commit: 78c472461e75e6c64589cfadf577a2004b8a26b3

Operatorを作成する


CRの値に応じてデプロイするイメージを切り替えるhello-operatorを作成します。helloconfigの値にhelloaもしくはhellobが指定されると、該当のイメージをデプロイするOperatorです。実用的ではないですが、まずは動くものを作っていきたいと思います。

helloaとhellobのコンテナイメージは、MicroK8sでIstioによるABテストを試してみる(1/2)で作成しています。よろしければご参照ください。

プロジェクトを作成する


以下のコマンドでプロジェクトを作成します。コマンドを実行すると、Operatorを作成するための雛形が作成されます。

$ operator-sdk new hello-operator
~~省略~~
INFO[0073] Project creation complete.

CRDを作成する


以下のコマンドを実行して、CRD用のAPIを作成します。

$ cd hello-operator/
$ operator-sdk add api --api-version=hello.example.com/v1alpha1 --kind=Hello

pkg/apis/hello/v1alpha1/hello_types.goが作成されるので、CRで設定できる設定項目を定義します。今回は、6行目にHelloConfig string `json:”helloconfig”`を加えただけになります。

~~省略~~
type HelloSpec 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
    HelloConfig string `json:"helloconfig"`
}
~~省略~~

変更を加えたら、以下のコマンドで必要なコードを再生成します。

$ operator-sdk generate k8s
INFO[0001] Running deepcopy code-generation for Custom Resource group versions: [hello:[v1alpha1], ] 
INFO[0006] Code-generation complete.

Controllerを追加する


以下のコマンドを実行して、Controllerのテンプレートを作成します。作成が完了すると、pkg/controller/hello/hello_controller.go が作成されています。

$ operator-sdk add controller --api-version hello.example.com/v1alpha1 --kind Hello
INFO[0000] Generating controller version hello.example.com/v1alpha1 for kind Hello. 
INFO[0000] Created pkg/controller/hello/hello_controller.go 
INFO[0000] Created pkg/controller/add_hello.go          
INFO[0000] Controller generation complete.

hello_controller.goは、定義したCRに対してどのような制御を行うか記述します。以下の部分を編集しました。HelloConfigの値を取得し、イメージ名に代入しているだけになります。

~~省略~~
func newPodForCR(cr *hellov1alpha1.Hello) *corev1.Pod {
    helloconfig := cr.Spec.HelloConfig
    labels := map[string]string{
        "app": cr.Name,
    }
    return &corev1.Pod{
        ObjectMeta: metav1.ObjectMeta{
            Name:      cr.Name + "-pod",
            Namespace: cr.Namespace,
            Labels:    labels,
        },
        Spec: corev1.PodSpec{
            Containers: []corev1.Container{
                {
                    Name:    "hello",
                    Image:   "localhost:32000/" + helloconfig,
                },
            },
        },
    }
}
~~省略~~

今日はここまでとして、続きのビルドとデプロイは明日実施します。

以上です!

MicroK8sにOperator Lifecycle Manager(OLM) をインストールしてみる

今日はMicroK8sにOperatorインストールを試みました。MicroK8sはMicroK8sをMacOSにインストールしてみるでインストール済みですので、よろしければご参照ください。

  • はじめに
  • kubectlのインストール
  • OLMのインストール
    • system:auth-delegatorの作成
    • extension-apiserver-authentication-readerの作成
    • OLMのインストール
  • Operatorのインストール

はじめに


OLMは以下のコマンドでインストールできますが、MicroK8sのコマンドはmicrok8s.kubectlなのでスクリプト実行が失敗します。従って、kubectlコマンドをインストールしてからOLMをインストールします。(スクリプトのkubectlコマンドをmicrok8s.kubectlコマンドに変更するでも構いません。)

ubuntu@microk8s-vm:~$ curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/0.15.1/install.sh | bash -s 0.15.1

kubectlのインストール


kubectlコマンドをインストールします。

ubuntu@microk8s-vm:~$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 41.9M  100 41.9M    0     0  1006k      0  0:00:42  0:00:42 --:--:-- 1016k
ubuntu@microk8s-vm:~$ chmod +x ./kubectl
ubuntu@microk8s-vm:~$ sudo mv ./kubectl /usr/local/bin/kubectl

以下のコマンドでMicroK8sのconfigを確認し、~/.kube/configにコピーします。

ubuntu@microk8s-vm:~$ microk8s.kubectl config view

上記の対応だけだと以下のエラーが発生するので、clusters.cluster.insecure-skip-tls-verify: trueを追加します。

Unable to connect to the server: x509: certificate signed by unknown authority

最終的な~/.kube/configは以下の通りです。

apiVersion: v1
clusters:
- cluster:
    insecure-skip-tls-verify: true
    server: https://127.0.0.1:16443
  name: microk8s-cluster
contexts:
- context:
    cluster: microk8s-cluster
    user: admin
  name: microk8s
current-context: microk8s
kind: Config
preferences: {}
users:
- name: admin
  user:
    password: パスワード(インストール時に割り当てられています。)
    username: admin

OLMのインストール


以下のコマンドでインストールします。

ubuntu@microk8s-vm:~$ curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/0.15.1/install.sh | bash -s 0.15.1

しかし、CSVのpackageserverのインストールが失敗します。一応Podは作成されるのですが、TerminateとContainerCreatingを永遠に繰り返します。

CSV "packageserver" failed to reach phase succeeded

eventを確認すると、以下のエラーが確認できました。

35m         Warning   APIServiceResourceIssue            clusterserviceversion/packageserver     clusterrole.rbac.authorization.k8s.io "system:auth-delegator" not found
3m30s       Warning   APIServiceResourceIssue            clusterserviceversion/packageserver     role.rbac.authorization.k8s.io "extension-apiserver-authentication-reader" not found

どうやらKubernetesにデフォルトで設定されるroleがMicroK8sでは設定されていないようですので、system:auth-delegatorとextension-apiserver-authentication-readerを作成します。

system:auth-delegatorの作成


以下のyamlを使って、clusterroleであるsystem:auth-delegatorを作成します。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  managedFields:
  - apiVersion: rbac.authorization.k8s.io/v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .: {}
          f:rbac.authorization.kubernetes.io/autoupdate: {}
        f:labels:
          .: {}
          f:kubernetes.io/bootstrapping: {}
      f:rules: {}
    manager: kube-apiserver
    operation: Update
  name: system:auth-delegator
rules:
- apiGroups:
  - authentication.k8s.io
  resources:
  - tokenreviews
  verbs:
  - create
- apiGroups:
  - authorization.k8s.io
  resources:
  - subjectaccessreviews
  verbs:
  - create

extension-apiserver-authentication-readerの作成


以下のyamlを使って、system:auth-delegator roleを作成します。

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  annotations:
    openshift.io/reconcile-protect: "false"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: extension-apiserver-authentication-reader
  namespace: kube-system
rules:
- apiGroups:
  - ""
  attributeRestrictions: null
  resourceNames:
  - extension-apiserver-authentication
  resources:
  - configmaps
  verbs:
  - get

OLMのインストール


以下のコマンドでリトライすると、エラーなくインストールが完了しました。PodがTerminateとCreatingを繰り返すということも無くなりました。

ubuntu@microk8s-vm:~$ curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/0.15.1/install.sh | bash -s 0.15.1
customresourcedefinition.apiextensions.k8s.io/catalogsources.operators.coreos.com created
customresourcedefinition.apiextensions.k8s.io/clusterserviceversions.operators.coreos.com created
customresourcedefinition.apiextensions.k8s.io/installplans.operators.coreos.com created
customresourcedefinition.apiextensions.k8s.io/operatorgroups.operators.coreos.com created
customresourcedefinition.apiextensions.k8s.io/subscriptions.operators.coreos.com created
namespace/olm created
namespace/operators created
serviceaccount/olm-operator-serviceaccount created
clusterrole.rbac.authorization.k8s.io/system:controller:operator-lifecycle-manager unchanged
clusterrolebinding.rbac.authorization.k8s.io/olm-operator-binding-olm unchanged
deployment.apps/olm-operator created
deployment.apps/catalog-operator created
clusterrole.rbac.authorization.k8s.io/aggregate-olm-edit unchanged
clusterrole.rbac.authorization.k8s.io/aggregate-olm-view unchanged
operatorgroup.operators.coreos.com/global-operators created
operatorgroup.operators.coreos.com/olm-operators created
clusterserviceversion.operators.coreos.com/packageserver created
catalogsource.operators.coreos.com/operatorhubio-catalog created
Waiting for deployment "olm-operator" rollout to finish: 0 of 1 updated replicas are available...
deployment "olm-operator" successfully rolled out
Waiting for deployment "catalog-operator" rollout to finish: 0 of 1 updated replicas are available...
deployment "catalog-operator" successfully rolled out
Package server phase: Installing
Package server phase: Succeeded
deployment "packageserver" successfully rolled out

Operatorのインストール


せっかくOLMをインストールしたので、試しにOperatorHubからGrafana Operatorをインストールしてみます。

ubuntu@microk8s-vm:~$ kubectl create -f https://operatorhub.io/install/grafana-operator.yaml
namespace/my-grafana-operator created
operatorgroup.operators.coreos.com/operatorgroup created
subscription.operators.coreos.com/my-grafana-operator created
ubuntu@microk8s-vm:~$ kubectl get csv -n my-grafana-operator
NAME                      DISPLAY            VERSION   REPLACES   PHASE
grafana-operator.v3.2.0   Grafana Operator   3.2.0                Installing

少し待つと、インストールが完了しました。

ubuntu@microk8s-vm:~$ kubectl get csv -n my-grafana-operator
NAME                      DISPLAY            VERSION   REPLACES   PHASE
grafana-operator.v3.2.0   Grafana Operator   3.2.0                Succeeded

本当はこの後Grafanaリソースを作成しますが、OLMが動いていることが確認できたので、今日はここまでとします。

以上です。

Operatorsを調査してみる

今日はOperatorsを調査しましたのでメモしました。

  • Operatorsとは
    • Operator Frameworkとは
      • Operator Lifecycle Managerとは
      • Operator SDKとは
      • Operator Meteringとは

Operatorsとは


Operatorsは、KubernetesアプリケーションをKubernetes APIやkubectlコマンドによってパッケージ化・デプロイ・管理する方法です。Kubernetesが提供していないリソースを自分で作成し、それをKubernetes APIやkubectlで操作することによってKubernetesアプリケーションを管理します。

参考元)https://coreos.com/operators/

運用のナレッジをソフトウェアに落とし込むことによって、運用の自動化を促進するだけでなく、他のユーザーと共有できることを目的としています。その象徴として、OperatorHubはKubernetes Communityで共有されたOperatorがアップロードされており、誰でも利用することができます。

参考元)https://www.redhat.com/en/blog/introducing-operator-framework-building-apps-kubernetes

Operator Frameworkとは


Operator Frameworkとは、OperatorのOSSツールキットです。Operator Frameworkには、以下の三つが備わっています。

  • Operator Lifecycle Manager
  • Operator SDK
  • Operator Metering

Operator Lifecycle Managerとは


Operator Lifecycle Managerとは、Operatorにおけるインストール・アップデート・管理などのライフサイクルを管理する機能を提供します。具体的には以下の機能があります。

  • Over-the-Air Updates and Catalogs・・・インストールして最新に保つためのカタログの概念で、メンテナーはアップデートパスをきめ細かくオーサリングできます。
  • Dependency Model・・・プラットフォームや他Operatorとの依存関係を表現します。
  • Discoverability・・・インストール可能なOperatorを検知し、管理者に表示します。
  • Cluster Stability・・・同じAPIを利用するOperator同士の衝突を防ぎ、クラスターの安定性を保ちます。
  • Declarative UI controls・・・コマンドラインがAPIであるため、GUIを使ってOperatorとの対話に豊富なインタフェースを付け加えることが可能です。

参考元)https://github.com/operator-framework/operator-lifecycle-manager/

Operator SDKとは


Operator SDKは、Kubernetes APIの知識が無くてもOperatorが開発できるようにするため、以下を提供しています。

  • より直感的に操作ロジックを作成するための高レベルAPIおよび抽象化
  • 新しいプロジェクトを素早くブートストラップするためのコード生成ツール
  • 一般的なオペレーターの使用例をカバーする拡張機能

SDKは、Golang、Ansible、Helmを利用することができます。

参考元)https://sdk.operatorframework.io/docs/

Operator Meteringとは


Operator Meteringとは、Kubernetesの使用量を観測しレポートする機能を提供します。CPUやメモリをレポートするだけでなく、AWSなどのCloudと連携することによってIaaSコストもレポートすることができるようになります。

参考元)https://www.redhat.com/en/blog/introducing-operator-framework-building-apps-kubernetes

ArgoRolloutsとIstioを連携したカナリアデプロイ

MicroK8sにArgoCDをインストールするでMicroK8sにArgoCDをインストールしたので機能を試しています。その一貫として、今日はItioとRolloutsを連携してカナリアデプロイメントを試します。

  • 事前準備
    • Itioをインストールする
    • Argo Rolloutsをインストールする
  • yamlを作成する
    • Rolloutを作成する
    • Gatewayを作成する
    • VirtualServiceを作成する
    • Serviceを作成する
  • デプロイする
    • Githubに登録する
    • ArgoCDのApplicationを作成する
    • GithubとArgoCDを同期する
  •  カナリアデプロイをする
    • Rolloutを編集する
    • GithubとArgoCDを同期する
    • カナリアデプロイを確認する

事前準備


Itioをインストールする


MicroK8sのIstioを有効化して、Istioをインストールします。インストールは以下の記事で実施しているので、今回はスキップします。

MicroK8sでIstioによるABテストを試してみる(1/2)

Argo Rolloutsをインストールする


カナリアデプロイをするにはRolloutというリソースが必要になるので、以下のURLを参考にそのArgo Rolloutsをインストールします。

https://argoproj.github.io/argo-rollouts/features/kubectl-plugin/

ArgoCDによるBlueGreenデプロイを試すでインストール済みなので、今回はスキップします。

yamlを作成する


以下のURLを参考に、IstioとArgo Rolloutsのyamlを作成します。

https://argoproj.github.io/argo-rollouts/features/traffic-management/istio/

Rolloutを作成する


IstioのSubsetでバージョン毎に流量制御を行うで作成したhelloaアプリケーションのv1/v2を利用して、カナリアデプロイ用のRolloutを作成します。

以下のyamlを使って、レプリカ数 4個のhelloa:v1アプリケーションを実行するhelloa-istio-canaryを作成します。helloa-istio-canaryは、アプリケーションアップデート時に25%分(1個)だけ新しいバージョンをデプロイし、30秒間canary-svcを経由してアクセスを受け付けます。30秒経過すると、新しいバージョンのアプリケーションを100%分(4個)デプロイし、stable-svcを経由してアクセスを受け付けます。

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: helloa-istio-canary
  namespace: istio-app
spec:
  replicas: 4
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: helloa
  template:
    metadata:
      labels:
        app: helloa
    spec:
      containers:
      - name: helloa
        image: localhost:32147/helloa:v1
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
  strategy:
    canary:
      steps:
      - setWeight: 25
      - pause:
          duration: 30s
      canaryService: canary-svc
      stableService: stable-svc
      trafficRouting:
        istio:
           virtualService:
            name: rollout-vsvc
            routes:
            - primary

Serviceを作成する


helloa-istio-canaryで指定したServiceを作成します。以下のyamlで、helloaをポート8080で公開するcanary-svcとstable-svcを作成します。

kind: Service
apiVersion: v1
metadata:
  name: canary-svc
  namespace: istio-app
spec:
  selector:
    app: helloa
  type: ClusterIP
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
---
kind: Service
apiVersion: v1
metadata:
  name: stable-svc
  namespace: istio-app
spec:
  selector:
    app: helloa
  type: ClusterIP
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080

Gatewayを作成する


外部からの通信を受け付けるために、IstioのGatewayを作成します。以下のyamlは、デフォルトのGatewayに対する80番ポートを全て受け付けるGatewayです。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: helloa-gateway
  namespace: istio-app
spec:
  selector:
    istio: ingressgateway # use Istio default gateway implementation
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

VirtualServiceを作成する


以下のyamlを使って、canary-svcとstable-svcに通信を割り振るrollout-vsvcを作成します。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: rollout-vsvc
  namespace: istio-app
spec:
  hosts:
  - "*"
  gateways:
  - helloa-gateway
  http:
    - name: primary
      route:
        - destination:
            host: stable-svc
          weight: 100
        - destination:
            host: canary-svc
          weight: 0

デプロイする


Githubに登録する


上記で作成したyamlをGitHubに登録し、その後以下のコマンドでArgoCDにGitHubを登録します。

$ argocd repo add https://github.com/[user名]/[レポジトリ名] --username [user名]

ArgoCDのApplicationを作成する


以下のコマンドで、登録したGitレポジトリを使ってApplicationを作成します。デプロイ先のnamespaceはIstioインジェクション済みであることに注意してください。インジェクション方法は、MicroK8sでIstioによるABテストを試してみる(2/2)で紹介しています。

$ argocd app create istio-canary --repo [レポジトリURL] --path [ディレクトリ] --dest-namespace [Injection済みnamespace] --dest-server [ClusterURL]

GithubとArgoCDを同期する


以下のコマンドで、ApplicationをGitHubと同期してyamlからデプロイします。

$ argocd app sync istio-canary

作成が完了すると、以下の図のようになります。

カナリアデプロイをする


Rolloutを編集する


helloa-istio-canaryのイメージタグをv1からv2に変更し、再度GitHubに登録します。

image: localhost:32147/helloa:v2

GithubとArgoCDを同期する


再度以下のコマンドでGitHubと同期して変更を反映します。

$ argocd app sync istio-canary

カナリアデプロイを確認する


GUIで確認すると、以下のように25%分をデプロイしていることがわかります。(以下の画像は、一世代前のPodが削除される直前です。)

通信経路を確認すると、stable-svcに75%(1世代前のPod)、canary-svcに25%(新規Pod)通信を割り振っていることがわかります。(以下の画像は、一世代前のPodが削除される直前です。)

30秒後、以下のように新規Podが100%になります。

最終的には以下の画面になります。一世代前のReplicaSetが残っていることが確認できます。

以上です!

ArgoCDのCanary Deploymentを試す

MicroK8sにArgoCDをインストールするでMicroK8sにArgoCDをインストールしたので機能を試しています。その一貫として、今日はCanary Deployment(カナリアデプロイメント)を試したいと思います。

  • Argo Rolloutsをインストールする
  • Rolloutを作成する
    • カナリアデプロイ用のRolloutを作成する
    • Serviceを作成する
  • カナリアデプロイを実行する

Argo Rolloutsをインストールする


カナリアデプロイをするにはRolloutというリソースが必要になるので、以下のURLを参考にそのArgo Rolloutsをインストールします。

https://argoproj.github.io/argo-rollouts/features/kubectl-plugin/

ArgoCDによるBlueGreenデプロイを試すでインストール済みなので、今回はスキップします。

Rolloutを作成する


以下の記事を参考に、カナリアデプロイ用のRolloutを作成します。

https://argoproj.github.io/argo-rollouts/features/canary/

カナリアリリース用のRolloutを作成する


IstioのSubsetでバージョン毎に流量制御を行うで作成したhelloaアプリケーションのv1/v2を利用して、カナリアリリース用のRolloutを作成します。

以下のyamlを使って、レプリカ数 4個のhelloa:v1アプリケーションを実行するhelloa-canaryを作成します。helloa-canaryは、アプリケーションアップデート時に25%分だけ新しいバージョンをデプロイし、その10秒後に50%になるようデプロイします。

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: helloa-canary
spec:
  replicas: 4
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: helloa
  template:
    metadata:
      labels:
        app: helloa
    spec:
      containers:
      - name: helloa
        image: localhost:32147/helloa:v1
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
  strategy:
    canary:
      steps:
      - setWeight: 25
      - pause:
          duration: 10 # 10 seconds
      - setWeight: 50
      - pause: {} # pause indefinitely

spec.strategy.canaryブロックでカナリアデプロイの設定が行えます。詳細なオプションについては、以下のURLを参照してください。

https://argoproj.github.io/argo-rollouts/features/canary/

作成すると、以下のようにPodとReplicaSetが作成されます。

ubuntu@microk8s-vm:~$ microk8s.kubectl create -f rollout.yaml 
rollout.argoproj.io/helloa-canary created
ubuntu@microk8s-vm:~$ microk8s.kubectl get rs
NAME                       DESIRED   CURRENT   READY   AGE
helloa-canary-5d86bb8b54   4         4         4       23s
ubuntu@microk8s-vm:~$ microk8s.kubectl get pods 
NAME                             READY   STATUS    RESTARTS   AGE
helloa-canary-5d86bb8b54-2djjc   1/1     Running   0          7s
helloa-canary-5d86bb8b54-hr8kf   1/1     Running   0          7s
helloa-canary-5d86bb8b54-nzdcl   1/1     Running   0          7s
helloa-canary-5d86bb8b54-spzcb   1/1     Running   0          7s

Serviceを作成する


上記で作成したhelloaを公開するため、NodePort 32000ポートで公開するcanary Serviceを作成します。

kind: Service
apiVersion: v1
metadata:
  name: canary
spec:
  selector:
    app: helloa
  type: NodePort
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
    nodePort: 32000

上記のyamlファイルを使い、以下のコマンドでcanary Serviceを作成します。

ubuntu@microk8s-vm:~/argocd/canary$ microk8s.kubectl create -f service.yaml 
service/canary created
ubuntu@microk8s-vm:~/argocd/canary$ microk8s.kubectl get svc canary
NAME     TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
canary   NodePort   10.152.183.151   <none>        8080:32000/TCP   9s

httpリクエストを送ると、以下の結果が返ってきます。

$ curl 192.168.64.2:32000
Hello A

カナリアデプロイを実行する


helloaのタグをv2にして、カナリアデプロイを実行します。

以下のコマンドで、helloaのタグをv1からv2に変更します。

ubuntu@microk8s-vm:~/argocd/canary$ microk8s.kubectl patch rollout helloa-canary --type merge -p '{"spec": {"template": {"spec": {"containers": [{"name":"helloa","image":"localhost:32147/helloa:v2"}]}}}}'
rollout.argoproj.io/helloa-canary patched

若干のタイムラグはあるのですが、以下のコマンドでデプロイ状況を確認しました。最初は3割v2に割り振られ、その10秒後に5割v2に割り振られているので、指定した割合と近しい値が出ていることを確認できました。

$ while true; do  date "+TIME: %H:%M:%S"; curl 192.168.64.2:32000; sleep 1; done
TIME: 20:54:19←setWeight 25%
Hello A
TIME: 20:54:20
Hello A v2
TIME: 20:54:21
Hello A
TIME: 20:54:22
Hello A
TIME: 20:54:23
Hello A
TIME: 20:54:24
Hello A
TIME: 20:54:25
Hello A v2
TIME: 20:54:26
Hello A
TIME: 20:54:27
Hello A
TIME: 20:54:28
Hello A v2
TIME: 20:54:29←setWeight 50%
Hello A
TIME: 20:54:30
Hello A
TIME: 20:54:31
Hello A
TIME: 20:54:32
Hello A v2
TIME: 20:54:33
Hello A v2
TIME: 20:54:34
Hello A v2
TIME: 20:54:35
Hello A
TIME: 20:54:36
Hello A
TIME: 20:54:37
Hello A v2
TIME: 20:54:38
Hello A v2

Podの状況を確認すると、指定した通りv1, v2のPodが2個ずつデプロイされて止まっていました。

ubuntu@microk8s-vm:~$ microk8s.kubectl get rs
NAME                       DESIRED   CURRENT   READY   AGE
helloa-canary-5695f95f6b   2         2         2       80s
helloa-canary-5d86bb8b54   2         2         2       15m
ubuntu@microk8s-vm:~$ microk8s.kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
helloa-canary-5695f95f6b-9mlh9   1/1     Running   0          76s
helloa-canary-5695f95f6b-9ppnk   1/1     Running   0          64s
helloa-canary-5d86bb8b54-hr8kf   1/1     Running   0          15m
helloa-canary-5d86bb8b54-spzcb   1/1     Running   0          15m

以上です。