軽量なKubernetes環境が欲しいと思って調べていたところ、ChatGPTにk3dをオススメされたので色々試してみる。
また、ついでにKong Gatewayの起動まで試してみる。
なお、k3d部分は特に適当に作ってるので真面目に使う人はちゃんと自分で調べた方がいいかも。
k3dとは
元々Rancherがk3sという軽量なK8sディストリビューションを提供していたが、これをDockerで提供するのがk3dとなっている。
コンテナ起動するため、単一VM内にも複数台構成のクラスタが簡単に組めるっぽい。
近いものとしてはkindがあるが、軽さやk8s周辺周りの機能はk3dが揃っているるしい。
検証
前提
今回はEC2上に構築する。
EC2上には以下が用意されているものとする。
- docker
- kubectl
インストール
ドキュメントのインストール手順に従いインストールする。
wget -q -O - https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
クラスタの作成
クラスタの作成はk3d cluster create
で行う。--servers
でControlPlaneノード、--agent
でWorkerノードの数が指定できるようなので、1CP2Worker構成で立ててみる。
k3d cluster create dev \
--servers 1 --agents 2 \
--port "80:80@loadbalancer" --port "443:443@loadbalancer"
--port
の部分はホストに公開するポートで、Ingress利用時にコンテナを起動している環境にアクセスして動作確認するために利用する。
実行後、自動で.kube/config
に設定が反映される。
$ kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://0.0.0.0:39039
name: k3d-dev
contexts:
- context:
cluster: k3d-dev
user: admin@k3d-dev
name: k3d-dev
current-context: k3d-dev
kind: Config
users:
- name: admin@k3d-dev
user:
client-certificate-data: DATA+OMITTED
client-key-data: DATA+OMITTED
構築は一瞬で終わり、ちゃんと3台構成で起動できた。
$ kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k3d-dev-agent-0 Ready <none> 44m v1.31.5+k3s1 172.18.0.3 <none> K3s v1.31.5+k3s1 6.14.0-1011-aws containerd://1.7.23-k3s2
k3d-dev-agent-1 Ready <none> 44m v1.31.5+k3s1 172.18.0.4 <none> K3s v1.31.5+k3s1 6.14.0-1011-aws containerd://1.7.23-k3s2
k3d-dev-server-0 Ready control-plane,master 44m v1.31.5+k3s1 172.18.0.2 <none> K3s v1.31.5+k3s1 6.14.0-1011-aws containerd://1.7.23-k3s2
なお、標準でIngressも使えるようになっている。
$ kubectl get ingressclass
NAME CONTROLLER PARAMETERS AGE
traefik traefik.io/ingress-controller <none> 45m
$ kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.43.0.10 <none> 53/UDP,53/TCP,9153/TCP 45m
metrics-server ClusterIP 10.43.18.115 <none> 443/TCP 45m
traefik LoadBalancer 10.43.32.197 172.18.0.2,172.18.0.3,172.18.0.4 80:32072/TCP,443:32627/TCP 45m
ただし、type:LoadBalancer
は使えず、利用するとIPが割り当てられず<pending>
となる。
なのでMetalLBをインストールする。
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.yaml
通常のインストール手順だとkube-proxyのstrictARP
をtrue
にする設定を挟むが、k3dではkube-proxyがいないのでその手順は割愛した。
なお、MetalLBを適用直後、Traefikに割り当てられているIPが<pending>
になって作成済みのIngressがある場合はアクセスできなくなるので注意。
IPプールを作成してアドバタイズする。
cat <<'EOF' | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default-pool
namespace: metallb-system
spec:
addresses:
- 172.18.0.2-172.18.0.254
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default-l2
namespace: metallb-system
spec:
ipAddressPools:
- default-pool
EOF
指定するIPレンジはdocker network inspect k3d-dev | jq -r '.[0].IPAM.Config[0].Subnet'
みたいな感じでdockerが使っているIPを引っ張ってくるとよさそう。
上述のコマンド実行後、TraefikのIPは再割当てされる。
$ kubectl get svc -n kube-system traefik
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
traefik LoadBalancer 10.43.37.226 172.18.0.2 80:31471/TCP,443:30750/TCP 20m
アクセスできることも確認できる。
$ curl 172.18.0.2 -i
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Mon, 15 Sep 2025 01:36:09 GMT
Content-Length: 19
404 page not found
動作確認
最初にBookinfoでいくつか試してみる。
使うManifestは以下となる。
https://raw.githubusercontent.com/istio/istio/release-1.27/samples/bookinfo/platform/kube/bookinfo.yaml
これにはService
, Deployment
等は含まれるがIstioのリソースやIngress等は含まれない。
取り敢えずデプロイする。
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.27/samples/bookinfo/platform/kube/bookinfo.yaml
構成としては以下のようになっており、Product pageが入口となる。
type:LoadBalancer
の確認
最初にProduct pageにtype:LoadBalancer
でIPを割り当ててアクセスしてみる。
kubectl patch
でtype
を変更する
$ kubectl patch service -p '{"spec":{"type":"LoadBalancer"}}' productpage -n default
無事にIPが割り当てられた。
$ kubectl get svc productpage -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
productpage LoadBalancer 10.43.20.48 172.18.0.3 9080:32463/TCP 12m
ただし、クラスタの起動時に9080ポートをホストに割り当てていなかったので、このポートへの動作確認が外部から出来ない。
しかし、k3dにはk3d cluster edit
というコマンドで稼働中のクラスタに対してポートを追加で公開する事ができる。
以下のような感じで公開するポートを追加する。
k3d cluster edit dev --port-add "9080:9080@loadbalancer"
公開後、ホストの9080ポートにブラウザでアクセスすると以下のようなページが確認できる。
/productpage
にアクセスすればIstioユーザにはお馴染みの画面も確認できる。
確認後は設定を元に戻しておく。
kubectl patch service -p '{"spec":{"type":"ClusterIP"}}' productpage -n default
Ingressの確認
Ingressも作成する。
cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: bookinfo
namespace: default
spec:
ingressClassName: traefik
rules:
- http:
paths:
- backend:
service:
name: productpage
port:
number: 9080
path: /
pathType: Prefix
EOF
今度は80ポートの/productpage
に対してアクセスする。
問題なくアクセスできた。
PVの確認
PVはBookinfoだと確認しづらいので、PostgreSQLをHelmで立ててアクセスしてみる。
最初にBitnamiをHelmリポジトリとして追加する。
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
Postgresをインストールする。なおパラメータの説明は[こちら]。(https://github.com/bitnami/charts/tree/main/bitnami/postgresql)。
helm install postgres bitnami/postgresql --set service.type=LoadBalancer -n postgres --create-namespace --wait --debug
作成後、PVとPVCを確認する。
$ kubectl get pvc,pv -n postgres
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/data-postgres-postgresql-0 Bound pvc-1ca007f5-b49a-4671-8761-a7633a15fb4c 8Gi RWO local-path <unset> 81s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
persistentvolume/pvc-1ca007f5-b49a-4671-8761-a7633a15fb4c 8Gi RWO Delete Bound postgres/data-postgres-postgresql-0 local-path <unset>
作成は問題なさそうだ。
次にアクセスしてデータが書き込めるかも確認する。
Postgresのポートを公開する。
k3d cluster edit dev --port-add "5432:5432@loadbalancer"
パスワードを環境変数PGPASSWORD
に設定する。
export PGPASSWORD=$(kubectl get secret postgres-postgresql -o jsonpath="{.data.postgres-password}" -n postgres | base64 -d)
アクセスしてみる。
psql -h <k3dのホストのIP> -U postgres -d postgres -p 5432 -c "SELECT version();"
問題なければ以下のようなバージョン情報が取得できる。
version
---------------------------------------------------------------------------------------------------
PostgreSQL 17.6 on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14+deb12u1) 12.2.0, 64-bit
(1 row)
次にデータを書き込んでみる。
対話型で起動する。
psql -h <k3dのホストのIP> -U postgres -d postgres -p 5432
DBを作成してデータをINSERTする。
CREATE TABLE test (id SERIAL PRIMARY KEY, msg TEXT);
INSERT INTO test (msg) VALUES ('hello from k3d');
\q
で抜けて、Podを再起動してデータが永続化されていることを確認する。
kubectl delete pod --all -n postgres
psql -h <k3dのホストのIP> -U postgres -d postgres -p 5432 -c "SELECT * FROM test;"
以下が取得できればOK。
id | msg
----+----------------
1 | hello from k3d
(1 row)
Kong Gatewayの起動
KongのChartリポジトリで配布されているサンプルを使ってKong Gatewayを起動してみる。
最初に使うManifestをダウンロードする。今回はminimal-k4k8s-with-kong-enterprise.yaml
というものにした。
wget https://raw.githubusercontent.com/Kong/charts/refs/heads/main/charts/kong/example-values/minimal-k4k8s-with-kong-enterprise.yaml
次にNamespaceを作成し、Manifest内で参照しているSecretを作成する。
kubectl create ns kong
kubectl create secret generic kong-enterprise-license --from-file license=./license.json -n kong --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic kong-enterprise-superuser-password --from-literal=password=kong -n kong
Helmのリポジトリを追加してインストールする。
helm repo add kong https://charts.konghq.com
helm repo update
helm install kong-minimal kong/kong -n kong --wait --debug -f ./minimal-k4k8s-with-kong-enterprise.yaml
デプロイ後、Proxyはtype:LoadBalancer
でポート80、Admin APIとKong Managerはtype:NodePort
で公開されている。
$ kubectl get svc -n kong
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kong-minimal-kong-admin NodePort 10.43.247.87 <none> 8001:30790/TCP,8444:30568/TCP 8m6s
kong-minimal-kong-manager NodePort 10.43.36.6 <none> 8002:31873/TCP,8445:32335/TCP 8m6s
kong-minimal-kong-metrics ClusterIP 10.43.6.97 <none> 10255/TCP,10254/TCP 8m6s
kong-minimal-kong-proxy LoadBalancer 10.43.76.71 172.18.0.3 80:31959/TCP,443:31082/TCP 8m6s
kong-minimal-kong-validation-webhook ClusterIP 10.43.233.125 <none> 443/TCP 8m6s
kong-minimal-postgresql ClusterIP 10.43.195.2 <none> 5432/TCP 8m6s
kong-minimal-postgresql-hl ClusterIP None <none> 5432/TCP 8m6s
Proxyにアクセスすると、正常にKong Gatewayとしての応答が返ってくる。
$ curl <k3dのホストのIP>:80
{
"message":"no Route matched with those values",
"request_id":"5e42056c523e7b3661717d1151701664"
}
Kong Managerをtype:LoadBalancer
に変更する。
kubectl patch service -p '{"spec":{"type":"LoadBalancer"}}' kong-minimal-kong-manager -n kong
k3d cluster edit dev --port-add "8002:8002@loadbalancer"
所感
起動が凄く簡単で、クラスタとしても使いづらいところはあまりなく、使い勝手は結構良かった。
ポートの追加が少し面倒だが、それもコマンドが用意されているのでそこまで不便ではなく、ちょっとしたクラスタを用意して検証する際にはかなり使えると思う。