지금까지 Part 1~7에서 K8s 클러스터 구축, 모니터링, 무중단 업그레이드를 다뤘습니다. 이제 실제로 운영할 때 반드시 필요한 보안을 구현해봅시다.
보안 없는 K8s는 불안전한 서버와 같습니다. 특히 다음 세 가지가 필수입니다:
- RBAC - 누가 뭘 할 수 있는지 정의
- 네트워크 정책 - 어떤 Pod끼리 통신할 수 있는지 제어
- Pod 보안 - 컨테이너가 어떤 권한으로 실행되는지 제한
1. RBAC (Role-Based Access Control)#
K8s RBAC는 역할 기반 접근 제어입니다. “누가(Subject) 어떤 자원(Resource)에 어떤 행동(Verb)을 할 수 있는가"를 정의합니다.
4가지 핵심 개념:
- Role: 권한의 모음 (특정 네임스페이스)
- ClusterRole: 권한의 모음 (전체 클러스터)
- RoleBinding: Role을 사용자/SA에 연결
- ClusterRoleBinding: ClusterRole을 사용자/SA에 연결
실전 예제: 앱 배포용 ServiceAccount#
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
51
52
| # 1. 앱 배포용 네임스페이스
apiVersion: v1
kind: Namespace
metadata:
name: app-ns
---
# 2. 앱 배포용 ServiceAccount (Pod가 API 호출할 때 사용)
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-deployer
namespace: app-ns
---
# 3. Role: Pod와 Deployment만 관리 가능
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-deployer-role
namespace: app-ns
rules:
# Pod 조회, 생성, 수정, 삭제
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Deployment 조회, 생성, 수정, 삭제
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# ConfigMap 조회 (앱 설정 읽기)
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
# 4. RoleBinding: ServiceAccount에 Role 연결
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-deployer-binding
namespace: app-ns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: app-deployer-role
subjects:
- kind: ServiceAccount
name: app-deployer
namespace: app-ns
|
적용하기#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # YAML 적용
kubectl apply -f rbac-example.yaml
# 권한 확인
kubectl get rolebindings -n app-ns
kubectl describe rolebinding app-deployer-binding -n app-ns
# 특정 SA의 권한 테스트
kubectl auth can-i get pods \
--as=system:serviceaccount:app-ns:app-deployer \
-n app-ns
# 결과: yes
# Pod 생성 권한 테스트
kubectl auth can-i create pods \
--as=system:serviceaccount:app-ns:app-deployer \
-n app-ns
# 결과: yes
# Secret 접근 권한 테스트 (정의하지 않음)
kubectl auth can-i get secrets \
--as=system:serviceaccount:app-ns:app-deployer \
-n app-ns
# 결과: no
|
Pod에서 ServiceAccount 사용#
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: apps/v1
kind: Deployment
metadata:
name: app-deployer-pod
namespace: app-ns
spec:
replicas: 1
selector:
matchLabels:
app: app-deployer
template:
metadata:
labels:
app: app-deployer
spec:
serviceAccountName: app-deployer # ← ServiceAccount 지정
containers:
- name: app
image: nginx:alpine
volumeMounts:
- name: token
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
readOnly: true
volumes:
- name: token
projected:
sources:
- serviceAccountToken:
audience: https://kubernetes.default.svc.cluster.local
expirationSeconds: 3600
path: token
|
2. 네트워크 정책 (Network Policies)#
기본적으로 K8s의 모든 Pod은 서로 통신할 수 있습니다. 네트워크 정책으로 “어떤 Pod끼리만 통신하게 할 것인가"를 정의합니다.
네트워크 정책이 필요한 이유:
- 앱 간 불필요한 통신 차단
- 데이터베이스 접근 제한
- 외부로부터의 접근 제어
실전 예제: 다계층 아키텍처#
Frontend (nginx)
↓
API Server (app-api)
↓
Database (postgres) - Frontend는 직접 접근 불가
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
| # 1. Namespace
apiVersion: v1
kind: Namespace
metadata:
name: multi-tier
---
# 2. Frontend Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: multi-tier
spec:
replicas: 2
selector:
matchLabels:
tier: frontend
template:
metadata:
labels:
tier: frontend
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
---
# 3. API Server Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: multi-tier
spec:
replicas: 2
selector:
matchLabels:
tier: api
template:
metadata:
labels:
tier: api
spec:
containers:
- name: api
image: python:3.11-slim
ports:
- containerPort: 8000
---
# 4. Database StatefulSet (간단한 예제)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: database
namespace: multi-tier
spec:
serviceName: database
replicas: 1
selector:
matchLabels:
tier: database
template:
metadata:
labels:
tier: database
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
---
# 5. Service (내부 통신용)
apiVersion: v1
kind: Service
metadata:
name: api-service
namespace: multi-tier
spec:
selector:
tier: api
ports:
- port: 8000
targetPort: 8000
clusterIP: None
---
apiVersion: v1
kind: Service
metadata:
name: database-service
namespace: multi-tier
spec:
selector:
tier: database
ports:
- port: 5432
targetPort: 5432
clusterIP: None
---
# 6. 기본: 모든 트래픽 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: multi-tier
spec:
podSelector: {} # 모든 Pod
policyTypes:
- Ingress
- Egress
---
# 7. Frontend → API 통신 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-to-api
namespace: multi-tier
spec:
podSelector:
matchLabels:
tier: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
tier: frontend
ports:
- protocol: TCP
port: 8000
---
# 8. API → Database 통신 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-to-database
namespace: multi-tier
spec:
podSelector:
matchLabels:
tier: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
tier: api
ports:
- protocol: TCP
port: 5432
---
# 9. Frontend 외부 트래픽 허용 (80 포트)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-ingress
namespace: multi-tier
spec:
podSelector:
matchLabels:
tier: frontend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector: {} # 모든 네임스페이스 (또는 특정 CIDR)
ports:
- protocol: TCP
port: 80
---
# 10. 모든 Pod의 DNS 통신 허용 (coreDNS)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: multi-tier
spec:
podSelector: {}
policyTypes:
- Egress
egress:
# DNS (coreDNS)
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
# API Server
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
|
적용하고 테스트#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 적용
kubectl apply -f network-policy-example.yaml
# 네트워크 정책 확인
kubectl get networkpolicies -n multi-tier
# Frontend Pod에서 API 접근 테스트
kubectl exec -it <frontend-pod> -n multi-tier -- sh
# curl http://api-service:8000
# → 성공 (정책으로 허용됨)
# Frontend Pod에서 Database 접근 테스트
# kubectl exec -it <frontend-pod> -n multi-tier -- sh
# nc -zv database-service 5432
# → 실패 (정책으로 차단됨) ✓
# API Pod에서 Database 접근 테스트
# kubectl exec -it <api-pod> -n multi-tier -- sh
# nc -zv database-service 5432
# → 성공 (정책으로 허용됨) ✓
|
3. Pod 보안 (Pod Security Policy / Pod Security Standards)#
Pod 보안은 컨테이너가 “어떤 권한으로” 실행되는지를 제한합니다.
주요 제어사항:
- 컨테이너가 root로 실행되는지 여부
- 특권 컨테이너(privileged) 사용 제한
- 호스트 네트워크 접근 제한
- 파일 시스템 읽기 전용 여부
참고: Pod Security Policy(PSP)는 deprecated되었고, **Pod Security Standards (PSS)**로 대체되었습니다.
실전 예제: Pod Security Standards#
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
| # 1. Namespace에 보안 레이블 추가
apiVersion: v1
kind: Namespace
metadata:
name: secure-app
labels:
# Pod Security Standards 레이블
pod-security.kubernetes.io/enforce: restricted # 가장 엄격
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
---
# 2. 보안이 강화된 Pod 정의
apiVersion: v1
kind: Pod
metadata:
name: secure-app
namespace: secure-app
spec:
securityContext:
runAsNonRoot: true # root 실행 금지
runAsUser: 1000 # UID 1000으로 실행
fsGroup: 2000 # 파일 그룹
seccompProfile: # Seccomp 프로필
type: RuntimeDefault
containers:
- name: app
image: python:3.11-slim
ports:
- containerPort: 8000
securityContext:
allowPrivilegeEscalation: false # 권한 상승 금지
readOnlyRootFilesystem: true # 루트 FS 읽기 전용
capabilities:
drop:
- ALL # 모든 Linux capability 제거
add:
- NET_BIND_SERVICE # 필요한 것만 추가
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
volumeMounts:
- name: tmp
mountPath: /tmp # 쓰기 가능한 영역
- name: app-data
mountPath: /app/data
volumes:
- name: tmp
emptyDir: {}
- name: app-data
emptyDir: {}
---
# 3. 보안이 적용된 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-deployment
namespace: secure-app
spec:
replicas: 2
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: nginx:1.25-alpine
ports:
- containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
volumeMounts:
- name: cache
mountPath: /var/cache/nginx
- name: run
mountPath: /var/run
- name: tmp
mountPath: /tmp
volumes:
- name: cache
emptyDir: {}
- name: run
emptyDir: {}
- name: tmp
emptyDir: {}
---
# 4. Pod Disruption Budget (선택사항이지만 권장)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: secure-app-pdb
namespace: secure-app
spec:
minAvailable: 1
selector:
matchLabels:
app: secure-app
|
적용 및 확인#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 적용
kubectl apply -f pod-security-example.yaml
# Namespace 레이블 확인
kubectl get namespace secure-app --show-labels
# Pod 보안 설정 확인
kubectl get pod secure-app -n secure-app -o yaml | grep -A 10 securityContext
# 제한된 권한으로 실행되는지 확인
kubectl exec -it secure-app -n secure-app -- id
# uid=1000 gid=2000 groups=2000
# → root가 아님 ✓
# 루트 FS가 읽기 전용인지 테스트
kubectl exec -it <pod-name> -n secure-app -- touch /test.txt
# Read-only file system (에러) ✓
# Capability 확인
kubectl exec -it <pod-name> -n secure-app -- grep Cap /proc/1/status
# CapEff: 0000000000000000 (모두 제거됨) ✓
|
4. 실전 보안 체크리스트#
클러스터 레벨#
네임스페이스 레벨#
Pod 레벨#
5. 실제 적용 시나리오#
다중 팀 환경 RBAC#
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
| # 팀 A: 앱 배포만
apiVersion: v1
kind: ServiceAccount
metadata:
name: team-a
namespace: team-a-ns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployer
namespace: team-a-ns
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list"]
---
# 팀 B: 로그만 조회 (read-only)
apiVersion: v1
kind: ServiceAccount
metadata:
name: team-b
namespace: team-b-ns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: viewer
namespace: team-b-ns
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list"]
|
마무리#
K8s 보안은 “한 번 설정하고 끝"이 아닙니다. 지속적으로 모니터링하고 업데이트해야 합니다.
다음 Part 9에서 다룰 예정인 것들:
- 패스워드 관리 (Secrets)
- 이미지 보안 (레지스트리, 스캔)
- 감시 및 모니터링
- 백업 및 재해 복구
이 글이 도움이 되었으면 좋겠습니다. 실전에서 만나는 문제가 있으면 언제든 물어봐주세요!