[k8s] 쿠버네티스의 Pod (Lifecycle, Readiness/Liveness 프로브, QoS 클래스, Node Scheduling)

1. Pod의 LifeCycle

1) Pending 단계

제일 먼저 Pod가 Node에 정상적으로 할당되면 PodScheduled가 True가 된다. 스케줄링 되는 방식은 직접 할당 & 쿠버네티스가 자원 상황에 따라 할당하는 방식이 있다. 

 

이후에 container가 실행되기전에 초기화 시켜야할 내용이 있다면 실행되는 InitContainer가 실행(볼륨/보안)되고 Initialized가 True가 된다. Init할 내용은 Pod의 yml정의 spec에 initContainers라는 항목에서 정의할 수 있다. 

 

이후 container의 이미지를 다운로드한다. 위 과정을 수행하면서 container의 status는 waiting이고, reason은 container creating이다. 

 

2) Running

Pod에서 컨테이너를 실행하지 못했다면 container의 status는 waiting이 되고 reason은 CrashLoopBackOff가 된다. 

 

하지만 위 상황에서도 Pod는 Container가 Running으로 판단한다. 내부적으론 ContainerReady, Ready는 False이다. 

 

시간이 흘러 컨테이너가 정상적으로 기됭된다면 위 사진 처럼 Container는 Running이고, ContainerReady, Ready 또한 True로 뜬다. 

 

따라서 Pod의 상태 뿐만 아니라 Container의 상태도 모니터링 해야한다. 

 

만약 Job이나 CronJob으로 생성된 Pod의 경우, 일을 수행하고 나면 Failed / Succeeded가 된다. 

 

 

 

 

 

 

 

 

 

2. Pod의 ReadinessProbe / LivenessProbe

ReadinessProbe

Pod가 생성되고 Container가 생성되고 그 안엔 Spring Boot 애플리케이션이 실행된다고 가정하자. Pod는 Container만 생성되도 Running으로 간주할 것이고, Service에서 Pod로 트래픽을 전달할 것이다. 하지만 Spring Boot는 뜨지 않았고 해당 Pod로 요청이 간 사용자는 에러 페이지를 만나게 될 것이다. 

 

이처럼 앱이 구동되는 순간에 traffic이 유입되는 걸 방지하기 위해 생긴 기능이 ReadinessProbe이다. 

 

테스트를 위해 Service와 Pod2개를 만들어보았다. 

# Service 정의
apiVersion: v1
kind: Service
metadata:
  name: svc-readiness
spec:
  selector:
    app: readiness
  ports:
  - port: 8080
    targetPort: 8080
---
# 일반 Pod 정의
apiVersion: v1
kind: Pod
metadata:
  name: pod1
  labels:
    app: readiness  
spec:
  containers:
  - name: container
    image: kubetm/app
    ports:
    - containerPort: 8080	
  terminationGracePeriodSeconds: 0
---
# rediness Pod 정의
apiVersion: v1
kind: Pod
metadata:
  name: pod-readiness-exec1
  labels:
    app: readiness  
spec:
  containers:
  - name: readiness
    image: kubetm/app
    ports:
    - containerPort: 8080	
    readinessProbe:
      exec:
        command: ["cat", "/readiness/ready.txt"]
      initialDelaySeconds: 5
      periodSeconds: 10
      successThreshold: 3
    volumeMounts:
    - name: host-path
      mountPath: /readiness
  volumes:
  - name : host-path
    hostPath:
      path: /tmp/readiness
      type: DirectoryOrCreate
  terminationGracePeriodSeconds: 0

 

일반 Pod는 정상적으로 띄워지겠지만 readiness Pod는 Volume으로 마운트된 디렉토리에 ready.txt가 없으니 에러가 뜰 것이다. 이 상황을 애플리케이션이 구동중이라 트래픽을 처리할 수 없는 상황이라고 가정했다. 

 

 

while true; do date && curl 10.103.160.58:8080/health; sleep 1; done

 

위와 같이 1분 마다 service로 요청을 보내보면 일반 Pod에게만 요청이 가는걸 확인할 수 있다. 

ReadinessProbe옵션 때문에 정상적으로 수행할 수 없는 Pod에겐 트래픽을 전달하지 않는 것이다. 

 

 

볼륨 마운트된 디렉토리에 ready.txt를 생성하고 나면 ReadinessProbe옵션이 붙은 Pod에게도 트래픽이 잘 전달된다. 이 상황이 Spring Boot 애플리케이션의 구동이 완료된 상황이다. 

 

 

 

LivenessProbe

만약 Spring Boot의 OOM이 나는 등 애플리케이션은 죽었지만 Container는 살아있는 경우가 있다. 이런 경우를 해결하기 위해 나온 기능이 LivenessProbe이다.

 

LivenessProbe는 K8S 서비스와 적접적으로 연결된 Container에는 문제가 없으나 컨테이너 내부의 구동 중인 App에 문제가 생긴 경우 Service가 문제를 감지하여 Traffic 실패를 해결하기 위한 기능이다.

 

테스트를 위해 아래와 같이 Service, Pod2개를 생성해줬다. 

 

apiVersion: v1
kind: Service
metadata:
  name: svc-liveness
spec:
  selector:
    app: liveness
  ports:
  - port: 8080
    targetPort: 8080
---
apiVersion: v1
kind: Pod
metadata:
  name: pod2
  labels:
    app: liveness
spec:
  containers:
  - name: container
    image: kubetm/app
    ports:
    - containerPort: 8080
  terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpget1
  labels:
    app: liveness
spec:
  containers:
  - name: liveness
    image: kubetm/app
    ports:
    - containerPort: 8080
    livenessProbe:
      httpGet:
        path: /health
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
      failureThreshold: 3
  terminationGracePeriodSeconds: 0

 

 

아까와 마찬가지로 1초마다 요청을 보내는데, 8080포트에 대한 응답값을 status500으로 고정시켜주었다.

curl 20.109.131.43:8080/status500

 

 

3번의 요청에 응답을 실패하니 LivenessProbe가 Container에 이상이 생겼읆을 감지하고 Container를 재시작하는 것을 확인할 수 있었다. 

 

 

 

ReadinessProbe와 LivenessProbe에서 Container에 확인 하는 방법은 동일하다.

아래와 같은 옵션을 주어 Container내부의 상황을 확인한다. 

 

 

 

 

 

 

 

 

3. QoS classes (Guaranteed, Burstable, BestEffort)

Node에 균등하게 자원을 사용하는 Pod가 여러개 동작하고 있다고 가정할 떄, 그 중 한개의 Pod가 자원을 추가적으로 사용해야하는 상황에서 추가 자원이 없는 경우 어떻게 동작할지에 대해 정의하는 기능이다. 

 

쿠버네티스에선 앱의 중요도에 따라 이것을 Control할 수 있도록 Qos라는 기능을 제공한다. 각 Pod들은 중요도 순으로 BestEffort < Burstable < Guranteed 로 구분된다. 

 

이런 구분은 명시적인게 아닌, Pod의 spec이 어떻게 정의됐느냐에 따라 자동으로 구분된다. 

 

1) Guaranteed

Guaranteed가 되기 위해선 3가지 조건을 만족해야 한다. 

 

1. Pod의 모든 Container에 Request와 Limit가 설정되어야 한다.

2. Request와 Limit에는 Memory와 CPU가 모두 설정되어 있어야 한다.

3. 각 Container내의 Memory와 CPU의 Request,Limit값은 같아야 한다. 

 

 

2) Burstable

Guaranteed와 BestEffort가 아닌 모든 경우이다. 

 

Burstable인 Pod들 사이의 스케줄링은 OOM Score가 더 높은 것이 먼저 삭제된다. 

OOM Score는 Request Memory 대비 실제 App의 Memory 사용량 등을 측정해서 계산하다. 

 

 

3) BestEffort

어떤 Container에도 Request와 Limit이 설정되지 않은 경우에 BestEffort로 본다. 

 

 

 

 

 

 

 

3. Node Scheduling

  • NodeName: 노드의 이름으로 Pod를 할당할 수 있음. 실제 Production 환경에서는 노드가 삭제되고 추가되면서 노드의 이름이 변경될 수 있기 때문에 범용설 떨어짐. 잘 사용하지 않는다.
  • NodeSelector: 가장 많이 사용되는 방법으로 key / label을 추가하면 해당 label이 달려있는 Node에 할당할 수 있음. 자원이 없어도 label에 맞는 Node에만 할당하려 한다. 또한 매칭되는 Node가 없다면 Pod는 아무데도 할당되지 못한다. 
  • NodeAffinity: Pod에 key만 설정하면 특정 Node에 할당 시킬 수 있음. NodeSelector와 달리 일치하는 Node가 없더라도 Pod가 다른 Node에 할당될 수 있도록 하는 옵션도 있음(preferred).

 

  • Pod Affinity: Pod를 Node에 할당할 때 집중하는 것을 지원하는 기능, 특정 2개의 Pod가 hostPath를 공유하는 상황에서 2개의 Pod는 같은 Node에 할당되어야만 하는데 이러한 상황에서 유용.
  • Pod Anti-Affinity: Pod를 Node에 할당할 때 분산하는 것을 지원하는 기능, 특정 2개의 Pod 중 1개의 Pod에 문제가 생겼을 때 나머지 Pod가 문제를 기록해야하는 상황에서 유용하다. Master, Slave Pod들이 대표적인 예이다.

 

  • Taint / Toleration: 특정 Node에 아무 Pod나 할당되지 않도록 제한하는 기능이다. Node가 하나 있고 해당 Node는 고성능 그래픽 자원을 사용하는데에 필요한 Node일 경우 그래픽 자원이 필요한 Pod만 할당되도록 설정할 수 있음.
    • Taint라는 옵션이 붙은 Node에는 Toleration이라고 옵션이 붙은 Pod만 할당 될 수 있음. 
    • Taint Node에는 Toleration이 아닌 Pod가 자동으로 할당 안될 뿐 아니라 직접 Node를 지정한다해도 할당될 수 없음.
    • 여기서 중요한 점은 Pod가 Taint Node를 찾아서 할당하는 것이 아니라 Node에 할당되는 순간에 Toleration인지를 판단하는 것이므로 Taint Node에 할당될 수 있도록 Node Scheduling을 직접해주어야만 한다.