vclusterを使ってKubernetesの開発プラットフォームをつくってみた
はじめに
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を起動しておきます。
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パススルーの構成にする必要があるため、下記の通りに設定します。
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]にはユーザー名を挿入します。
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
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」の下記プログラムをきっかけに作成しました。プラットフォーム運用の鍵となる考え方についてわかりやすく話されていて面白かったので、興味があればぜひ見てみてください。
参考
株式会社D2C d2c.co.jp のテックブログです。 D2Cは、NTTドコモと電通などの共同出資により設立されたデジタルマーケティング企業です。 ドコモの膨大なデータを活用した最適化を行える広告配信システムの開発をしています。
Discussion