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に直接アクセスする必要がある。