[Kubernetes]NetworkPolicyの動作を確認する

#はじめに
クラスタ内の全てのPodは、Pod同士で通信可能です。しかし、別サービスのシステムで共有する場合など通信させたくない場合もあると思います。
NetworkPolicyを使用するとPod同士の通信を制御することができます。NetworkPolicyを使用することで、特定のPodやプロトコルの通信を拒否／許可することができます。

#NetworkPolicyの設定概要

[Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/)

##前提条件
NetworkPolicyを使用するには、NetworkPolicyをサポートするネットワークプロバイダーでクラスタを構築している必要があります。サポートするネットワークプロバイダーは以下になります。

* Calico
* Cilium
* Kube-router
* Romana
* Weave Net

[Declare Network Policy](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/)

今回はクラスタにCalicoを使用しています。

##policyType
NetworkPolicyを設定するには、向きを意識する必要があります。通信の向きをpolicyTypeで指定します。指定するパラメータは以下の2つです。

* Ingress：あるPodからの通信（インバウンド）
* Egress：あるPodへの通信（アウトバウンド）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/cb7aabba-0fbe-9edd-3ede-ff8de16ecaff.png)

##設定Policy
NetworkPolicyで設定できるPolicyは以下の3つがあります。以下の3つをNamespaceごとに指定します。また、IngressとEgressもそれぞれ指定しますが、向きが異なるだけで設定できるPolicyは同じです。

|Policy|概要
|:---|:---
|podSelector|あるPodからの/への通信を許可
|namespaceSelector|あるNamespace内のPodからの/への通信を許可
|ipBlock|あるIPアドレスからの/への通信を許可

PodのIPアドレスはデプロイのたびに自動で割り振られますので、クラスタ内での制御はpodSelectorとnamespaceSelectorを主に使用します。今回はこの2つの動作を確認したいと思います。
ipBlockはクラスタ外との通信を制御する場合に利用することをお勧めします。

#podSelector
今回は以下のように3つのPod間での通信制御を確認します。
nginx-app1からnginx-app3への通信のみ許可して、そのほかは拒否するように設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/72782252-3fcd-8b8b-0095-8a14212beb29.png)

##Podのデプロイ
まずは以下3つのPodをデプロイします。

```nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-app1
  labels:
    app: app1
spec:
  containers:
    - name: nginx-app1
      image: nginx:latest
      ports:
      - containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-app2
  labels:
    app: app2
spec:
  containers:
    - name: nginx-app2
      image: nginx:latest
      ports:
      - containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-app3
  labels:
    app: app3
spec:
  containers:
    - name: nginx-app3
      image: nginx:latest
      ports:
      - containerPort: 80
---
```

```
$ kubectl apply -f nginx-pod.yaml
pod/nginx-app1 created
pod/nginx-app2 created
pod/nginx-app3 created
$ kubectl get pod -o wide -L app
NAME         READY   STATUS    RESTARTS   AGE   IP               NODE           NOMINATED NODE   READINESS GATES   APP
nginx-app1   1/1     Running   0          20s   192.168.69.195   k8s-worker02   <none>           <none>            app1
nginx-app2   1/1     Running   0          19s   192.168.69.212   k8s-worker02   <none>           <none>            app2
nginx-app3   1/1     Running   0          19s   192.168.69.202   k8s-worker02   <none>           <none>            app3
```

コンテナにログインして、コンテナ間で通信できることを確認します。

```
$ kubectl exec -it nginx-app1 /bin/bash
root@nginx-app1:/# curl 192.168.69.212
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
・・・
root@nginx-app1:/# curl 192.168.69.202
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
・・・
```
nginx-app1からnginx-app2およびnginx-app3へ通信できていることが確認できます。
また、ログは省略しますが、nginx-app2/nginx-app3からも同様に各Podへ通信できることを確認しています。

##デフォルトポリシーの設定
NetworkPolicyは特定のPodやNamespaceに対して通信を許可する設定になりますので、まずはデフォルトで全ての通信を拒否しておいて、個別にPodやNamespaceに対して許可していきます。
デフォルトポリシーは「全てのIngress（インバウンド）は拒否して、全てのEgress（アウトバウンド）は許可する」など、環境やシステムごとに異なると思います。今回は「全てのIngress/Egressを拒否する」とします。

以下のマニフェストをapplyします。

```deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
```

```
$ kubectl apply -f deny-all.yaml
networkpolicy.networking.k8s.io/default-deny-all created
$ kubectl get networkpolicies
NAME               POD-SELECTOR   AGE
default-deny-all   <none>         20s
$ kubectl describe networkpolicies default-deny-all
Name:         default-deny-all
Namespace:    default
Created on:   2020-07-01 21:41:26 +0900 JST
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"networking.k8s.io/v1","kind":"NetworkPolicy","metadata":{"annotations":{},"name":"default-deny-all","namespace":"default"},...
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    <none> (Selected pods are isolated for ingress connectivity)
  Allowing egress traffic:
    <none> (Selected pods are isolated for egress connectivity)
  Policy Types: Ingress, Egress
```

念のためPodにログインして拒否されることを確認します。

```
$ kubectl exec -it nginx-app1 /bin/bash
root@nginx-app1:/# curl -m 30 192.168.69.212
curl: (28) Connection timed out after 30002 milliseconds
root@nginx-app1:/# curl -m 30 192.168.69.202
curl: (28) Connection timed out after 30002 milliseconds
```

nginx-app2/nginx-app3からも同様に各Podへの通信が拒否されています。

##PodSelectorの設定
現時点では全てのPod間での通信が拒否されていますので、nginx-app1からnginx-app3への通信を許可するようにNetworkPolicyを設定します。
この場合は、nginx-app3のIngressと、nginx-app1のEgressを許可します。ラベル単位で設定しますので、2つのNetworkPolicyを設定します。

```podSelector.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: pod-selector1
spec:
  podSelector:
    matchLabels:
      app: app3 #設定対象のPodのラベル
  policyTypes:
  - Ingress #Ingressを許可する
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: app1 #どのラベルのPodからの通信を許可するか
    ports:
    - protocol: TCP #許可するプロトコル
      port: 80 #許可するポート
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: pod-selector2
spec:
  podSelector:
    matchLabels:
      app: app1 #設定対象のPodのラベル
  policyTypes:
  - Egress #Egressを許可する
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: app3 #どのラベルのPodへの通信を許可するか
    ports:
    - protocol: TCP #許可するプロトコル
      port: 80 #許可するポート
```

```
$ kubectl apply -f podSelector.yaml
networkpolicy.networking.k8s.io/pod-selector1 created
networkpolicy.networking.k8s.io/pod-selector2 created
$ kubectl get networkpolicies
NAME               POD-SELECTOR   AGE
default-deny-all   <none>         36m
pod-selector1      app=app3       9s
pod-selector2      app=app1       9s
```

###動作確認
nginx-app1にログインして動作を確認します。

```
$ kubectl exec -it nginx-app1 /bin/bash
root@nginx-app1:/# curl -m 10 192.168.69.202
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>

root@nginx-app1:/# curl -m 10 192.168.69.212
curl: (28) Connection timed out after 10001 milliseconds
```

nginx-app3（192.168.69.202）へは疎通できていますが、nginx-app2（192.168.69.212）へは拒否されていますね。

反対にnginx-app3からnginx-app1へ疎通できないことも確認します。

```
$ kubectl exec -it nginx-app3 /bin/bash
root@nginx-app3:/# curl -m 10 192.168.69.195
curl: (28) Connection timed out after 10003 milliseconds
```
タイムアウトしていますね。


#namespaceSelector
次はnamespaceSelectorを使用して、Namespace単位で通信を制御したいと思います。

以下の図のような設定にしたいと思います。

* 同一クラスタ内に二つのNamespace（production/staging）がある。
* productionからstagingへの通信は許可するが、stagingからproductionへの通信は拒否する。
* 同一Namespace内の通信は許可する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/382849f1-29dc-be8b-893f-b2fe74f7993d.png)

##事前準備
###Namespaceの作成
productionとstagingのNamespaceを作成します。NetworkPolicyはラベルで制御しますので、各Namespaceにもラベルを付与します。

以下のマニフェストをapplyしてラベルを作成します。

```namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    env: prd
---
apiVersion: v1
kind: Namespace
metadata:
  name: staging
  labels:
    env: stg
```

###Podの作成

以下のマニフェストの4つのPodを作成します。NamespaceとLabelは上図のように設定します。

```nginx-pod-ns.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-prd-app1
  namespace: production
  labels:
    app: app1
spec:
  containers:
    - name: nginx-prd-app1
      image: nginx:latest
      ports:
      - containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-prd-app2
  namespace: production
  labels:
    app: app2
spec:
  containers:
    - name: nginx-prd-app2
      image: nginx:latest
      ports:
      - containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-stg-app1
  namespace: staging
  labels:
    app: app1
spec:
  containers:
    - name: nginx-stg-app1
      image: nginx:latest
      ports:
      - containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-stg-app2
  namespace: staging
  labels:
    app: app2
spec:
  containers:
    - name: nginx-stg-app2
      image: nginx:latest
      ports:
      - containerPort: 80
```


```
$ kubectl apply -f nginx-pod-ns.yaml
pod/nginx-prd-app1 created
pod/nginx-prd-app2 created
pod/nginx-stg-app1 created
pod/nginx-stg-app2 created
$ kubectl get pod -n production -o wide -L app
NAME             READY   STATUS    RESTARTS   AGE     IP               NODE           NOMINATED NODE   READINESS GATES   APP
nginx-prd-app1   1/1     Running   1          7h18m   192.168.69.194   k8s-worker02   <none>           <none>            app1
nginx-prd-app2   1/1     Running   1          7h18m   192.168.69.227   k8s-worker02   <none>           <none>            app2
$ kubectl get pod -n staging -o wide -L app
NAME             READY   STATUS    RESTARTS   AGE     IP               NODE           NOMINATED NODE   READINESS GATES   APP
nginx-stg-app1   1/1     Running   1          7h18m   192.168.69.238   k8s-worker02   <none>           <none>            app1
nginx-stg-app2   1/1     Running   1          7h18m   192.168.69.224   k8s-worker02   <none>           <none>            app2
```

##NetworkPolicyの設定
###デフォルトポリシーの設定
productionのIngress/Egressを全て拒否します。

```deny-all-prd.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all-prd
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
```

```
$ kubectl apply -f deny-all-prd.yaml
networkpolicy.networking.k8s.io/default-deny-all-prd created
$ kubectl get networkpolicies -n production
NAME                   POD-SELECTOR   AGE
default-deny-all-prd   <none>         18s
```
現時点では以下の図のように制御されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/41cd74f6-d689-69fb-de51-ccdef204807c.png)

###Namespace間通信の設定
以下のNetworkPolicyでproductionからstagingへの通信を許可します。

```namespaceSelector.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: namespace-selector
  namespace: production
spec:
  podSelector: {} #Namespace内の全てのPodが対象
  policyTypes:
  - Egress #Egressを設定
  egress:
  - to:
    - namespaceSelector: #Namespace単位で設定
        matchLabels:
          env: stg #「env: stg」ラベルを持つNamespaceへの通信を許可
    ports:
    - protocol: TCP #許可するプロトコル
      port: 80 #許可するポート
```
```
$ kubectl apply -f namespaceSelector.yaml
networkpolicy.networking.k8s.io/namespace-selector created
```
この時点では以下のように制御されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/5d69bd21-b79f-92fe-e9ea-11444cfc3dc1.png)

###Pod間通信の設定
最後にproduction内のPod間通信を許可します。

```podSelector-prd.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: pod-selector-prd-app1
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: app1
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: app2
    ports:
    - protocol: TCP
      port: 80
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: app2
    ports:
    - protocol: TCP
      port: 80
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: pod-selector-prd-app2
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: app2
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: app1
    ports:
    - protocol: TCP
      port: 80
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: app1
    ports:
    - protocol: TCP
      port: 80
```

```
$ kubectl apply -f podSelector-prd.yaml
networkpolicy.networking.k8s.io/pod-selector-prd-app1 created
networkpolicy.networking.k8s.io/pod-selector-prd-app2 created
```
これで目標としていた以下のように制御されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/57e7e464-3f53-5946-283f-2b5c0cf2ab6c.png)


##動作確認
コンテナにログインして疎通を確認します。まずはproductionからです。

```
$ kubectl exec -it -n production nginx-prd-app1 /bin/bash
root@nginx-prd-app1:/# curl -m 10 192.168.69.227
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
・・・
root@nginx-prd-app1:/# curl -m 10 192.168.69.238
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
・・・
root@nginx-prd-app1:/# curl -m 10 192.168.69.224
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
・・・
```
productionからは全てのPodに疎通できていますね。

次にstagingです。

```
$ kubectl exec -it -n staging nginx-stg-app1 /bin/bash
root@nginx-stg-app1:/# curl -m 10 192.168.69.194
curl: (28) Connection timed out after 10000 milliseconds
root@nginx-stg-app1:/# curl -m 10 192.168.69.227
curl: (28) Connection timed out after 10005 milliseconds
root@nginx-stg-app1:/# curl -m 10 192.168.69.224
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
・・・
```

productionへの通信は全て拒否されています。staging内でのみ通信可能ですね。

#まとめ
NetworkPolicyを使用することで細かく通信を制御できますね。
今回はいくつかのポリシーを組み合わせて想定した制御に設定しました。そのため、パズルみたいな感じで少し考えてしまいました。もしかしたら、もっとスマートに設定できるのかも知れません。