Network Policy 개념, 실습
WEB, WAS, DB를 namespace로 나누고 NetworkPolicy로 통신 방향과 포트를 제한합니다. 연결 테스트를 통해 namespaceSelector의 label 설정과 DNS를 위한 egress 허용 조건을 정리합니다.
WEB, WAS, DB 네임스페이스 생성.
WEB <=> WAS <=> DB 이렇게만 통신이 가능해야함
WEB - 80 포트로만 ingress, egress
WAS - 8080 포트로만 ingress, egress
DB - 3306 포트로만 ingress, egress
ClusterIP 실습입니다. Nginx는 web, Tomcat은 was, MySQL은 db namespace에 있습니다. 각 정책은 podSelector.matchLabels: {}로 해당 namespace의 모든 Pod를 선택합니다.
name: wasname: db허용 연결의 stateful reply는 역방향 신규 연결 허용과 다릅니다. web과 db 사이의 신규 연결 허용 규칙은 없습니다. namespaceSelector.matchLabels의 name: web, name: was, name: db label이 실제 namespace에 있어야 합니다.
namespaceSelector.matchLabels: {}에 UDP / TCP 53을 허용합니다. 모든 namespace의 Pod가 대상이며 kube-dns Pod만 선택하지 않습니다.DB/WAS 역방향은 실제 적용 정책과 테스트 시점 상태를 재확인해야 합니다. 포트를 임의로 고치거나 재실험 성공으로 표시하지 않습니다.
TL;DR
- web, was, db 3계층을 namespace로 나누고 NetworkPolicy로 허용 방향만 남기는 실습입니다.
namespaceSelector가 동작하지 않은 원인은 CNI가 아니라 namespace에 label이 자동으로 붙지 않는다는 점이었습니다. Calico를 Cilium으로 바꿔도 증상은 그대로였습니다.- label을 붙인 뒤에도 Pod 사이 DNS 이름 조회가 실패했는데, egress 정책이 kube-dns로 향하는 53번 포트를 막고 있었기 때문입니다.
1. Network Policy란?
Network Policy는 쿠버네티스에서 네트워크 트래픽을 제어하기 위한 리소스입니다. 이를 통해 특정 Pod 간의 통신을 허용하거나 차단할 수 있습니다. 주로 보안 목적으로 사용되며, 클러스터 내의 트래픽을 세부적으로 관리할 수 있습니다. 예를 들어, 3계층 아키텍처에서 웹 서버, 애플리케이션 서버, 데이터베이스 서버 간의 통신 규칙을 정의하여 외부 접근을 제한할 수 있습니다.
2. Namespace 및 label 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
root@Zest ~# kubectl create ns web
namespace/web created
root@Zest ~# kubectl create ns was
namespace/was created
root@Zest ~# kubectl create ns db
namespace/db created
root@Zest ~/Code/k8s-network-policy-3-tier/test# kubectl label namespace db name=db
namespace/db labeled
root@Zest ~/Code/k8s-network-policy-3-tier/test# kubectl label namespace was name=was
namespace/was labeled
root@Zest ~/Code/k8s-network-policy-3-tier/test# kubectl label namespace web name=web
namespace/web labeled
3. Pod 및 Service 생성
WEB 서버는 nginx:latest 이미지를 사용했고, WAS 서버는 tomcat:latest 이미지를 사용했고 DB 서버는 arm64v8/mysql:latest 이미지를 사용했습니다. arm 기반 OS를 사용 중이기 때문에 기존 mysql:latest 이미지를 사용하면 동작하지 않습니다. 또한 실습에서 Service는 외부에서 접속하지 않아도 되기 때문에 ClusterIP Type으로 생성했습니다. [ Type을 지정하지 않으면 ClusterIP 타입으로 생성됨 ]
3.1. WEB manifest file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: v1
kind: Pod
metadata:
name: web-server
namespace: web
labels:
name: web-server
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web-service
namespace: web
spec:
selector:
name: web-server
ports:
- protocol: TCP
port: 80
targetPort: 80
3.2. WAS manifest file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: v1
kind: Pod
metadata:
name: was-server
namespace: was
labels:
name: was-server
spec:
containers:
- name: tomcat
image: tomcat:latest
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: was-service
namespace: was
spec:
selector:
name: was-server
ports:
- protocol: TCP
port: 8080
targetPort: 8080
3.3. DB manifest file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: v1
kind: Pod
metadata:
name: db-server
namespace: db
labels:
name: db-server
spec:
containers:
- name: mysql
image: arm64v8/mysql:latest
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: "test"
- name: MYSQL_DATABASE
value: "mydb"
---
apiVersion: v1
kind: Service
metadata:
name: db-service
namespace: db
spec:
selector:
name: db-server
ports:
- protocol: TCP
port: 3306
targetPort: 3306
3.4. Pod 생성 & 확인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
root@Zest ~/Code/k8s-network-policy-3-tier/web# kubectl apply -f .
pod/web-server created
service/web-service created
root@Zest ~/Code/k8s-network-policy-3-tier/was# kubectl apply -f .
pod/was-server created
service/was-service created
root@Zest ~/Code/k8s-network-policy-3-tier/db# kubectl apply -f .
pod/db-server created
service/db-service created
root@Zest ~/Code/k8s-network-policy-3-tier/db# kubectl get all -A
NAMESPACE NAME READY STATUS RESTARTS AGE
db pod/db-server 1/1 Running 0 28s
was pod/was-server 1/1 Running 0 37s
web pod/web-server 1/1 Running 0 51s
...
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
db service/db-service ClusterIP 10.152.183.88 <none> 3306/TCP 28s
was service/was-service ClusterIP 10.152.183.155 <none> 8080/TCP 37s
web service/web-service ClusterIP 10.152.183.96 <none> 80/TCP 51s
...
4. Network Policy 생성
DNS 포트와 프로토콜인 53번 포트로 나가는 UDP, TCP 프로토콜 반드시 명시해야 합니다.
4.1. WEB-Network Policy manifest file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-policy
namespace: web
spec:
podSelector:
matchLabels: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: was
ports:
- protocol: TCP
port: 80
egress:
- to:
- namespaceSelector:
matchLabels:
name: was
ports:
- protocol: TCP
port: 8080
- to:
- namespaceSelector:
matchLabels: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
4.2. WAS-Network Policy manifest file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: was-policy
namespace: was
spec:
podSelector:
matchLabels: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: web
ports:
- protocol: TCP
port: 8080
- from:
- namespaceSelector:
matchLabels:
name: db
ports:
- protocol: TCP
port: 3306
egress:
- to:
- namespaceSelector:
matchLabels:
name: web
ports:
- protocol: TCP
port: 80
- to:
- namespaceSelector:
matchLabels:
name: db
ports:
- protocol: TCP
port: 3306
- to:
- namespaceSelector:
matchLabels: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
4.3. DB-Network Policy manifest file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-policy
namespace: db
spec:
podSelector:
matchLabels: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: was
ports:
- protocol: TCP
port: 3306
egress:
- to:
- namespaceSelector:
matchLabels:
name: was
ports:
- protocol: TCP
port: 8080
- to:
- namespaceSelector:
matchLabels: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
4.4. Network Policy 생성 & 확인
1
2
3
4
5
6
7
8
9
10
11
12
root@Zest ~/Code/k8s-network-policy-3-tier/Network-Policy# kubectl apply -f web-policy.yaml
networkpolicy.networking.k8s.io/web-policy created
root@Zest ~/Code/k8s-network-policy-3-tier/Network-Policy# kubectl apply -f was-policy.yaml
networkpolicy.networking.k8s.io/was-policy created
root@Zest ~/Code/k8s-network-policy-3-tier/Network-Policy# kubectl apply -f db-policy.yaml
networkpolicy.networking.k8s.io/db-policy created
root@Zest ~/Code/k8s-network-policy-3-tier/Network-Policy# kubectl get netpol -A
NAMESPACE NAME POD-SELECTOR AGE
db db-policy <none> 19s
was was-policy <none> 22s
web web-policy <none> 25s
5. Test
busybox 이미지를 사용했고, 각각의 namespace에 test pod를 생성해 테스트 했습니다.
5.1. web namespace pod에서 연결 확인
1
2
3
4
5
6
root@Zest ~/Code/k8s-network-policy-3-tier# kubectl exec -it -n web web-test-pod -- sh
/ # wget -qO- was-service.was.svc.cluster.local:8080
wget: server returned error: HTTP/1.1 404
/ # nc -zv db-service.db.svc.cluster.local 3306
nc: db-service.db.svc.cluster.local (10.152.183.88:3306): Connection timed out
5.2. was namespace pod에서 연결 확인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
root@Zest ~/Code/k8s-network-policy-3-tier# kubectl exec -it -n was was-test-pod -- sh
/ # wget -qO- web-service.web.svc.cluster.local:80
<!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>
/ # nc -zv db-service.db.svc.cluster.local 3306
db-service.db.svc.cluster.local (10.152.183.88:3306) open
5.3. db namespace pod에서 연결 확인
1
2
3
4
5
6
root@Zest ~/Code/k8s-network-policy-3-tier# kubectl exec -it -n db db-test-pod -- sh
/ # wget -qO- was-service.was.svc.cluster.local:8080
wget: server returned error: HTTP/1.1 404
/ # wget -qO- web-service.web.svc.cluster.local:80
wget: can't connect to remote host (10.152.183.144): Connection timed out
6. 결과
web -> was ( O )
web -> db ( X )
was -> web ( O )
was -> db ( O )
db -> web ( X )
db -> was ( O )
7. Trouble Shooting
네임스페이스를 생성할 때, 네임스페이스에 레이블이 자동으로 추가되지 않는다는 사실과 network policy를 생성 직후 pod에서 다른 pod에 DNS 이름으로 접근이 불가능하다는 문제를 직면했습니다.
7.1. 해결과정
- 네임스페이스를 생성하고 네임스페이스 셀렉터를 사용하여 네트워크 정책을 생성했으나, 기대한 대로 통신이 되지 않음
- Calico 네트워크 플러그인의 문제를 의심하여 Cilium으로 교체했으나, 여전히 문제가 해결되지 않음
- 네임스페이스에 적절한 레이블이 설정되지 않아 네임스페이스 셀렉터가 작동하지 않았음.
- 레이블 확인
1 2
kubectl get namespaces --show-labels # [kubernetes.io/metadata.name=web]와 같은 형식으로 나타남.
- 네임스페이스에 올바른 레이블 설정
1 2 3
kubectl label namespace db name=db kubectl label namespace was name=was kubectl label namespace web name=web
- 네임스페이스에 레이블을 설정했음에도 불구하고 문제가 해결되지 않음
- Pod 내부에서
nslookup명령어를 사용하여 DNS 이름으로 접근을 시도 ⇒ DNS 이름으로 접근이 불가능한 것을 확인1
kubectl exec -n web web-test-pod -- nslookup was-service.was.svc.cluster.local
- DNS 트래픽을 명시적으로 허용하지 않으면 DNS 이름으로 접근할 수 없다는 사실 확인 https://www.cncf.io/blog/2020/02/10/guide-to-kubernetes-egress-network-policies/
- 네트워크 정책에 DNS에 대한 egress 규칙을 추가
1 2 3 4 5 6 7 8 9 10
egress: - to: - namespaceSelector: matchLabels: {} ports: - protocol: UDP port: 53 - protocol: TCP port: 53
8. 결론
네임스페이스를 생성할 때 레이블이 자동으로 설정되지 않으므로, 네트워크 정책을 설정할 때 반드시 레이블을 수동으로 설정해야 합니다. 또한, DNS 트래픽을 명시적으로 허용하지 않으면 DNS 이름으로 접근할 수 없습니다.
핵심 정리
9. Reference
궁금하신 점이나 추가해야 할 부분은 댓글이나 아래의 링크를 통해 문의해주세요.
Written with KKamJi
