🐕

vclusterを使ってKubernetesの開発プラットフォームをつくってみた

2023/09/28に公開

はじめに

D2Cのエンジニアの小川です。
ここ数年、D2Cではデータ分析回りの環境をEKSのKubernetes上で運用してきました。さまざまな課題があった中で、より難しいなと感じたのが以下になります。

  • 自由度の高さの代償
    マネージドサービスと比較して自由度が高く開発が可能だが、その代償として学習コストがとにかく高い(独自のリソース定義が必要、機能更新の頻度が多いなど)。また、運用が複雑化しやすい。

  • Kubernetesが提供しているマルチテナント戦略が微妙
    複数のユーザー(=テナント)がKubernetesリソースを共有する運用の場合、以下2つの管理方法が一般的だが、どちらもデメリットが目立つ。

    • クラスターをわける:コントロールプレーンがクラスター毎に必要になり、オーバーヘッドが大きくコスト高になりやすい。またコントロールプレーンの構築コストがかかる。
    • namespaceをわける:クラスタースコープのリソースも存在するためシステム分離が難しい。

この記事では、これらのKubernetesの運用における課題に焦点を当て、仮想化ツール「vcluster」を使ったプラットフォーム運用について紹介します。

vclusterとは

vclusterは「Virtual Kubernetes Cluster」の略称で、Kubernetesクラスターの上で仮想的なクラスターを立てられるツールです。ホストクラスターと異なり、vclusterの仮想クラスターはノードグループやネットワークリソースを持っておらず、代わりに独自のコントロールプレーンとsyncerというコンポーネントを持っています。これにより、以下のようにして仮想クラスターの一部ワークロードをホストクラスター上に同期する仕組みになっています。

  • vclusterの実態はホストクラスターのpod。vclusterを起動するとホストクラスターの指定のnamespace上でpodが立ち上がる。
  • vcluster上にlow-levelのリソース(pod, serviceなど)が作成されると、syncerがそれらをホストクラスター上にコピーする。その後ホストクラスターがスケジューリング機能によりvclusterとホストクラスター間の同期を保つ。
  • vcluster上にhigh-levelのリソース(deployment, statefulset, crdなど)が作成されると、vcluster内のデータストアに保存され管理される。

単一のホストクラスター内に複数の仮想クラスターを持つことができ、またシステム分離をvclusterに任せられることで、以下のように従来のマルチテナント戦略のデメリットをかなり解消することができます。これにより、多様なプラットフォーム提供が可能になります。

namespaceをわける クラスターをわける vclusterを使う
システム分離 ×
リソースの共有 ×
コスト ×
オーバーヘッド ×

vclusterを使ってやりたいこと

先にあげたKubernetes運用の問題で一番深刻なのは、「学習コストが高いこと」だと思っています。具体的には、学習コストが高いと、

  • 運用の属人化が起こる
  • → 数少ない運用メンバーがいなくなる
  • → サービスの安定運用が難しくなる

ということが起きやすいです。そうならないよう、vclusterを活用し、以下のような他のテナントを意識せずに気軽につくったり壊したりできる、学習のためのサンドボックスを作っていきます。

  • チームメンバーに1人1クラスター提供する。
  • ユーザーは自分のvclusterにのみアクセス可能で、ホストクラスターにはアクセスできないように、適切なアクセス制御を行う。
  • 学習に必要なバックエンドサービス(external-secret, aws-loadbalancer-controler)はvcluster起動時に自動デプロイするようにしておく。

構築手順

0. 環境

EKS上にクラスターと1つ以上のノードグループを立てておきます(ここの手順は割愛します)。
vclusterの構築では、kubectl, helm, helmfile, vclusterを使います。

$ kubectl version --short
Client Version: v1.27.0
Kustomize Version: v5.0.1
Server Version: v1.26.7-eks-2d98532

$ helm version
version.BuildInfo{Version:"v3.12.1", GitCommit:"f32a527a060157990e2aa86bf45010dfb3cc8b8d", GitTreeState:"clean", GoVersion:"go1.20.4"}

$ helmfile version

▓▓▓ helmfile

  Version            0.154.0
  Git Commit         c498af3
  Build Date         23 May 23 23:31 UTC (3 months ago)
  Commit Date        23 May 23 23:29 UTC (3 months ago)
  Dirty Build        no
  Go version         1.20.4
  Compiler           gc
  Platform           linux/amd64
 
$ vcluster version
vcluster version 0.15.7

以下、helmfileのディレクトリ構成です。

kubernetes/
├── ingress-nginx/
│   ├── config.yaml
│   └── helmfile.yaml
└── vcluster/
    ├── tom/
    │   ├── config.yaml
    │   └── helmfile.yaml
    └── jerry/
        ├── config.yaml
        └── helmfile.yaml

1. ホストクラスターにIngress-Nginx Controllerを起動する

デフォルトでは、vclusterへはホストクラスターのポートフォワーディング経由でアクセス可能です。しかし、今回はユーザーが直接vclusterにアクセスできるようにしたいので、vclusterの受け口としてIngressを立てる必要があります。そのため、予めホストクラスター上にIngress-Nginx Controllerを起動しておきます。

helmfile.yaml
repositories:
  - name: ingress-nginx 
    url: https://kubernetes.github.io/ingress-nginx

releases:
  - name: ingress-nginx 
    namespace: kube-system
    chart: ingress-nginx/ingress-nginx
    version: 4.7.2
    missingFileHandler: Error
    values:
      - image:
          tag: v1.8.2
      - ./config.yaml

またこの構成の場合、SSLパススルーの構成にする必要があるため、下記の通りに設定します。

config.yaml
controller:
  extraArgs: 
    enable-ssl-passthrough: ""

helmfile.yaml、config.yamlのあるディレクトリ配下で、以下コマンドを実行し、Ingress-Nginx Controllerをデプロイします。

$ ls
config.yaml  helmfile.yaml
$ helmfile apply
$ kubectl get po -n kube-system ingress-nginx-controller-6475df77f4-qzmf8
NAME                                        READY   STATUS    RESTARTS   AGE
ingress-nginx-controller-6475df77f4-qzmf8   1/1     Running   0          19h

2. ロードバランサーのドメイン登録をする

Ingress-Nginx Controller起動によりデプロイされたロードバランサーに、ドメインを登録します。
デプロイされたロードバランサーのDNSを確認し、

$ kubectl get svc -n kube-system ingress-nginx-controller-6475df77f4-qzmf8
NAME                                        TYPE           CLUSTER-IP   EXTERNAL-IP                         PORT(S)                      AGE
ingress-nginx-controller-6475df77f4-qzmf8   LoadBalancer   X.X.X.X      XXXXX.us-east-1.elb.amazonaws.com   80:30719/TCP,443:31194/TCP   3h37m

ロードバランサーのDNSに「tom-vcluster.example.com」「jerry-vcluster.example.com」の2名分のドメインを、以下のようにRoute53のコンソールより登録します。

3. vclusterを起動する

「tom-vcluster」「jerry-vcluster」の2つのvclusterを作ります。
以下[USERNAME]にはユーザー名を挿入します。

helmfile.yaml
repositories:
  - name: vcluster
    url: https://charts.loft.sh

releases:
  - name: [USERNAME]-vcluster
    namespace: [USERNAME]
    chart: vcluster/vcluster-eks
    version: 0.15.7
    missingFileHandler: Error
    values:
      - vcluster:
          iamge: rancher/k3s:v1.23.5-k3s1
      - ./config.yaml
config.yaml
syncer:
  extraArgs:
  # SSLの処理をvcluster上で行うため、TLS証明書のサブジェクト代替名としてホスト名を設定
  - --tls-san=[USERNAME]-vcluster.example.com
  # ホストクラスター外からvclusterにアクセスできるようにするために以下設定
  - --out-kube-config-server=https://[USERNAME]-vcluster.example.com

# vcluster毎にingressを作成する
ingress:
  enabled: true
  ingressClassName: "nginx"
  host: [USERNAME]-vcluster.example.com

# vcluster起動時にバックエンドサービスを自動デプロイするように設定する
init:
  helm:
  - chart:
      name: aws-load-balancer-controller
      repo: https://aws.github.io/eks-charts
      version: 1.5.3
    release:
      name: aws-load-balancer-controller
      namespace: kube-system
    values: |-
      clusterName: [USERNAME]-vcluster
      replicaCount: 1
      image:
        repository: 602401143452.dkr.ecr.us-east-1.amazonaws.com/amazon/aws-load-balancer-controller
      clusterSecretsPermissions:
        allowAllSecrets: true
  - chart:
      name: external-secrets
      repo: https://charts.external-secrets.io
      version: 0.8.3
    release:
      name: external-secrets
      namespace: kube-system
    values: |-
      podSecurityContext:
        fsGroup: 65534

helmfile.yaml、config.yamlのあるディレクトリ配下で、以下コマンドを実行し、vclusterをデプロイします。

$ ls
config.yaml  helmfile.yaml
$ helmfile apply

以下コマンドを実行するとvcluster用のkubeconfigが作成されるので、それをユーザーに渡します。

$ vcluster connect [USERNAME]-vcluster -n [USERNAME] --update-current=false --server=https://[USERNAME]-vcluster.example.com
done √ Virtual cluster kube config written to: ./kubeconfig.yaml
- Use `kubectl --kubeconfig ./kubeconfig.yaml get namespaces` to access the vcluster
$ ls ./kubeconfig.yaml
./kubeconfig.yaml

4. vclusterに接続する

ユーザーはvcluster用のkubeconfigを使い、vclusterに接続します。
config.yamlのinitで設定したバックエンドサービス(aws-load-balancer-controller, external-secrets)がデプロイされていることが確認できます。また、ホストクラスターのリソース(Ingress-Nginx Controllerなど)にはアクセスできないことも確認できます。

$ export KUBECONFIG=./kubeconfig.yaml
$ kubectl get all -A
NAMESPACE     NAME                                                    READY   STATUS    RESTARTS   AGE
kube-system   pod/aws-load-balancer-controller-5d76f7dbf7-jd9fk       1/1     Running   0          15h
kube-system   pod/coredns-559f46f85c-f5clj                            1/1     Running   0          15h
kube-system   pod/external-secrets-785fd778d7-c7sph                   1/1     Running   0          15h
kube-system   pod/external-secrets-cert-controller-5f4545cccf-fjn7q   1/1     Running   0          15h
kube-system   pod/external-secrets-webhook-c44997d8-jn7cz             1/1     Running   0          15h

NAMESPACE     NAME                                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
default       service/kubernetes                          ClusterIP   X.X.X.X          <none>        443/TCP                  15h
kube-system   service/aws-load-balancer-webhook-service   ClusterIP   X.X.X.X          <none>        443/TCP                  15h
kube-system   service/external-secrets-webhook            ClusterIP   X.X.X.X          <none>        443/TCP                  15h
kube-system   service/kube-dns                            ClusterIP   X.X.X.X          <none>        53/UDP,53/TCP,9153/TCP   15h

NAMESPACE     NAME                                               READY   UP-TO-DATE   AVAILABLE   AGE
kube-system   deployment.apps/aws-load-balancer-controller       1/1     1            1           15h
kube-system   deployment.apps/coredns                            1/1     1            1           15h
kube-system   deployment.apps/external-secrets                   1/1     1            1           15h
kube-system   deployment.apps/external-secrets-cert-controller   1/1     1            1           15h
kube-system   deployment.apps/external-secrets-webhook           1/1     1            1           15h

NAMESPACE     NAME                                                          DESIRED   CURRENT   READY   AGE
kube-system   replicaset.apps/aws-load-balancer-controller-5d76f7dbf7       1         1         1       15h
kube-system   replicaset.apps/coredns-559f46f85c                            1         1         1       15h
kube-system   replicaset.apps/external-secrets-785fd778d7                   1         1         1       15h
kube-system   replicaset.apps/external-secrets-cert-controller-5f4545cccf   1         1         1       15h
kube-system   replicaset.apps/external-secrets-webhook-c44997d8             1         1         1       15h

5. ホストクラスターからvclusterのリソースを眺めてみる

以下より、vclusterの実態となるリソース群が存在していることがわかります。

$ kubectl get all -A  | grep -v -e kube-system -e default -e "aws-load-balancer-controller" -e "external-secrets"
NAMESPACE     NAME                                                                  READY   STATUS    RESTARTS      AGE
jerry         pod/jerry-vcluster-api-677b754b88-wtdkv                               1/1     Running   1 (15h ago)   15h
jerry         pod/jerry-vcluster-c88458869-phqqd                                    1/1     Running   0             15h
jerry         pod/jerry-vcluster-controller-5ccd46c8f6-cvrrf                        1/1     Running   2 (15h ago)   15h
jerry         pod/jerry-vcluster-etcd-0                                             1/1     Running   0             15h
tom           pod/tom-vcluster-7698d45cd9-dktg6                                     1/1     Running   0             15h
tom           pod/tom-vcluster-api-75ccb87d7b-4drsc                                 1/1     Running   1 (15h ago)   15h
tom           pod/tom-vcluster-controller-85fbf5d8dd-86nlp                          1/1     Running   2 (15h ago)   15h
tom           pod/tom-vcluster-etcd-0                                               1/1     Running   0             15h

NAMESPACE     NAME                                                                      TYPE           CLUSTER-IP       EXTERNAL-IP                                                              PORT(S)                      AGE
jerry         service/jerry-vcluster                                                    ClusterIP      X.X.X.X          <none>                                                                   443/TCP,10250/TCP            15h
jerry         service/jerry-vcluster-api                                                ClusterIP      X.X.X.X          <none>                                                                   443/TCP                      15h
jerry         service/jerry-vcluster-etcd                                               ClusterIP      X.X.X.X          <none>                                                                   2379/TCP,2380/TCP            15h
jerry         service/jerry-vcluster-etcd-headless                                      ClusterIP      None             <none>                                                                   2379/TCP,2380/TCP            15h
jerry         service/jerry-vcluster-node-ip-X-X-X-X -ec2-internal                      ClusterIP      X.X.X.X          <none>                                                                   10250/TCP                    15h
tom           service/tom-vcluster                                                      ClusterIP      X.X.X.X          <none>                                                                   443/TCP,10250/TCP            15h
tom           service/tom-vcluster-api                                                  ClusterIP      X.X.X.X          <none>                                                                   443/TCP                      15h
tom           service/tom-vcluster-etcd                                                 ClusterIP      X.X.X.X          <none>                                                                   2379/TCP,2380/TCP            15h
tom           service/tom-vcluster-etcd-headless                                        ClusterIP      None             <none>                                                                   2379/TCP,2380/TCP            15h
tom           service/tom-vcluster-node-ip-X-X-X-X-ec2-internal                         ClusterIP      X.X.X.X          <none>                                                                   10250/TCP                    15h

NAMESPACE     NAME                                        READY   UP-TO-DATE   AVAILABLE   AGE
jerry         deployment.apps/jerry-vcluster              1/1     1            1           15h
jerry         deployment.apps/jerry-vcluster-api          1/1     1            1           15h
jerry         deployment.apps/jerry-vcluster-controller   1/1     1            1           15h
tom           deployment.apps/tom-vcluster                1/1     1            1           15h
tom           deployment.apps/tom-vcluster-api            1/1     1            1           15h
tom           deployment.apps/tom-vcluster-controller     1/1     1            1           15h

NAMESPACE     NAME                                                   DESIRED   CURRENT   READY   AGE
jerry         replicaset.apps/jerry-vcluster-api-677b754b88          1         1         1       15h
jerry         replicaset.apps/jerry-vcluster-c88458869               1         1         1       15h
jerry         replicaset.apps/jerry-vcluster-controller-5ccd46c8f6   1         1         1       15h
tom           replicaset.apps/tom-vcluster-7698d45cd9                1         1         1       15h
tom           replicaset.apps/tom-vcluster-api-75ccb87d7b            1         1         1       15h
tom           replicaset.apps/tom-vcluster-controller-85fbf5d8dd     1         1         1       15h

NAMESPACE   NAME                                   READY   AGE
jerry       statefulset.apps/jerry-vcluster-etcd   1/1     15h
tom         statefulset.apps/tom-vcluster-etcd     1/1     15h

また、vcluster上で起動しているバックエンドサービスを見ていきます。
low-levelのリソースであるpod, serviceは、syncerによりホストクラスター上にコピーされていますが、high-levelのリソースであるdeploymentなどはないことがわかります。

$ kubectl get all -A  | grep -e "aws-load-balancer-controller" -e "external-secrets"
jerry         pod/aws-load-balancer-controller-5d76f7dbf7-jd9fk-x-kube-0fb15be897   1/1     Running   0             15h
jerry         pod/external-secrets-785fd778d7-c7sph-x-kube-system-x-je-12eb75951a   1/1     Running   0             15h
jerry         pod/external-secrets-cert-controller-5f4545cccf-fjn7q-x--c1f7c94ee1   1/1     Running   0             15h
jerry         pod/external-secrets-webhook-c44997d8-jn7cz-x-kube-syste-0f41d421f0   1/1     Running   0             15h
tom           pod/aws-load-balancer-controller-5d76f7dbf7-t7bx7-x-kube-7e37a25d97   1/1     Running   0             15h
tom           pod/external-secrets-785fd778d7-9fmh2-x-kube-system-x-tom-vcluster    1/1     Running   0             15h
tom           pod/external-secrets-cert-controller-5f4545cccf-29mgr-x--671d54895d   1/1     Running   0             15h
tom           pod/external-secrets-webhook-c44997d8-2j9db-x-kube-syste-0d3b5f66bb   1/1     Running   0             15h
jerry         service/external-secrets-webhook-x-kube-system-x-jerry-vcluster           ClusterIP      172.20.112.154   <none>   443/TCP   15h
tom           service/external-secrets-webhook-x-kube-system-x-tom-vcluster             ClusterIP      172.20.161.151   <none>   443/TCP   15h

おわりに

今回はKubernetes運用をしてきた中で感じた課題や、それを解決するためのvclusterを使った開発プラットフォームづくりについて紹介しました。

なお、この記事は、2022年11月4日に行われた「KubeDay Japan 2022」の下記プログラムをきっかけに作成しました。プラットフォーム運用の鍵となる考え方についてわかりやすく話されていて面白かったので、興味があればぜひ見てみてください。

https://youtu.be/xobwuY_rZKs?feature=shared

https://static.sched.com/hosted_files/kubedayjapan2022/6a/KUBEDAY - JAPAN - Building FaaS Platforms.pdf

参考

https://www.vcluster.com/
https://helmfile.readthedocs.io/en/latest/

D2C m-tech

Discussion