kind 製 Kubernetes クラスタ内サービスへのアクセス (for macOS)

はじめに

kind を使うと複数 Node のクラスタが簡単に作成できて便利ですが、ホスト (macOS) 側から kind 上で提供されているサービスにどうアクセスするのか少し悩みました。

kind では docker コンテナ1つを1 Nodeとして Kubernetes クラスタを構築できますが、 Docker Desktop for Mac の制約 によりそもそもホストからコンテナへ IP で到達することができません。そのため kind で作成されたクラスタtype: NodePort の Service をデプロイしたとてホスト側からその Service にアクセスできないのです。

kind の Issue にも関連がありそうな話題が。

github.com

やってみよう

kind で作ったクラスタ内のサービスにアクセスするいくつかの方法を試します。
環境は以下の通り。

まずは kind でクラスタを作成します。

❯ cat <<EOF | kind create cluster --config -
kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
nodes:
- role: control-plane
- role: worker
- role: worker
- role: worker
EOF

❯ export KUBECONFIG="$(kind get kubeconfig-path --name="kind")"
❯ kubectl get nodes
NAME                 STATUS   ROLES    AGE   VERSION
kind-control-plane   Ready    master   84s   v1.15.3
kind-worker          Ready    <none>   50s   v1.15.3
kind-worker2         Ready    <none>   50s   v1.15.3
kind-worker3         Ready    <none>   50s   v1.15.3

テスト用のアプリケーションをデプロイ

 ❯ cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
EOF

❯ kubectl get pods -o wide
NAME                     READY   STATUS    RESTARTS   AGE     IP           NODE           NOMINATED NODE   READINESS GATES
nginx-68c7f5464c-8b58k   1/1     Running   0          2m16s   10.244.2.6   kind-worker2   <none>           <none>
nginx-68c7f5464c-m6tz5   1/1     Running   0          2m16s   10.244.1.5   kind-worker3   <none>           <none>
nginx-68c7f5464c-tcnhc   1/1     Running   0          2m16s   10.244.3.7   kind-worker    <none>           <none>
❯ for PODNAME in `kubectl get pods -o jsonpath='{.items[*].metadata.name}'`; do
  kubectl exec -it ${PODNAME} -- cp /etc/hostname /usr/share/nginx/html/index.html;
done

HTTP でアクセスすると Pod 名が表示されます。

ホスト (macOS) から如何にしてクラスタ内で展開されるサービスを見ることができるでしょうか。思いついたものを試してみます。

kubectl port-forward

デバッグでよく使うやつ。ただし Pod に直接接続するため今回のようなケースではロードバランスされません。

❯ kubectl port-forward deploy/nginx 8080:80
## 別ターミナルにて
❯ curl 127.0.0.1:8080
nginx-68c7f5464c-8b58k
❯ curl 127.0.0.1:8080
nginx-68c7f5464c-8b58k
❯ curl 127.0.0.1:8080
nginx-68c7f5464c-8b58k

kind の extraPortMappings

kindの公式ドキュメント にある extraPortMappings を使うと hostPort に割当が可能なようです。

❯ cat <<EOF | kind create cluster --config -
kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
nodes:
- role: control-plane
- role: worker
- role: worker
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 8080
EOF

## Deployment 作成とアプリの設定は前述したものと同様のコマンドを実行
❯ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: NodePort
  ports:
  - name: "http-port"
    protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30000
  selector:
    app: nginx
EOF

❯ curl 0.0.0.0:8080
nginx-68c7f5464c-w4h7v
❯ curl 0.0.0.0:8080
nginx-68c7f5464c-rd6hs
❯ curl 0.0.0.0:8080
nginx-68c7f5464c-vl2r6

kind クラスタの config として3つ目の Node に extraPortMappings を設定し立ち上げました。 ホストのポート (extraPortMappings[*].hostPort: 8080) へのアクセスが Node のコンテナのポート (extraPortMappings[*].containerPort: 30000) に転送されます。そのため nodePort: 30000 で指定した NodePort タイプの Service を用意することでロードバランスも可能です。

❯ docker ps -a
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                                  NAMES
81b82a6df489        kindest/node:v1.15.3   "/usr/local/bin/entr…"   8 minutes ago       Up 8 minutes        53372/tcp, 127.0.0.1:53372->6443/tcp   kind-control-plane
6f9bf2c0662b        kindest/node:v1.15.3   "/usr/local/bin/entr…"   8 minutes ago       Up 8 minutes                                               kind-worker
0d5c32d65b11        kindest/node:v1.15.3   "/usr/local/bin/entr…"   8 minutes ago       Up 8 minutes                                               kind-worker2
8b955ef61737        kindest/node:v1.15.3   "/usr/local/bin/entr…"   8 minutes ago       Up 8 minutes        0.0.0.0:8080->30000/tcp                kind-worker3

実際に docker コンテナの状態を確認すると kind-worker3PORTS の項目にて 0.0.0.0:8080->30000/tcp されていることが分かります。

そのため以下のような記述をしても、1つ目の Node をデプロイした時点でホスト側の 8080 ポートは使用されてしまい、複数の Node に同じ extraPortMappings[*].hostPort を割り当てることはできません。

❯ cat <<EOF | kind create cluster --config -
kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
nodes:
- role: control-plane
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 8080
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 8080
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 8080
EOF

## エラーが発生
ERRO[00:46:47] docker: Error response from daemon: driver failed programming external connectivity on endpoint kind-worker2 (17e1142d52d7f985f716cf49a2fc77a2af235bb8eca3918218b1d900f64831aa): Bind for 0.0.0.0:8080 failed: port is already allocated.

つまりは NodePort と言いつつも実際にユーザからのトラフィックを受け取るのは1つの Node のみということになります。

kubectl-open-svc-plugin

@superbrothers さんが作成された kubectl-open-svc-plugin というプラグインを試します。 kubectl port-forward の ClusterIP 版だと理解しています。

qiita.com

これは、クラスタ外からアクセスできない ClusterIP タイプの Service にクラスタの外から簡単にアクセスするためのプラグインです。

kubectl plugin マネージャである krew を使えば簡単に導入可能でした。

❯ kubectl krew install open-svc
❯ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: ClusterIP
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
  selector:
    app: nginx
EOF

❯ curl http://127.0.0.1:8080/api/v1/namespaces/default/services/nginx:http-port/proxy/
nginx-68c7f5464c-m6tz5
❯ curl http://127.0.0.1:8080/api/v1/namespaces/default/services/nginx:http-port/proxy/
nginx-68c7f5464c-tcnhc
❯ curl http://127.0.0.1:8080/api/v1/namespaces/default/services/nginx:http-port/proxy/
nginx-68c7f5464c-tcnhc
❯ curl http://127.0.0.1:8080/api/v1/namespaces/default/services/nginx:http-port/proxy/
nginx-68c7f5464c-8b58k

生成された URL にアクセスすると ClusterIP によるロードバランスが確認できます。
なんて便利!色々と使い所がありそうです。

おわり

kind や minikube など手元で試せる Kubernetes にデプロイしたサービスにアクセスする方法はどれもそのツールの実装方法に依存しており、なかなかに直感的でないように感じています。今回の話だと macOS ではなく Linux を使えばシンプルに解決するとも言えますが。 kubectl-open-svc-plugin のような補助ツールがあることでマルチクラスタにおけるロードバランスを手軽に試すことができるのは非常に有り難いと感じました。

Go 力養成のため自分も何かプラグインを作ってみたいと思いました。