Kubernetes and Cloud Native Associate (KCNA) #3 Kubernetes Fundamentals 2: API, 컨테이너, 스케줄링
#2에서 control plane과 worker node의 구성 요소, 그리고 Pod,ReplicaSet,Deployment,Service,Namespace 같은 핵심 리소스를 정리했습니다. 클러스터의 뼈대와 그 위에서 돌아가는 객체의 종류를 잡은 셈입니다. 이번 글은 같은 Domain 1(Kubernetes Fundamentals, 46%)의 후반부로, 그 객체들을 실제로 다루는 방식을 다룹니다.
구체적으로는 세 축입니다. 첫째, 모든 리소스가 통과하는 쿠버네티스 API의 구조와 선언형,명령형 조작 방식입니다. 둘째, Pod 안에서 실제로 실행되는 컨테이너와 이미지,레지스트리,런타임의 관계입니다. 셋째, kube-scheduler가 Pod를 어느 노드에 둘지 결정하는 스케줄링 과정입니다. 마지막으로 ConfigMap과 Secret으로 설정을 주입하는 방법까지 정리하겠습니다. 이 네 가지가 Domain 1의 나머지 절반을 채웁니다.
쿠버네티스 API: 모든 것이 객체다 #
쿠버네티스를 한 문장으로 요약하면 거대한 API 서버입니다. Pod를 띄우는 일도, Service를 노출하는 일도, 네임스페이스를 만드는 일도 전부 apiserver에 객체를 등록하거나 수정하는 행위입니다. 그래서 KCNA는 “쿠버네티스에서 X를 한다"는 말을 “apiserver에 X라는 API 객체를 보낸다"로 바꿔 읽는 감각을 요구합니다.
모든 리소스는 같은 5칸 구조를 가진다 #
Pod든 Deployment든 ConfigMap이든, 쿠버네티스 객체는 거의 항상 같은 네다섯 칸으로 이루어집니다.
| 필드 | 역할 |
|---|---|
apiVersion | 이 객체가 속한 API group과 버전 (예: v1, apps/v1) |
kind | 객체의 종류 (예: Pod, Deployment, Service) |
metadata | 이름,네임스페이스,label,annotation 등 식별 정보 |
spec | 사용자가 원하는 상태(desired state). “이렇게 되어 있어야 한다” |
status | 시스템이 채우는 현재 상태(actual state). 사용자가 쓰지 않음 |
여기서 시험에 가장 자주 나오는 포인트는 spec과 status의 구분입니다. spec은 사용자가 선언하는 목표이고, status는 컨트롤러가 현재 상태를 기록해 채워 넣는 칸입니다. 쿠버네티스의 컨트롤러는 항상 status를 spec에 맞추려고 일하며, 이 끝없는 조정 과정을 reconciliation loop라고 부릅니다. #2에서 본 controller-manager가 바로 이 루프를 돌리는 주체입니다.
다음은 Pod 매니페스트의 최소 형태입니다.
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
spec:
containers:
- name: nginx
image: nginx:1.27apiVersion: v1, kind: Pod, metadata.name, 그리고 spec 아래의 컨테이너 정의가 보입니다. status는 여기에 없습니다. 사용자가 spec만 적어 보내면, apiserver가 받아 etcd에 저장하고, 스케줄러와 kubelet이 일을 진행한 뒤 status를 채워 넣습니다.
API group과 버전 #
apiVersion 값이 두 가지 형태로 나뉘는 점을 짚어 두면 시험에서 헷갈리지 않습니다.
- core group.
v1하나만 적습니다. Pod,Service,ConfigMap,Secret,Namespace 같은 가장 기본적인 리소스가 여기에 속합니다. 그룹 이름이 비어 있어서 버전만 적습니다. - named group.
<group>/<version>형태입니다. Deployment,ReplicaSet,StatefulSet,DaemonSet은apps/v1에, Job,CronJob은batch/v1에, Ingress는networking.k8s.io/v1에 속합니다.
버전 표기에는 v1(안정), v1beta1(베타), v1alpha1(알파)의 성숙도 단계가 있습니다. 안정 버전일수록 하위 호환이 보장됩니다. KCNA가 모든 group을 외우라고 요구하지는 않지만, Pod는 core(v1), Deployment는 apps/v1 정도는 기억해 두면 보기 구분에 도움이 됩니다.
선언형 vs 명령형 #
쿠버네티스를 조작하는 방식은 크게 두 갈래입니다. 이 둘의 구분은 KCNA 단골 출제 포인트입니다.
명령형(imperative): 동작을 직접 지시한다 #
명령형은 “이 동작을 지금 실행하라"고 직접 지시하는 방식입니다. kubectl run, kubectl create, kubectl delete, kubectl scale 같은 명령이 여기 속합니다.
kubectl run nginx --image=nginx:1.27
kubectl create deployment web --image=nginx:1.27
kubectl scale deployment web --replicas=3빠르고 직관적이지만, 무엇을 했는지가 파일로 남지 않습니다. 같은 상태를 다시 만들려면 명령을 다시 외워서 입력해야 하고, 변경 이력을 추적하기 어렵습니다.
선언형(declarative): 원하는 상태를 선언한다 #
선언형은 “최종적으로 이런 상태가 되어야 한다"를 YAML 매니페스트로 적어 두고 kubectl apply로 적용하는 방식입니다.
kubectl apply -f deployment.yaml매니페스트 자체가 desired state이며, 쿠버네티스가 현재 상태를 그 목표에 맞춰 조정합니다. 같은 파일을 다시 apply해도 안전하고(멱등성), 파일을 Git에 두면 변경 이력이 그대로 남습니다. 이 성질이 #7에서 다룰 GitOps의 토대가 됩니다. 쿠버네티스 운영의 정석은 선언형이며, 시험에서도 “권장 방식"을 물으면 선언형(kubectl apply)이 답입니다.
| 구분 | 명령형(imperative) | 선언형(declarative) |
|---|---|---|
| 대표 명령 | kubectl run, create, scale | kubectl apply -f |
| 지정 대상 | 실행할 동작 | 원하는 최종 상태 |
| 이력 추적 | 어려움 | 파일/Git로 남음 |
| 재실행 | 충돌 가능 | 멱등적, 안전 |
| 권장 여부 | 빠른 실습/디버깅용 | 운영 표준 |
kubectl의 정체 #
여기서 kubectl의 역할을 분명히 해 둘 필요가 있습니다. kubectl은 클러스터의 일부가 아니라 apiserver와 HTTP로 통신하는 클라이언트일 뿐입니다. kubectl apply를 실행하면 kubectl이 YAML을 읽어 apiserver에 API 요청을 보내고, 이후의 모든 조정은 control plane 안에서 일어납니다. 그래서 KCNA는 kubectl을 “사용자가 API를 편하게 호출하도록 돕는 도구"로 정의합니다. kubectl 없이 curl로 apiserver를 직접 호출해도 결과는 같습니다. kubectl과 Pod의 기본 조작은 실무 트랙 #3에서 손으로 다뤄 두면 개념이 단단해집니다.
컨테이너: Pod 안에서 실제로 도는 것 #
#2에서 Pod가 스케줄링의 최소 단위라고 했습니다. 그 Pod 안에서 실제로 실행되는 것이 컨테이너입니다.
이미지와 레지스트리 #
컨테이너는 이미지에서 만들어집니다. 이미지는 애플리케이션 바이너리,라이브러리,런타임을 한 묶음으로 포장한 읽기 전용 패키지이며, 이미지의 표준 형식은 OCI(Open Container Initiative)가 정의합니다. 이미지는 레지스트리에 저장되고 배포됩니다. Docker Hub,GitHub Container Registry,Amazon ECR 같은 곳이 레지스트리입니다.
image: nginx:1.27에서 nginx는 이미지 이름, 1.27은 태그입니다. 태그를 생략하면 latest가 기본으로 붙는데, latest는 “최신"을 보장하지 않고 그저 태그 이름일 뿐이라 운영에서는 명시적 버전 태그를 권장합니다. 이 점은 #7의 배포 안정성 논의와도 이어집니다.
컨테이너 런타임 #
이미지를 받아 실제 프로세스로 실행하는 주체가 컨테이너 런타임입니다. 쿠버네티스는 특정 런타임에 묶이지 않고 **CRI(Container Runtime Interface)**라는 표준 인터페이스로 런타임과 통신하며, 그 자리에 containerd나 CRI-O가 들어갑니다. worker node의 kubelet이 CRI를 통해 런타임에게 “이 이미지로 컨테이너를 띄워라"라고 지시합니다. 런타임과 CRI의 자세한 계층 구조는 Domain 2의 주제이므로 #4에서 깊이 다루겠습니다. 여기서는 Pod 안의 컨테이너는 런타임이 실행하고, kubelet이 CRI로 그 런타임을 부린다는 관계만 잡아 두면 충분합니다.
Pod 안에 컨테이너가 여러 개일 때 #
Pod는 컨테이너를 1개 이상 담을 수 있습니다. 대부분은 1개지만, 두 종류의 보조 컨테이너 패턴이 시험에 등장합니다.
- init container. 메인 컨테이너보다 먼저, 순서대로 실행되고 끝나는 컨테이너입니다. 설정 파일 내려받기, 의존 서비스 대기 같은 준비 작업을 맡습니다. 모든 init container가 성공해야 메인 컨테이너가 시작됩니다.
- sidecar. 메인 컨테이너와 나란히 실행되며 로그 수집,프록시 같은 보조 기능을 더하는 컨테이너입니다. Service Mesh의 프록시 주입이 대표적인 sidecar 사례이며, 이는 #4에서 다룹니다.
같은 Pod 안의 컨테이너들은 **같은 네트워크 네임스페이스(같은 IP, localhost 통신)**와 같은 볼륨을 공유한다는 점이 핵심입니다.
스케줄링: Pod를 어느 노드에 둘 것인가 #
새 Pod가 생성되면 처음에는 어느 노드에도 배치되지 않은 상태입니다. 이 Pod를 어느 worker node에서 실행할지 결정하는 일이 스케줄링이고, 그 결정을 내리는 control plane 컴포넌트가 #2에서 본 kube-scheduler입니다.
두 단계: filtering과 scoring #
kube-scheduler는 두 단계로 노드를 고릅니다.
- filtering(필터링). 이 Pod를 실행할 수 없는 노드를 걸러냅니다. 자원(CPU,메모리)이 부족한 노드, 요구 조건에 맞지 않는 노드 등이 여기서 제외됩니다. 남은 노드를 feasible node라고 합니다.
- scoring(점수화). 남은 노드에 점수를 매겨 가장 적합한 노드를 고릅니다. 자원이 골고루 분산되도록, 또는 특정 정책에 맞도록 점수가 계산됩니다.
filtering에서 살아남은 노드가 하나도 없으면 Pod는 배치되지 못하고 Pending 상태에 머뭅니다. 시험에서 “Pod가 Pending이다"라는 상황은 흔히 스케줄링 실패(자원 부족 또는 조건 불일치)를 가리킵니다.
자원 요청(resource requests)이 스케줄링을 좌우한다 #
스케줄링의 첫 번째 기준은 자원입니다. Pod의 컨테이너에 resources.requests로 필요한 CPU,메모리를 적으면, 스케줄러는 그 요청을 채워 줄 여유가 있는 노드만 후보로 남깁니다.
spec:
containers:
- name: app
image: myapp:1.0
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"- requests. 스케줄링에 쓰이는 값입니다. “최소 이만큼은 보장되어야 한다"는 요청이며, 스케줄러는 이 값으로 노드 적합성을 판단합니다.
- limits. 실행 중 사용할 수 있는 상한입니다. 스케줄링이 아니라 런타임에서 강제되며, 메모리 limit을 넘으면 컨테이너가 OOM으로 종료될 수 있습니다.
requests는 스케줄링, limits는 실행 시 제한이라는 구분이 자주 출제됩니다.
nodeSelector: 가장 단순한 노드 지정 #
특정 노드에만 Pod를 두고 싶을 때 가장 단순한 도구가 nodeSelector입니다. 노드에 붙은 label과 정확히 일치하는 곳에만 배치합니다.
spec:
nodeSelector:
disktype: ssddisktype=ssd label이 붙은 노드에만 배치됩니다. 조건이 단순한 일치 하나뿐이라 표현력이 약하지만, 그만큼 이해하기 쉽습니다.
node affinity / anti-affinity: 더 유연한 규칙 #
nodeSelector보다 풍부한 조건을 표현하려면 affinity를 씁니다.
- node affinity. “이런 label을 가진 노드를 선호한다 또는 요구한다"를 표현합니다.
requiredDuringScheduling...(필수)과preferredDuringScheduling...(선호)로 강도를 나눌 수 있어, 가능하면 두되 안 되면 다른 노드도 허용하는 식의 규칙이 가능합니다. - pod affinity / anti-affinity. 노드의 label이 아니라 다른 Pod의 위치를 기준으로 삼습니다. affinity는 “특정 Pod와 같은 노드(또는 영역)에 모아라”, anti-affinity는 “특정 Pod와 떨어뜨려라"를 뜻합니다. 같은 앱의 복제본을 서로 다른 노드에 흩어 가용성을 높일 때 anti-affinity를 씁니다.
KCNA 수준에서는 nodeSelector는 단순 일치, affinity는 선호/필수와 더 복잡한 규칙이라는 정도의 구분이면 충분합니다.
taints와 tolerations: 노드가 Pod를 밀어낸다 #
지금까지는 Pod가 노드를 고르는 방향이었습니다. taint와 toleration은 반대로, 노드가 Pod를 밀어내는 메커니즘입니다.
- taint는 노드에 붙이는 “표식"입니다. 노드에 taint가 있으면, 기본적으로 그 노드에는 Pod가 스케줄링되지 않습니다. 노드가 Pod를 거부하는 셈입니다.
- toleration은 Pod에 붙이는 “면제권"입니다. Pod가 특정 taint에 대한 toleration을 가지면, 그 taint가 붙은 노드에도 배치될 수 있습니다.
즉 taint(노드의 거부) + toleration(Pod의 허용)이 짝을 이뤄야 해당 노드에 Pod가 들어갈 수 있습니다. control plane 노드에 일반 워크로드가 안 뜨는 이유가 바로 control plane 노드에 taint가 걸려 있기 때문입니다. 전용 GPU 노드를 특정 Pod에만 내주는 용도로도 쓰입니다.
여기서 nodeSelector/affinity와 taint/toleration의 방향을 헷갈리지 않는 것이 중요합니다. affinity 계열은 Pod가 노드를 끌어당기는(attract) 쪽이고, taint/toleration은 노드가 Pod를 밀어내되(repel) toleration이 있는 Pod만 받아주는 쪽입니다. 이 방향 구분이 단골 함정입니다.
설정 주입: ConfigMap과 Secret #
애플리케이션의 설정값을 이미지 안에 박아 두면 환경마다 이미지를 새로 만들어야 합니다. 쿠버네티스는 설정을 컨테이너 밖에서 주입하는 두 리소스를 제공합니다.
ConfigMap: 비밀이 아닌 설정 #
ConfigMap은 환경 변수,설정 파일,플래그처럼 민감하지 않은 설정값을 키-값으로 담는 리소스입니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
APP_MODE: "production"Secret: 민감한 정보, 단 기본은 암호화가 아니다 #
Secret은 비밀번호,토큰,인증서처럼 민감한 정보를 담는 리소스로, 구조는 ConfigMap과 비슷합니다. 여기서 KCNA가 반드시 짚는 시험 포인트가 있습니다.
Secret의 값은 base64로 인코딩될 뿐, 기본적으로 암호화되지 않습니다.
base64는 누구나 즉시 디코딩할 수 있는 인코딩이지 암호화가 아닙니다. 따라서 Secret 자체가 값을 안전하게 보호한다고 착각하면 안 됩니다. 실제로 안전하게 보관하려면 etcd 저장 시 암호화(encryption at rest)를 별도로 켜거나, RBAC으로 접근을 제한하거나, 외부 비밀 관리 도구를 연동해야 합니다. **“Secret은 기본적으로 암호화되지 않는다”**는 문장은 시험에 거의 반드시 나오는 함정이므로 그대로 외워 두는 편이 좋습니다.
컨테이너에 주입하는 두 방법 #
ConfigMap이든 Secret이든 컨테이너에 전달하는 방식은 두 가지입니다.
| 방식 | 설명 | 특징 |
|---|---|---|
| 환경 변수(env) | 키-값을 컨테이너의 환경 변수로 주입 | 단순. 단, 실행 중 변경이 반영되지 않음 |
| 볼륨 마운트(volume) | ConfigMap/Secret을 파일로 마운트 | 파일 형태가 필요할 때. 변경이 반영될 수 있음 |
설정값 하나하나를 환경 변수로 넣을지, 설정 파일 전체를 볼륨으로 마운트할지는 애플리케이션이 설정을 어떻게 읽느냐에 달려 있습니다. ConfigMap과 Secret의 실제 사용 예는 실무 트랙 #6에서 손으로 다뤄 두면 개념이 명확해집니다.
이 글의 시험 포인트 정리 #
Domain 1 후반부에서 객관식 보기로 자주 찔리는 지점입니다.
- 선언형 vs 명령형.
kubectl apply는 선언형(원하는 상태 선언, 운영 표준),kubectl run/create는 명령형(동작 직접 지시). “권장 방식"을 물으면 선언형 - spec vs status. spec은 사용자가 적는 desired state, status는 시스템이 채우는 actual state
- apiVersion. Pod,Service,ConfigMap,Secret은 core(
v1), Deployment 계열은apps/v1 - requests vs limits. requests는 스케줄링 기준, limits는 실행 시 사용 상한
- affinity vs taint/toleration. affinity는 Pod가 노드를 끌어당김, taint/toleration은 노드가 Pod를 밀어내되 toleration 있는 Pod만 허용
- Secret은 기본적으로 암호화가 아니라 base64 인코딩. 이 문장 하나가 단골 함정
정리 #
이번 글에서 잡은 것:
- 쿠버네티스 API. 모든 리소스는 apiVersion,kind,metadata,spec,status의 같은 구조를 가지며, 컨트롤러가 status를 spec에 맞춰 조정함
- 선언형/명령형.
kubectl apply(선언형, 운영 표준)와kubectl run/create(명령형)의 구분. kubectl은 apiserver와 통신하는 클라이언트 - 컨테이너. 이미지는 레지스트리에 저장되고 CRI를 통해 런타임이 실행함. Pod는 컨테이너 1개 이상을 담고 init container,sidecar 패턴이 존재
- 스케줄링. kube-scheduler가 filtering〜scoring으로 노드를 고름. requests,nodeSelector,affinity,taint/toleration이 결정에 영향
- 설정 주입. ConfigMap(비밀 아님),Secret(민감, 단 기본은 base64 인코딩이지 암호화 아님)을 env 또는 volume으로 주입
이로써 비중 46%의 Domain 1(Kubernetes Fundamentals)을 #2와 이번 글 두 편으로 마무리했습니다. 가장 큰 도메인을 넘었으니 합격에 가장 가까운 점수대를 확보한 셈입니다.
다음: Container Orchestration #
쿠버네티스 자체의 핵심은 정리했습니다. 이제 그 아래에서 컨테이너를 떠받치는 계층으로 내려갑니다.
#4 Container Orchestration (22%): 런타임, 보안, 네트워킹, 스토리지, Service Mesh에서는 컨테이너 런타임(containerd,CRI-O)과 CRI, 보안(RBAC,NetworkPolicy), 네트워킹(CNI,Service,DNS), 스토리지(CSI,PV,PVC), 그리고 Service Mesh까지, 두 번째로 큰 도메인을 정리하겠습니다.