Help us understand the problem. What is going on with this article?

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はKubernetesのリソースの1つで、namespace内のPodに対するアクセスをラベルセレクターを利用して制御できます。

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

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

具体例を見てみます。

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: bookstorerole: apiというラベルをもつPodに関して」、「リクエストが許可されるのはapp: bookstoreというラベルをもったPodだけ」という通信制約を課すことができます。

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

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

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

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

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

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

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

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

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を利用します。kindは、テスト用のKubernetesクラスタをローカル環境に手軽に構築できるツールです。

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

# デフォルト設定で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という、Kubernetes JobをGoのソースコードから実行するCLIのE2Eテストに利用しました。

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

kindのデフォルトCNIであるkindnetdは、NetworkPolicyをサポートしていません。そのため、デフォルトのCNIを無効化し、Calicoをインストールする方法がkindのissueで紹介されています。

設定ファイルを書き、

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を利用できます。

# 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で管理し、Namespaceなどテナント周りのリソースはTerraformで管理しています。NetworkPolicyもテナント系リソースとして管理する方針のため、本記事でもTerraformで検証しています。

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

TerraformでKubernetesリソースを管理するにあたっては、hashicorp/terraform-provider-kubernetesを利用します。

最近出たterraform-provider-kubernetes-alphaと比較して制約はあるものの成熟しており、現状のユースケースでは不自由ありません。

Kubernetes関連のプロバイダーの比較記事がkubernetesブログに出ています。

Kubernetes Providerでkindを設定する

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

さまざまな設定方法がありますが、今回はkubectlでも使っているcontextを利用して簡易に設定します。

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

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

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

# クラスター情報の確認
$ 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とアプリケーションの準備

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"
    }
  }
}
# 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.
# 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
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
# 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にリクエストします。

# 別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>
...

全通信を拒否する

1.deny-from-any.png

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

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を登録します。

$ 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

動作を確認します。

# 別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)

全通信を許可する

2.allow-from-any.png

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

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を登録します。

$ 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

動作を確認します。

# 別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からの通信を拒否

3.deny-from-other-namespaces.png

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

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を登録します。

$ 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

動作を確認します。

# 別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を許可

4.allow-from-other-namespaces-1.png

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

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を登録します。

$ 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

動作を確認します。

# 指定した別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からの通信を許可する例を見ます。

5.allow-from-other-namespaces-2.png

allow_from_other_namespacesを変更しています。

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を登録します。

# 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

動作を確認します。

# 指定した別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からの通信を許可

6.allow-from-special.png

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

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を登録します。

$ 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

動作を確認します。

# 別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への通信を許可

7.allow-from-any-to-port.png

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

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を登録します。

$ 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

動作を確認します。

# 別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:80192.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用のプロバイダーがあるので、試してみましょう。

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

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

$ 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用のディレクトリを用意し、移動ます。

$ 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.

いったん削除します。

$ terraform destroy -auto-approve

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

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にデプロイします。

$ 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でクラスタを構築する際、つぎのような設定を利用しました。

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リソースで利用できる属性はつぎのとおりです。

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

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

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
}

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

参考情報

toshi0607
最近はGoとServerlessが好きです!
https://toshi0607.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした