Kubernetes NodePortにNetworkPolicyを使う

Kubernetes上でWebサービスを動かすなら、通常はIngressコントローラーを経由してのアクセス構成になると思うのでNodePortを使うことは無いのではないかと思うが、NodePortでサービスを公開して、それにNetworkPolicyを使って通信制限を掛けられるのか？というのを調べる機会があり、その検証メモ。

結論から言うと、NodePortの公開サービスにNetworkPolicyで通信制限を掛けることは出来る。ただ、通常のNodePortがKubernetesクラスター上のどのノードを経由してもアクセスできるのに対して、Podが稼働するノードのみのNodePort経由での通信に実質制限される。

# 環境
前回作成したKubernetes v1.27のクラスター。CNIはCalico。
https://qiita.com/rk05231977/items/032feaed7b46fc2bbabd

CPノード1台、Workerノード1台構成。IPアドレスは、c1:192.168.0.204、w1:192.168.0.205。
その上に、Pod1としてnginxを、Pod2としてhttpdをデプロイし、2つともNodePortでサービスを公開する。Pod1の公開ポート番号は30080、Pod2は30081。

Podにアクセスする元の端末を2つ用意し、それぞれのIPアドレスは、p1:192.168.0.202、p2:192.168.0.100とする。

# Podのデプロイ
アクセス先となるPodをテスト用のネームスペースに2つデプロイし、NodePortで公開する。
c1ノードにrootでssh接続し、以下を実行する。

```
# kubectl create ns test

# cat << EOF > pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod1
  namespace: test
  labels:
    app: pod1
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80
EOF
# kubectl create -f pod1.yaml

# cat << EOF > svc1.yaml
apiVersion: v1
kind: Service
metadata:
  name: svc1
  namespace: test
spec:
  type: NodePort
  selector:
    app: pod1
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30080
EOF
# kubectl create -f svc1.yaml

# cat << EOF > pod2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod2
  namespace: test
  labels:
    app: pod2
spec:
  containers:
  - name: httpd
    image: httpd:latest
    ports:
    - containerPort: 80
EOF
# kubectl create -f pod2.yaml
# cat << EOF > svc2.yaml
apiVersion: v1
kind: Service
metadata:
  name: svc2
  namespace: test
spec:
  type: NodePort
  selector:
    app: pod2
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30081
EOF
# kubectl create -f svc2.yaml
```

Podを確認すると、どちらもw1ノードで動作している。
```
[root@c1 ~]# kubectl get pod -n test -o wide
NAME   READY   STATUS    RESTARTS   AGE     IP              NODE   NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          3m19s   172.16.190.67   w1     <none>           <none>
pod2   1/1     Running   0          2m16s   172.16.190.68   w1     <none>           <none>
```

特に通信制限していないので、どちらのPodにもNodePort経由でアクセスできる。

```
（p1からアクセス）
# curl http://192.168.0.205:30080
...
<title>Welcome to nginx!</title>
...
# curl http://192.168.0.205:30081
<html><body><h1>It works!</h1></body></html>

（p2からアクセス）
# curl http://192.168.0.205:30080
...
<title>Welcome to nginx!</title>
...
# curl http://192.168.0.205:30081
<html><body><h1>It works!</h1></body></html>
```

上記はWorkerノード(192.168.0.205)経由でのアクセスだが、CPノード(192.168.0.204)経由でもアクセスできる。

```
（p1からのアクセス）
# curl http://192.168.0.204:30080
...
<title>Welcome to nginx!</title>
...
# curl http://192.168.0.204:30081
<html><body><h1>It works!</h1></body></html>

（p2からのアクセス）
# curl http://192.168.0.204:30080
...
<title>Welcome to nginx!</title>
...
# curl http://192.168.0.204:30081
<html><body><h1>It works!</h1></body></html>
```

# NetworkPolicyを設定する
上記状態から、クライアントp1（192.168.0.202）からpod1へのアクセスのみを許可するようにNetworkPolicyを設定してみる。
KubernetesのNetwork Policyの説明は以下。
https://kubernetes.io/docs/concepts/services-networking/network-policies/

まずはdeny allのポリシーをtest nsに設定する。
c1ノードで以下を実行する。

```
# cat << EOF > np1.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: test
spec:
  podSelector: {}
  policyTypes:
  - Ingress
EOF
# kubectl create -f np1.yaml
```

この状態でクライアントp1、p2ともどちらのPodにも接続できなくなる。
次に、NetworkPolicyにp1のIPアドレスをipBlockで指定して、pod1へのアクセスが出来るようにする。

```
# cat << EOF > np2.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-pod1
  namespace: test
spec:
  podSelector:
    matchLabels:
      app: pod1
  policyTypes:
    - Ingress
  ingress:
    - from:
        - ipBlock:
            cidr: 192.168.0.202/32
EOF
# kubectl create -f np2.yaml
```

接続を試してみれば、期待通りにアクセス制限が出来ている様子。

```
（p1からのアクセス）
# curl http://192.168.0.205:30080
...
<title>Welcome to nginx!</title>
...
# curl http://192.168.0.205:30081
curl: (28) Failed to connect to 192.168.0.205 port 30081 after 21032 ms: Couldn't connect to server

（p2からのアクセス）
# curl http://192.168.0.205:30080
curl: (28) Failed to connect to 192.168.0.205 port 30080 after 21002 ms: Timed out

# curl http://192.168.0.205:30081
curl: (28) Failed to connect to 192.168.0.205 port 30081 after 21000 ms: Timed out

```

# NodePortなのでc1ノードからもアクセスさせたい
冒頭にも書いたが、この時点でw1ノード(192.168.0.205)経由でのアクセスは出来るのだが、c1ノード(192.168.0.204)経由でのアクセスはクライアントp1からも出来なくなる。

```
（p1からのアクセス）
# curl http://192.168.0.204:30080
curl: (28) Failed to connect to 192.168.0.204 port 30080 after 21049 ms: Couldn't connect to server
```

これはp1のIPアドレスが、c1のNodePort経由時にNATでアドレス変換されているためだと思われる。例えばNetworkPolicyにc1ノードのIPアドレスを追加すれば、c1ノード経由でもアクセス可能にすることは出来る。

```
# cat << EOF > np2.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-pod1
  namespace: test
spec:
  podSelector:
    matchLabels:
      app: pod1
  policyTypes:
    - Ingress
  ingress:
    - from:
        - ipBlock:
            cidr: 192.168.0.202/32
        - ipBlock:
            cidr: 192.168.0.204/32
EOF
# kubectl apply -f np2.yaml
```

```
（p1からのアクセス）
# curl http://192.168.0.204:30080
...
<title>Welcome to nginx!</title>
...
```

のだが、この場合はクライアントp2からもc1ノードのNodePort経由でpod1にアクセスできるようになってしまうので、多分当初の目的にしていたpod1へのアクセスをp1のみに許可するという要件は満たさなくなる。

```
（p2からのアクセス）
# curl http://192.168.0.204:30080
...
<title>Welcome to nginx!</title>
...
```

なので、Ingressコントローラー配置ノードのような外部アクセスを集中させるノードを設けて、NodePortで公開するサービスをNetworkPolicyを使用して制限するという構成は上手くいかず、クライアントはPodが稼働するWorkerノードのNodePortに直接アクセスする必要がある。