[Kubernetes] 4. POD

2023. 6. 3. 08:45쿠버네티스 (Kubernetes)


Pod이란

 

동일한 실행 환경에서 구동되는 컨테이너들을 모아놓은 컬렉션입니다.

클러스터에서 배포하는 최소 단위이기 때문에 Pod에 있는 컨테이너들은 언제나 동일한 기기에 위치해 있습니다.

 

따라서 Pod에 있는 컨테이너들은 많은 것을 공유하는데, IP 주소나 Port, 호스트 이름 뿐 아니라 심지어 IPC도 가능합니다.

반대로 다른 Pod들은 서로 독립적입니다.

 

그렇다면 Pod이라는 개념은 왜 만들어졌을까요?

 

하나의 Pod에 두 개 컨테이너가 파일시스템을 공유하고 있습니다.

 

 

위 이미지는 영화 다운로드 서비스를 위한 예시입니다.

하나는 영화 다운로드와 관련해서 사용자들과 상호작용할 수 있는 웹 서비스이고 하나는 영화 파일을 원격 저장소에서 가져오거나 또는 로컬 저장소에서 제거하는 동기화 서비스입니다.

 

이 두 컨테이너를 하나의 컨테이너로 통합하지 않고 왜 구분되어야 할까요?

먼저 웹 컨테이너는 사용자들의 요청에 빠르게 응답해야 하고 서버 안정성이 높아야 합니다.

하지만 동기화 컨테이너는 그럴 필요까진 없고 요청이 오는 대로 파일을 가져오거나 삭제해주면 됩니다.

 

만약 파일을 제대로 가져오지 못했거나 디스크 용량이 꽉 차서 동기화 서비스가 종료되었다고 가정해보면

  • 구분된 경우, 웹 컨테이너는 여전히 돌고있기에 사용자에게 적절하게 대응할 수 있습니다. (가령 동기화 서비스가 이용 불가한다던가 디스크 용량이 꽉 찼다던가...)
  • 통합된 경우, 사용자가 아예 웹 서비스조차 이용하지 못합니다. 

 

또한 웹 서비스가 과도하게 메모리를 많이 잡아먹는다고 가정해보면

  • 구분된 경우, 웹 컨테이너에 대한 메모리 사용량을 타협할 수 있습니다. 사실 동기화는 IO가 주된 목적이므로 메모리를 적게 줘도 괜찮지만, 어쨌든 최소 메모리 용량을 확보했으니 돌아가긴 할 것 입니다.
  • 통합된 경우, 동기화 서비스를 아예 사용하지 못할 수 있습니다. 또는 어플리케이션 수준에서 설정해야 합니다.

 

다만 필자의 가치관도 그렇고, 사실 역할이 분명히 구분된다면 Pod 수준에서 분리하는 게 좋습니다.

두 개의 Pod을 만들어 각각 웹 컨테이너와 동기화 컨테이너를 위치한다면, 만일 웹 서비스를 업그레이드하거나 복구할 일이 있을 때 해당 Pod만 재배포를 하면 되니까요.

 

Pod의 큰 장점은 로드 밸런싱(load balancing)이기도 합니다. 이는 추후 포스팅에서 다루겠습니다.

 

 

 

 


Pod Manifest

 

Pod은 Pod Manifest에서 정의됩니다. 쿠버네티스는 선언적 구성(declarative configuration)을 선호하기 때문인데, 이는 처음부터 설정값을 작성해서 원하는 상태를 만들어 놓고 Pod을 생성하는 것입니다.

반대로 일단 생성하고 명령어를 통해 원하는 상태를 만드는 것을 명령형 구성(imperative configuration)이라고 합니다.

명령형과 비교해서, 선언적 구성의 장점은 설정값들이 어떠한 기록물 형태(사실상 파일)로 남는다는 것인데 이를 공유하여 리뷰를 주고 받을 수 있다는 장점이 있습니다.

 

다시 돌아와서, 설정값을 작성한 파일을 Pod Manifest라고 합니다.

쿠버네티스 API 서버는 이 파일을 받으면 일단 Pod을 생성하는 데, 스케줄러(scheduler)는 API 서버에 방문해 이러한 Pod과 해당 Pod manifest를 찾아 Pod을 구성하고 노드에 위치시킵니다.

 

 

Pod 만들어보기

예제 어플리케이션을 실행해봅니다.

kubectl run kuard --image=gcr.io/kuar-demo/kuard-amd64:blue

아래 명령어를 실행해서 Pod의 상태를 확인할 수 있습니다.

kubectl get pods
더보기

NAME    READY   STATUS    RESTARTS   AGE
kuard   1/1     Running   0          9s

 

이 어플리케이션은 8080 포트를 사용하고 있는데, 바로 http://localhost:8080에 접속하지 못합니다.

이때는 아래처럼 포트 포워딩을 하면 됩니다.

kubectl port-forward kuard 8080:8080

 

 

Pod 삭제하기

다음 단계로 넘어가기 위해 여기서 만들었던 Pod을 제거해줍니다.

kubectl delete pods/kuard

 

 

다만 바로 삭제되진 않고, 먼저 보통 30초 정도 Terminating 상태를 가집니다.

주의할 점은 해당 Pod의 컨테이너들에 저장된 데이터까지 모두 삭제된다는 건데, 영속(persist) 데이터를 가지고 싶다면 Persistent Volumns 를 요청하면 됩니다.

 

 

Pod Manifest 로 실행해보기

YAML 또는 JSON으로 작성할 수 있습니다. 여기서는 YAML 파일 형식으로 작성하겠습니다.

 

kuard-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containers:
    - image: gcr.io/kuar-demo/kuard-amd64:blue
      name: kuard
      ports:
        - containerPort: 8080
          name: http
          protocol: TCP​

 

다 만들었다면 아래 명령어를 통해 Pod을 생성할 수 있습니다.

kubectl apply -f kuard-pod.yaml

이러면 yaml 데이터가 API 서버로 전달되고 시스템은 Pod을 생성해 정상 노드에 구동될 수 있게 스케줄링합니다.

 

여담으로 - 가 붙은 친구들은 배열을 의미합니다. 가령 - containerPort: 8080 [{"containerPort": "8080"}] 처럼 만듭니다.

 

 

 

 


Pod Detail

 

특정 Pod의 자세한 정보를 보고 싶다면 아래 명령어를 실행합니다.

kubectl describe pods kuard
더보기

Name:             kuard
Namespace:        default
Priority:         0
Service Account:  default
Node:             kind-control-plane/172.18.0.2
Start Time:       Sat, 03 Jun 2023 08:41:32 +0900
Labels:           <none>
Annotations:      <none>
Status:           Running
IP:               10.244.0.5
IPs:
  IP:  10.244.0.5
Containers:
  kuard:
    Container ID:   containerd://4294cf4d753d7f3f159f0719621f6afc0092846d53392173bde434eaea0a8857
    Image:          gcr.io/kuar-demo/kuard-amd64:blue
    Image ID:       gcr.io/kuar-demo/kuard-amd64@sha256:1ecc9fb2c871302fdb57a25e0c076311b7b352b0a9246d442940ca8fb4efe229
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sat, 03 Jun 2023 08:41:36 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-zk7z2 (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  kube-api-access-zk7z2:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  2m53s  default-scheduler  Successfully assigned default/kuard to kind-control-plane
  Normal  Pulling    2m53s  kubelet            Pulling image "gcr.io/kuar-demo/kuard-amd64:blue"
  Normal  Pulled     2m49s  kubelet            Successfully pulled image "gcr.io/kuar-demo/kuard-amd64:blue" in 3.444567765s (3.444574966s including waiting)
  Normal  Created    2m49s  kubelet            Created container kuard
  Normal  Started    2m49s  kubelet            Started container kuard

 

 

 

 

 


Health Checks

 

컨테이너 내 어플리케이션이 정상 동작하는 지 확인하는 것을 Health Checking이라고 합니다.

 

염두할 점은, 프로세스 수준에서 정상 동작 여부가 아니라 실질적으로 어플리케이션이 응답하는 것을 보는 걸 말합니다.

예를 들어 프로세스에 데드락(deadlock)이 걸려서 사용자 요청을 처리할 수 없다고 했을 때, 프로세스는 여전히 정상 동작하고 기능상으로는 문제가 없지만 어플리케이션이 비정상인 상태일 수 있습니다. 따라서 어플리케이션 수준에서 확인 로직을 가져야 합니다.

이렇게 어플리케이션 수준에서 정상 동작하는 것을 Liveness라고 합니다.

 

Liveness Probe

일반적으로 어플리케이션에서는 HTTP/HTTPS 통신으로 /healthy나 /hello, /live 등을 만들어 200을 보내 정상 여부를 따집니다.

쿠버네티스에서도 이를 적용하여 비정상이면 조치를 취할 수 있게 만들 수 있는데 아래 예시를 보면서 함께 살펴보겠습니다.

 

kuard-pod-health.yaml

apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containers:
    - image: gcr.io/kuar-demo/kuard-amd64:blue
      name: kuard
      livenessProbe:
        httpGet:
          path: /healthy
          port: 8080
        initialDelaySeconds: 5
        timeoutSeconds: 1
        periodSeconds: 10
        failureThreshold: 3
      ports:
        - containerPort: 8080
          name: http
          protocol: TCP

 

본 예시에서는 HTTP GET /healthy 를 이용해 만들었습니다.

일반적으로 쿠버네티스는 응답 상태 코드가 200 이상이고 400 미만일 때 정상이라고 봅니다.

 

작성한 파라미터들을 설명해보면,

파라미터명 설명
initaliDelaySeconds 컨테이너가 생성되고 첫 번째로 요청을 보내기 전 지연시간
timeoutSeconds 최소 응답 지연 시간, 해당 시간 안에 응답이 안 오면 비정상이라고 판단
periodSeconds 주기적으로 요청을 보낼 시간 간격
failureThreshold 비정상을 용인하는 연속 개수, 예를 들어 연속 3번까지는 비정상 상태여도 괜찮지만 이를 넘어가면 어플리케이션에 문제가 있다고 판단

 

아래 명령어를 통해 실행해봅니다. 사이트에 직접 접속해볼 것이니 포트 포워딩도 해줍니다.

kubectl apply -f kuard-pod-health.yaml
kubectl port-forward kuard 8080:8080

 

localhost:8080에 접속한 뒤 Liveness Probe 탭을 눌러보면 10초마다 상태가 기록되는 것을 볼 수 있습니다.

Fail 버튼을 누르면 500 상태가 기록되는 데, 3번을 넘어가면 컨테이너가 재시작합니다.

재시작 후에는 다시 정상적으로 200이 기록되는 데, 이 때 아래 명령어를 실행해서 Pod의 상태를 보면,

kubectl get pods
더보기

NAME    READY   STATUS    RESTARTS      AGE
kuard   1/1     Running   1 (17s ago)   6m27s

와 같이 RESTARTS에 카운트가 올라가 있는 것을 볼 수 있습니다.

 

기본적으로 어플리케이션에 문제가 있으면, Pod은 restartPolicy에 따라서 동작합니다.

  Zero Exit Code Liveness Failure
Always (default) O O
OnFailure X O
Never X X

 

 

 

Readiness Probe

Liveness는 어플리케이션 자체의 상태를 확인하는 용도로 사용된다면,

Readiness는 이와 다르게 사용자 요청을 처리할 수 있는 지 확인합니다.

주로 로드 밸런싱(load balancing)에서 쓰이는 데, 예를 들어 Pod에 A, B, C 컨테이너가 있을 때 C의 Readiness가 비정상이라면 밸런서는 트래픽을 A, B에 보내 요청을 처리하도록 합니다.

 

자세한 내용은 추후 포스팅에서 다루겠습니다.

 

 

 

 


non-HTTP Health Checks

 

본인의 어플리케이션이 HTTP를 지원하지 않을 때 여전히 health checking 할 수 있는 방법이 있습니다.

 

첫 번째로는 tcpSocket을 이용하는 방법입니다.

이는 TCP 통신을 통해 연결이 되면 정상, 연결이 안되는 비정상으로 판단합니다.

 

Configure Liveness, Readiness and Startup Probes

This page shows how to configure liveness, readiness and startup probes for containers. The kubelet uses liveness probes to know when to restart a container. For example, liveness probes could catch a deadlock, where an application is running, but unable t

kubernetes.io

 

두 번째로는 exec으로 스크립트를 실행해보는 방법입니다.

실행 결과가 zero exit code면 정상, 아니면 비정상으로 판단합니다.

 

Configure Liveness, Readiness and Startup Probes

This page shows how to configure liveness, readiness and startup probes for containers. The kubelet uses liveness probes to know when to restart a container. For example, liveness probes could catch a deadlock, where an application is running, but unable t

kubernetes.io

 

 

 

 

 


Resource Management

 

시스템 자원을 효율적으로 분배하는 것을 Utilization이라고 합니다.

당연하게도 쿠버네티스는 오케스트레이터(orchestrator)이기 때문에 자원 분배를 쉽고 편리하게 할 수 있도록 해줍니다.

 

 

최소 요구 자원 만들기

만일 컨테이너에 현재 컴퓨터의 0.5개의 CPU와 128MB 메모리를 최소 요구 자원으로 주고 싶을 때, 아래와 같이 작성하면 됩니다.

 

kuard-pod-resreq.yaml

apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containers:
    - image: gcr.io/kuar-demo/kuard-amd64:blue
      name: kuard
      resources:
        requests:
          cpu: "500m"
          memory: "128Mi"
      ports:
        - containerPort: 8080
          name: http
          protocol: TCP

 

최소 자원을 요청한다는 말은, 해당 컨테이너가 구동하기 위한 최소 자원을 말합니다.

따라서 모르고 최대로 놓으면 Pod이 제대로 생성되지 않을 수 있습니다.

 

요청 자원은 때로는 해당 컨테이너가 가져가는 비율을 나타낼 수도 있습니다.

예를 들어 2 CPU 컴퓨터에서 A라는 컨테이너가 500m의 CPU 요청을 가지고 생성되었을 때,

  1.  A가 2 CPU 자원이 필요하다고 하면 혼자 2 CPU를 사용합니다.
  2. 동일하게 B가 들어와서 2 CPU가 필요하다고 하면 A, B 모두 1 CPU씩 받습니다.
  3. 동일하게 C가 들어오고 2 CPU가 필요하다고 하면 3개 모두 0.66 CPU씩 받습니다.

 

 

최대 제한 자원 만들기

이전에는 최소 요구이지만, 이번에는 최대 제한을 만드는 것입니다. 간단하게 limits를 추가하면 됩니다.

 

kuard-pod-reslim.yaml

apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containers:
    - image: gcr.io/kuar-demo/kuard-amd64:blue
      name: kuard
      resources:
        requests:
          cpu: "500m"
          memory: "128Mi"
        limits:
          cpu: "1000m"
          memory: "256Mi"
      ports:
        - containerPort: 8080
          name: http
          protocol: TCP

 

 

 

자원 사용량 보기

먼저 노드 리스트를 확인합니다.

kubectl get nodes
더보기

NAME                 STATUS   ROLES           AGE   VERSION
kind-control-plane   Ready    control-plane   96m   v1.27.1

 

필자는 kind를 이용해 도커에서 쿠버네티스를 구동하고 있으므로 kind-control-plane 이 노드가 존재합니다.

아래 명령어로 노드 정보를 보면

kubectl describe nodes kind-control-plane
더보기

...

ProviderID:                   kind://docker/kind/kind-control-plane
Non-terminated Pods:          (10 in total)
  Namespace                   Name                                          CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                                          ------------  ----------  ---------------  -------------  ---
  default                     kuard                                         500m (4%)     1 (8%)      128Mi (0%)       256Mi (0%)     119s
  kube-system                 coredns-5d78c9869d-7fn4h                      100m (0%)     0 (0%)      70Mi (0%)        170Mi (0%)     96m
  kube-system                 coredns-5d78c9869d-smxph                      100m (0%)     0 (0%)      70Mi (0%)        170Mi (0%)     96m
  kube-system                 etcd-kind-control-plane                       100m (0%)     0 (0%)      100Mi (0%)       0 (0%)         96m
  kube-system                 kindnet-wgzns                                 100m (0%)     100m (0%)   50Mi (0%)        50Mi (0%)      96m
  kube-system                 kube-apiserver-kind-control-plane             250m (2%)     0 (0%)      0 (0%)           0 (0%)         96m
  kube-system                 kube-controller-manager-kind-control-plane    200m (1%)     0 (0%)      0 (0%)           0 (0%)         96m
  kube-system                 kube-proxy-gftn8                              0 (0%)        0 (0%)      0 (0%)           0 (0%)         96m
  kube-system                 kube-scheduler-kind-control-plane             100m (0%)     0 (0%)      0 (0%)           0 (0%)         96m
  local-path-storage          local-path-provisioner-6bc4bddd6b-fwjv6       0 (0%)        0 (0%)      0 (0%)           0 (0%)         96m

...

 

요구 및 제한 자원 및 사용량을 볼 수 있습니다.

 

이 밖에 파일시스템 및 용량을 관리하는 것이 있긴 한데, 필자는 관심이 크게 없어서 문서로 대체합니다.

다만 volumeMounts.mountPath는 조금 중요한데, 컨테이너에서 생산하는 데이터가 저장되는 경로를 말합니다.

 

Volumes

On-disk files in a container are ephemeral, which presents some problems for non-trivial applications when running in containers. One problem occurs when a container crashes or is stopped. Container state is not saved so all of the files that were created

kubernetes.io