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

# Network Policy とは
簡単に言うと Pod のファイアウォールのようなものです。
この Network Policy により Pod 間の通信を制御することができます。

Network Policy では Ingress と Egress の2種類に対してポリシーを定義できます。
Ingress は Pod のインバウンドに対して、 Egress は Pod のアウトバウンドに対して制限をかけることができます。

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

* IPアドレス
* Namespase/Pod のラベル

[Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/ "GCP - Kubernetes Engine") がまだベータですが Network Policy に[対応](https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy "GCP - Creating a Cluster Network Policy")したので、そちらで色々と試してみてケース別にまとめてみました。

# 環境
## クライアント

* Windows 10 Pro （ただしWSLを使っています）

WSLには [Cloud SDK](https://cloud.google.com/sdk/downloads?hl=ja "GCP - 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 が機能しませんでした。

```shell-session
$ 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

```yaml
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 を作成するのが基本になるかと思います。

#### 試す
```shell-session
$ 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
$ 
```

```shell-session
$ 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` コマンドでテストします。

```shell-session
$ 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
$
```

#### クリーンアップ

```shell
kubectl delete svc web
kubectl delete deploy web
kubectl delete netpol default-deny-ingress
kubectl delete ns outside
```

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

```yaml
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 へのインバウンドを許可します。
それ以外は拒否します。

#### 試す
```shell-session
$ 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
$ 
```

```shell-session
$ kubectl run web --labels="app=web" --image=nginx
deployment "web" created
$ kubectl expose deployment web --port=80
service "web" exposed
$ 
```

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

```shell-session
$ 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
$ 
```

#### クリーンアップ

```shell
kubectl delete svc web
kubectl delete deploy web
kubectl delete netpol default-deny-ingress
kubectl delete netpol allow-web
```

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

```yaml
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 からのインバウンドを許可するという意味になります。

```yaml
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
```

#### 試す
```shell-session
$ 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
$ 
```

```shell-session
$ kubectl run web --labels="app=web" --image=nginx
deployment "web" created
$ kubectl expose deployment web --port=80
service "web" exposed
$ 
```

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

```shell-session
$ 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
$ 
```

#### クリーンアップ

```shell
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

```yaml
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) 負荷分散の設定](https://cloud.google.com/compute/docs/load-balancing/http/?hl=ja#components "GCE - HTTP(S) 負荷分散の設定 - コンポーネント")で確認しました。
ノードが所属しているネットワークはコンソール等で確認してください。

#### 試す
```shell-session
$ 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 は以下になります。

```yaml
kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: web
spec:
  backend:
    serviceName: web
    servicePort: 80
```

```shell-session
$ 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アドレスに置き換えてください。

```shell-session
$ 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

$ 
```

#### クリーンアップ

```shell
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

```yaml
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](#https-gclb-%E3%81%8B%E3%82%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%90%E3%82%A6%E3%83%B3%E3%83%89%E3%82%92%E8%A8%B1%E5%8F%AF-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](https://hub.docker.com/r/hirsim/hello-server/ "Docker Hub - hirsim/hello-server")という、 Hello World! をレスポンスとして返すイメージを使用します（ちなみに私が作っています）。
また api Pod へのパスを追加するために、 ConfigMap を使用して nginx の `conf.d/default.conf` の設定を上書きします。

```yaml
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/;
        }
    }
```

```shell-session
$ 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 を用意します。

```yaml
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 は以下になります。

```yaml
kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: web
spec:
  backend:
    serviceName: web
    servicePort: 80
```

```shell-session
$ 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アドレスに置き換えてください。

```shell-session
$ 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 にアクセスできないかをテストします。

```shell-session
$ 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
$ 
```

#### クリーンアップ

```shell
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
### 全てのアウトバウンドを拒否
```yaml
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 を作成するのが基本になるかと思います。

#### 試す
```shell-session
$ 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
$ 
```

```shell-session
$ 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` コマンドでテストします。
全てのアウトバウンドを拒否しているため、名前解決にも失敗します。

```shell-session
$ 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
$
```

#### クリーンアップ

```shell
kubectl delete svc web
kubectl delete deploy web
kubectl delete netpol default-deny-egress
```

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

```yaml
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 サーバへのアウトバウンドも合わせて許可します。
それ以外は拒否します。

#### 試す
```shell-session
$ 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
$ 
```

```shell-session
$ 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 コマンドでテストします。

```shell-session
$ 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
$ 
```

#### クリーンアップ

```shell
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

```yaml
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 コンテナをデプロイしたインスタンスを立ち上げ、それをターゲットとします。

#### 試す
```shell-session
$ # 先に 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 コマンドでテストします。

```shell-session
$ 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
$ 
```

#### クリーンアップ

```shell
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
```

# クラスタ削除

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

# おわりに
このように Network Policy を使うことにより柔軟かつ容易に Pod 間の通信を制御することができます。これにより不必要な通信を制限する事ができますね。
ただしインバウンド/アウトバウンドの制限はできても、当然の事ながら通信経路の暗号化やクライアント認証はされません。
よりセキュアにするには gRPC のTLSによる暗号化/クライアント認証や Istio などのフレームワークやミドルウェアを使いましょう。

# 参考

* GKE ドキュメント [Creating a Cluster Network Policy](https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy "GCP - GKE Creating a Cluster Network Policy")
* Kubernetes ドキュメント [Declare Network Policy](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/ "Kubernetes - Declare Network Policy")
* Kubernetes ドキュメント [Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/ "Kubernetes - Network Policies")