목록으로 가기

Kubernetes Probe, HPA, RBAC — 상태 점검, 오토스케일링, 접근 제어

헬스체크(Probe), 리소스 요청/제한, 오토스케일링(HPA), 무중단 배포, 접근 제어(RBAC)까지. K8s 클러스터를 안정적으로 운영하기 위한 핵심을 정리했습니다.

Kubernetes Probe, HPA, RBAC — 상태 점검, 오토스케일링, 접근 제어 ko posts kubernetes 헬스체크(Probe), 리소스 요청/제한, 오토스케일링(HPA), 무중단 배포, 접근 제어(RBAC)까지. K8s 클러스터를 안정적으로 운영하기 위한 핵심을 정리했습니다.

개요

기본편이 “Kubernetes의 구성요소와 사용법"이었다면, 이 글은 Probe, 리소스 requests/limits, HPA, 배포 전략, RBAC, NetworkPolicy, 스케줄링 제어를 다룹니다.

1. Probe — 상태 점검

정리 보기

“떠 있는 것"과 “정상 동작하는 것"은 다르다

프로세스는 살아있는데 응답을 못 하는 경우가 있습니다. DB 커넥션 풀이 고갈되거나, 데드락에 빠지거나. Probe는 K8s가 Pod의 실제 건강 상태를 주기적으로 확인하는 메커니즘입니다.

3종류의 Probe

Probe 질문 실패 시 동작
livenessProbe “살아있냐?” kubelet이 해당 컨테이너를 죽이고 restartPolicy에 따라 재시작
readinessProbe “요청 받을 준비 됐냐?” 엔드포인트 컨트롤러가 해당 Pod의 IP를 매칭되는 모든 Service의 엔드포인트에서 제거 (컨테이너는 계속 실행)
startupProbe “아직 부팅 중이냐?” kubelet이 컨테이너를 죽이고 restartPolicy에 따라 재시작
  • 재시작 대상은 Pod 전체가 아니라 문제가 된 컨테이너입니다. 다른 컨테이너는 영향을 받지 않습니다
  • startupProbe의 “liveness/readiness 비활성화"는 실패 시 동작이 아니라, startupProbe가 성공하기 전까지의 동작입니다. startupProbe가 설정돼 있으면 성공할 때까지 liveness/readiness를 실행하지 않습니다
  • startup과 liveness는 한 번만 실행되는 게 아니라 failureThreshold 횟수만큼 연속 실패해야 재시작으로 이어집니다. readiness는 컨테이너 수명 전체에 걸쳐 계속 실행됩니다

YAML 예시

spec:
  containers:
    - name: app
      image: myapp:1.0
      ports:
        - containerPort: 8080
      livenessProbe:
        httpGet:
          path: /healthz
          port: 8080
        initialDelaySeconds: 10    # 시작 후 10초 대기 (기본값 0)
        periodSeconds: 15          # 15초마다 체크 (기본값 10)
        failureThreshold: 3        # 3번 연속 실패하면 재시작 (기본값 3)
      readinessProbe:
        httpGet:
          path: /ready
          port: 8080
        periodSeconds: 5
        failureThreshold: 2        # 2번 연속 실패하면 Ready=false
      startupProbe:
        httpGet:
          path: /healthz
          port: 8080
        failureThreshold: 30       # 30번까지 허용 (× periodSeconds = 최대 대기)
        periodSeconds: 10

공식 기본값

필드 기본값 비고
initialDelaySeconds 0 최소 0
periodSeconds 10 최소 1
timeoutSeconds 1 최소 1
successThreshold 1 liveness/startup은 1만 허용
failureThreshold 3 최소 1

즉 아무것도 지정하지 않으면 probe는 컨테이너 시작 직후부터 10초 간격, 1초 타임아웃으로 실행되고, 연속 3회 실패가 재시작 기준이 됩니다.

Probe 방식 4가지

# 1. HTTP GET — 지정한 경로/포트로 HTTP GET 요청
httpGet:
  path: /healthz
  port: 8080

# 2. TCP 소켓 — 포트 열려있는지만 확인 (DB 등)
tcpSocket:
  port: 3306

# 3. 명령어 실행 — 종료 코드 0이면 성공
exec:
  command: ["pg_isready", "-U", "postgres"]

# 4. gRPC — gRPC 헬스체크 프로토콜 사용
grpc:
  port: 9000

probe별 확인 대상

  • readiness: 요청 처리 가능 여부 (DB 연결, 캐시 워밍업 등 포함 가능)
  • startup: 시작이 느린 컨테이너를 위한 probe

2. Resources — 리소스 관리

정리 보기

requests = “최소 이만큼 보장해줘”, limits = “최대 이만큼만 써”

리소스를 지정하지 않으면 한 Pod가 노드 자원을 다 먹어서 다른 Pod까지 죽일 수 있습니다(noisy neighbor). Docker 심화편의 --memory/--cpus와 같은 개념입니다.

spec:
  containers:
    - name: app
      image: myapp:1.0
      resources:
        requests:
          memory: "256Mi"    # 스케줄러가 이만큼 남은 노드에 배치
          cpu: "250m"        # 0.25 코어
        limits:
          memory: "512Mi"    # 초과 시 OOMKill (종료 코드 137)
          cpu: "1000m"       # 1 코어. 초과 시 쓰로틀링 (죽이진 않음)

단위 설명

리소스 단위 의미
CPU cpu, 밀리코어(m) 1 cpu = 물리 코어 1개(또는 vCPU 1개). 250m = 0.25 cpu, 1000m = 1 코어
메모리 E, P, T, G, M, k / Ei, Pi, Ti, Gi, Mi, Ki 바이트 단위. 접미사 없는 정수도 가능

메모리 접미사에서 MiMB를 같은 것으로 적으면 안 됩니다. Mi는 2의 거듭제곱 단위라서 256Mi = 256 × 2^20 = 268,435,456바이트이고, 10의 거듭제곱인 256M은 256,000,000바이트입니다. 1Gi도 1GB가 아니라 2^30바이트입니다. 대소문자도 의미가 다릅니다. M은 메가바이트, m은 밀리바이트(1/1000 바이트)라서 400m은 0.4바이트 요청이 됩니다.

  • CPU는 1m(0.001 cpu)보다 세밀하게 지정할 수 없습니다
  • 접미사가 서로 다른 값이라도 바이트 수가 같으면 같은 양입니다: 128974848, 129e6, 129M, 128974848000m, 123Mi

CPU vs 메모리 초과의 차이

CPU limits 초과 메모리 limits 초과
동작 커널이 다음 스케줄링 구간까지 실행을 미룸 (쓰로틀링) 커널 OOM 서브시스템이 개입해 메모리를 할당하려던 프로세스를 종료
Pod 상태 계속 Running. 컨테이너 런타임은 CPU 과다 사용으로 컨테이너를 종료하지 않음 종료된 프로세스가 컨테이너의 PID 1이고 재시작 대상이면 컨테이너 재시작 (reason: OOMKilled, exitCode: 137)
위험도 응답 지연 컨테이너 재시작으로 이어질 수 있음
  • limits를 넘지 않아도, requests를 초과한 상태에서 노드 메모리가 부족해지면 그 Pod는 축출(eviction) 대상이 됩니다

QoS 클래스 — K8s가 자원 부족 시 누구를 먼저 죽이나

QoS 클래스 조건 축출 순서
Guaranteed 모든 컨테이너가 CPU/메모리 requests와 limits를 0보다 크게 가지고, 각각 requests = limits 가장 마지막
Burstable Guaranteed 조건은 아니지만, 컨테이너 중 하나 이상이 CPU 또는 메모리의 request나 limit을 가짐 중간
BestEffort 어떤 컨테이너에도 CPU/메모리 request와 limit이 없음 가장 먼저
  • 노드 리소스가 부족하면 BestEffort → Burstable → Guaranteed 순으로 축출됩니다. 다만 리소스 압박으로 인한 축출에서는 requests를 초과한 Pod만 후보가 됩니다
  • QoS 클래스는 Pod 생성 시점에 결정되고 수명 동안 바뀌지 않습니다
  • kube-scheduler가 선점(preemption) 대상을 고를 때는 QoS 클래스를 보지 않습니다
# Guaranteed 예시 (requests = limits)
resources:
  requests:
    memory: "512Mi"
    cpu: "500m"
  limits:
    memory: "512Mi"
    cpu: "500m"

참고

  • 스케줄러는 각 리소스 타입별로 “스케줄된 컨테이너들의 requests 합 < 노드 capacity"를 확인합니다. requests를 안 적으면 그 컨테이너는 0으로 계산되니, 실제 사용량이 늘어난 뒤 노드가 부족해질 수 있습니다
  • 메모리 requests는 주로 스케줄링에 쓰이고, CPU requests는 경합 시 가중치로 동작합니다. 즉 CPU requests가 큰 워크로드가 더 많은 CPU 시간을 배분받습니다
  • CPU limits는 하드 실링이라 초과하면 커널이 실행을 미룹니다. 반면 CPU 과다 사용으로 컨테이너가 종료되지는 않습니다
  • kubectl top pods는 Metrics API로 실제 사용량을 보여줍니다
# 리소스 사용량 확인
kubectl top pods
kubectl top nodes

# 특정 Pod의 리소스 설정 확인
kubectl get pod <이름> -o jsonpath='{.spec.containers[*].resources}'

3. HPA — 오토스케일링

정리 보기

HPA = 목표 사용률을 정해두면 컨트롤러가 레플리카 수를 조절하는 것

HPA(Horizontal Pod Autoscaler)는 메트릭(CPU, 메모리, 커스텀)을 보고 Pod 수를 자동으로 늘리고 줄입니다.

YAML 예시

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 2           # 최소 2개
  maxReplicas: 10          # 최대 10개
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70    # CPU 평균 70% 넘으면 스케일 아웃
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

동작 원리

공식 계산식은 desiredReplicas = ceil[currentReplicas × (currentMetricValue / desiredMetricValue)]입니다. 소수점은 버림이 아니라 올림입니다.

현재 Pod 3개, 평균 CPU 85%
→ 비율 = 85/70 = 1.214
→ 필요 Pod 수 = ceil(3 × 1.214) = ceil(3.64) = 4개
→ 스케일 아웃
  • 목표값 주변에는 허용 오차(tolerance)가 있고, 이 안에서는 스케일링이 일어나지 않습니다. behavior에서 지정하지 않으면 클러스터 전체 기본값 10%가 적용됩니다(kube-controller-manager의 --horizontal-pod-autoscaler-tolerance)
  • 메트릭을 여러 개 지정하면 각각에 대해 계산한 뒤 가장 큰 replica 수를 선택합니다

전제 조건

  • 사용률(Utilization) 계산은 Pod의 resources.requests를 기준으로 하므로 requests가 설정돼 있어야 합니다
  • Resource 메트릭은 Metrics API에서 오고, 일반적으로 metrics-server가 이를 제공합니다

명령어

# HPA 생성 (명령어)
kubectl autoscale deployment web-app --min=2 --max=10 --cpu-percent=70

# HPA 상태 확인
kubectl get hpa

# 상세 정보 (현재 메트릭, 타겟, 현재 replica 수)
kubectl describe hpa web-hpa

스케일링 기본 동작

  • 스케일 아웃은 빠르고 스케일 인은 느립니다. 공식 기본값으로 scaleUp.stabilizationWindowSeconds는 0, scaleDown.stabilizationWindowSeconds는 300초(5분)입니다. 후자는 kube-controller-manager의 --horizontal-pod-autoscaler-downscale-stabilization 플래그로 바꿀 수 있고 그 기본값도 5분입니다
  • 안정화 기간 동안 컨트롤러는 해당 윈도우 내 권고값 중 가장 높은 값을 선택합니다. 그래서 메트릭이 요동칠 때 축소가 완만하게 일어납니다
  • 기본 정책은 15초마다 스케일 아웃 시 현재 replica의 100% 또는 4개까지 추가, 스케일 인 시 현재 replica의 100%까지 제거입니다

4. 배포 전략 — 무중단 배포

정리 보기

RollingUpdate와 Recreate

Deployment의 .spec.strategy.typeRecreateRollingUpdate 두 가지이고, 기본값은 RollingUpdate입니다. 새 Pod를 띄우면서 이전 Pod를 내립니다. Recreate는 기존 Pod를 전부 종료한 뒤에 새 Pod를 만듭니다.

Rolling Update 동작

v1 v1 v1     ← 현재 상태 (3개)
v1 v1 v1 v2  ← 새 Pod 1개 추가
v1 v1 v2 v2  ← 이전 Pod 1개 제거, 새 Pod 1개 추가
v1 v2 v2 v2  ← 반복
v2 v2 v2     ← 완료

배포 전략 설정

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1          # 원하는 수보다 1개 더 띄울 수 있음
      maxUnavailable: 0    # 동시에 내리는 Pod 0개 (항상 전체 수 유지)
설정 의미 효과
maxSurge: 1, maxUnavailable: 0 하나씩 추가 후 제거 가용 replica 수를 유지, 교체 시간은 길어짐
maxSurge: 25%, maxUnavailable: 25% 동시에 여러 개 빠르지만 일시적 용량 감소
  • maxSurge, maxUnavailable 모두 기본값은 25%입니다. 아무것도 지정하지 않으면 두 번째 줄이 적용됩니다
  • 퍼센트를 절대값으로 환산할 때 maxSurge는 올림, maxUnavailable은 버림입니다
  • 둘 다 0으로 둘 수는 없습니다. 한쪽이 0이면 다른 쪽은 0이 아니어야 합니다

종료 절차에 관여하는 설정

  1. readinessProbe가 성공하기 전에는 Pod IP가 매칭되는 Service의 EndpointSlice에 추가되지 않습니다
  2. 컨테이너 PID 1에 TERM 시그널이 전달되고, grace period가 끝나면 남아있는 프로세스에 SIGKILL이 전달됩니다
  3. preStop hook은 컨테이너에 TERM이 전달되기 전에 실행됩니다
spec:
  containers:
    - name: app
      lifecycle:
        preStop:
          exec:
            command: ["sleep", "5"]    # preStop hook은 TERM 전송 전에 실행됨 (5는 예시값)
  terminationGracePeriodSeconds: 30    # 기본값 30초. 초과하면 강제 종료

왜 preStop이 필요한가

1. Pod에 종료 시각과 grace period가 기록됨 (STATUS는 Terminating)
2. kubelet이 로컬 Pod 종료 절차 시작 → preStop hook 실행
   동시에, 컨트롤 플레인이 해당 Pod를 EndpointSlice에서 제거할지 평가
3. preStop 완료 후 컨테이너 PID 1에 TERM 시그널 전송
4. 앱이 graceful shutdown
5. grace period가 끝나도 남아있는 프로세스에는 SIGKILL

문제: EndpointSlice 업데이트 전파보다 preStop이 먼저 끝나면
→ TERM을 받은 뒤에도 이미 라우팅된 트래픽이 들어올 수 있음
  • terminationGracePeriodSeconds 기본값은 30초입니다. kubectl delete도 기본 grace period 30초로 동작합니다
  • preStop hook이 grace period를 넘겨서 실행 중이면 kubelet이 2초를 한 번 더 줍니다. preStop이 그보다 오래 걸려야 한다면 terminationGracePeriodSeconds를 늘려야 합니다
  • 종료 중인 엔드포인트가 EndpointSlice에서 즉시 사라지는 것은 아니고, terminating 상태가 노출됩니다. 이 엔드포인트의 ready는 항상 false라서 로드밸런서가 일반 트래픽에 쓰지 않습니다
  • terminationGracePeriodSeconds: 0이면 preStop hook은 실행되지 않습니다
  • 사이드카 컨테이너(restartPolicy가 Always인 init 컨테이너)를 쓰면 kubelet이 메인 컨테이너 종료 후에 사이드카에 TERM을 보내므로, 순서 제어용 preStop을 대체할 수 있습니다

롤백

# 직전 리비전으로 롤백
kubectl rollout undo deployment/web-app

# 특정 버전으로 롤백
kubectl rollout undo deployment/web-app --to-revision=3

# 배포 히스토리
kubectl rollout history deployment/web-app

Blue-Green / Canary

Deployment의 strategy가 제공하는 것은 RollingUpdateRecreate 두 가지입니다. Blue-Green이나 Canary는 strategy 필드로 지정하는 값이 아닙니다.

다만 Deployment를 릴리스별로 여러 개 만들어서 Canary 패턴을 구성할 수 있습니다. 즉 “쓸 수 없다"가 아니라 “레이블과 Service로 직접 구성하거나 별도 도구를 쓴다"에 가깝습니다.

전략 방식 구현
Rolling Update 점진적 교체 Deployment strategy.type: RollingUpdate (기본값)
Recreate 전부 종료 후 새로 생성 Deployment strategy.type: Recreate
Blue-Green 새 환경 전체 준비 후 한번에 전환 Service selector 전환으로 직접 구성, 또는 Argo Rollouts / Flagger
Canary 일부 트래픽만 새 버전으로 릴리스별 Deployment + 레이블 분리, 또는 Istio / Argo Rollouts

5. RBAC — 권한 관리

정리 보기

RBAC = “누가 무엇을 할 수 있는가”

RBAC(Role-Based Access Control)은 사용자나 서비스가 클러스터에서 어떤 작업을 할 수 있는지 제어합니다.

4가지 구성요소

리소스 역할 범위
Role 권한 정의 네임스페이스 내
ClusterRole 권한 정의 클러스터 전체 (네임스페이스에 속하지 않음)
RoleBinding 권한 부여 (Role 또는 ClusterRole → 사용자/그룹/SA) 네임스페이스 내
ClusterRoleBinding 권한 부여 (ClusterRole → 사용자/그룹/SA) 클러스터 전체
  • RoleBinding은 같은 네임스페이스의 Role뿐 아니라 ClusterRole도 참조할 수 있습니다. 이때 부여되는 권한은 그 RoleBinding이 속한 네임스페이스로 한정됩니다. 공통 ClusterRole을 하나 만들어 여러 네임스페이스에서 재사용하는 방식입니다
  • ClusterRole을 모든 네임스페이스에 걸쳐 부여하고 싶을 때만 ClusterRoleBinding을 씁니다

흐름

사용자/ServiceAccount
  RoleBinding / ClusterRoleBinding  (누구에게)
  Role / ClusterRole  (무엇을 허용)
  리소스 (pods, services, deployments 등)

Role 예시 — dev 네임스페이스에서 Pod 조회/로그만 허용

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: dev
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]

RoleBinding — 위 Role을 특정 사용자에게 부여

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods-binding
  namespace: dev
subjects:
  - kind: User
    name: junior-dev
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

ServiceAccount — Pod가 API Server에 접근할 때 사용하는 계정

apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-bot
  namespace: dev
# ServiceAccount 목록
kubectl get serviceaccounts -n dev

# 현재 사용자의 권한 확인
kubectl auth can-i create deployments --namespace dev

# 다른 사용자의 권한 확인
kubectl auth can-i delete pods --namespace dev --as junior-dev

6. NetworkPolicy — 네트워크 접근 제어

정리 보기

NetworkPolicy = “이 Pod는 저 Pod하고만 통신 가능"이라는 방화벽 규칙

기본적으로 K8s의 모든 Pod는 서로 자유롭게 통신할 수 있습니다. NetworkPolicy를 설정하면 허용된 트래픽만 통과시킬 수 있습니다.

예시 — backend Pod는 frontend에서만 접근 가능

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-allow-frontend
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend          # 이 정책이 적용되는 대상
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend   # frontend에서 오는 것만 허용
      ports:
        - protocol: TCP
          port: 8080

모든 인바운드 차단 (기본 거부 정책)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: production
spec:
  podSelector: {}          # 네임스페이스 내 모든 Pod
  policyTypes:
    - Ingress
  ingress: []              # 허용 목록 없음

이 상태에서 필요한 통신만 하나씩 열어주는 방식으로 씁니다.

  • 다만 “아무것도 못 들어온다"는 아닙니다. ingress 격리 상태에서도 Pod가 실행 중인 노드에서 오는 연결은 항상 허용됩니다. kubelet의 probe가 막히지 않는 이유가 여기 있습니다
  • 허용된 연결의 응답 트래픽은 암묵적으로 허용됩니다
  • 정책은 서로 충돌하지 않고 합집합으로 더해집니다(additive)
  • 기본값은 egress/ingress 모두 비격리입니다. 해당 Pod를 선택하고 policyTypes에 그 방향이 들어있는 NetworkPolicy가 하나라도 있을 때만 그 방향이 격리됩니다

전제 조건

  • NetworkPolicy는 네트워크 플러그인이 구현합니다. 이를 구현하는 컨트롤러 없이 NetworkPolicy 리소스만 만들면 아무 효과가 없습니다
  • NetworkPolicy 지원 여부는 네트워크 플러그인마다 다릅니다
  • 적용 범위는 IP/포트 레벨(OSI 3~4계층)이고 TCP, UDP, SCTP를 다룹니다
# NetworkPolicy 목록
kubectl get networkpolicies -n production

# 상세 확인
kubectl describe networkpolicy backend-allow-frontend -n production

정책 구성 예

시나리오 정책
DB는 backend에서만 DB Pod에 backend만 ingress 허용
외부 API 호출 제한 egress 정책으로 특정 IP만 허용
네임스페이스 간 격리 namespaceSelector로 제어

7. Pod 스케줄링 제어

정리 보기

“이 Pod는 특정 노드에만 / 특정 Pod와 같이 / 떨어져서 배치해줘”

Scheduler의 기본 배치 외에 배치 조건을 지정하는 필드가 있습니다.

nodeSelector — 노드 레이블로 배치 제한

spec:
  nodeSelector:
    disk: ssd              # label이 disk=ssd인 노드에만 배치
# 노드에 label 추가
kubectl label node node-1 disk=ssd

Taint & Toleration — “이 노드에 아무나 못 옴”

Taint는 노드에 설정하고, Toleration은 Pod가 해당 Taint를 허용하는 값입니다.

# 노드에 taint 추가 (GPU 전용 노드)
kubectl taint nodes gpu-node-1 gpu=true:NoSchedule
# 이 Pod만 gpu 노드에 배치 가능
spec:
  tolerations:
    - key: "gpu"
      operator: "Equal"
      value: "true"
      effect: "NoSchedule"

Affinity — 세밀한 조건 배치

spec:
  affinity:
    # 노드 어피니티: SSD 노드 선호
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 80
          preference:
            matchExpressions:
              - key: disk
                operator: In
                values: ["ssd"]
    # Pod 안티어피니티: 같은 앱의 Pod끼리 다른 노드에 분산
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchLabels:
              app: web
          topologyKey: kubernetes.io/hostname

용도 정리

기능 용도 예시
nodeSelector 특정 노드에 고정 SSD 노드에 DB
Taint/Toleration 특정 Pod만 허용 GPU 노드에 ML 워크로드만
podAntiAffinity 같은 앱 분산 배치 가용성 확보 (노드 장애 대비)
podAffinity 같이 배치 레이턴시 최소화

관련 포스트

Helm 차트 관리와 Ingress 설정, K8s 트러블슈팅 방법 Helm으로 패키지 관리하는 법, Ingress Controller 설정, Pod가 안 뜰 때 확인하는 디버 … Kubernetes 클러스터 구조와 핵심 오브젝트 이해하기 Pod, Service, Deployment가 뭔지, 클러스터는 어떻게 구성되는지, kubectl 명령어까지 …