Kubernetes theory
May 5, 2023

NetworkPolicy

Объект в кластере k8s, позволяющий ограничивать доступ к ресурсам по сети внутри кластера.

Особенности.

  1. Зависит от используемого CNI. Если плагин не поддерживает netpol, то они не будут работать. Хотя и можно их создать. Flannel, например, не поддерживает их;
  2. Используется на уровне namespace;
  3. С его помощью можно заблокировать трафик к какому-либо поду;
  4. Можно заблокировать доступ к ресурсам для сторонних ip адресов;
  5. Когда открываем входящий трафик (ingress), то исходящий трафик (egress) открывается автоматически;
  6. Если политики не определены, Kubernetes по умолчанию разрешает весь трафик. Все pod'ы свободно могут обмениваться информацией между собой;
  7. Порядок политик в ветках ingress/egress не играет значения;
  8. Каждый ресурс, затронутый хотя бы одной из политик, становится изолированным в соответствии с дизъюнкцией (логическим ИЛИ) всех политик, которые его выбрали;
  9. Применяется к ресурсу, используя labels/selector;
  10. Может работать с протоколами TCP, UDP, SCTP.

Пример

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: internal-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      name: internal
  policyTypes:
  - Egress
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          name: payroll
    ports:
    - port: 8080
      protocol: TCP
  egress:
  - to:
    - podSelector:
        matchLabels:
          name: mysql
    ports:
    - protocol: TCP
      port: 3306
  - to:
    - podSelector:
        matchLabels:
          name: payroll
    ports:
    - protocol: TCP
      port: 8080
  - ports:
    - port: 53
      protocol: UDP
    - port: 53
      protocol: TCP
EOF

Открываем трафик к поду с PostgreSQL.

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access.postgres
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: postgres
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: balance
  policyTypes:
  - Ingress
EOF

Открываем входящий трафик для определённых подов или определённого namespace или определённых адресов.

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          ns: prod
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978
EOF

Если опустить policyTypes, политика будет интерпретироваться следующим образом:

  • По умолчанию предполагается, что она определяет ingress-сторону. Если явных указаний на этот счет в политике не содержится, система будет считать, что весь трафик запрещен.
  • Поведение на egress-стороне будет определяться наличием или отсутствием соответствующего egress-параметра.

В соответствии с приведенной выше логикой в случае, если параметры ingress и/или egress опущены, политика будет запрещать весь трафик.

Блок namespaceSelector говорит о том, на какой namespace влияют правила NetworkPloicy.

- namespaceSelector:
        matchLabels:
          project: default

Namespace выбирается с помощью label.

Открыть весь входящий трафик.

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all
  namespace: default
spec:
  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Ingress
EOF

Закрыть весь входящий трафик.

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Ingress
EOF

Закрыть весь исходящий трафик.

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-egress
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Egress
EOF

Включить входящий трафик к определённому набору подов в конкретном namespace.

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-to-balance
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: balance
  ingress:
  - {}
  policyTypes:
  - Ingress
EOF

Действительно для подов с лейблами app=balance и из namespace default.

Политика разрешает весь входящий (ingress) и исходящий (egress) трафик, включая доступ к любому IP за пределами кластера

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all
  namespace: default
spec:
  podSelector: {}
  ingress:
  - {}
  egress:
  - {}
  policyTypes:
  - Ingress
  - Egress
EOF

Входящий трафик разрешается от подов с разными лейблами.

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.postgres
  namespace: default
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: indexer
    - podSelector:
        matchLabels:
          app: admin
  podSelector:
    matchLabels:
      app: postgres
  policyTypes:
  - Ingress
EOF

Для подов с лейблами app=postgres разрешён входящий трафик со стороны подов с лейблами app=indexer и app=admin

Пример с созданием трёх подов и хождением трафика между ними с разными правилами NetworkPolicy

kubectl create ns test
kubens test
kubectl create deploy cam-nginx1 --image=nginx:latest --replicas=1
kubectl create deploy cam-nginx2 --image=nginx:latest --replicas=1
kubectl create deploy cam-nginx3 --image=nginx:latest --replicas=1
kubectl get po --show-labels -owide
NAME                          READY   STATUS    RESTARTS   AGE   IP             NODE                        NOMINATED NODE   READINESS GATES   LABELS
cam-nginx1-7d55b6dcc6-7qsck   1/1     Running   0          72m   10.95.129.26   cl1qncbjrtn09rafjhns-emol   <none>           <none>            app=cam-nginx1,pod-template-hash=7d55b6dcc6
cam-nginx2-f57b74467-f7lmj    1/1     Running   0          73m   10.95.128.31   cl1qncbjrtn09rafjhns-esuj   <none>           <none>            app=cam-nginx2,pod-template-hash=f57b74467
cam-nginx3-d4d99bbc8-8mbqr    1/1     Running   0          51s   10.95.129.27   cl1qncbjrtn09rafjhns-emol   <none>           <none>            app=cam-nginx3,pod-template-hash=d4d99bbc8
# Пингую с первого пода второй
kubectl exec --tty --stdin cam-nginx1-7d55b6dcc6-7qsck -- ping 10.95.128.31
PING 10.95.128.31 (10.95.128.31) 56(84) bytes of data.
64 bytes from 10.95.128.31: icmp_seq=1 ttl=61 time=1.16 ms

Без правил netpol, ping между подами проходит без помех. Т.е. по дефолту используются правила "Всем всё".

Создаём правило на входящий трафик для пода с лейблом cam-nginx2. Разрешается входящий трафик с пода cam-nginx1.

# Правило установлено для второго пода cam-nginx2-f57b74467-f7lmj.
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access.nginx
  namespace: test
spec:
  podSelector:
    matchLabels:
      app: cam-nginx2
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: cam-nginx1
  policyTypes:
  - Ingress
EOF
kubectl get netpol
NAME           POD-SELECTOR     AGE
access.nginx   app=cam-nginx2   31s

kubectl describe netpol access.nginx
Name:         access.nginx
Namespace:    test
Created on:   2023-05-06 01:12:20 +0300 MSK
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     app=cam-nginx2
  Allowing ingress traffic:
    To Port: <any> (traffic allowed to all ports)
    From:
      PodSelector: app=cam-nginx1
  Not affecting egress traffic
  Policy Types: Ingress
# Пингую с первого пода второй
kubectl exec --tty --stdin cam-nginx1-7d55b6dcc6-7qsck -- ping 10.95.128.31
PING 10.95.128.31 (10.95.128.31) 56(84) bytes of data.
64 bytes from 10.95.128.31: icmp_seq=1 ttl=61 time=1.27 ms
# Пингую с третьего пода второй. Нет соединения.
kubectl exec --tty --stdin cam-nginx3-d4d99bbc8-8mbqr -- ping 10.95.128.31
PING 10.95.128.31 (10.95.128.31) 56(84) bytes of data.
# Пингую со второго пода первый
kubectl exec --tty --stdin cam-nginx2-f57b74467-f7lmj -- ping 10.95.129.26
PING 10.95.129.26 (10.95.129.26) 56(84) bytes of data.
64 bytes from 10.95.129.26: icmp_seq=1 ttl=61 time=54.2 ms

# Пингую со второго пода третий
kubectl exec --tty --stdin cam-nginx2-f57b74467-f7lmj -- ping 10.95.129.27
PING 10.95.129.27 (10.95.129.27) 56(84) bytes of data.
64 bytes from 10.95.129.27: icmp_seq=1 ttl=61 time=0.955 ms

Если пингуем с пода с которого трафик разрешён, то пинг проходит. Если с того, с которого не разрешён входящий трафик, то пинг не проходит.
С пода, на который установлена политика можно пинговать оба пода.

Разрешаем исходящий трафик с пода cam-nginx2 на под cam-nginx3

# Правило установлено для второго пода.
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access.nginx
  namespace: test
spec:
  podSelector:
    matchLabels:
      app: cam-nginx2
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: cam-nginx1
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: cam-nginx3
  policyTypes:
  - Ingress
  - Egress
EOF
kubectl describe netpol access.nginx
Name:         access.nginx
Namespace:    test
Created on:   2023-05-06 01:12:20 +0300 MSK
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     app=cam-nginx2
  Allowing ingress traffic:
    To Port: <any> (traffic allowed to all ports)
    From:
      PodSelector: app=cam-nginx1
  Allowing egress traffic:
    To Port: <any> (traffic allowed to all ports)
    To:
      PodSelector: app=cam-nginx3
  Policy Types: Ingress, Egress
# Пингую со второго пода первый
kubectl exec --tty --stdin cam-nginx2-f57b74467-f7lmj -- ping 10.95.129.26
PING 10.95.129.26 (10.95.129.26) 56(84) bytes of data.
^C
--- 10.95.129.26 ping statistics ---
17 packets transmitted, 0 received, 100% packet loss, time 16363ms

# Пингую со второго пода третий
kubectl exec --tty --stdin cam-nginx2-f57b74467-f7lmj -- ping 10.95.129.27
PING 10.95.129.27 (10.95.129.27) 56(84) bytes of data.
64 bytes from 10.95.129.27: icmp_seq=1 ttl=61 time=1.08 ms

# Пингую с первого пода третий
kubectl exec --tty --stdin cam-nginx1-7d55b6dcc6-7qsck -- ping 10.95.129.27
PING 10.95.129.27 (10.95.129.27) 56(84) bytes of data.
64 bytes from 10.95.129.27: icmp_seq=1 ttl=63 time=0.065 ms

После установки блока egress исходящий трафик может уходить только на те поды, которые прописаны в блоке egress.

Пересоздал поды.

kubectl get po -owide --show-labels
NAME                          READY   STATUS    RESTARTS   AGE   IP             NODE                        NOMINATED NODE   READINESS GATES   LABELS
cam-nginx1-7d55b6dcc6-bhrgz   1/1     Running   0          12h   10.95.130.13   cl1qncbjrtn09rafjhns-ataw   <none>           <none>            app=cam-nginx1,pod-template-hash=7d55b6dcc6
cam-nginx2-f57b74467-vf4j7    1/1     Running   0          12h   10.95.130.14   cl1qncbjrtn09rafjhns-ataw   <none>           <none>            app=cam-nginx2,pod-template-hash=f57b74467
cam-nginx3-d4d99bbc8-gmkqz    1/1     Running   0          12h   10.95.130.15   cl1qncbjrtn09rafjhns-ataw   <none>           <none>            app=cam-nginx3,pod-template-hash=d4d99bbc8
cam-nginx4-56695f4c8c-2pl78   1/1     Running   0          12h   10.95.130.3    cl1qncbjrtn09rafjhns-ataw   <none>           <none>            app=cam-nginx4,pod-template-hash=56695f4c8c

Разрешаем входящий трафик на второй под с конкретного адреса или с подов определённым селектором.

# Правило установлено для второго пода cam-nginx2-f57b74467-vf4j7.
# Блок ipBlock и podSelector в блоке ingress имеют логику ИЛИ. 
# Т.е доступ к поду будет, если сработает одно из правил.
# Входящий трафик разрешён с адреса 10.95.130.3/32 или от пода с лейблом cam-nginx3
# Исходящий трафик разрешён на под с лейблом cam-nginx3
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access.nginx
  namespace: test
spec:
  podSelector:
    matchLabels:
      app: cam-nginx2
  ingress:
  - from:
    - ipBlock:
        cidr: 10.95.130.3/32
    - podSelector:
        matchLabels:
          app: cam-nginx3
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: cam-nginx3
  policyTypes:
  - Ingress
  - Egress
EOF
kubectl exec --tty --stdin cam-nginx4-56695f4c8c-2pl78 -- ping 10.95.130.14
PING 10.95.130.14 (10.95.130.14) 56(84) bytes of data.
64 bytes from 10.95.130.14: icmp_seq=1 ttl=63 time=0.075 ms

kubectl exec --tty --stdin cam-nginx3-d4d99bbc8-gmkqz -- ping 10.95.130.14
PING 10.95.130.14 (10.95.130.14) 56(84) bytes of data.
64 bytes from 10.95.130.14: icmp_seq=1 ttl=63 time=0.072 ms

Если в спецификации NetworkPolicy поменять адрес в блоке ipBlock на 10.95.130.3/32, то доступ с четвёртого пода ко второму пропадает.

В примере выше между блоками ipBlock и podSelector используется логика ИЛИ.

Ограничение доступа по портам.

# До ограничения доступа на порты
kubectl exec --tty --stdin cam-nginx3-d4d99bbc8-gmkqz -- nc -vz 10.95.130.14 80
Connection to 10.95.130.14 80 port [tcp/*] succeeded!
# Правило установлено для второго пода cam-nginx2-f57b74467-vf4j7.
# Ограничим доступ к поду по 80 порту.
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access.nginx
  namespace: test
spec:
  podSelector:
    matchLabels:
      app: cam-nginx2
  ingress:
  - from:
    - ipBlock:
        cidr: 10.95.130.3/32
    - podSelector:
        matchLabels:
          app: cam-nginx3
    ports:
    - port: 443
      protocol: TCP
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: cam-nginx3
    ports:
    - port: 443
      protocol: TCP
  policyTypes:
  - Ingress
  - Egress
EOF
# После ограничения доступа на порты
kubectl exec --tty --stdin cam-nginx3-d4d99bbc8-gmkqz -- nc -vz 10.95.130.14 80
command terminated with exit code 130

# Поменял порт с 443->80
kubectl exec --tty --stdin cam-nginx3-d4d99bbc8-gmkqz -- nc -vz 10.95.130.14 80
Connection to 10.95.130.14 80 port [tcp/*] succeeded!

Ограничение доступа по namespace.

# Создаём ещё 1 namespace, под. И из этого пода попингуем тестовый под.
kubectl create ns prod
kubens prod
kubectl create deploy cam-nginxx3 --image=nginx:latest --replicas=1

kubectl get po -owide
NAME                          READY   STATUS    RESTARTS   AGE   IP             NODE                        NOMINATED NODE   READINESS GATES
cam-nginxx3-6b4df76bb-jb8f7   1/1     Running   0          16s   10.95.130.22   cl1qncbjrtn09rafjhns-ataw   <none>           <none>

# До ограничения доступа по ns
kubectl exec --tty --stdin cam-nginx3-d4d99bbc8-gmkqz -- ping 10.95.130.14
PING 10.95.130.14 (10.95.130.14) 56(84) bytes of data.
64 bytes from 10.95.130.14: icmp_seq=1 ttl=63 time=0.091 ms
# Правило установлено для второго пода cam-nginx2-f57b74467-vf4j7.
# Входящий трафик разрешён только из ns с лейблом test по 80 порту.
# Исходящий трафик разрешён только по 80 порту на поды из ns test. 
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access.nginx
  namespace: test
spec:
  podSelector:
    matchLabels:
      app: cam-nginx2
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          ns: test
    ports:
    - port: 80
      protocol: TCP
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          ns: test
    ports:
    - port: 80
      protocol: TCP
  policyTypes:
  - Ingress
  - Egress
EOF
# После применения политики ограничения по namspace
kubectl exec --tty --stdin cam-nginxx3-6b4df76bb-jb8f7 — ping 10.95.130.14
PING 10.95.130.14 (10.95.130.14) 56(84) bytes of data.
^C
-— 10.95.130.14 ping statistics -— 8 packets transmitted, 0 received, 100% packet loss, time 7176ms

Логика И. Ограничить поды по неймспейсам И лейблам.

# В данном примере доступ к поду 2 будет у пода из namespace с лейблом ns: test
# И у этого пода должен быть лейбл cam-nginx3
# Исходящий трафик идёт только по 80 порту на под с лейблом cam-nginx3
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access.nginx
  namespace: test
spec:
  podSelector:
    matchLabels:
      app: cam-nginx2
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          ns: test
      podSelector:
        matchLabels:
          app: cam-nginx3
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: cam-nginx3
    ports:
    - port: 80
      protocol: TCP
  policyTypes:
  - Ingress
  - Egress
EOF
# Подключение из другого ns не удалось.
kubectl exec --tty --stdin cam-nginxx3-6b4df76bb-jb8f7 -- ping 10.95.130.14
PING 10.95.130.14 (10.95.130.14) 56(84) bytes of data.
^C
--- 10.95.130.14 ping statistics ---
5 packets transmitted, 0 received, 100% packet loss, time 4084ms

# Подключение из того же ns.
kubectl exec --tty --stdin cam-nginx3-d4d99bbc8-gmkqz -- ping 10.95.130.14
PING 10.95.130.14 (10.95.130.14) 56(84) bytes of data.
64 bytes from 10.95.130.14: icmp_seq=1 ttl=63 time=0.066 ms

Логика И работает не со всеми блоками в ingress/egress. Например, если использовать вместе ipBlock и podSelector, то правило не применится и будет выдана ошибка.

Разрешим входящий и исходящий трафик к поду, но только по 80 и 443 портам.

# Входящий трафик разрешён для всех по 80 и 443 портам.
# Исходящий трафик разрешён для всех по 80 и 443 портам. 
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access.nginx
  namespace: test
spec:
  podSelector:
    matchLabels:
      app: cam-nginx2
  ingress:
  - from:
    - ipBlock:
        cidr: 0.0.0.0/0
    ports:
    - port: 80
      protocol: TCP
    - port: 443
      protocol: TCP
    - port: 53
      protocol: TCP
    - port: 53
      protocol: UDP
  egress:
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
    ports:
    - port: 80
      protocol: TCP
    - port: 443
      protocol: TCP
    - port: 53
      protocol: TCP
    - port: 53
      protocol: UDP
  policyTypes:
  - Ingress
  - Egress
EOF
kubectl exec --tty --stdin cam-nginx3-d4d99bbc8-gmkqz -- curl 10.95.130.14
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

kubectl exec --tty --stdin cam-nginx3-d4d99bbc8-gmkqz -- ping 10.95.130.14
PING 10.95.130.14 (10.95.130.14) 56(84) bytes of data.
^C
--- 10.95.130.14 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3056ms

Если создать сервис, то сайт под управлением Nginx свободно отображается.