[ NetworkPolicy ]
1. Ingress vs Egress
네트워크 트래픽은 외부로부터 유입되는 Ingress(inbound)와 내부로부터 외부로 나가는 Egress(outbound)로 구분됩니다. 우리가 공부하고 있는 쿠버네티스는 기본적으로 non-isolated 네트워크이므로 클러스터 내부 Pod끼리의 트래픽 흐름은 자유롭습니다. 하지만 Namespace(네임스페이스)로 단일 클러스터의 리소스 격리 매커니즘을 사용하여 논리적인 가상 클러스터를 만드는 것처럼, 일부 환경에서는 다른 네임스페이스의 애플리케이션을 적절히 격리하거나 트래픽 제어를 할 필요가 있습니다.
Ingress라는 이름의 API 오브젝트도 있으나, HTTP/HTTPS로 요청하는 주소를 구분하여 애플리케이션 서비스에 라우팅할 수 있게 해주는 OSI Layer-7 계층에서 작동하는 리소스입니다. TCP(L4 레이어) 단에서 로드밸런싱하지 않고, MSA 개발에서는 서비스가 하나의 url로서 API 통신을 하는 경우도 있어 L7 레이어 단에서 가볍게 라우팅해줄 때 유용합니다. Ingress는 IngressController와 함께 정의하여 로드밸런싱은 물론 TLS/SSL 인증서 처리 및 특정 path 라우팅도 가능하기 때문에 추후 다뤄보도록 하겠습니다.
이때 우리는 NetworkPolicy API 오브젝트를 사용하여 IP 주소(OSI Layer-3) 또는 Port(OSI Layer-4)를 통해 트래픽 격리/제어를 정의할 수 있습니다. 트래픽을 iptable rule로 제어해야 하기 때문에 컨테이너 간 네트워크를 관리하는 플러그인인 CNI이 필요합니다. (CNI 플러그인이 없이도 설정은 되지만 네트워크 제어는 불가능하다는 점..) 쿠버네티스는 기본적으로 자체 CNI 플러그인 kubelet을 통해 Node 내의 통신을 제공하지만, 그 기능이 매우 제한적이라는 단점으로 인해 Flannel, Calico, WeaveNet, NSX 등 3rd-party CNI 플러그인이 외주를 맡고 있습니다.
NetworkPolicy 개념을 보다 자세히 설명하기 위해 우선, Pod의 통신 대상은 아래 세 조건을 조합하여 정해집니다.
1. Pod (단, Pod 자기 자신에 대한 접근을 차단할 수 없음)
2. Namespace
3. IP Block (단, Pod가 실행 중인 Node와의 inbound/outbound 트래픽은 항상 허용됨)
위 조건들을 잘 조합하면 마치 Pod 전용 방화벽처럼 특정 IP나 Port로 Pod의 트래픽을 내보내거나(Egress/Outbound), 들어오게(Ingress/Inbound) 할 수 있습니다. 두 개 이상의 NetworkPolicy가 충돌하는 경우는 없는데, 특정 Pod의 방향(in/out)에 대해 여러 Policy가 만들어지면 합집합으로 작용하기 때문에 생성된 순서는 최종적인 Policy에 따른 트래픽 결과에 영향을 미치지 않습니다. Source(송신) Pod에서 Destination(수신) Pod로의 연결이 가능하려면 Source Pod의 Egress 정책과 Destination Pod의 Ingress 정책의 허락을 받아야 합니다. 만약 어느 한 쪽이라도 해당 연결을 허용하지 않으면 연결이 되지 않습니다.
Whitelist 방식이기 때문에, 10.144.1.2를 허용하는 NetworkPolicy와 10.1441.3을 허용하는 NetworkPolicy 두 정책이 하나의 Pod에 된다면 둘 중 하나만 적용하는 것이 아니라 두 IP Address를 모두 허용하게 된다. (단, ingress/egress는 명시가 필요하겠지요.) 이 말인즉슨 NetworkPolicy를 하나라도 잘못 설정하게 된다면 기존에 훌륭하게 세팅을 끝낸 NetworkPolicy 여러 개의 트래픽 컨트롤리 무용지물이 된다는 뜻입니다.
(1) Ingress 트래픽 제어
- ipBlock
- CIDR IP 대역으로, 특정 IP 대역에서만 트래픽이 들어오도록 지정할 수 있습니다.
('막다'라는 뜻의 Block이 아님에 주의) - podSelector
- label을 이용하여, 특정 label을 가지고 있는 Pod들에서 들어오는 트래픽만 받을 수 있습니다. 가령, Database 이미지 컨테이너 Pod는 API Server로부터 들어오는 트래픽만 받을 수 있도록 Policy 설정이 가능합니다. - namespaceSelector
- 특정 Namespace로부터 들어오는 트래픽만을 받을 수 있습니다. Prometheus와 같은 Logging Server의 운영(Prod) 환경에서는 prod로 명명한 Namespace로부터 들어오는 트래픽만을 허용하도록 정의 가능합니다. 반대로, 출시 전 개발 환경에서 새로운 서비스 컴포넌트를 오픈하고 베타테스트가 필요할 때, 특정 팀에게만 특정 서비스를 오픈하고 싶을 때 목적에 따라 유용합니다. - Protocol/Port
- 받을 수 있는 프로토콜과 허용되는 포트를 정의할 수 있습니다.
(2) Egress 트래픽 제어
- ipBlock
- 트래픽이 나갈 수 있는 IP 대역을 정의하고, 지정된 IP 대역 범위로만 Outbound 호출하게 제한할 수 있습니다.
('막다'라는 뜻의 Block이 아님에 주의) - Protocol & Port
- 트래픽을 내보낼 수 있는 프로토콜과 포트를 정의할 수 있습니다.
2. NetworkPolicy 리소스 뜯어보기(.yaml)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
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
- spec.podSelector
: NetworkPolicy가 적용될 Pod를 선택합니다. spec.podSelector.matchLabels 내부에 [key]: [value] 형태로, metadata.namespace의 Pod들 중에서 label을 탐색하여 Pod 그룹을 지정하게 됩니다. podSelector를 비워두게 되면 네임스페이스의 모든 Pod들이 선택됩니다.
- spec.policyTypes
: Ingress 또는 Egress 두 type의 policy를 명시합니다. policyTypes를 명시하지 않으면 기본적으로 Ingress로 인식하고 모든 Egress 트래픽으로부터 격리시킵니다.
- spec.ingress / spec.egress
: from과 ports 두 부분으로 구성됩니다. from 내의 namespaceSelector와 podSelector는 matchLablels로써, ipBlock은 cidr와 except로써 필터를 설정하여 트래픽 Whitelist를 작성할 수 있습니다. ports 내에서는 protocol과 port (+endPort)를 지정할 수 있습니다. 규칙을 지정하고 싶은 port가 32000 ~ 32768 사이에 있는 경우, 아래와 같이 구성할 수 있습니다.
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 32000
endPort: 32768
* CNI 플러그인 중 NetworkPolicy 명세의 endPort를 지원하지 않는 경우, port 필드까지만 적용됩니다.
앞서 여러 개의 NetworkPolicy는 합집합 개념으로 받아들이면 된다고 말씀드렸는데, ingress와 egress 내의 from과 ports는 교집합 개념이므로 두 조건 모두를 만족하는 Whitelist 규칙이 생성됩니다. 네임스페이스의 모든 Pod로부터 오는 트래픽을 전부 허용하려면 아래와 같이 설정하면 됩니다.
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- {}
반대로, 모든 Ingress 및 Egress 트래픽을 통제하는 NetworkPolicy는 아래처럼 구성됩니다.
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
* 하지만 위의 두 정책이 공존하면 Pod에서 나가는 트래픽을 막으려는 두 번째 NetworkPolicy이 무용지물이라는 점을 기억합시다.
3. CKA 예제
- NetworkPolicy 구성하여 curl로 트래픽 제어 확인하기
Q. app:sh 라벨 Pod, app:api 라벨 Pod, app:loadbalancer 라벨 Pod를 생성하고 app:sh -> app:api로 HTTP 호출을 테스트합니다. app:api -> app:loadbalancer로의 호출만 가능하도록 네트워크 정책을 만들고, app:sh -> app:api 호출을 다시 시도해봅니다. app:api 라벨을 가지는 Pod의 이미지는 아래와 같은 서버를 담고 있습니다.
var os = require('os');
var http = require('http');
var handleRequest = function(request, response) {
response.writeHead(200);
response.end("Hello World! This is an API Server : " + os.hostname());
}
var www = http.createServer(handleRequest);
www.listen(8080);
A.
- 우선 문제에서 요구한 라벨을 잘 명시한 세 Pod를 생성합니다. 이후, pod-sh 컨테이너의 shell에 접근하여 pod-api의 주소로 curl 명령을 날려봅니다.
master$ alias k=kubectl
master$ k run pod-sh --image=busybox:1.28 --labels="app:sh"
master$ k run pod-api --image=marcel/api:v1 --labels="app:api"
master$ k run pod-loadbalancer --image=busybox:1.28 --labels="app:loadbalancer"
master$ k exec -it pod-sh -- /bin/bash
root@pod-sh:/# curl [pod-api IP address] -max-time 5
Hello World! This is an API Server : pod-api
- app:api Pod에 대해 NetworkPolicy를 정의하여 app:loadbalancer 라벨을 갖는 Pod로부터의 트래픽만 허용하도록 만들어줍니다.
# api-ingress-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-ingress-policy
namespace: default
spec:
policyTypes:
- Ingress
podSelector:
matchLabels:
app: api
ingress:
- from:
- podSelector:
matchLabels:
app: loadbalancer
- 앞서 정의한 api-ingress-policy.yaml 파일로 NetworkPolicy API 오브젝트를 생성해주고, pod-sh 컨테이너로 접속하여 pod-api와의 통신을 확인해봅니다. (Connection fail이 되어야 문제 요구사항을 잘 처리한 것입니다.)
root@pod-sh:/# exit
master$ k create -f api-ingress-policy
master$ k exec -it pod-sh -- /bin/bash
root@pod-sh:/# curl [pod-api IP Address]:8080 --max-time 5
curl: (28) Connection timed out after 5001 milliseconds
댓글