ローカル環境の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: 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に対し、全受信トラフィックを許可します。
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.ingress
とspec.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で紹介されています。
設定ファイルを書き、
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のprovider
のconfig_context_cluster
にkind-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とアプリケーションの準備
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
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>
...
全通信を拒否する
もとのproviderとnamespace設定に変更はありません。deny-from-any
を追加しています。
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)
全通信を許可する
deny-from-any
を残したままallow-from-any
を追加しています。
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からの通信を拒否
deny-from-any
、allow-from-any
を削除し、deny-from-other-namespaces
を追加しています。
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を許可
deny-from-other-namespaces
を削除し、allow-from-other-namespaces
を追加しています。
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からの通信を許可する例を見ます。
allow_from_other_namespaces
を変更しています。
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からの通信を許可
allow-from-other-namespaces
を削除し、allow-from-special
を追加しています。
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への通信を許可
allow-from-special
を削除し、allow-from-any-to-port
を追加しています。
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: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用のプロバイダーがあるので、試してみましょう。
このプロバイダーは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を用意し、アプリケーションをデプロイしてみましょう。
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でクラスタを構築する際、つぎのような設定を利用しました。
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上はつぎのようにヒアドキュメント形式で書きます。
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
- Kubernertesの公式ドキュメント
-
ahmetb/kubernetes-network-policy-recipes
- 公式ドキュメントからも参照されているユースケース集
-
About Calico
- Calicoの公式ドキュメント