티스토리 뷰

728x170

개요

기본적으로 Kubernetes는 클러스터 내에서 실행되는 Pod 간 트래픽을 제한하지 않는다. 이와 같은 특징은 때때로 보안과 성능 또는 장애와 연관이 되는 문제들이 발생할 수 있다. NetworkPolicy는 Pod간 통신을 제어하는 오브젝트이다. 통신이 허용되는 네임스페이스를 지정하거나 더 구체적으로 각 정책을 적용할 수 있다. 즉 NetworkPolicy는 Pod 간 ACL을 수행하는 Kubernetes 오브젝트이다.

NetworkPolicy는 실시간으로 적용되며, Pod 간에 연결이 열려 있는 경우 해당 연결을 방지하는 NetworkPolicy를 적용하면 연결이 즉시 종료된다.


제약조건

NetworkPolicy는 Inbound 트래픽과 Outbount 트래픽을 구분하여 정의할 수 있으며, 사용중인 CNI(Container Network Interface)에 따라 NetworkPolicy 오브젝트 지원 여부를 확인해야 한다.

  • Ingress/Egress NetworkPolicy 모두 지원 : Calico, Canal, Cilium
  • Ingress NetworkPolicy만 지원 : Kube-router, WeaveNet
  • 미지원 : Romana, Flannel, EKS(Amazon VPC CNI)/GKE(kubenet)/AKS(Azure CNI)

많이 사용되는 CNI인 Flannel이나, EKS / GKE / AKS 등 Public Cloud Managed Service에서도 기본 NetworkPolicy가 활성화 되어 있지 않다는 점에 유의해야 한다. 이 경우 위와 같이 Ingress/Egress 모두를 지원하는 Calico, Canal, Cilium CNI를 구축함으로써 NetworkPolicy를 사용할 수 있다.

본 포스팅에서는 Amazon EKS 환경에서 작성되고 있으며, Calico를 설치하여 NetworkPolicy 기능을 검증해 보자.


Calico 설치

1) Calico Manifest 적용

[root@ip-192-168-78-195 app (iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io:default)]# kubectl apply -f https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/master/config/master/calico-operator.yaml
customresourcedefinition.apiextensions.k8s.io/apiservers.operator.tigera.io 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/imagesets.operator.tigera.io 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
customresourcedefinition.apiextensions.k8s.io/installations.operator.tigera.io created
customresourcedefinition.apiextensions.k8s.io/tigerastatuses.operator.tigera.io created
namespace/tigera-operator created
podsecuritypolicy.policy/tigera-operator created
clusterrolebinding.rbac.authorization.k8s.io/tigera-operator created
clusterrole.rbac.authorization.k8s.io/tigera-operator created
serviceaccount/tigera-operator created
deployment.apps/tigera-operator created
[root@ip-192-168-78-195 app (iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io:default)]# kubectl apply -f https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/master/config/master/calico-crs.yaml
installation.operator.tigera.io/default created
[root@ip-192-168-78-195 app (iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io:default)]#

2) DaemonSet 확인

[root@ip-192-168-78-195 app (iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io:default)]# kubectl get ds -A
NAMESPACE       NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
calico-system   calico-node   2         2         2       2            2           kubernetes.io/os=linux   2m52s
kube-system     aws-node      2         2         2       2            2           <none>                   26h
kube-system     kube-proxy    2         2         2       2            2           <none>                   26h
[root@ip-192-168-78-195 app (iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io:default)]#

NetworkPolicy 검증

지금부터 몇가지 사례를 가지고 검증을 진행해 보자. 테스트 환경은 다음과 같다.

  • default namespace - my-nginx service
  • network namespace - my-nginx-network service
  • policy namespace : my-nginx-policy service

1) NetworkPolicy 적용 전

[root@ip-192-168-78-195 ~ (iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io:default)]# kubectl exec -it my-nginx-5b56ccd65f-nw4pp /bin/bash
root@my-nginx-5b56ccd65f-nw4pp:/# curl -v my-nginx-network.network.svc.cluster.local
*   Trying 10.100.114.127:80...
* Connected to my-nginx-network.network.svc.cluster.local (10.100.114.127) port 80 (#0)
> GET / HTTP/1.1
> Host: my-nginx-network.network.svc.cluster.local
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.21.6
< Date: Sat, 19 Mar 2022 15:14:25 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Tue, 25 Jan 2022 15:03:52 GMT
< Connection: keep-alive
< ETag: "61f01158-267"
< Accept-Ranges: bytes
< 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host my-nginx-network.network.svc.cluster.local left intact
root@my-nginx-5b56ccd65f-nw4pp:/#

위와 같이 default namespace 내에서 network namespace의 my-nginx-network pod를 호출해 보면 정상적으로 호출되는 것을 확인할 수 있다.

NetworkPolicy를 적용하기 전 주요 yaml을 구성하는 항목에 대해 알아보자.

상위 필드 하위 필드 설명
podSelector NetworkPolicy에는 정책이 적용되는 Pod 그룹을 선택하는 podSelector가 포함된다. podSelector가 비어있을 경우 네임스페이스의 모든 파드를 선택한다.
policyTypes NetworkPolicy에는 Ingress, Egress 또는 두 가지 모두를 포함할 수 있는 policyTypes 목록이 포함된다. policyTypes 필드는 podSelector에 의해 선택된 Pod에 대한 Ingress 트래픽 정책, 선택한 Pod에 대한 Egress 트래픽 정책 또는 두 가지 모두에 지정된 정책의 적용 여부를 나타낸다.
ingress from NetworkPolicy에는 whitelist ingress 규칙 목록이 포함될 수 있다. 각 규칙은 from과 ports 부분과 모두 일치하는 트래픽을 허용한다.
> ipBlock은 CIDR 대역을 지정하며, 특정 IP대역에서만 트래픽이 유입되도록 제한
> podSelector와 namespaceSelector는 특정 Label을 포함하는 Object에서 요청되는 트래픽만 유입되도록 제한. podSelector는 해당 label을 포함하는 pod의 요청만 허용하며, namespaceSelector는 해당 namespace에서 유입되는 요청만 허용
이를 기반으로 개발, 검증, 운영 환경을 구분하여 설계하거나, 배포방식의 다변화를 가져갈 수 있다.
ports NetworkPolicy에는 whitelist ingress 규칙 목록이 포함될 수 있다. 각 규칙은 from과 ports 부분과 모두 일치하는 트래픽을 허용한다.
> protocol : 허용되는 프로토콜의 종류
> port : 유입이 허용되는 포트
egress to NetworkPolicy에는 whitelist egress 규칙이 포함될 수 있다. 각 규칙은 to 와 ports 부분과 모두 일치하는 트래픽을 허용한다.
> ipBlock은 CIDR 대역을 지정하며, 특정 IP 대역으로만 트래픽을 반환하도록 제한
ports NetworkPolicy에는 whitelist egress 규칙이 포함될 수 있다. 각 규칙은 to 와 ports 부분과 모두 일치하는 트래픽을 허용한다.
> protocol : 반환되는 프로토콜의 종류
> port : 반환이 허용되는 포트

2) 어플리케이션에 대한 모든 트래픽 거부

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: web-deny-all
  namespace: network
spec:
  podSelector:
    matchLabels:
      run: my-nginx-network
  ingress: []

위 NetworkPolicy는 network namespace 내 run: my-nginx-network Label을 갖는 Pod로의 Ingress를 차단한다.

  • Case 1. 다른 Namespace에서 my-nginx-network pod 호출 시 > 차단
  • Case 2. 동일 Namespace 내 다른 서비스에서 my-nginx-network pod 호출 시 > 차단
  • Case 3. network Namespace 내 my-nginx-network pod에서 외부 Namespace로의 Egress 호출 시 > 허용
root@my-nginx-5b56ccd65f-nw4pp:/# curl -v my-nginx-network.network.svc.cluster.local
*   Trying 10.100.114.127:80...
* connect to 10.100.114.127 port 80 failed: Connection timed out
* Failed to connect to my-nginx-network.network.svc.cluster.local port 80: Connection timed out
* Closing connection 0
curl: (28) Failed to connect to my-nginx-network.network.svc.cluster.local port 80: Connection timed out
root@my-nginx-5b56ccd65f-nw4pp:/#

위와 같이 기존 정상적으로 호출되었던 network namespace 내 my-nginx-network pod에 대한 호출이 Connection timed out이 발생하는 것을 알 수 있다.

root@my-nginx-network-5588897f5-hr9q2:/# curl -v my-nginx-policy.policy.svc.cluster.local              
*   Trying 10.100.179.121:80...
* Connected to my-nginx-policy.policy.svc.cluster.local (10.100.179.121) port 80 (#0)
> GET / HTTP/1.1
> Host: my-nginx-policy.policy.svc.cluster.local
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.21.6
< Date: Sun, 20 Mar 2022 05:34:10 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Tue, 25 Jan 2022 15:03:52 GMT
< Connection: keep-alive
< ETag: "61f01158-267"
< Accept-Ranges: bytes
< 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host my-nginx-policy.policy.svc.cluster.local left intact
root@my-nginx-network-5588897f5-hr9q2:/#

반대로 network namespace 내 my-nginx-network pod에서 외부 namespace로의 egress 호출은 정상적으로 연결되는 것을 알 수 있다.

근본적으로 ACL을 구성하기 위해 먼저 이 정책을 사용하여 트래픽을 차단하고 white list 정책으로 오픈해 나가는 것이 바람직하다.

3) 특정 label을 갖는 어플리케이션에 대한 요청만 허용

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: api-allow
  namespace: network
spec:
  podSelector:
    matchLabels:
      run: my-nginx-network
  ingress:
  - from:
      - podSelector:
          matchLabels:
            run: my-nginx-network2

어플리케이션에 대한 모든 트래픽 거부에서 살펴본 내용과의 차잊머은 podSelector에서 선택된 Pod 대상으로 ingress whitelist 방식으로 연결을 허용하는 대상을 정의했다는 점이다.

위 NetworkPolicy는 run: my-nginx-netowrk2 label을 갖는 service의 요청을 허용한다.

  • Case 1. 다른 Namespace에서 my-nginx-network pod 호출 시 > 차단
  • Case 2. 동일 Namespace 내 다른 서비스에서 my-nginx-network pod 호출 시 > 차단
  • Case 3. 동일 Namespace 내 run: my-nginx-network2 label을 갖는 서비스에서 호출 시 > 허용
  • Case 4. network Namespace 내 my-nginx-network pod에서 외부 Namespace로의 Egress 호출 시 > 허용
root@my-nginx-network2-774bb6b4ff-d8mj8:/# curl -v my-nginx-network.network.svc.cluster.local
*   Trying 10.100.114.127:80...
* Connected to my-nginx-network.network.svc.cluster.local (10.100.114.127) port 80 (#0)
> GET / HTTP/1.1
> Host: my-nginx-network.network.svc.cluster.local
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.21.6
< Date: Sun, 20 Mar 2022 06:19:18 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Tue, 25 Jan 2022 15:03:52 GMT
< Connection: keep-alive
< ETag: "61f01158-267"
< Accept-Ranges: bytes
< 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host my-nginx-network.network.svc.cluster.local left intact
root@my-nginx-network2-774bb6b4ff-d8mj8:/#

위와 같이 label이 run: my-nginx-netowrk2인 network namespace 내 my-nginx-network2 service로 부터의 요청이 허용된다.

4) 특정 Namespace로 부터의 요청 허용

[root@ip-192-168-78-195 NetworkPolicy (iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io:default)]# kubectl label namespace/default purpose=default
namespace/default labeled
[root@ip-192-168-78-195 NetworkPolicy (iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io:default)]# kubectl label namespace/network purpose=network
namespace/network labeled
[root@ip-192-168-78-195 NetworkPolicy (iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io:default)]# kubectl label namespace/policy purpose=policy
namespace/policy labeled
[root@ip-192-168-78-195 NetworkPolicy (iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io:default)]#

위와 같이 각 namespace에 label을 설정한다.

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: web-allow-prod
  namespace: network
spec:
  podSelector:
    matchLabels:
      run: my-nginx-network
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          purpose: policy

위 NetworkPolicy는 network namesapce 내 run: my-nginx-network label을 갖는 pod를 대상으로 purpose: policy label을 갖는 namespace의 요청을 허용한다.

  • Case 1. 다른 Namespace에서 my-nginx-network pod 호출 시 > 차단
  • Case 2. 동일 Namespace 내 다른 서비스에서 my-nginx-network pod 호출 시 > 차단
  • Case 3. namespace label이 purpose: policy인 namespace에서 호출 시 > 허용
  • Case 4. network Namespace 내 my-nginx-network pod에서 외부 Namespace로의 Egress 호출 시 > 허용
> 타 네임스페이스
root@my-nginx-network2-774bb6b4ff-89wqs:/# curl -v my-nginx-network.network.svc.cluster.local
*   Trying 10.100.114.127:80...
* connect to 10.100.114.127 port 80 failed: Connection timed out
* Failed to connect to my-nginx-network.network.svc.cluster.local port 80: Connection timed out
* Closing connection 0
curl: (28) Failed to connect to my-nginx-network.network.svc.cluster.local port 80: Connection timed out
root@my-nginx-network2-774bb6b4ff-89wqs:/#

> 동일 네임스페이스 내 타 서비스
root@my-nginx-network2-774bb6b4ff-d8mj8:/# curl -v my-nginx-network.network.svc.cluster.local
*   Trying 10.100.114.127:80...
* connect to 10.100.114.127 port 80 failed: Connection timed out
* Failed to connect to my-nginx-network.network.svc.cluster.local port 80: Connection timed out
* Closing connection 0
curl: (28) Failed to connect to my-nginx-network.network.svc.cluster.local port 80: Connection timed out
root@my-nginx-network2-774bb6b4ff-d8mj8:/#

> purpose: policy label을 갖는 policy namespace 내 호출 시
root@my-nginx-policy-786684cb7f-gj5gj:/# curl -v my-nginx-network.network.svc.cluster.local
*   Trying 10.100.114.127:80...
* Connected to my-nginx-network.network.svc.cluster.local (10.100.114.127) port 80 (#0)
> GET / HTTP/1.1
> Host: my-nginx-network.network.svc.cluster.local
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.21.6
< Date: Sun, 20 Mar 2022 06:53:39 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Tue, 25 Jan 2022 15:03:52 GMT
< Connection: keep-alive
< ETag: "61f01158-267"
< Accept-Ranges: bytes
< 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host my-nginx-network.network.svc.cluster.local left intact
root@my-nginx-policy-786684cb7f-gj5gj:/#

아래와 같이 policy namespace 내 호출이 정상적으로 요청되는 것을 확인할 수 있다.


결론

NetworkPolicy의 동작방식을 정의해 보자면 다음과 같다. 먼저 단일 Namespace 내 NetworkPolicy에는 다음과 같은 규칙으로 동작한다.

  • NetworkPolicy에 의해 지정된 Pod는 트래픽이 제한된다.
  • Pod에 NetworkPolicy가 정의되지 않은 경우 모든 Namespace의 모든 Pod이 해당 Pod에 연결할 수 있다. 즉, 기본적으로는 특정 Pod에 대해 정의된 네트워크 정책이 없으며 암묵적으로 "allow all"을 의미한다.
  • Pod A에 대한 트래픽이 제한되고 Pod B가 Pod A에 연결해야 하는 경우 Pod A를 NetworkPolicy에 의해 지정하고, ingress rule에 의해 Pod B를 선택하여 허용한다.

다음으로 멀티 Namespace 내 NetworkPolicy는 다음과 같은 규칙으로 동작한다.

  • NetworkPolicy가 적용된 네임스페이스에 있는 Pod 연결에 대해서만 규칙을 적용할 수 있다.
  • ingress 규칙의 podSelector는 NetworkPolicy가 배포된 네임스페이스 내의 pod만 선택할 수 있다.
  • Pod A가 다른 네임스페이스의 Pod B에 연결해야 하는 경우 Pod B에 Pod A를 지정하는 namespaceSelector가 추가되어야 한다.

지금까지 살펴본 NetworkPolicy는 Firewall과 같은 네트워크 트래픽을 캡처하고, 관리하는 장비는 아니다. 단순하게 네트워크의 흐름을 제어하는 논리적인 도구라고 볼 수 있다.

따라서 Kubernetes 내 Pod 간 통신을 제어하는데 NetworkPolicy에 전적으로 의존해서는 안된다. Pod 간 통신을 제어하기 위해 NetworkPolicy는 물론 TLS(Istio mTLS) 방식의 트래픽 인증을 적용하거나, ingress로의 유입을 제어하는 별도의 도구를 도입하는 것을 함께 고려해야 할 것이다.

그리드형
댓글
댓글쓰기 폼