ry's Tech blog

Cloud Native技術などについて書いていきます。

入門 vcluster

What's vcluster

今回ご紹介させていただくのは、Virtual Clusters For Kubernetesを実現するvclusterです!

既存のkubernetes上で簡単に仮想的なkubernetes clusterを構築することができます。

Architecture

f:id:ryo_xjsbx:20210521230607p:plain
vcluster-1

Basics | vcluster docs | Virtual Clusters for Kubernetes

主なコンポーネントとしては以下の2つです。

以下のコンポーネントは、stateful-setによってスケジューリングされるpodの中に2つのコンテナとして作成されます。

  • control-plane:
    • このコンテナは、API サーバー、コントローラマネージャ、およびデータストアの接続 (またはマウント) の役割を果たします。
    • デフォルトでは、vclusterは sqlite をデータストアとして使用し、K3s の API サーバーとコントローラーマネージャーを実行します。
  • syncer:
    • vcluster上のPod等のリソースは、ホストクラスター上でcontrol-planeを展開したNamespace上に集約されます。syncerはその仮想cluster上のポッドとhost cluster上のポッドの同期を維持します。

Installation

Download vcluster CLI

まず、vcluster cliを導入していきます。

# curl -s -L "https://github.com/loft-sh/vcluster/releases/latest" | sed -nE 's!.*"([^"]*vcluster-linux-amd64)".*!https://github.com\1!p' | xargs -n 1 curl -L -o vcluster && chmod +x vcluster;
sudo mv vcluster /usr/local/bin;

Create a vcluster

準備はこれだけです。 では、以下のコマンドを実行しましょう。

※ こちらを用いる際、helmをインストールしておいてください。

# vcluster create vcluster-1 -n vcluster-system

kubectlでインストールする場合は、こちらのmanifestを利用してください。

▸ vcluster.yaml (clickして展開)

apiVersion: v1
kind: ServiceAccount
metadata:
  name: vcluster-1
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: vcluster-1
rules:
  - apiGroups: [""]
    resources: ["configmaps", "secrets", "services", "services/proxy", "pods", "pods/proxy", "pods/attach", "pods/portforward", "pods/exec", "pods/log", "events", "endpoints", "persistentvolumeclaims"]
    verbs: ["*"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["*"]
  - apiGroups: [""]
    resources: ["namespaces"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["statefulsets"]
    verbs: ["get", "list", "watch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: vcluster-1
subjects:
  - kind: ServiceAccount
    name: vcluster-1
roleRef:
  kind: Role
  name: vcluster-1
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Service
metadata:
  name: vcluster-1
spec:
  type: ClusterIP
  ports:
    - name: https
      port: 443
      targetPort: 8443
      protocol: TCP
  selector:
    app: vcluster-1
---
apiVersion: v1
kind: Service
metadata:
  name: vcluster-1-headless
spec:
  ports:
    - name: https
      port: 443
      targetPort: 8443
      protocol: TCP
  clusterIP: None
  selector:
    app: vcluster-1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: vcluster-1
  labels:
    app: vcluster-1
spec:
  serviceName: vcluster-1-headless
  replicas: 1
  selector:
    matchLabels:
      app: vcluster-1
  template:
    metadata:
      labels:
        app: vcluster-1
    spec:
      terminationGracePeriodSeconds: 10
      serviceAccountName: vcluster-1
      containers:
      - image: rancher/k3s:v1.19.5-k3s2
        name: virtual-cluster
        command:
          - "/bin/k3s"
        args:
          - "server"
          - "--write-kubeconfig=/k3s-config/kube-config.yaml"
          - "--data-dir=/data"
          - "--disable=traefik,servicelb,metrics-server,local-storage"
          - "--disable-network-policy"
          - "--disable-agent"
          - "--disable-scheduler"
          - "--disable-cloud-controller"
          - "--flannel-backend=none"
          - "--kube-controller-manager-arg=controllers=*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle"  
          - "--service-cidr=10.96.0.0/12"  
        volumeMounts:
          - mountPath: /data
            name: data
      - name: syncer
        image: "loftsh/virtual-cluster:0.0.27"
        args:
          - --service-name=vcluster-1
          - --suffix=vcluster-1
          - --owning-statefulset=vcluster-1
          - --out-kube-config-secret=vcluster-1
        volumeMounts:
          - mountPath: /data
            name: data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: 5Gi

namespaceを作成してからapplyしていきます。

# kubectl create ns vcluster-system
namespace/vcluster-system created

# kubectl apply -f vcluster.yaml -n vcluster-system
serviceaccount/vcluster-1 created
role.rbac.authorization.k8s.io/vcluster-1 created
rolebinding.rbac.authorization.k8s.io/vcluster-1 created
service/vcluster-1 created
service/vcluster-1-headless created
statefulset.apps/vcluster-1 created

では、リソースができているか確認していきます。

# kubectl get pods,svc -n vcluster-system -o wide
NAME                                                      READY   STATUS    RESTARTS   AGE   IP           NODE                   NOMINATED NODE   READINESS GATES
pod/coredns-66c464876b-98ln7-x-kube-system-x-vcluster-1   1/1     Running   0          42h   172.17.0.4   minikube.mydom.local   <none>           <none>
pod/vcluster-1-0                                          2/2     Running   1          42h   172.17.0.3   minikube.mydom.local   <none>           <none>

NAME                                          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE   SELECTOR
service/kube-dns-x-kube-system-x-vcluster-1   ClusterIP   10.107.141.148   <none>        53/UDP,53/TCP,9153/TCP   42h   <none>
service/vcluster-1                            ClusterIP   10.96.161.85     <none>        443/TCP                  42h   app=vcluster-1
service/vcluster-1-headless                   ClusterIP   None             <none>        443/TCP                  42h   app=vcluster-1

Connect to the cluster

では接続していきたいと思います。

# vcluster connect vcluster-1 -n vcluster-system --local-port 9443 --kube-config vcluster.yaml &
[1] 9589
[done] √ Virtual cluster kube config written to: ./kubeconfig.yaml. You can access the cluster via `kubectl --kubeconfig ./kubeconfig.yaml get namespaces`
[info]   Starting port forwarding: kubectl port-forward --namespace vcluster-system vcluster-1-0 9443:8443
Forwarding from 127.0.0.1:9443 -> 8443

見ていただければわかると思いますが、裏で kubectl port-forward をしています。

以下のようなルールで書きます。

vcluster connect [cluster名] -n [control-planeがいるnamespace] --local-port [port-forwardするlocal port] --kube-config <kubeconfig ファイル名> &

では、実際に仮想clusterに接続していきます。

# kubectl get namespace --kubeconfig vcluster.yaml 
Handling connection for 9443
NAME              STATUS   AGE
default           Active   43h
kube-system       Active   43h
kube-public       Active   43h
kube-node-lease   Active   43h

先ほど作成した、vcluster-system namespaceは見えてきてないですね!

Create resources

では、実際にリソースを作成していきたいと思います。

まずnamespaceを作成していきます。

# kubectl create ns demo1 --kubeconfig vcluster.yaml

仮想clusterでは作成され、host cluster側には当然見えてきてないです。

/// host cluster
# kubectl get ns
NAME              STATUS   AGE
default           Active   119d
kube-node-lease   Active   119d
kube-public       Active   119d
kube-system       Active   119d
vcluster-system   Active   2d

/// 仮想cluster
# kubectl get ns --kubeconfig vcluster.yaml
NAME              STATUS   AGE
default           Active   2d
kube-system       Active   2d
kube-public       Active   2d
kube-node-lease   Active   2d
demo1             Active   93m

では、defaultdemo1 の2つのnamespaceにリソースを作成していきます。

kubectl create deploy nginx --image=nginx:1.19.5 --replicas=5 --kubeconfig vcluster.yaml
kubectl create deploy nginx --image=nginx:1.19.5 --replicas=3 -n demo1 --kubeconfig vcluster.yaml

以下の通り作成されました。

# kubectl get pods -n default -n default --kubeconfig vcluster.yaml
NAME                     READY   STATUS    RESTARTS   AGE
nginx-7c4b6d99fd-sjnh2   1/1     Running   0          89m
nginx-7c4b6d99fd-r2rsh   1/1     Running   0          89m
nginx-7c4b6d99fd-7z6wx   1/1     Running   0          89m
nginx-7c4b6d99fd-4srv2   1/1     Running   0          89m
nginx-7c4b6d99fd-m5t62   1/1     Running   0          89m

# kubectl get pods -n default -n demo1 --kubeconfig vcluster.yaml
NAME                     READY   STATUS    RESTARTS   AGE
nginx-7c4b6d99fd-v67kb   1/1     Running   0          89m
nginx-7c4b6d99fd-wwswz   1/1     Running   0          89m
nginx-7c4b6d99fd-lbjdw   1/1     Running   0          89m

では、host clusterではどう見えているのか確認してみましょう。

# kubectl get pod -n vcluster-system
NAME                                                  READY   STATUS    RESTARTS   AGE
coredns-66c464876b-98ln7-x-kube-system-x-vcluster-1   1/1     Running   0          2d
nginx-7c4b6d99fd-4srv2-x-default-x-vcluster-1         1/1     Running   0          95m
nginx-7c4b6d99fd-7z6wx-x-default-x-vcluster-1         1/1     Running   0          95m
nginx-7c4b6d99fd-lbjdw-x-demo1-x-vcluster-1           1/1     Running   0          95m
nginx-7c4b6d99fd-m5t62-x-default-x-vcluster-1         1/1     Running   0          95m
nginx-7c4b6d99fd-r2rsh-x-default-x-vcluster-1         1/1     Running   0          95m
nginx-7c4b6d99fd-sjnh2-x-default-x-vcluster-1         1/1     Running   0          95m
nginx-7c4b6d99fd-v67kb-x-demo1-x-vcluster-1           1/1     Running   0          95m
nginx-7c4b6d99fd-wwswz-x-demo1-x-vcluster-1           1/1     Running   0          95m
vcluster-1-0                                          2/2     Running   1          2d

最初の方に説明した通り、仮想clusterで作成されたリソースが、control-planeのいるnamespaceに集約されていることがわかるかと思います。

[pod名]-x-[namespace名]-x-[vcluster名] という規則で作成されていることがわかります。

etc...

その他のリソースもどうなっているのか見ていきましょう。

service

サービスもPodと同様な規則でhost cluster側では見えてきます。

/// host cluster
# kubectl get service -n vcluster-system
NAME                                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
kube-dns-x-kube-system-x-vcluster-1   ClusterIP   10.107.141.148   <none>        53/UDP,53/TCP,9153/TCP   47h
vcluster-1                            ClusterIP   10.96.161.85     <none>        443/TCP                  47h
vcluster-1-headless                   ClusterIP   None             <none>        443/TCP                  47h

/// 仮想cluster
# kubectl get service -A --kubeconfig vcluster.yaml
Handling connection for 9443
NAMESPACE     NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
default       kubernetes   ClusterIP   10.96.161.85     <none>        443/TCP                  47h
kube-system   kube-dns     ClusterIP   10.107.141.148   <none>        53/UDP,53/TCP,9153/TCP   47h

Service Account

こちらは、Ageが違うので同期はされてなさそうです。

/// host cluster
# kubectl get sa -A
NAMESPACE          NAME                                 SECRETS   AGE
default            default                              1         119d
host-namespace-1   default                              1         47h
kube-node-lease    default                              1         119d
kube-public        default                              1         119d
kube-system        attachdetach-controller              1         119d
kube-system        bootstrap-signer                     1         119d
kube-system        certificate-controller               1         119d
kube-system        clusterrole-aggregation-controller   1         119d
kube-system        coredns                              1         119d
kube-system        cronjob-controller                   1         119d
kube-system        daemon-set-controller                1         119d
kube-system        default                              1         119d
kube-system        deployment-controller                1         119d
kube-system        disruption-controller                1         119d
kube-system        endpoint-controller                  1         119d
kube-system        endpointslice-controller             1         119d
kube-system        endpointslicemirroring-controller    1         119d
kube-system        expand-controller                    1         119d
kube-system        generic-garbage-collector            1         119d
kube-system        horizontal-pod-autoscaler            1         119d
kube-system        job-controller                       1         119d
kube-system        kube-proxy                           1         119d
kube-system        namespace-controller                 1         119d
kube-system        node-controller                      1         119d
kube-system        persistent-volume-binder             1         119d
kube-system        pod-garbage-collector                1         119d
kube-system        pv-protection-controller             1         119d
kube-system        pvc-protection-controller            1         119d
kube-system        replicaset-controller                1         119d
kube-system        replication-controller               1         119d
kube-system        resourcequota-controller             1         119d
kube-system        root-ca-cert-publisher               1         119d
kube-system        service-account-controller           1         119d
kube-system        service-controller                   1         119d
kube-system        statefulset-controller               1         119d
kube-system        storage-provisioner                  1         119d
kube-system        token-cleaner                        1         119d
kube-system        ttl-controller                       1         119d
vcluster-system    default                              1         47h
vcluster-system    vcluster-1                           1         47h

/// 仮想cluster
# kubectl get sa -A --kubeconfig vcluster.yaml
NAMESPACE         NAME                                 SECRETS   AGE
kube-system       pv-protection-controller             1         47h
kube-system       endpoint-controller                  1         47h
kube-system       daemon-set-controller                1         47h
kube-system       coredns                              1         47h
kube-system       service-controller                   1         47h
kube-system       job-controller                       1         47h
kube-system       disruption-controller                1         47h
kube-system       certificate-controller               1         47h
kube-system       clusterrole-aggregation-controller   1         47h
kube-system       pvc-protection-controller            1         47h
kube-system       endpointslice-controller             1         47h
kube-system       replication-controller               1         47h
kube-system       resourcequota-controller             1         47h
kube-system       namespace-controller                 1         47h
kube-system       horizontal-pod-autoscaler            1         47h
kube-system       statefulset-controller               1         47h
kube-system       pod-garbage-collector                1         47h
kube-system       deployment-controller                1         47h
kube-system       cronjob-controller                   1         47h
kube-system       ttl-controller                       1         47h
kube-system       endpointslicemirroring-controller    1         47h
kube-system       service-account-controller           1         47h
kube-system       generic-garbage-collector            1         47h
kube-system       replicaset-controller                1         47h
kube-system       default                              1         47h
kube-public       default                              1         47h
default           default                              1         47h
kube-node-lease   default                              1         47h
demo1             default                              1         7m19s

Node

Nodeは面白くて、Fake Kubernetes ImageというOS Imageを使ってるというように表記されてます。

# kubectl get node -A --kubeconfig vcluster.yaml -o wide
NAME                   STATUS   ROLES    AGE   VERSION   INTERNAL-IP       EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION      CONTAINER-RUNTIME
minikube.mydom.local   Ready    <none>   47h   v1.19.1   192.124.212.254   <none>        Fake Kubernetes Image   4.19.76-fakelinux   docker://19.3.12

# kubectl get nodes -o wide
NAME                   STATUS   ROLES                  AGE    VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION          CONTAINER-RUNTIME
minikube.mydom.local   Ready    control-plane,master   119d   v1.20.0   [node IP]       <none>        CentOS Linux 7 (Core)   3.10.0-957.el7.x86_64   docker://20.10.2

終わりに

一つのnamespaceに集約できるので、Resource Quotaを用いて簡単に、リソース制御ができるのはいいですね!

デモ環境を渡してあげたりとかが簡単にできると思うので、ぜひ使ってみてください。