securityContext、リソース、クォータ、QoS
한국어 원문으로 표시합니다.
목표
파드가 노드에서 무엇을 할 수 있는지(securityContext, ServiceAccount)와 얼마를 쓸 수 있는지(resources, LimitRange, ResourceQuota)를 매니페스트로 정하고, 그 결과 QoS 클래스가 어떻게 결정되는지 확인할 수 있게 된다.
왜 중요한가
컨테이너는 기본적으로 root 로 돈다. 이미지가 그렇게 만들어졌기 때문이다. 컨테이너 탈출 취약점이 하나라도 있으면 그 root 가 노드의 root 로 이어질 수 있다. runAsNonRoot: true 는 UID 0 으로 뜨려는 컨테이너를 아예 시작시키지 않고, capabilities.drop: ["ALL"] 은 커널이 주는 특권을 전부 회수한다. 웹 서버가 80 포트를 열어야 한다면 NET_BIND_SERVICE 하나만 다시 더한다. 이것이 최소 권한 원칙의 구체적인 모습이다.
리소스 쪽은 두 층으로 이해한다. requests 는 스케줄러의 언어다. 노드에 남은 할당 가능량이 requests 합계보다 커야 파드가 얹힌다. 실제 사용량과는 무관하다. limits 는 노드의 언어다. CPU 는 cgroup 쿼터로 스로틀링되고 메모리는 넘는 순간 OOMKilled 된다. 그래서 메모리 limits 를 실제 피크보다 낮게 잡으면 파드가 조용히 반복 재시작한다.
LimitRange 와 ResourceQuota 는 반드시 세트로 이해해야 한다. 쿼터가 걸린 네임스페이스에 requests/limits 없는 파드를 만들면 생성 자체가 거부된다. 팀 네임스페이스에 쿼터만 걸어 두면 아무것도 안 뜨는 사고가 나므로, LimitRange 로 기본값을 채워 주는 것이 사실상 필수다.
단계
- 네임스페이스
ckad-secure를 만들고 그 안에 ServiceAccountapp-sa를 만든다. - 파드
sa-pod를 만든다. 이미지nginx:1.27,serviceAccountName: app-sa,automountServiceAccountToken: false. - 파드
nonroot를 만든다. 이미지nginx:1.27, 파드 수준securityContext에runAsNonRoot: true,runAsUser: 1000,fsGroup: 2000. - 파드
hardened를 만든다. 이미지nginx:1.27, 컨테이너 이름app, 컨테이너 수준securityContext에allowPrivilegeEscalation: false,readOnlyRootFilesystem: true,capabilities.drop: ["ALL"],capabilities.add: ["NET_BIND_SERVICE"]. - Deployment
api를 만든다. 레플리카 2, 라벨app=api, 이미지nginx:1.27, 컨테이너resources.requests는cpu: 100m/memory: 128Mi,resources.limits는cpu: 500m/memory: 512Mi. - LimitRange
defaults를 만든다.type: Container로default(limits)cpu: 200m/memory: 256Mi,defaultRequestcpu: 100m/memory: 128Mi,maxcpu: "1"/memory: 1Gi,mincpu: 50m/memory: 64Mi. - ResourceQuota
team-quota를 만든다.requests.cpu: "2",requests.memory: 4Gi,limits.cpu: "4",limits.memory: 8Gi,pods: "10". - 파드 두 개를 더 만든다.
qos-guaranteed는 requests 와 limits 가 모두cpu: 250m/memory: 256Mi로 같아야 하고,qos-burstable은 requests 만cpu: 100m/memory: 128Mi로 준다. 둘 다 이미지nginx:1.27.
참고
kubectl create deployment api --image=nginx:1.27 --replicas=2 -n ckad-secure --dry-run=client -o yaml로 뼈대를 뽑고resources를 손으로 채우거나, 만든 뒤kubectl set resources deployment api -n ckad-secure --requests=... --limits=...를 쓴다.- 7단계 이후에는 이 네임스페이스에 requests/limits 없는 파드를 만들 수 없다. 앞 단계 파드를 다시 만들 일이 있으면 LimitRange 가 기본값을 채워 주는지 확인한다.
- 흔한 실수 1:
fsGroup이나runAsNonRoot를 컨테이너 밑에 쓰는 것.fsGroup은 파드 수준에만 있다. - 흔한 실수 2:
capabilities를 파드 수준에 쓰는 것. 컨테이너 수준에만 존재한다. - 확인:
kubectl get pod qos-guaranteed -n ckad-secure -o jsonpath='{.status.qosClass}'
네임스페이스와 ServiceAccount
네임스페이스 ckad-secure 를 만들고 그 안에 ServiceAccount app-sa 를 만든다.
kubectl create serviceaccount 로 만든다. 네임스페이스를 먼저 만들고 그 안에 만든다.
파드에 ServiceAccount 지정하고 토큰 자동 마운트 끄기
파드 sa-pod 를 만든다. 이미지 nginx:1.27, serviceAccountName: app-sa, automountServiceAccountToken: false.
필드 이름은 spec.serviceAccountName 이다(serviceAccount 는 옛 별칭). 토큰 자동 마운트를 끄는 불리언 필드도 파드 스펙 최상위에 있다.
파드 수준 securityContext
파드 nonroot 를 만든다. 이미지 nginx:1.27, 파드 수준 securityContext 에 runAsNonRoot: true, runAsUser: 1000, fsGroup: 2000.
spec.securityContext 에 들어가는 것은 파드 전체에 적용되는 값들이다. 볼륨 소유 그룹을 지정하는 필드는 파드 수준에만 존재한다.
컨테이너 수준 securityContext 와 capabilities
파드 hardened 를 만든다. 이미지 nginx:1.27, 컨테이너 이름 app, 컨테이너 수준 securityContext 에 allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities.drop: ["ALL"], capabilities.add: ["NET_BIND_SERVICE"].
capabilities 는 컨테이너 수준에만 있다. 전부 떨어뜨린 뒤 필요한 것만 다시 더하는 방식이 최소 권한 원칙이다. drop 과 add 는 각각 문자열 배열이다.
Deployment 에 requests 와 limits 지정
Deployment api 를 만든다. 레플리카 2, 라벨 app=api, 이미지 nginx:1.27, 컨테이너 resources.requests 는 cpu: 100m / memory: 128Mi, resources.limits 는 cpu: 500m / memory: 512Mi.
resources 는 컨테이너 밑에 있다. requests 가 스케줄러가 보는 값, limits 가 런타임이 강제하는 값이다. CPU 단위 m 은 1/1000 코어다.
LimitRange 로 기본값과 상하한 정하기
LimitRange defaults 를 만든다. type: Container 로 default(limits) cpu: 200m / memory: 256Mi, defaultRequest cpu: 100m / memory: 128Mi, max cpu: "1" / memory: 1Gi, min cpu: 50m / memory: 64Mi.
spec.limits 는 배열이고 각 원소에 type 이 있다. 기본 limits 를 정하는 키와 기본 requests 를 정하는 키의 이름이 다르니 kubectl explain limitrange.spec.limits 로 확인한다.
ResourceQuota 로 네임스페이스 총량 제한
ResourceQuota team-quota 를 만든다. requests.cpu: "2", requests.memory: 4Gi, limits.cpu: "4", limits.memory: 8Gi, pods: "10".
spec.hard 는 맵이고 키 이름이 requests.cpu, limits.memory 처럼 점을 포함한다. 개수 제한은 pods 처럼 문자열 값으로 준다.
QoS 클래스를 의도한 대로 만들기 (종합)
파드 두 개를 더 만든다. qos-guaranteed 는 requests 와 limits 가 모두 cpu: 250m / memory: 256Mi 로 같아야 하고, qos-burstable 은 requests 만 cpu: 100m / memory: 128Mi 로 준다. 둘 다 이미지 nginx:1.27.
QoS 는 직접 지정하는 필드가 아니라 requests/limits 조합으로 결정되는 결과값이다. 모든 컨테이너에서 CPU·메모리 모두 requests 와 limits 가 같아야 최상위 등급이 된다. kubectl get pod -o jsonpath='{.status.qosClass}' 로 확인한다.