Edited at

Kubernetes (GKE) の Network Policy を使ってみる

More than 1 year has passed since last update.


Network Policy とは

簡単に言うと Pod のファイアウォールのようなものです。

この Network Policy により Pod 間の通信を制御することができます。

Network Policy では Ingress と Egress の2種類に対してポリシーを定義できます。

Ingress は Pod のインバウンドに対して、 Egress は Pod のアウトバウンドに対して制限をかけることができます。

Ingress/Egress の送信元/送信先の指定には以下が使用できます。


  • IPアドレス

  • Namespase/Pod のラベル

Google Kubernetes Engine がまだベータですが Network Policy に対応したので、そちらで色々と試してみてケース別にまとめてみました。


環境


クライアント


  • Windows 10 Pro (ただしWSLを使っています)

WSLには Cloud SDK をインストールし、アカウント認証および kubectl のインストールなど必要な初期設定を済ませています。


サーバ


  • Kubernetes (GKE) 1.8.4

  • n1-standard-1 ノード3台


クラスタ作成

CLI からクラスタを作成します。

--enable-network-policyオプションをつけるだけで Network Policy がクラスタで有効になります。

Network Policy は Kubernetes のバージョンが 1.7.6 以降である必要があるため、以下のコマンドでは執筆時点で最新の 1.8.4 を指定して作成しています。

ちなみに 1.8.3 だと Ingress は機能しましたが、 Egress が機能しませんでした。

$ gcloud beta container clusters create CLUSTER_NAME --cluster-version=1.8.4-gke.1 --enable-network-policy --project=PROJECT_ID --zone=asia-northeast1-a

Creating cluster CLUSTER_NAME...done.
Created [https://container.googleapis.com/v1/projects/PROJECT_ID/zones/asia-northeast1-a/clusters/CLUSTER_NAME].
kubeconfig entry generated for CLUSTER_NAME.
NAME ZONE MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
CLUSTER_NAME asia-northeast1-a 1.8.4-gke.1 xxx.xxx.xxx.xxx n1-standard-1 1.8.4-gke.1 3 RUNNING
$ # 認証情報取得
$ gcloud container clusters get-credentials CLUSTER_NAME --zone asia-northeast1-a --project PROJECT_ID
Fetching cluster endpoint and auth data.
kubeconfig entry generated for CLUSTER_NAME.
$ kubectl get no
NAME STATUS ROLES AGE VERSION
gke-CLUSTER_NAME-default-pool-db9852df-cmvz Ready <none> 3m v1.8.4-gke.1
gke-CLUSTER_NAME-default-pool-db9852df-j7d3 Ready <none> 3m v1.8.4-gke.1
gke-CLUSTER_NAME-default-pool-db9852df-qt1f Ready <none> 3m v1.8.4-gke.1


デフォルトの Network Policy

Network Policy が作成されていない Namespace では、その Namespace 内の Pod のインバウンド/アウトバウンドが制限されていない状態です。


ケース別Network Policy


Ingress


全てのインバウンドを拒否


Network Policy

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-ingress
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress

spec.podSelector を空にし、その Namespace 内の全 Pod を対象に全てのインバウンドを拒否します。

デフォルトではインバウンドは制限されていないため、この Network Policy を作成して全てのインバウンドを拒否し、個別に許可する Network Policy を作成するのが基本になるかと思います。


試す

$ cat << EOF | kubectl apply -f -

> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: default-deny-ingress
> namespace: default
> spec:
> podSelector: {}
> policyTypes:
> - Ingress
> EOF
networkpolicy "default-deny-ingress" created
$

$ kubectl run web --image=nginx

deployment "web" created
$ kubectl expose deployment web --port=80
service "web" exposed
$ kubectl create ns outside
namespace "outside" created
$

接続テスト用の Pod をデプロイして wget コマンドでテストします。

$ kubectl run busybox --rm -ti --image=busybox /bin/sh

If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web
Connecting to web (10.47.245.25:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-6bf46598ff-sg8p4 -c busybox -i -t' command when the pod is running
$ # outside Namespace に Pod をデプロイ
$ kubectl run busybox --rm -ti --image=busybox --namespace="outside" /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web.default
Connecting to web.default (10.47.245.25:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-6bf46598ff-6xh8x -c busybox -i -t' command when the pod is running
$


クリーンアップ

kubectl delete svc web

kubectl delete deploy web
kubectl delete netpol default-deny-ingress
kubectl delete ns outside


特定 Pod からのインバウンドを許可


Network Policy

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-ingress
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-web
namespace: default
spec:
podSelector:
matchLabels:
app: web
ingress:
- from:
- podSelector:
matchLabels:
app: client1
- podSelector:
matchLabels:
app: client2
ports:
- protocol: TCP
port: 80

この Network Policy ではラベル app=client1 または app=client2 が付与された Pod から、ラベル app=web が付与された Pod の TCP:80 へのインバウンドを許可します。

それ以外は拒否します。


試す

$ cat << EOF | kubectl apply -f -

> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: default-deny-ingress
> namespace: default
> spec:
> podSelector: {}
> policyTypes:
> - Ingress
> ---
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: allow-web
> namespace: default
> spec:
> podSelector:
> matchLabels:
> app: web
> ingress:
> - from:
> - podSelector:
> matchLabels:
> app: client1
> - podSelector:
> matchLabels:
> app: client2
> ports:
> - protocol: TCP
> port: 80
> EOF
networkpolicy "default-deny-ingress" created
networkpolicy "allow-web" created
$

$ kubectl run web --labels="app=web" --image=nginx

deployment "web" created
$ kubectl expose deployment web --port=80
service "web" exposed
$

接続テスト用の Pod をデプロイして wget コマンドでテストします。

$ kubectl run busybox --rm -ti --image=busybox /bin/sh

If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web
Connecting to web (10.23.246.28:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-6bf46598ff-7n4m6 -c busybox -i -t' command when the pod is running
$ # 許可されているラベルを付与します
$ kubectl run busybox --rm -ti --image=busybox --labels="app=client1" /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web
Connecting to web (10.23.246.28:80)
/ # exit
Session ended, resume using 'kubectl attach busybox-76dc4689d5-lnkp8 -c busybox -i -t' command when the pod is running
$ kubectl run busybox --rm -ti --image=busybox --labels="app=client2" /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web
Connecting to web (10.23.246.28:80)
/ # exit
Session ended, resume using 'kubectl attach busybox-5f686457bd-hbpvc -c busybox -i -t' command when the pod is running
$ # 許可されていないラベルを付与します
$ kubectl run busybox --rm -ti --image=busybox --labels="app=fake" /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web
Connecting to web (10.23.246.28:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-66cdf95844-b6qpv -c busybox -i -t' command when the pod is running
$


クリーンアップ

kubectl delete svc web

kubectl delete deploy web
kubectl delete netpol default-deny-ingress
kubectl delete netpol allow-web


特定 Namespace 内の全 Pod からのインバウンドを許可


Network Policy

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-ingress
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-web-from-foo-ns
namespace: default
spec:
podSelector:
matchLabels:
app: web
ingress:
- from:
- namespaceSelector:
matchLabels:
name: foo
ports:
- protocol: TCP
port: 80

この Network Policy ではラベル name=foo が付与された Namespace の全 Pod から、ラベル app=web が付与された Pod のTCP:80 へのインバウンドを許可します。

ここで少し注意が必要なのが、特定 Namespace 内の特定 Pod からのインバウンドだけを許可をする、というのができません。

以下のように Namespace と Pod の両方を記載しても論理積にはならず、その Namespace または同一 Namespace 内のその Pod からのインバウンドを許可するという意味になります。

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1
metadata:
name: allow-web-from-foo-ns
namespace: default
spec:
podSelector:
matchLabels:
app: web
ingress:
- from:
- podSelector:
matchLabels:
app: client1
- namespaceSelector:
matchLabels:
name: foo
ports:
- protocol: TCP
port: 80


試す

$ cat << EOF | kubectl apply -f -

> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: default-deny-ingress
> namespace: default
> spec:
> podSelector: {}
> policyTypes:
> - Ingress
> ---
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: allow-web-from-foo-ns
> namespace: default
> spec:
> podSelector:
> matchLabels:
> app: web
> ingress:
> - from:
> - namespaceSelector:
> matchLabels:
> name: foo
> ports:
> - protocol: TCP
> port: 80
> EOF
networkpolicy "default-deny-ingress" created
networkpolicy "allow-web-from-foo-ns" created
$
$ kubectl create ns foo
namespace "foo" created
$ kubectl label namespace/foo name=foo
namespace "foo" labeled
$ kubectl create ns bar
namespace "bar" created
$

$ kubectl run web --labels="app=web" --image=nginx

deployment "web" created
$ kubectl expose deployment web --port=80
service "web" exposed
$

接続テスト用の Pod をデプロイして wget コマンドでテストします。

$ kubectl run busybox --rm -ti --image=busybox /bin/sh

If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web
Connecting to web (10.23.250.206:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-6bf46598ff-4ng2j -c busybox -i -t' command when the pod is running
$ # 許可されている foo Namespace に Pod をデプロイします
$ kubectl run busybox --rm -ti --image=busybox --namespace="foo" /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web.default
Connecting to web.default (10.23.250.206:80)
/ # exit
Session ended, resume using 'kubectl attach busybox-6bf46598ff-fr2m7 -c busybox -i -t' command when the pod is running
$ # 許可されていない bar Namespace に Pod をデプロイします
$ kubectl run busybox --rm -ti --image=busybox --namespace="bar" /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web.default
Connecting to web.default (10.23.250.206:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-6bf46598ff-84v8c -c busybox -i -t' command when the pod is running
$


クリーンアップ

kubectl delete svc web

kubectl delete deploy web
kubectl delete netpol default-deny-ingress
kubectl delete netpol allow-web-from-foo-ns
kubectl delete ns foo
kubectl delete ns bar


HTTP(S) GCLB からのインバウンドを許可 1


Network Policy

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-ingress
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-gclb
spec:
podSelector:
matchLabels:
app: web
ingress:
- from:
- ipBlock:
cidr: 10.146.0.0/20
- ipBlock:
cidr: 130.211.0.0/22
- ipBlock:
cidr: 35.191.0.0/16
ports:
- protocol: TCP
port: 80

この Network Policy では HTTP(S) GCLB から、ラベル app=web が付与された Pod のTCP:80 へのインバウンドを許可します。

許可する送信元として GCLB のネットワークとノードが所属しているネットワークを指定しています。

なぜノードが所属しているネットワークも指定しているかと言うと、そうしないと GCLB のヘルスチェックに失敗するためです(正確に言うと、 Pod が配置されているノードのヘルスチェックはOKになりますが、配置されていないノードのヘルスチェックがNGとなります)。

GCLB のネットワークはGCE HTTP(S) 負荷分散の設定で確認しました。

ノードが所属しているネットワークはコンソール等で確認してください。


試す

$ cat << EOF | kubectl apply -f -

> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: default-deny-ingress
> namespace: default
> spec:
> podSelector: {}
> policyTypes:
> - Ingress
> ---
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: allow-gclb
> spec:
> podSelector:
> matchLabels:
> app: web
> ingress:
> - from:
> - ipBlock:
> cidr: 10.146.0.0/20
> - ipBlock:
> cidr: 130.211.0.0/22
> - ipBlock:
> cidr: 35.191.0.0/16
> ports:
> - protocol: TCP
> port: 80
> EOF
networkpolicy "default-deny-ingress" created
networkpolicy "allow-gclb" created
$

作成する Ingress は以下になります。

kind: Ingress

apiVersion: extensions/v1beta1
metadata:
name: web
spec:
backend:
serviceName: web
servicePort: 80

$ kubectl run web --labels="app=web" --image=nginx

deployment "web" created
$ kubectl expose deployment web --port=80 --type=NodePort
service "web" exposed
$ cat << EOF | kubectl apply -f -
> kind: Ingress
> apiVersion: extensions/v1beta1
> metadata:
> name: web
> spec:
> backend:
> serviceName: web
> servicePort: 80
> EOF
ingress "web" created
$ kubectl get ing
NAME HOSTS ADDRESS PORTS AGE
web * xxx.xxx.xxx.xxx 80 7m
$

接続テスト用の Pod をデプロイして wget コマンドで接続テスト後、クライアントPCで接続テストをします。

xxx.xxx.xxx.xxx は GCLB に割り当てられた実際のIPアドレスに置き換えてください。

$ kubectl run busybox --rm -ti --image=busybox /bin/sh

If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web
Connecting to web (10.11.254.186:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-6bf46598ff-n7f6r -c busybox -i -t' command when the pod is running
$ curl -I xxx.xxx.xxx.xxx
HTTP/1.1 200 OK
Server: nginx/1.13.7
Date: Wed, 20 Dec 2017 16:00:05 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 21 Nov 2017 14:28:04 GMT
ETag: "5a1437f4-264"
Accept-Ranges: bytes
Via: 1.1 google

$


クリーンアップ

kubectl delete ing web

kubectl delete svc web
kubectl delete deploy web
kubectl delete netpol default-deny-ingress
kubectl delete netpol allow-gclb


HTTP(S) GCLB からのインバウンドを許可 2


Network Policy

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-ingress
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-gclb
spec:
podSelector:
matchLabels:
app: web
ingress:
- from:
- ipBlock:
cidr: 10.146.0.0/20
- ipBlock:
cidr: 130.211.0.0/22
- ipBlock:
cidr: 35.191.0.0/16
ports:
- protocol: TCP
port: 80
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-api-from-web
spec:
podSelector:
matchLabels:
app: api
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080

HTTP(S) GCLB からのインバウンドを許可 1 に+αして、 web Pod から api Pod のTCP:8080へのインバウンドを許可する設定を付け加えました。

図にするとこのような形になります。

                                        +-------+       +-------+

+--------+ +------+ | |------>| |
| Client |---Internet--->| GCLB |------>| web | | api |
+--------+ +------+ | |X------| |
+--Pod--+ +--Pod--+

web Pod はインターネット(GCLB)からのインバウンドを許可し、 api Pod は web Pod からのインバウンドを許可します。

web Pod は GCLB からのインバウンドしか許可していないため、 api Pod からのインバウンドは拒否されます。


試す

api Pod の Docker イメージにはhirsim/hello-serverという、 Hello World! をレスポンスとして返すイメージを使用します(ちなみに私が作っています)。

また api Pod へのパスを追加するために、 ConfigMap を使用して nginx の conf.d/default.conf の設定を上書きします。

kind: ConfigMap

apiVersion: v1
metadata:
name: web-config
data:
default: |
server {
listen 80;
server_name localhost;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}

location /api/ {
proxy_pass http://api/;
}
}

$ cat << EOF | kubectl apply -f -

> kind: ConfigMap
> apiVersion: v1
> metadata:
> name: web-config
> data:
> default: |
> server {
> listen 80;
> server_name localhost;
>
> location / {
> root /usr/share/nginx/html;
> index index.html index.htm;
> }
>
> location /api/ {
> proxy_pass http://api/;
> }
> }
> EOF
configmap "web-config" created
$
$ cat << EOF | kubectl apply -f -
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: default-deny-ingress
> namespace: default
> spec:
> podSelector: {}
> policyTypes:
> - Ingress
> ---
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: allow-gclb
> spec:
> podSelector:
> matchLabels:
> app: web
> ingress:
> - from:
> - ipBlock:
> cidr: 10.146.0.0/20
> - ipBlock:
> cidr: 130.211.0.0/22
> - ipBlock:
> cidr: 35.191.0.0/16
> ports:
> - protocol: TCP
> port: 80
> ---
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: allow-api-from-web
> spec:
> podSelector:
> matchLabels:
> app: api
> ingress:
> - from:
> - podSelector:
> matchLabels:
> app: web
> ports:
> - protocol: TCP
> port: 8080
> EOF
networkpolicy "default-deny-ingress" created
networkpolicy "allow-gclb" created
networkpolicy "allow-api-from-web" created
$

今回 web Pod は ConfigMap を使用して nginx の設定を上書きするため、Deployment を用意します。

kind: Deployment

apiVersion: apps/v1beta2
metadata:
name: web
labels:
app: web
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx/conf.d
name: nginx-config-default
volumes:
- name: nginx-config-default
configMap:
name: web-config
items:
- key: default
path: default.conf

作成する Ingress は以下になります。

kind: Ingress

apiVersion: extensions/v1beta1
metadata:
name: web
spec:
backend:
serviceName: web
servicePort: 80

$ cat << EOF | kubectl apply -f -

> kind: Deployment
> apiVersion: apps/v1beta2
> metadata:
> name: web
> labels:
> app: web
> namespace: default
> spec:
> replicas: 1
> selector:
> matchLabels:
> app: web
> template:
> metadata:
> labels:
> app: web
> spec:
> containers:
> - name: web
> image: nginx
> ports:
> - containerPort: 80
> volumeMounts:
> - mountPath: /etc/nginx/conf.d
> name: nginx-config-default
> volumes:
> - name: nginx-config-default
> configMap:
> name: web-config
> items:
> - key: default
> path: default.conf
> EOF
deployment "web" created
$ kubectl expose deployment web --port=80 --type=NodePort
service "web" exposed
$ kubectl run api --labels="app=api" --image=hirsim/hello-server
deployment "api" created
$ kubectl expose deployment api --port=80 --target-port=8080
service "api" exposed
$ cat << EOF | kubectl apply -f -
> kind: Ingress
> apiVersion: extensions/v1beta1
> metadata:
> name: web
> spec:
> backend:
> serviceName: web
> servicePort: 80
> EOF
ingress "web" created
$ kubectl get ing
NAME HOSTS ADDRESS PORTS AGE
web * xxx.xxx.xxx.xxx 80 7m
$

クライアントPCから接続テストをします。

xxx.xxx.xxx.xxx は GCLB に割り当てられた実際のIPアドレスに置き換えてください。

$ curl -i xxx.xxx.xxx.xxx

HTTP/1.1 200 OK
Server: nginx/1.13.7
Date: Fri, 22 Dec 2017 15:57:44 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 21 Nov 2017 14:28:04 GMT
ETag: "5a1437f4-264"
Accept-Ranges: bytes
Via: 1.1 google

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
~省略~
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
$ curl -i xxx.xxx.xxx.xxx/api/hello.html
HTTP/1.1 200 OK
Server: nginx/1.13.7
Date: Fri, 22 Dec 2017 15:59:10 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 289
Via: 1.1 google

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
$

また、 api Pod から web Pod にアクセスできないかをテストします。

$ kubectl get po

NAME READY STATUS RESTARTS AGE
api-7d8dd57bcc-x7tcc 1/1 Running 0 12m
web-85d4d4b659-cg4jv 1/1 Running 0 12m
$ kubectl exec api-7d8dd57bcc-x7tcc -ti /bin/sh
/ # wget --spider --timeout=1 web
Connecting to web (10.11.242.36:80)
wget: download timed out
/ # exit
command terminated with exit code 1
$


クリーンアップ

kubectl delete ing web

kubectl delete svc api
kubectl delete svc web
kubectl delete deploy web
kubectl delete deploy api
kubectl delete cm web-config
kubectl delete netpol default-deny-ingress
kubectl delete netpol allow-gclb
kubectl delete netpol allow-api-from-web


Egress


全てのアウトバウンドを拒否

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-egress
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress

spec:podSelector を空にし、その Namespace 内の全 Pod を対象に全てのアウトバウンドを拒否します。

Ingress 同様、デフォルトではアウトバウンドも制限されていないため、この Network Policy を作成して全てのアウトバウンドを拒否し、個別に許可する Network Policy を作成するのが基本になるかと思います。


試す

$ cat << EOF | kubectl apply -f -

> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: default-deny-egress
> namespace: default
> spec:
> podSelector: {}
> policyTypes:
> - Egress
> EOF
networkpolicy "default-deny-egress" created
$

$ kubectl run web --image=nginx

deployment "web" created
$ kubectl expose deployment web --port=80
service "web" exposed
$ kubectl get svc web
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.11.241.54 <none> 80/TCP 2m
$

接続テスト用の Pod をデプロイして wget コマンドでテストします。

全てのアウトバウンドを拒否しているため、名前解決にも失敗します。

$ kubectl run busybox --rm -ti --image=busybox /bin/sh

If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web
wget: bad address 'web'
/ # wget --spider --timeout=1 www.example.com
wget: bad address 'www.example.com'
/ # # 上で確認した Services の CLUSTER-IP で接続を試みます
/ # wget --spider --timeout=1 10.11.241.54
Connecting to 10.11.241.54 (10.11.241.54:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-6bf46598ff-nv7gr -c busybox -i -t' command when the pod is running
$


クリーンアップ

kubectl delete svc web

kubectl delete deploy web
kubectl delete netpol default-deny-egress


特定 Pod へのアウトバウンドを許可


Network Policy

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-egress
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-dns-egress
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-client-to-web
namespace: default
spec:
podSelector:
matchLabels:
app: client
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 80

この Network Policy ではラベル app=client が付与された Pod から、ラベル app=web が付与された Pod の TCP:80 へのアウトバウンドを許可します。

また、名前解決をするために Namespace 内の全 Pod の DNS サーバへのアウトバウンドも合わせて許可します。

それ以外は拒否します。


試す

$ cat << EOF | kubectl apply -f -

> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: default-deny-egress
> namespace: default
> spec:
> podSelector: {}
> policyTypes:
> - Egress
> ---
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: allow-dns-egress
> namespace: default
> spec:
> podSelector: {}
> policyTypes:
> - Egress
> egress:
> - to:
> - namespaceSelector: {}
> ports:
> - port: 53
> protocol: UDP
> - port: 53
> protocol: TCP
> ---
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: allow-client-to-web
> namespace: default
> spec:
> podSelector:
> matchLabels:
> app: client
> policyTypes:
> - Egress
> egress:
> - to:
> - podSelector:
> matchLabels:
> app: web
> ports:
> - protocol: TCP
> port: 80
> EOF
networkpolicy "default-deny-egress" created
networkpolicy "allow-dns-egress" created
networkpolicy "allow-client-to-web" created
$

$ kubectl run web --labels="app=web" --image=nginx

deployment "web" created
$ kubectl expose deployment web --port=80
service "web" exposed
$ kubectl run api --labels="app=api" --image=hirsim/hello-server
deployment "api" created
$ kubectl expose deployment api --port=80 --target-port=8080
service "api" exposed
$

接続テスト用の Pod をデプロイして wget コマンドでテストします。

$ kubectl run busybox --rm -ti --image=busybox /bin/sh

If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web
Connecting to web (10.11.248.11:80)
wget: download timed out
/ # wget --spider --timeout=1 api
Connecting to api (10.11.246.25:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-6bf46598ff-p45gt -c busybox -i -t' command when the pod is running
$ # 許可されているラベルを付与します
$ kubectl run busybox --rm -ti --image=busybox --labels="app=client" /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web
Connecting to web (10.11.248.11:80)
/ # wget --spider --timeout=1 api
Connecting to api (10.11.246.25:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-5874b665bb-xgrp8 -c busybox -i -t' command when the pod is running
$


クリーンアップ

kubectl delete svc web

kubectl delete svc api
kubectl delete deploy web
kubectl delete deploy api
kubectl delete netpol default-deny-egress
kubectl delete netpol allow-dns-egress
kubectl delete netpol allow-client-to-web


特定のIPアドレスへのアウトバウンドを許可


Network Policy

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-egress
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-dns-egress
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-client-to-web
namespace: default
spec:
podSelector:
matchLabels:
app: client
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: xxx.xxx.xxx.xxx/32
ports:
- protocol: TCP
port: 80

この Network Policy ではラベル app=client が付与された Pod から、 IPアドレス xxx.xxx.xxx.xxx の TCP:80 へのアウトバウンドを許可します。

また、名前解決をするために Namespace 内の全 Pod の DNS サーバへのアウトバウンドも合わせて許可します。

それ以外は拒否します。

xxx.xxx.xxx.xxx にはアクセスしたいサーバのIPアドレスに置き換えてください。

ここでは、ノードと同じサブネット内に nginx コンテナをデプロイしたインスタンスを立ち上げ、それをターゲットとします。


試す

$ # 先に nginx コンテナをデプロイしたインスタンスを立ち上げ、IPアドレスを取得します。

$ gcloud beta compute instances create-with-container web-instance --container-image nginx --project=PROJECT_ID --zone=asia-northeast1-a
Created [https://www.googleapis.com/compute/beta/projects/PROJECT_ID/zones/asia-northeast1-a/instances/web-instance].
NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
web-instance asia-northeast1-a n1-standard-1 10.146.0.5 yyy.yyy.yyy.yyy RUNNING
$ # INTERNAL_IP が 10.146.0.5 になったため、 xxx.xxx.xxx.xxx をそのIPに置き換え
$ cat << EOF | kubectl apply -f -
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: default-deny-egress
> namespace: default
> spec:
> podSelector: {}
> policyTypes:
> - Egress
> ---
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: allow-dns-egress
> namespace: default
> spec:
> podSelector: {}
> policyTypes:
> - Egress
> egress:
> - to:
> - namespaceSelector: {}
> ports:
> - port: 53
> protocol: UDP
> - port: 53
> protocol: TCP
> ---
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: allow-client-to-web
> namespace: default
> spec:
> podSelector:
> matchLabels:
> app: client
> policyTypes:
> - Egress
> egress:
> - to:
> - ipBlock:
> cidr: 10.146.0.5/32
> ports:
> - protocol: TCP
> port: 80
> EOF
networkpolicy "default-deny-egress" created
networkpolicy "allow-dns-egress" created
networkpolicy "allow-client-to-web" created
$

接続テスト用の Pod をデプロイして wget コマンドでテストします。

$ kubectl run busybox --rm -ti --image=busybox /bin/sh

If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web-instance
Connecting to web-instance (10.146.0.5:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-6bf46598ff-hwk8w -c busybox -i -t' command when the pod is running
$ # 許可されているラベルを付与します
$ kubectl run busybox --rm -ti --image=busybox --labels="app=client" /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget --spider --timeout=1 web-instance
Connecting to web-instance (10.146.0.5:80)
/ # wget --spider --timeout=1 www.exmaple.com
Connecting to www.exmaple.com (85.119.83.248:80)
wget: download timed out
/ # exit
Session ended, resume using 'kubectl attach busybox-5874b665bb-bvrf5 -c busybox -i -t' command when the pod is running
$


クリーンアップ

kubectl delete netpol default-deny-egress

kubectl delete netpol allow-dns-egress
kubectl delete netpol allow-client-to-web
gcloud compute instances delete web-instance --project=PROJECT_ID --zone=asia-northeast1-a


クラスタ削除

gcloud beta container clusters delete CLUSTER_NAME --project=PROJECT_ID --zone=asia-northeast1-a


おわりに

このように Network Policy を使うことにより柔軟かつ容易に Pod 間の通信を制御することができます。これにより不必要な通信を制限する事ができますね。

ただしインバウンド/アウトバウンドの制限はできても、当然の事ながら通信経路の暗号化やクライアント認証はされません。

よりセキュアにするには gRPC のTLSによる暗号化/クライアント認証や Istio などのフレームワークやミドルウェアを使いましょう。


参考