CUBE SUGAR CONTAINER

技術系のこと書きます。

k3d (k3s) を使って Ingress と NetworkPolicy を検証する

Kubernetes を手元で検証しようとすると CNI (Container Network Interface) プラグインの機能が障壁になることがある。 たとえば kind を使う場合はデフォルトで kindnetd という CNI プラグインがインストールされる。 しかし、この CNI プラグインは動作する上で最低限の機能しか有していない。 そのため、Ingress リソースや NetworkPolicy リソースはデフォルトでは利用できない。 もちろん、別途 Calico などの CNI プラグインをインストールすることも考えられるが、その分の手間はかかる。

そこで、今回は k3s という IoT 向けの軽量な Kubernetes ディストリビューションを k3d というツールでインストールして試してみる。 k3d (k3s) ではデフォルトで Flannel が CNI プラグインとしてインストールされる。 そのため Ingress リソースを扱うことができる。 また、Flannel 自体は NetworkPolicy リソースをサポートしていないが k3s は kube-router の機能を使ってサポートしているらしい。

使った環境は次のとおり。

$ sw_vers
ProductName:    macOS
ProductVersion: 12.6.3
BuildVersion:   21G419
$ uname -srm
Darwin 21.6.0 x86_64
$ k3d version
k3d version v5.4.7
k3s version v1.25.6-k3s1 (default)
$ docker version
Client:
 Cloud integration: v1.0.29
 Version:           20.10.22
 API version:       1.41
 Go version:        go1.18.9
 Git commit:        3a2c30b
 Built:             Thu Dec 15 22:28:41 2022
 OS/Arch:           darwin/amd64
 Context:           default
 Experimental:      true

Server: Docker Desktop 4.16.2 (95914)
 Engine:
  Version:          20.10.22
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.18.9
  Git commit:       42c8b31
  Built:            Thu Dec 15 22:26:14 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.14
  GitCommit:        9ba4b250366a5ddde94bb7c9d1def331423aa323
 runc:
  Version:          1.1.4
  GitCommit:        v1.1.4-0-g5fd4c4d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

もくじ

下準備

ホストには、あらかじめ Docker と Homebrew がインストールされた状態を仮定する。

そして Homebrew で k3d をインストールする。

$ brew install k3d

インストールすると k3d コマンドが使えるようになる。

$ k3d version
k3d version v5.4.7
k3s version v1.25.6-k3s1 (default)

これで下準備が整った。

Ingress リソースを試す

まずは Ingress リソースから試してみよう。

その前に k3d cluster create サブコマンドを使ってクラスタを作成する。 このとき Ingress リソースを試すときは -p オプションを付ける必要がある。 これはワーカーノードの前段に入るプロキシの機能を提供するホストの特定のポートをローカルホストのポートに対応付けて扱うため。

今回は Nginx の Pod を使って HTTP を公開する Ingress リソースを作って試してみる。 そこで、プロキシ機能を提供するホストの 80 ポートをローカルホストの 8080 ポートに対応付ける。 これには -p オプションに "8080:80@loadbalancer" という引数を渡す。

$ k3d cluster create helloworld \
    -p "8080:80@loadbalancer"

上記を実行するとクラスタが作成される。 作成されたクラスタは k3d cluster list で状態を確認できる。

$ k3d cluster list
NAME         SERVERS   AGENTS   LOADBALANCER
helloworld   1/1       0/0      true

作成されたクラスタを操作するための kubeconfig は k3d kubeconfig write サブコマンドで生成できる。 環境変数の KUBECONFIG にセットしよう。

$ export KUBECONFIG=$(k3d kubeconfig write helloworld)

このコマンドは kubeconfig を生成しつつ、そのパスを標準出力に返す。

$ k3d kubeconfig write helloworld
/Users/amedama/.k3d/kubeconfig-helloworld.yaml

kubectl config current-context サブコマンドで k3d で作ったクラスタが操作対象になっていることを確認する。

$ kubectl config current-context 
k3d-helloworld

作成した直後はシステムの作成するリソースの準備が整っていないことがある。 そこで kubectl get all サブコマンドなどを使ってシステムのリソースが稼働していることを確認する。

$ kubectl get all -A
NAMESPACE     NAME                                          READY   STATUS      RESTARTS   AGE
kube-system   pod/local-path-provisioner-79f67d76f8-hpcs9   1/1     Running     0          64s
kube-system   pod/coredns-597584b69b-n268h                  1/1     Running     0          64s
kube-system   pod/metrics-server-5f9f776df5-qhzz2           1/1     Running     0          64s
kube-system   pod/helm-install-traefik-crd-7m896            0/1     Completed   0          64s
kube-system   pod/helm-install-traefik-mnlxz                0/1     Completed   1          64s
kube-system   pod/svclb-traefik-07031ac5-j8cx2              2/2     Running     0          36s
kube-system   pod/traefik-66c46d954f-qgfh7                  1/1     Running     0          36s

NAMESPACE     NAME                     TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)                      AGE
default       service/kubernetes       ClusterIP      10.43.0.1     <none>        443/TCP                      79s
kube-system   service/kube-dns         ClusterIP      10.43.0.10    <none>        53/UDP,53/TCP,9153/TCP       76s
kube-system   service/metrics-server   ClusterIP      10.43.76.82   <none>        443/TCP                      75s
kube-system   service/traefik          LoadBalancer   10.43.82.71   172.18.0.3    80:31992/TCP,443:32477/TCP   36s

NAMESPACE     NAME                                    DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
kube-system   daemonset.apps/svclb-traefik-07031ac5   1         1         1       1            1           <none>          36s

NAMESPACE     NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
kube-system   deployment.apps/local-path-provisioner   1/1     1            1           76s
kube-system   deployment.apps/coredns                  1/1     1            1           76s
kube-system   deployment.apps/metrics-server           1/1     1            1           75s
kube-system   deployment.apps/traefik                  1/1     1            1           36s

NAMESPACE     NAME                                                DESIRED   CURRENT   READY   AGE
kube-system   replicaset.apps/local-path-provisioner-79f67d76f8   1         1         1       65s
kube-system   replicaset.apps/coredns-597584b69b                  1         1         1       65s
kube-system   replicaset.apps/metrics-server-5f9f776df5           1         1         1       65s
kube-system   replicaset.apps/traefik-66c46d954f                  1         1         1       36s

NAMESPACE     NAME                                 COMPLETIONS   DURATION   AGE
kube-system   job.batch/helm-install-traefik-crd   1/1           32s        74s
kube-system   job.batch/helm-install-traefik       1/1           34s        74s

準備が整っていることを確認したらリソースを作成していく。 まずは Pod を作る。

$ kubectl run nginx-pod \
    --image=nginx \
    --labels="app=web"
pod/nginx-pod created
$ kubectl get pod
NAME        READY   STATUS    RESTARTS   AGE
nginx-pod   1/1     Running   0          11s

続いて Pod に対応する Service を作成する。 Service では nginx-pod の TCP/80 ポートを公開する。 Pod に対応する Service を作るには kubectl expose pod サブコマンドを使うと手っ取り早い。

$ kubectl expose pod nginx-pod \
    --port 80 \
    --protocol TCP \
    --name nginx-service
service/nginx-service exposed
$ kubectl get service
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes      ClusterIP   10.43.0.1       <none>        443/TCP   9m50s
nginx-service   ClusterIP   10.43.138.181   <none>        80/TCP    22s

最後に Service に対応する Ingress を作る。

$ kubectl apply -f - << EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx-service
            port:
              number: 80
EOF
ingress.networking.k8s.io/nginx-ingress created

Ingress リソースが作成されたことを確認する。

$ kubectl get ingress
NAME            CLASS     HOSTS   ADDRESS      PORTS   AGE
nginx-ingress   traefik   *       172.18.0.3   80      18s

これでローカルホストの TCP/8080 ポート経由で Ingress リソースにアクセスできる。 実際に curl(1) を使ってアクセスしてみよう。

$ curl -sL http://localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

ちゃんと Nginx がデフォルトで提供するウェルカムページの内容が得られた。

確認が終わったら作成したリソースを一通り削除して掃除する。

$ kubectl delete ingress nginx-ingress
ingress.networking.k8s.io "nginx-ingress" deleted
$ kubectl delete service nginx-service
service "nginx-service" deleted
$ kubectl delete pod nginx-pod        
pod "nginx-pod" deleted

NetworkPolicy リソースを試す

続いては NetworkPolicy リソースを試す。

まずは次のように Pod を 3 つ作成する。 名前やラベルは pod[123] として付けておく。

$ kubectl apply -f - << EOF
apiVersion: v1
kind: Pod
metadata:
  name: pod1
  labels:
    app: pod1
spec:
  containers:
  - name: pod1
    image: nginx
---
apiVersion: v1
kind: Pod
metadata:
  name: pod2
  labels:
    app: pod2
spec:
  containers:
  - name: pod2
    image: nginx
---
apiVersion: v1
kind: Pod
metadata:
  name: pod3
  labels:
    app: pod3
spec:
  containers:
  - name: pod3
    image: nginx
EOF
pod/pod1 created
pod/pod2 created
pod/pod3 created

次のように Pod に IP アドレスが割り当てられて、ちゃんと起動したことを確認する。

$ kubectl get pod -o wide
NAME   READY   STATUS    RESTARTS   AGE    IP           NODE                      NOMINATED NODE   READINESS GATES
pod2   1/1     Running   0          5m5s   10.42.0.13   k3d-helloworld-server-0   <none>           <none>
pod3   1/1     Running   0          5m5s   10.42.0.14   k3d-helloworld-server-0   <none>           <none>
pod1   1/1     Running   0          5m5s   10.42.0.12   k3d-helloworld-server-0   <none>           <none>

NetworkPolicy のないデフォルトの状態では、同じネームスペース内で自由に通信できる。 たとえば pod2pod3 から pod1 に HTTP GET してみよう。

$ kubectl exec -it pod2 -- curl 10.42.0.12 | head -n 5
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
$ kubectl exec -it pod3 -- curl 10.42.0.12 | head -n 5
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>

いずれのリクエストにも、ちゃんと Nginx のウェルカムページの内容が返ってきている。

では、ここで pod1 を対象とする NetworkPolicy を作成してみよう。 以下の NetworkPolicy は pod1 に送信元が pod2 からの通信だけを許可する。

$ kubectl apply -f - << EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: pod1-nwpolicy
spec:
  podSelector:
    matchLabels:
      app: pod1
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: pod2
EOF
networkpolicy.networking.k8s.io/pod1-nwpolicy created

NetworkPolicy を作成した状態で pod2 から pod1 に HTTP GET してみよう。

$ kubectl exec -it pod2 -- curl 10.42.0.12 | head -n 5
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>

これは明示的に NetworkPolicy で通信を許可しているのでうまくいく。

では pod3 から pod1 に HTTP GET した場合はどうだろうか。

$ kubectl exec -it pod3 -- curl 10.42.0.12 | head -n 5
curl: (7) Failed to connect to 10.42.0.12 port 80: Connection refused
command terminated with exit code 7

こちらは NetworkPolicy で許可されていないため Connection refused となった。 どうやら、ちゃんと動作しているようだ。

まとめ

今回は k3d を使って k3s のクラスタを作成し、Ingress リソースと NetworkPolicy リソースが動作することを確かめた。

参考

k3s.io

k3d.io

k3d.io