Howdunnit

存在証明と生存確認

Kubernetes Network Policy を kind で遊んでみた

はじめに

KubernetesのNetwork Policyについては以前から興味があったのですが、なかなか触る機会がなかったので、今回触ってみました。
Kubernetesを勉強するにあたってGKEやEKSの選択肢はあったのですが、手元でコストをかけず触ってみるということでkindを選択しました。

使用ツール

今回の記事で使用するツールは以下になります。

  • kind
  • kubectl

kind Cluster 構築

Network Policyを試してみるための環境を構築します。 今回は、kindを使用してKubernetes Clusterを構築します。

kind.sigs.k8s.io

kindは簡単にKubernetes Clusterを構築できますが、今回はNetwork Policyを使用するため、Network Policyに対応しているネットワークプラグイン(CNIプラグイン)を使用しなければなりません。そのため、kindデフォルトのCNI(kindnetd)を無効化すること、また、Node数を増やす設定を入れた設定ファイルを適用してKubernetes Clusterを構築します。

$ cat kind-config.yaml
kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
networking:
  disableDefaultCNI: true
nodes:
  - role: control-plane
  - role: worker
  - role: worker
  - role: worker

$ kind create cluster --config kind-config.yaml

構築が完了し、KUBECONFIGを設定した後、Kubernetes Cluster内のPodの状態を確認してみます。 CNIを無効にしているため、CoreDNSがPendingになっていることが分かると思います。

$ kubectl get pods --all-namespaces
NAMESPACE     NAME                                         READY   STATUS    RESTARTS   AGE
kube-system   coredns-5c98db65d4-9zb27                     0/1     Pending   0          8m42s
kube-system   coredns-5c98db65d4-lp89j                     0/1     Pending   0          8m42s
kube-system   etcd-kind-control-plane                      1/1
Running   0          7m52s
kube-system   kube-apiserver-kind-control-plane            1/1     Running   0          8m2s
kube-system   kube-controller-manager-kind-control-plane   1/1     Running   0          7m57s
kube-system   kube-proxy-8pjd4                             1/1     Running   0          8m25s
kube-system   kube-proxy-ggjcr                             1/1     Running   0          8m42s
kube-system   kube-proxy-qt2jz                             1/1     Running   0          8m25s
kube-system   kube-proxy-x7w9l                             1/1     Running   0          8m25s
kube-system   kube-scheduler-kind-control-plane            1/1     Running   0          7m43s

次に、このKubernetes Clusterに割り当てられたCIDR範囲を確認します。

$ kubectl cluster-info dump | grep -- --cluster-cidr
                            "--cluster-cidr=10.244.0.0/16",

次に、CalicoをKubernetes Clisterに適用していきます。 CalicoはNetwork Policyに対応しているネットワークプラグインです。

www.projectcalico.org

CIDR範囲をKubernetes Clusterに割り当てられたものと合わせるため、calico.yamlCALICO_IPV4POOL_CIDROを変更します。

$ curl -s https://docs.projectcalico.org/v3.11/manifests/calico.yaml | sed 's,192.168.0.0/16,10.244.0.0/16,' > calico.yaml

CalicoをKubernetes Clusterに適用します。

$ kubectl apply -f calico.yaml
configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.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/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を適用した後、Kubernetes Cluster内のPodの状態を再度確認してみます。 CNIが有効化されたため、CalicoのPodとCoreDNSのPodがRunningになっていることが確認できると思います。

$ kubectl get pods --all-namespaces
NAMESPACE     NAME                                         READY   STATUS    RESTARTS   AGE
kube-system   calico-kube-controllers-758d7b8fd-trwb5      1/1     Running   0          14m
kube-system   calico-node-bf6qg                            0/1     Running   4          14m
kube-system   calico-node-cs5cz                            0/1     Running   5          14m
kube-system   calico-node-twkkl                            0/1     Running   4          14m
kube-system   calico-node-zpgz2                            0/1     Running   6          14m
kube-system   coredns-5c98db65d4-9zb27                     1/1     Running   0          55m
kube-system   coredns-5c98db65d4-lp89j                     1/1     Running   0          55m
kube-system   etcd-kind-control-plane                      1/1     Running   0          54m
kube-system   kube-apiserver-kind-control-plane            1/1     Running   0          54m
kube-system   kube-controller-manager-kind-control-plane   1/1     Running   0          54m
kube-system   kube-proxy-8pjd4                             1/1     Running   0          54m
kube-system   kube-proxy-ggjcr                             1/1     Running   0          55m
kube-system   kube-proxy-qt2jz                             1/1     Running   0          54m
kube-system   kube-proxy-x7w9l                             1/1     Running   0          54m
kube-system   kube-scheduler-kind-control-plane            1/1     Running   0          54m

しかし、calico-nodeというDaemonSetが再起動を繰り返し、Readyになっていません。
同じ問題を抱えている issue: installing calico requires a change to net.ipv4.conf.all.rp_filter · Issue #891 · kubernetes-sigs/kind · GitHub があったので、issue内に記載がある通りcalico-nodeのPodのFELIX_IGNORELOOSERPFという環境変数をtrueに変更します。

$ kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true

daemonset.extensions/calico-node env updated

caloco-nodeが更新、再作成され、Readyになっていることを確認できました。

$ kubectl get pods --all-namespaces
NAMESPACE     NAME                                         READY   STATUS    RESTARTS   AGE
kube-system   calico-kube-controllers-758d7b8fd-trwb5      1/1     Running   0          54m
kube-system   calico-node-bf6qg                            1/1     Running   0          2m46s
kube-system   calico-node-cs5cz                            1/1     Running   0          2m46s
kube-system   calico-node-twkkl                            1/1     Running   0          2m46s
kube-system   calico-node-zpgz2                            1/1     Running   0          2m45s
kube-system   coredns-5c98db65d4-9zb27                     1/1     Running   0          95m
kube-system   coredns-5c98db65d4-lp89j                     1/1     Running   0          95m
kube-system   etcd-kind-control-plane                      1/1     Running   0          95m
kube-system   kube-apiserver-kind-control-plane            1/1     Running   0          95m
kube-system   kube-controller-manager-kind-control-plane   1/1     Running   0          95m
kube-system   kube-proxy-8pjd4                             1/1     Running   0          95m
kube-system   kube-proxy-ggjcr                             1/1     Running   0          95m
kube-system   kube-proxy-qt2jz                             1/1     Running   0          95m
kube-system   kube-proxy-x7w9l                             1/1     Running   0          95m
kube-system   kube-scheduler-kind-control-plane            1/1     Running   0          94m

Network Policy の作成

今回はNetwork Policyのフィールドの簡単な説明と簡単なテストを実施していきます。 詳しくは、公式のページをご確認ください。

kubernetes.io

kubernetes.io

まず、Network PolicyのManifest例を記載します。

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

Network Policyを有効化するためにアクセス制御したい対象のPodと同じNamespaceにNetworkPolicyリソースを作成し、アクセス制御ルールを定義します。アクセス制御したい対象のPodはspec.podSelector.matchLabelsにて、対象のPodをラベルセレクトします。
spec.policyTypesでは、このNetworkPolicyリソースで扱うルールを選択します。ルールには、Ingress(インバウンドのトラフィック)とEgress(アウトバウンドのトラフィック)に関するルールが定義できます。
Ingress、Egressそれぞれのルールの定義については、spec.ingressspec.egressに設定します。Ingress、Egress共にfrom、toの違いはありますが、設定方法は同じです。from/toにて対象を指定し、portsにて対象のportとプロトコルを指定します。
from/toで使用される対象の指定方法には以下3つがあります。

policy type target
podSelector 特定のPodが対象
namespaceSelector 特定のNamespaceが対象
ipBlock 特定のCIDRが対象

最後に、簡単な例として全てのIngress/Egressルールを許可するManifestと遮断するManifestを記載します。

  • 全てのIngress/Egressルールを許可
  $ cat network-policy.yaml
  apiVersion: networking.k8s.io/v1
  kind: NetworkPolicy
  metadata:
    name: network-policy
  spec:
    podSelector: {}
    policyTypes:
    - Ingress
    - Egress
    ingress:
    - {}
    egress:
    - {}
  • 全てのIngress/Egressルールを遮断
  $ cat network-policy.yaml
  apiVersion: networking.k8s.io/v1
  kind: NetworkPolicy
  metadata:
    name: network-policy
  spec:
    podSelector: {}
    policyTypes:
    - Ingress
    - Egress

Network Policyについて簡単なテストを行っていきます。
Namespaceが違うA, B, Cというアプリがある状態で、Bに対してNetwork Policyを適用し、A => Bへの接続を拒否し、C => Bへの接続を許可するということをやってみたいと思います。また、podSelector、namespaceSelectorを使用してみたいので、Cが属するNamespaceには、Namespace自体にもlabelを付与します。

以下Manifestを適用して、test-atest-btest-cというNamespaceを作成し、それぞれtest-pod-atest-pod-btest-pod-cというPodを起動します。また、labelについてもapp: test-aapp: test-bapp: test-cと設定します。

$ cat test-pods.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: test-a

---
apiVersion: v1
kind: Namespace
metadata:
  name: test-b

---
apiVersion: v1
kind: Namespace
metadata:
  name: test-c
  labels:
    ns: test-c

---
apiVersion: v1
kind: Pod
metadata:
  name: test-pod-a
  namespace: test-a
  labels:
    app: test-a
spec:
  containers:
    - name: nginx
      image: nginx:1.12-alpine

---
apiVersion: v1
kind: Pod
metadata:
  name: test-pod-b
  namespace: test-b
  labels:
    app: test-b
spec:
  containers:
    - name: nginx
      image: nginx:1.12-alpine

---
apiVersion: v1
kind: Pod
metadata:
  name: test-pod-c
  namespace: test-c
  labels:
    app: test-c
spec:
  containers:
    - name: nginx
      image: nginx:1.12-alpine

次に、作成されたA、B、CのPodのIPアドレスを確認します。

$ kubectl get pods -o wide -n test-a
NAME         READY   STATUS    RESTARTS   AGE     IP               NODE           NOMINATED NODE   READINESS GATES
test-pod-a   1/1     Running   0          4m18s   10.244.195.194   kind-worker3   <none>           <none>

$ kubectl get pods -o wide -n test-b
NAME         READY   STATUS    RESTARTS   AGE   IP               NODE           NOMINATED NODE   READINESS GATES
test-pod-b   1/1     Running   0          17m   10.244.110.131   kind-worker2   <none>           <none>

$ kubectl get pods -o wide -n test-c
NAME         READY   STATUS    RESTARTS   AGE     IP               NODE          NOMINATED NODE   READINESS GATES
test-pod-c   1/1     Running   0          4m23s   10.244.162.131   kind-worker   <none>           <none>

ここまでで以下のような状態になります。

次に、以下のNetworkPolicyを適用します。

$ cat network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: network-policy
  namespace: test-b
spec:
  podSelector:
    matchLabels:
      app: test-b
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          ns: test-c
    ports:
    - protocol: TCP
      port: 80

この状態でA => B、C => B、A => Cへの接続を試してみます。

# A => B
$ kubectl -n test-a exec -it test-pod-a -- wget -T 3 10.244.110.131
Connecting to 10.244.110.131 (10.244.110.131:80)
wget: download timed out
command terminated with exit code 1

# C => B
$ kubectl -n test-c exec -it test-pod-c -- wget -T 3 10.244.110.131
Connecting to 10.244.110.131 (10.244.110.131:80)
index.html           100% |**************************************************|   612   0:00:00 ETA

# A => C
kubectl -n test-a exec -it test-pod-a -- wget -T 3 10.244.162.131
Connecting to 10.244.162.131 (10.244.162.131:80)
index.html           100% |*******************************|   612   0:00:00 ETA

A -> Bだと接続できずタイムアウトになり、C => Bだと接続できていることが分かります。また、A => C は特に制御していないため、接続できていることがわかります。
以下の状態になっていることが確認できました。

まとめ

KubernetesのNetwork Policyについて調べて使ってみました。以前から試そうと思っていたのですが、実際に触ってみるとCalicoのことなど知らなかったことも知ることができてとても良かったです。次はPod Security Policyや、Open Policy Agentなどに挑戦してみたいです。
最後に、今回紹介した設定ファイルなどをリポジトリにまとめましたので、ファイルとして参照したい方は以下のリポジトリを利用してみてください。

github.com