Terraform Kubernetes Providerとkindで試すNetworkPolicy

ローカル環境のKubernetesクラスターで、NetworkPolicyを検証する話です。諸事情により、業務上Kubernetesのリソースの一部をTerraformで管理しています。そのため、NetworkPolicyの検証にもTerraformを利用しています。

* 書いてあること
  * NetworkPolicyの概要
  * kindでNetworkPolicyを利用する方法
  * NetworkPolicyをTerraformで管理する方法
  * NetworkPolicyのingressのユースケース
  * kindをTerraformで管理する方法
* 書いてないこと
  * NetworkPolicyをなぜ使うのか
  * 組織に導入するにあたってのラベル命名規則
  * 既存システムへのNetworkPolicy導入方法
  * NetworkPolicyのegressのユースケース

# NetworkPolicyとは
[NetworkPolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/)はKubernetesのリソースの1つで、namespace内のPodに対するアクセスをラベルセレクターを利用して制御できます。

つぎのような特徴があります。

* ポリシーはnamespaceスコープ
* ポリシーはラベルセレクターを利用してPodに適用される
* ポリシールールはPodからの通信、Podへの通信、namespace、CIDRを指定できる
* ポリシールールはプロトコル（TCP、UDP、SCTP）、名前付きport、port番号を指定できる

具体例を見てみます。

```yaml
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: api-allow
  namespace: api
spec:
  podSelector:
    matchLabels:
      app: bookstore
      role: api
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: bookstore
```
この`api-allow`というNetworkPolicyリソースを登録すると、

「`api` namespaceにおいて」「`app: bookstore`、`role: api`というラベルをもつPodに関して」、「リクエストが許可されるのは`app: bookstore`というラベルをもったPodだけ」という通信制約を課すことができます。

`spec.podSelector`が通信制約を課す対象、`spec.ingress`が対象**へ**のリクエストへの制限、`spec.egress`が対象**から**のリクエストへの制限です。

`spec.podSelector`を`{}`にするとnamespace内のすべてのPodが対象になります。`spec.ingress.from`や`spec.egress.to`を`- {}`にすると全通信を許可します。`spec.policyTypes`で明示的に`Ingress`と`Egress`とした上で`spec.ingress`や`spec.egress`を書かなければ全通信を拒否します。

このNetworkPolicyは、default namespaceのすべてのPodに対し、全受信トラフィックを許可します。

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

このNetworkPolicyは、default namespaceのすべてのPodに対し、全送信トラフィックを拒否します。

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

`spec.ingress`と`spec.egress`ともに、podSelectorの他にもport、プロトコル、namespaceSelectorが指定できます。

指定できるものをたくさん盛り込んだ例です。

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: test
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978
```

通信を制御する機能（コントローラー）自体は、Kubernetesに標準で組み込まれていいません。そのため、NetworkPolicyに対応したnetwork pluginを利用する必要があります。

つぎのnetwork pluginでサポートされています。

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

NetworkPolicyをサポートしていないnetwork pluginを利用しているときは、NetworkPolicyリソースを設定しても無効です。Podへの通信も、Podからの通信もすべて許可されます。

# ローカルクラスターでNetworkPolicyを使えるようにする

今回はローカル環境で手軽に検証するために、ローカルに環境構築します。

## kindとは

クラスターを構築するにあたっては、[kind](https://kind.sigs.k8s.io/)を利用します。kindは、テスト用のKubernetesクラスタをローカル環境に手軽に構築できるツールです。

Dockerを起動し、つぎのようにコマンドを実行するとクラスタが立ち上がります。

```shell
# デフォルト設定でkindでクラスターを構築
$ kind create cluster
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.18.2) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂
```

kindはKubernetes IN Dockerという名のとおり、クラスターの各ノードがDockerコンテナとして実行されます。ローカル環境でクラスターを構築するためのツールは他にもありますが、マルチクノードのクラスタ構築に対応していたり、Kubernetesの本体のテストツールとして利用されているのが特徴です。

僕も[jctl](https://github.com/toshi0607/jctl)という、Kubernetes JobをGoのソースコードから実行するCLIの[E2Eテスト](https://github.com/toshi0607/jctl/blob/master/.github/workflows/ci.yaml)に利用しました。

## kindでNetworkPolicyを利用できるようにする

kindのデフォルトCNIであるkindnetdは、NetworkPolicyをサポートしていません。そのため、デフォルトのCNIを無効化し、Calicoをインストールする方法がkindの[issue](https://github.com/kubernetes-sigs/kind/issues/842)で紹介されています。

設定ファイルを書き、

```yaml:config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  # Defaulr CNI (kindnet) doesn't support NetworkPolicy
  disableDefaultCNI: true 
  podSubnet: 192.168.0.0/16
```

コマンドオプションに渡して実行すれば、kindでもNetworkPolicyを利用できます。

```shell
# kindのバージョン確認（kindコマンドが利用できるパスにあることを確認）
$ kind version
kind v0.8.1 go1.14.2 darwin/amd64

# config.yamlを利用してkindでクラスターを構築
$ kind create cluster --config=config.yaml
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.18.2) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

# Calicoのリソース登録
$ kubectl apply -f https://docs.projectcalico.org/v3.15/manifests/calico.yaml
configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org created
clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrole.rbac.authorization.k8s.io/calico-node created
clusterrolebinding.rbac.authorization.k8s.io/calico-node created
daemonset.apps/calico-node created
serviceaccount/calico-node created
deployment.apps/calico-kube-controllers created
serviceaccount/calico-kube-controllers created

# Calicoで利用するDaemonSetの環境変数変更
$ kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
daemonset.apps/calico-node env updated
```

# KubernetesとTerraform

今回は、NetworkPolicyをTerraformで管理します。

業務では、Kubernetes（GKE）をマイクロサービス開発基盤に採用しています。その中で、各マイクロサービスの開発開始時にTerraform moduleを利用してブートストラップしており、GCPやKubernetesの一部のリソースをTerraformで管理しています。

Kubernetesに関しては、Deployment、Service、HPA、PDBなどアプリケーション周りのリソースについてはYAML、[kustomize](https://github.com/kubernetes-sigs/kustomize)で管理し、Namespaceなどテナント周りのリソースはTerraformで管理しています。NetworkPolicyもテナント系リソースとして管理する方針のため、本記事でもTerraformで検証しています。

このあたりは、場を改めて紹介できたらと思っています。

TerraformでKubernetesリソースを管理するにあたっては、[hashicorp/terraform-provider-kubernetes](https://github.com/hashicorp/terraform-provider-kubernetes)を利用します。

最近出た[terraform-provider-kubernetes-alpha](https://github.com/hashicorp/terraform-provider-kubernetes-alpha)と比較して制約はあるものの成熟しており、現状のユースケースでは不自由ありません。

[Kubernetes関連のプロバイダーの比較記事](https://kubernetes.io/blog/2020/06/working-with-terraform-and-kubernetes/)がkubernetesブログに出ています。

## Kubernetes Providerでkindを設定する

kindでKubernetes Providerを使うにあたっては、Kubernetes master APIのエンドポイント情報とクレデンシャルが必要です。

さまざまな設定方法がありますが、今回はkubectlでも使っている[context](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/#define-clusters-users-and-contexts)を利用して簡易に設定します。

contextには、clusterとuser双方の情報が含まれています。Terraformの`provider`の`config_context_cluster`に`kind-kind`を指定すればOKです。

```hcl
provider "kubernetes" {
  config_context_cluster = "kind-kind"
}
```

`kind create cluster`実行時に出力されたコマンドを実行すると、つぎのようにKubernetes masterとKubeDNSのエンドポイント情報が得られます。

```shell
# クラスター情報の確認
$ kubectl cluster-info --context kind-kind
Kubernetes master is running at https://127.0.0.1:56619
KubeDNS is running at https://127.0.0.1:56619/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
```

ここまでの設定で、kindで構築したローカルクラスターでTerraformを利用したNetworkPolicyの設定ができるようになりました。

# NetworkPolicy（ingressのみ）のユースケース別に挙動を確認する

ユースケース毎に設定と挙動を確認します。検証にはつぎの環境を利用します。

* クラスター: kindで構築したものを利用
* namespace: ms-a、ms-b、ms-cの3つ。それぞれkeyが`namespace`、valueがnamespace名と同じラベルをもつ。
* アプリケーション: NGINXのPodをnamespace ms-bにデプロイ。80番Portで受けてPodの80番PortをターゲットにするServiceと、90番Portで受けてPodの80番PortをターゲットにするServiceで公開。

## namespaceとアプリケーションの準備

```hcl:main.tf
provider "kubernetes" {
  config_context_cluster = "kind-kind"
}

resource "kubernetes_namespace" "ms_a" {
  metadata {
    name = "ms-a"
    labels = {
      namespace = "ms-a"
    }
  }
}

resource "kubernetes_namespace" "ms_b" {
  metadata {
    name = "ms-b"
    labels = {
      namespace = "ms-b"
    }
  }
}

resource "kubernetes_namespace" "ms_c" {
  metadata {
    name = "ms-c"
    labels = {
      namespace = "ms-c"
    }
  }
}
```

```shell
# main.tfファイルのあるディレクトリに移動

# プロバイダーのダウンロードなど初期化
$ terraform init

# main.tfとプロバイダーをもとにTerraformでKubernetesリソースを登録
$ terraform apply -auto-approve
kubernetes_namespace.ms_c: Refreshing state... [id=ms-c]
kubernetes_namespace.ms_b: Refreshing state... [id=ms-b]
kubernetes_namespace.ms_a: Refreshing state... [id=ms-a]
kubernetes_network_policy.deny_from_any: Refreshing state... [id=ms-b/deny-from-any]
kubernetes_network_policy.allow_from_any_to_port: Refreshing state... [id=ms-b/allow-from-any-to-port]
kubernetes_network_policy.allow_from_from_same_namespace: Refreshing state... [id=ms-b/allow-from-same-namespace]
kubernetes_namespace.ms_c: Creating...
kubernetes_namespace.ms_a: Creating...
kubernetes_namespace.ms_b: Creating...
kubernetes_namespace.ms_b: Creation complete after 0s [id=ms-b]
kubernetes_namespace.ms_a: Creation complete after 0s [id=ms-a]
kubernetes_namespace.ms_c: Creation complete after 0s [id=ms-c]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
```

```shell
# 3つのnamespaceが登録されていることを確認
$ kubectl get ns | grep ms-
ms-a                 Active   13m
ms-b                 Active   13m
ms-c                 Active   13m

# namespaceにラベルがあることを確認
$ kubectl describe ns ms-a | grep Labels
Labels:       namespace=ms-a
```

```yaml:app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: ms-b
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: ms-b
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-90
  namespace: ms-b
spec:
  ports:
  - port: 90
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
```

```shell
# contextをkind-kindに変更
$ kubectx kind-kind
Switched to context "kind-kind".

# アプリケーションをデプロイして公開
$ kubectl apply -f ./app.yaml
deployment.apps/nginx created
service/nginx created
service/nginx-90 created

# namespace ms-bに2つのPodが登録されていることを確認
$ kubectl get pod -n ms-b
NAME                     READY   STATUS    RESTARTS   AGE
nginx-6b474476c4-hqh9x   1/1     Running   0          20m
nginx-6b474476c4-nflzw   1/1     Running   0          20m

# namespace ms-bにServiceが登録されていることを確認
$ kubectl get svc -n ms-b
NAME       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
nginx      ClusterIP   10.111.154.253   <none>        80/TCP    10m
nginx-90   ClusterIP   10.96.148.51     <none>        90/TCP    10m
```

現時点ではnamespace ms-aからms-bにアクセスできることを確認します。namespace ms-aでbusyboxを実行し、ms-bのNGINXにリクエストします。

```shell
# 別namespace ms-aからnginx Serviceの80番Portを経由してアクセスできる
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-a -- /bin/sh -c 'wget -O- nginx.ms-b:80'
Connecting to nginx.ms-b:80 (10.99.3.231:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

# 別namespace ms-aからnginx-90 Serviceの90番Portを経由してアクセスできる
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-a -- /bin/sh -c 'wget -O- nginx-90.ms-b:90'
Connecting to nginx-90.ms-b:90 (10.96.245.47:90)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

# 同namespace ms-bからnginx Serviceの80番Portを経由してアクセス
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-b -- /bin/sh -c 'wget -O- nginx.ms-b:80'
Connecting to nginx.ms-b:80 (10.99.3.231:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
```

## 全通信を拒否する

<img width="744" alt="1.deny-from-any.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/9f105f75-d539-8bde-a0ea-eec096ac8e21.png">

もとのproviderとnamespace設定に変更はありません。`deny-from-any`を追加しています。


```hcl:main.tf
provider "kubernetes" {
  config_context_cluster = "kind-kind"
}

resource "kubernetes_namespace" "ms_a" {
  metadata {
    name = "ms-a"
    labels = {
      namespace = "ms-a"
    }
  }
}

resource "kubernetes_namespace" "ms_b" {
  metadata {
    name = "ms-b"
    labels = {
      namespace = "ms-b"
    }
  }
}

resource "kubernetes_namespace" "ms_c" {
  metadata {
    name = "ms-c"
    labels = {
      namespace = "ms-c"
    }
  }
}

resource "kubernetes_network_policy" "deny_from_any" {
  metadata {
    name      = "deny-from-any"
    # namespace ms-b内のPodに対するポリシー
    namespace = "ms-b"
  }

  spec {
    # ポリシーの対象となるPodはフィルタリングされない。
    # namespace内のすべてのPodが対象になる。
    pod_selector {}

    policy_types = ["Ingress"]

    # policy_typesにIngressを指定した上でingress.fromを書かなければ
    # 通信は拒否される
  }
}
```

NetworkPolicyを登録します。

```shell
$ tf apply -auto-approve

$ kubectl get netpol -n ms-b
NAME            POD-SELECTOR   AGE
deny-from-any   <none>         2m31s

$ kubectl describe netpol deny-from-any -n ms-b
Name:         deny-from-any
Namespace:    ms-b
Created on:   2020-07-04 20:33:42 +0900 JST
Labels:       <none>
Annotations:  <none>
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
```

動作を確認します。

```shell
# 別namespace ms-aからのアクセスが拒否される
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-a -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
If you don't see a command prompt, try pressing enter.
wget: download timed out
pod "busybox" deleted
pod ms-a/busybox terminated (Error)

# 同namespace（ms-b）からのアクセスも拒否される
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-b -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
If you don't see a command prompt, try pressing enter.
wget: download timed out
pod "busybox" deleted
pod ms-b/busybox terminated (Error)
```
## 全通信を許可する

<img width="742" alt="2.allow-from-any.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/3370c02e-3966-22d7-004b-9d7aa88f0a0c.png">

`deny-from-any`を残したまま`allow-from-any`を追加しています。

```hcl:main.tf
provider "kubernetes" {
  config_context_cluster = "kind-kind"
}

resource "kubernetes_namespace" "ms_a" {
  metadata {
    name = "ms-a"
    labels = {
      namespace = "ms-a"
    }
  }
}

resource "kubernetes_namespace" "ms_b" {
  metadata {
    name = "ms-b"
    labels = {
      namespace = "ms-b"
    }
  }
}

resource "kubernetes_namespace" "ms_c" {
  metadata {
    name = "ms-c"
    labels = {
      namespace = "ms-c"
    }
  }
}

resource "kubernetes_network_policy" "deny_from_any" {
  metadata {
    name      = "deny-from-any"
    namespace = "ms-b"
  }

  spec {
    pod_selector {}

    policy_types = ["Ingress"]
  }
}

resource "kubernetes_network_policy" "allow_from_any" {
  metadata {
    name      = "allow-from-any"
    namespace = "ms-b"
  }

  spec {
    pod_selector {}
    # 全ingressを許可する
    ingress {}

    policy_types = ["Ingress"]
  }
}
```

NetworkPolicyを登録します。

```shell
$ tf apply -auto-approve

$ kubectl get netpol -n ms-b
NAME             POD-SELECTOR   AGE
allow-from-any   <none>         7s
deny-from-any    <none>         13m

$ kubectl describe netpol allow-from-any -n ms-b
Name:         allow-from-any
Namespace:    ms-b
Created on:   2020-07-04 20:35:03 +0900 JST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    To Port: <any> (traffic allowed to all ports)
    From: <any> (traffic not restricted by source)
  Allowing egress traffic:
    <none> (Selected pods are isolated for egress connectivity)
  Policy Types: Ingress
```

動作を確認します。

```shell
# 別namespace ms-aからアクセスできる
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-a -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
Connecting to nginx.ms-b:80 (10.99.3.231:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

# 同namespace ms-bからアクセスできる
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-b -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
Connecting to nginx.ms-b:80 (10.99.3.231:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
```

同じnamespace・Podを対象とするポリシーで許可・拒否が混在する場合、許可が優先されます。

## 他のnamespaceからの通信を拒否

<img width="741" alt="3.deny-from-other-namespaces.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/1da5de2e-29e5-f7cd-636f-3365e53d0ed7.png">

`deny-from-any`、`allow-from-any`を削除し、`deny-from-other-namespaces`を追加しています。

```hcl:main.tf
provider "kubernetes" {
  config_context_cluster = "kind-kind"
}

resource "kubernetes_namespace" "ms_a" {
  metadata {
    name = "ms-a"
    labels = {
      namespace = "ms-a"
    }
  }
}

resource "kubernetes_namespace" "ms_b" {
  metadata {
    name = "ms-b"
    labels = {
      namespace = "ms-b"
    }
  }
}

resource "kubernetes_namespace" "ms_c" {
  metadata {
    name = "ms-c"
    labels = {
      namespace = "ms-c"
    }
  }
}

resource "kubernetes_network_policy" "deny_from_other_namespaces" {
  metadata {
    name      = "deny-from-other-namespaces"
    namespace = "ms-b"
  }

  spec {
    pod_selector {}

    policy_types = ["Ingress"]
    # ingress {}ではなく、ingress { from { pod_selector {} } }にする。
    # PodSelector: <none> という状態になる。
    ingress {
      from {
        pod_selector {}
      }
    }
  }
}

```

NetworkPolicyを登録します。

```shell
$ tf apply -auto-approve

$ kubectl get netpol -n ms-b
NAME                         POD-SELECTOR   AGE
deny-from-other-namespaces   <none>         15m

$ kubectl describe netpol deny-from-other-namespaces -n ms-b
Name:         deny-from-other-namespaces
Namespace:    ms-b
Created on:   2020-07-04 20:06:35 +0900 JST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    To Port: <any> (traffic allowed to all ports)
    From:
      PodSelector: <none>
  Allowing egress traffic:
    <none> (Selected pods are isolated for egress connectivity)
  Policy Types: Ingress
```

動作を確認します。

```shell
# 別namespace ms-aからのアクセスが拒否される
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-a -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
If you don't see a command prompt, try pressing enter.
wget: download timed out
pod "busybox" deleted
pod ms-a/busybox terminated (Error)

# 同namespace（ms-b）からのアクセスできる
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-b -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
Connecting to nginx.ms-b:80 (10.99.3.231:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
```

## 特定のnamespaceを許可

<img width="741" alt="4.allow-from-other-namespaces-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/27b1daad-f4da-dea1-d116-b2e3dc40cc83.png">

`deny-from-other-namespaces`を削除し、`allow-from-other-namespaces`を追加しています。

```hcl:main.tf
provider "kubernetes" {
  config_context_cluster = "kind-kind"
}

resource "kubernetes_namespace" "ms_a" {
  metadata {
    name = "ms-a"
    labels = {
      namespace = "ms-a"
    }
  }
}

resource "kubernetes_namespace" "ms_b" {
  metadata {
    name = "ms-b"
    labels = {
      namespace = "ms-b"
    }
  }
}

resource "kubernetes_namespace" "ms_c" {
  metadata {
    name = "ms-c"
    labels = {
      namespace = "ms-c"
    }
  }
}

resource "kubernetes_network_policy" "allow_from_other_namespaces" {
  metadata {
    name      = "allow-from-other-namespaces"
    namespace = "ms-b"
  }

  spec {
    pod_selector {}

    policy_types = ["Ingress"]
    ingress {
      from {
        namespace_selector {
          # namespaceを選択する際も指定できるのはラベル
          match_labels = {
            "namespace" = "ms-a"
          }
        }
      }
    }
  }
}
```

NetworkPolicyを登録します。

```shell
$ tf apply -auto-approve

$ kubectl get netpol -n ms-b
NAME                          POD-SELECTOR   AGE
allow-from-other-namespaces   <none>         7m55s

$ kubectl describe netpol allow-from-other-namespaces -n ms-b
Name:         allow-from-other-namespaces
Namespace:    ms-b
Created on:   2020-07-04 21:20:07 +0900 JST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    To Port: <any> (traffic allowed to all ports)
    From:
      NamespaceSelector: namespace=ms-a
  Allowing egress traffic:
    <none> (Selected pods are isolated for egress connectivity)
  Policy Types: Ingress
```

動作を確認します。

```shell
# 指定した別namespace ms-aからアクセスできる
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-a -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
Connecting to nginx.ms-b:80 (10.99.3.231:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

# 指定していないnamespace ms-cからのアクセスが拒否される
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-c -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
If you don't see a command prompt, try pressing enter.
wget: download timed out
pod "busybox" deleted
pod ms-c/busybox terminated (Error)

# 同namespace（ms-b）からのアクセスは拒否される
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-b -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
If you don't see a command prompt, try pressing enter.
wget: download timed out
pod "busybox" deleted
pod ms-b/busybox terminated (Error)
```

同じnamespaceからのアクセスでも、明示的に許可しない限り拒否されます。

許可するnamespaceをvariableで複数指定する場合の書き方も見ておきましょう。今回はnamespace ms-a、ms-bからの通信を許可する例を見ます。

<img width="742" alt="5.allow-from-other-namespaces-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/b54dba4b-b8b2-31b4-e784-814bdd6752cd.png">

`allow_from_other_namespaces`を変更しています。

```hcl:main.tf
provider "kubernetes" {
  config_context_cluster = "kind-kind"
}

resource "kubernetes_namespace" "ms_a" {
  metadata {
    name = "ms-a"
    labels = {
      namespace = "ms-a"
    }
  }
}

resource "kubernetes_namespace" "ms_b" {
  metadata {
    name = "ms-b"
    labels = {
      namespace = "ms-b"
    }
  }
}

resource "kubernetes_namespace" "ms_c" {
  metadata {
    name = "ms-c"
    labels = {
      namespace = "ms-c"
    }
  }
}

variable "allowed_namespaces" {
  type    = list(string)
  default = ["ms-b"]
}

resource "kubernetes_network_policy" "allow_from_other_namespaces" {
  metadata {
    name      = "allow-from-other-namespaces"
    namespace = "ms-b"
  }

  spec {
    pod_selector {}

    policy_types = ["Ingress"]
    ingress {
      # ラベルのvalueだけ指定する場合、spec.ingress.from全体をループさせる
      dynamic "from" {
        for_each = var.allowed_namespaces

        content {
          namespace_selector {
            match_labels = {
              namespace = "${from.value}"
            }
          }
        }
      }
    }
  }
}
```

NetworkPolicyを登録します。

```shell
# allowed_namespacesにms-aとms-bを指定する
$ tf apply -var='allowed_namespaces=["ms-a","ms-b"]' -auto-approve

$ kubectl get netpol -n ms-b
NAME                          POD-SELECTOR   AGE
allow-from-other-namespaces   <none>         33m

$ kubectl describe netpol allow-from-other-namespaces -n ms-b
Name:         allow-from-other-namespaces
Namespace:    ms-b
Created on:   2020-07-04 21:20:07 +0900 JST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    To Port: <any> (traffic allowed to all ports)
    From:
      NamespaceSelector: namespace=ms-a
    From:
      NamespaceSelector: namespace=ms-b
  Allowing egress traffic:
    <none> (Selected pods are isolated for egress connectivity)
  Policy Types: Ingress
```

動作を確認します。

```shell
# 指定した別namespace ms-aからアクセスできる
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-a -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
Connecting to nginx.ms-b:80 (10.99.3.231:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

# 指定していないnamespace ms-cからのアクセスが拒否される
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-c -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
If you don't see a command prompt, try pressing enter.
wget: download timed out
pod "busybox" deleted
pod ms-c/busybox terminated (Error)

# 同namespace（ms-b）も指定したのでアクセスできる
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-b -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
Connecting to nginx.ms-b:80 (10.99.3.231:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
```

## 特定のラベルをもつPodからの通信を許可

<img width="743" alt="6.allow-from-special.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/18fead04-8ae7-af89-c207-dd43e92cd710.png">

`allow-from-other-namespaces`を削除し、`allow-from-special`を追加しています。

```hcl:main.tf
provider "kubernetes" {
  config_context_cluster = "kind-kind"
}

resource "kubernetes_namespace" "ms_a" {
  metadata {
    name = "ms-a"
    labels = {
      namespace = "ms-a"
    }
  }
}

resource "kubernetes_namespace" "ms_b" {
  metadata {
    name = "ms-b"
    labels = {
      namespace = "ms-b"
    }
  }
}

resource "kubernetes_namespace" "ms_c" {
  metadata {
    name = "ms-c"
    labels = {
      namespace = "ms-c"
    }
  }
}

resource "kubernetes_network_policy" "allow_from_special" {
  metadata {
    name      = "allow-from-special"
    namespace = "ms-b"
  }

  spec {
    pod_selector {}

    policy_types = ["Ingress"]
    ingress {
      from {
        # この1行がなければ、Podがspecial=trueというラベルを持っていても
        # アクセスが許可されないので要注意
        namespace_selector {}
        pod_selector {
          match_labels = {
            "special" = "true"
          }
        }
      }
    }
  }
}
```

NetworkPolicyを登録します。

```shell
$ tf apply -auto-approve

$ kubectl get netpol -n ms-b
NAME                 POD-SELECTOR   AGE
allow-from-special   <none>         115s

$ kubectl describe netpol allow-from-special -n ms-b
Name:         allow-from-special
Namespace:    ms-b
Created on:   2020-07-04 22:11:47 +0900 JST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    To Port: <any> (traffic allowed to all ports)
    From:
      NamespaceSelector: <none>
      PodSelector: special=true
  Allowing egress traffic:
    <none> (Selected pods are isolated for egress connectivity)
  Policy Types: Ingress
```

動作を確認します。

```shell
# 別namespace ms-aの指定したラベルをもつPodからアクセスできる
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-a --labels special=true -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
Connecting to nginx.ms-b:80 (10.99.3.231:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

# 別namespace ms-aの指定したラベルを持たないPodからのアクセスが拒否される
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-a -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
If you don't see a command prompt, try pressing enter.
wget: download timed out
pod "busybox" deleted
pod ms-a/busybox terminated (Error)
```

## 特定のportへの通信を許可

<img width="741" alt="7.allow-from-any-to-port.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/b417ddbf-8bb3-d821-85d6-710afbe19cba.png">

`allow-from-special`を削除し、`allow-from-any-to-port`を追加しています。

```hcl:main.tf
provider "kubernetes" {
  config_context_cluster = "kind-kind"
}

resource "kubernetes_namespace" "ms_a" {
  metadata {
    name = "ms-a"
    labels = {
      namespace = "ms-a"
    }
  }
}

resource "kubernetes_namespace" "ms_b" {
  metadata {
    name = "ms-b"
    labels = {
      namespace = "ms-b"
    }
  }
}

resource "kubernetes_namespace" "ms_c" {
  metadata {
    name = "ms-c"
    labels = {
      namespace = "ms-c"
    }
  }
}

resource "kubernetes_network_policy" "allow_from_any_to_port" {
  metadata {
    name      = "allow-from-any-to-port"
    namespace = "ms-b"
  }

  spec {
    pod_selector {}
    ingress {
      ports {
        port     = "80"
        protocol = "TCP"
      }
    }

    policy_types = ["Ingress"]
  }
}
```

NetworkPolicyを登録します。

```shell
$ tf apply -auto-approve

$ kubectl get netpol -n ms-b
NAME                     POD-SELECTOR   AGE
allow-from-any-to-port   <none>         49s

$ kubectl describe netpol allow-from-any-to-port -n ms-b
Name:         allow-from-any-to-port
Namespace:    ms-b
Created on:   2020-07-04 22:24:04 +0900 JST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    To Port: 80/TCP
    From: <any> (traffic not restricted by source)
  Allowing egress traffic:
    <none> (Selected pods are isolated for egress connectivity)
  Policy Types: Ingress
```

動作を確認します。

```shell
# 別namespace ms-aからnginx Serviceの80番Portを経由してアクセスできる
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-a -- /bin/sh -c 'wget -O- --timeout=5 nginx.ms-b:80'
Connecting to nginx.ms-b:80 (10.99.3.231:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

# 別namespace ms-aからnginx-90 Serviceの90番Portを経由してアクセスできる
$ kubectl run busybox --image=busybox -it --rm --restart=Never -n ms-a -- /bin/sh -c 'wget -O- nginx-90.ms-b:90'
Connecting to nginx-90.ms-b:90 (10.96.245.47:90)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
```

2つめの例がアクセスできてしまうと、一見「Port 80だけ許可」が設定できていないようです。しかし、nginx-90 Serviceの`TargetPort`は80で、ひもづくエンドポイントは`192.168.82.1:80`と`192.168.82.2:80`です。

`spec.ingress.ports.port`を指定するときは、ServiceのPortではなく、Serviceの`TargetPort`かPodの`Port`に注目してください。

```
$ kubectl get svc -n ms-b
NAME       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx      ClusterIP   10.99.3.231    <none>        80/TCP    5h42m
nginx-90   ClusterIP   10.96.245.47   <none>        90/TCP    5h42m

$ kubectl describe svc nginx-90 -n ms-b
Name:              nginx-90
Namespace:         ms-b
Labels:            <none>
Annotations:       kubectl.kubernetes.io/last-applied-configuration:
                     {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"nginx-90","namespace":"ms-b"},"spec":{"ports":[{"port":90,"protoc...
Selector:          app=nginx
Type:              ClusterIP
IP:                10.96.245.47
Port:              <unset>  90/TCP
TargetPort:        80/TCP
Endpoints:         192.168.82.1:80,192.168.82.2:80
Session Affinity:  None
Events:            <none>

$ kubectl describe po nginx-6b474476c4-hqh9x -n ms-b | grep Port:
    Port:           80/TCP
    Host Port:      0/TCP
```

ロードバランサーやクラスタ外から受けるリクエストは、ラベルを持っているわけではありません。そのため、デフォルトで全通信を拒否する場合などは特定のPortやCIRDを許可して対応します。

# ローカルクラスターもTerraformで管理してみる

オマケです。今回KubernetesのリソースはTerraformで管理しましたが、kindで作成したリソースはTerraformで管理しませんでした。

[kind用のプロバイダー](https://github.com/kyma-incubator/terraform-provider-kind)があるので、試してみましょう。

このプロバイダーはTerraform Registryに登録されていないため、`terraform init`を実行しても自動でダウンロードされません。

そのため、プロバイダーのソースコードをリポジトリからダウンロード、ビルド、所定の位置に移動します。

```shell
$ git clone git@github.com:kyma-incubator/terraform-provider-kind.git
$ cd terraform-provider-kind
# Goの環境が必要です
$ go build -o terraform-provider-kind
# OSとアーキテクチャによって異なります
# https://www.terraform.io/docs/configuration/providers.html#os-and-architecture-directories
# これはグローバルにプラグインを管理する例です。
# Terraformプロジェクト毎に管理する場合、プロジェクトの .terraform/plugins/darwin_amd64 
# ディレクトリを利用してください。
$ mkdir -p $HOME/.terraform.d/plugins/darwin_amd64
$ cp ./terraform-provider-kind $HOME/.terraform.d/plugins/darwin_amd64/
```

kind用のディレクトリを用意し、移動ます。

```shell
$ cd ..
$ mkdir kind
$ cd kind

main.tf
```hcl
provider "kind" {}

resource "kind" "my-cluster" {
    # これに kind- プレフィックスをつけたものがコンテキスト名になります
    name = "test-cluster"
}
```

ローカルにクラスターを構築します。

```
$ terraform init
$ terraform apply -auto-approve
kind.my-cluster: Creating...
kind.my-cluster: Still creating... [10s elapsed]
...
kind.my-cluster: Still creating... [1m50s elapsed]
kind.my-cluster: Creation complete after 1m56s [id=test-cluster-kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

いったん削除します。

```shell
$ terraform destroy -auto-approve
```

namespaceを用意し、アプリケーションをデプロイしてみましょう。

```hcl:main.tf
provider "kind" {}

resource "kind" "my-cluster" {
    name = "test-cluster"
}

provider "kubernetes" {
  config_context_cluster = "kind-kind"
}

resource "kubernetes_namespace" "ms_a" {
  metadata {
    name = "ms-a"
    labels = {
      namespace = "ms-a"
    }
  }
}
```

クラスタを作成し、NGINXをnamespace ms-aにデプロイします。

```shell
$ terraform init

$ kubectx kind-my-cluster
Switched to context "kind-my-cluster".

$ kubectl get ns | grep ms-
ms-a                 Active   72s

$ kubectl run nginx --image=nginx --replicas=2 -n ms-a
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx created

$ kubectl get po -n ms-a
NAME                     READY   STATUS    RESTARTS   AGE
nginx-6db489d4b7-2gn24   1/1     Running   0          30s
nginx-6db489d4b7-pdzn4   1/1     Running   0          30s
```

NetworkPolicyの検証用にkindでクラスタを構築する際、つぎのような設定を利用しました。

```yaml:config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  # Defaulr CNI (kindnet) doesn't support NetworkPolicy
  disableDefaultCNI: true 
  podSubnet: 192.168.0.0/16
```

kyma-incubator/terraform-provider-kindの実装上、現在[kindリソース](https://github.com/kyma-incubator/terraform-provider-kind/blob/master/kind/resource_kind.go#L19)で利用できる属性はつぎのとおりです。

* name
* node_image
* kind_config
* kubeconfig
* kubeconfig_path
* client_certificate
* client_key
* cluster_ca_certificate
* endpoint

config.yamlの設定内容はkind_configにstringとして渡されるため、Terraform上はつぎのようにヒアドキュメント形式で書きます。

```hcl:main.tf
provider "kind" {}

resource "kind" "my-cluster" {
    name = "test-cluster"
    kind_config =<<KIONF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  disableDefaultCNI: true 
  podSubnet: 192.168.0.0/16
KIONF
}
```

リソースや属性の粒度は十分変わり得ます。

# 参考情報

* [Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
  * Kubernertesの公式ドキュメント
* [ahmetb/kubernetes-network-policy-recipes](https://github.com/ahmetb/kubernetes-network-policy-recipes)
  * 公式ドキュメントからも参照されているユースケース集
* [About Calico](https://docs.projectcalico.org/introduction/)
  * Calicoの公式ドキュメント