kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

kindでお手軽Kubernetesマルチクラスタを試す②

f:id:kun432:20210901161635p:plain

前回の続き。

kun432.hatenablog.com

目次

ローカルPCからクラスタ内podへのネットワークアクセス(マルチnodeの場合)

extraPortmappingsで、ホストのポートとnodeのポートを紐付けることで、ローカルPCからnode内のpodへのアクセスができます。ただ、ホストポートを使うので、マルチnodeの場合はポートを分けてやる必要があります。クラスタのマニフェストはこんな感じになります。

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
name: sample
nodes:
- role: control-plane
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30001
    protocol: TCP
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30002
    protocol: TCP
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30003
    protocol: TCP

クラスタを作成します。

$ kind create cluster --config multi-host-ports.yaml

ローカルポート30001〜30003が、それぞれのnodeのポート30000にフォワードされます。

$ kubectl get node
NAME                   STATUS   ROLES                  AGE     VERSION
sample-control-plane   Ready    control-plane,master   3m9s    v1.21.1
sample-worker          Ready    <none>                 2m39s   v1.21.1
sample-worker2         Ready    <none>                 2m39s   v1.21.1
sample-worker3         Ready    <none>                 2m38s   v1.21.1

$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS         PORTS                       NAMES
a08c381ddc75   kindest/node:v1.21.1   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes   0.0.0.0:30003->30000/tcp    sample-worker3
dba747f1183d   kindest/node:v1.21.1   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes   127.0.0.1:59931->6443/tcp   sample-control-plane
c8a586bbc81b   kindest/node:v1.21.1   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes   0.0.0.0:30002->30000/tcp    sample-worker2
edd676ec1132   kindest/node:v1.21.1   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes   0.0.0.0:30001->30000/tcp    sample-worker

nodeportでserviceを立ち上げます。

apiVersion: v1
kind: Pod
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  containers:
  - name: nginx
    image: nginx:1.19.2
    ports:
      - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  type: NodePort
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30000
$ kubectl apply -f nodeport.yaml

nodeportとして動作はしていますがが、これはちょっとわかりにくいですね・・・

$ curl -s localhost:30001
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(snip)

$ curl -s localhost:30002
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(snip)

$ curl -s localhost:30003
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(snip)

Ingressを使う

ingress-controllerを使う場合もextraPortMappingを使います。公式のドキュメントに従ってみましょう。

クラスタマニフェストはこんな感じです。

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
name: sample
nodes:
- role: control-plane
- role: worker
  kubeadmConfigPatches:
  - |
    kind: JoinConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP

kindはkubeadmを使ってクラスタを作成しますので、kubeadmConfigPatchesを使うとカスタマイズできます。上記ではworkerに対して、kubeadm joinの際に設定を追加して、"ingress-ready=true"というラベルを付けています。(後述するnginx-ingressのサンプルではnodeSelectorでこれを見るようになっています。)

クラスタ作成後にnodeを見てみると、ラベルが付いているのがわかりますね。

$ kubectl get node
NAME                   STATUS   ROLES                  AGE     VERSION
sample-control-plane   Ready    control-plane,master   3m      v1.21.1
sample-worker          Ready    <none>                 2m32s   v1.21.1

$ kubectl get node sample-worker -o json | jq ".metadata.labels"
{
  "beta.kubernetes.io/arch": "amd64",
  "beta.kubernetes.io/os": "linux",
  "ingress-ready": "true",
  "kubernetes.io/arch": "amd64",
  "kubernetes.io/hostname": "sample-worker",
  "kubernetes.io/os": "linux"
}

ではingressです。公式のドキュメントでは以下のingress controllerが紹介されています。

  • Ambassador
  • Contour
  • Ingress NGINX

Ingress NGINX以外はちょっと知らないですね・・・・ということで、Ingress NGINXのサンプルのマニフェストを使いましょう。

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
configmap/ingress-nginx-controller created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
service/ingress-nginx-controller-admission created
service/ingress-nginx-controller created
deployment.apps/ingress-nginx-controller created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
serviceaccount/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created

準備ができるまで待ちます。

$ kubectl wait --namespace ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=90s

ingress-nginx-controllerが起動しました。

$ kubectl get pod -n ingress-nginx
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-jsbpq        0/1     Completed   0          60s
ingress-nginx-admission-patch-sqlcs         0/1     Completed   0          60s
ingress-nginx-controller-6c85cb7b5d-rqn6l   1/1     Running     0          61s

では、pod/service/ingressを設定します。公式のまんまです。

$ kubectl apply -f https://kind.sigs.k8s.io/examples/ingress/usage.yaml
pod/foo-app created
service/foo-service created
pod/bar-app created
service/bar-service created
ingress.networking.k8s.io/example-ingress created

usage.yamlの中身はこんな感じです。/fooと/barで振り分けています。

kind: Pod
apiVersion: v1
metadata:
  name: foo-app
  labels:
    app: foo
spec:
  containers:
  - name: foo-app
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=foo"
---
kind: Service
apiVersion: v1
metadata:
  name: foo-service
spec:
  selector:
    app: foo
  ports:
  # Default port used by the image
  - port: 5678
---
kind: Pod
apiVersion: v1
metadata:
  name: bar-app
  labels:
    app: bar
spec:
  containers:
  - name: bar-app
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=bar"
---
kind: Service
apiVersion: v1
metadata:
  name: bar-service
spec:
  selector:
    app: bar
  ports:
  # Default port used by the image
  - port: 5678
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
spec:
  rules:
  - http:
      paths:
      - pathType: Prefix
        path: "/foo"
        backend:
          service:
            name: foo-service
            port:
              number: 5678
      - pathType: Prefix
        path: "/bar"
        backend:
          service:
            name: bar-service
            port:
              number: 5678

実際にアクセスしてみると、ingressが動作しているのがわかります。

$ curl localhost/foo
foo
$ curl localhost/bar
bar

マルチクラスタでやるならばこうなりますかね。

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
name: sample
nodes:
- role: control-plane
- role: worker
  kubeadmConfigPatches:
  - |
    kind: JoinConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30001
    protocol: TCP
  - containerPort: 30443
    hostPort: 30011
    protocol: TCP
- role: worker
  kubeadmConfigPatches:
  - |
    kind: JoinConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30002
    protocol: TCP
  - containerPort: 30443
    hostPort: 30012
    protocol: TCP
- role: worker
  kubeadmConfigPatches:
  - |
    kind: JoinConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30003
    protocol: TCP
  - containerPort: 30443
    hostPort: 30013
    protocol: TCP
$ kind create cluster --config multi-node-ingress.yaml

// 以下は公式と同じ
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
$ kubectl wait --namespace ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=90s

// service: ingress-nginx-controllerのnodeportを変更
$ kubectl -n ingress-nginx patch service ingress-nginx-controller --type='json' -p='[{"op": "replace", "path": "/spec/ports/0/nodePort", "value": 30080}]'
$ kubectl -n ingress-nginx patch service ingress-nginx-controller --type='json' -p='[{"op": "replace", "path": "/spec/ports/1/nodePort", "value": 30443}]'

// ingress/service/podをapply
$ kubectl apply -f https://kind.sigs.k8s.io/examples/ingress/usage.yaml
$ for i in 1 2 3; do curl http://localhost:3000${i}/foo; done
foo
foo
foo
$ for i in 1 2 3; do curl http://localhost:3000${i}/bar; done
bar
bar
bar
$ for i in 1 2 3; do curl -k https://localhost:3001${i}/foo; done
foo
foo
foo
$ for i in 1 2 3; do curl -k https://localhost:3001${i}/bar; done
bar
bar
bar

想定どおりですね。

まとめ

ネットワーク周りには制約がありますが、ローカルでマルチnodeの確認ができるのは良いですね。extraPortMappingsあたりの設定はクラスタ作成時にやってしまう必要があり(あとから設定変更する方法はわかっていない)、場合によってはクラスタ作り直しの必要があるのですが、サクッとできてしまうのでそれほど苦にならない気がしました。