[쿠버네티스] 3. 워크로드 - 파드(2)

rin·2021년 3월 17일
0
post-thumbnail

https://kubernetes.io/ko/docs/concepts/workloads/pods/disruptions/

중단(Disruption)

자발적 중단 vs. 비자발적 중단

파드는 다음과 같은 경우가 아니면 사라지지 않는다.

  • 누군가(사람 or 컨트롤러)가 파괴
  • 불가피한 하드웨어 오류 발생
  • 불가피한 시스템 소프트웨어 오류 발생

그렇담 "불가피한" 오류란 무엇을 의미하는 걸까? 🤔
아래에 그 예시가 몇 가지 있고, 이런 경우 비자발적 중단이 일어날 것이다.

  • 노드를 지원하는 물리 머신의 하드웨어 오류
  • 클러스터 관리자의 실수로 VM(인스턴스) 삭제
  • 클라우드 공급자 또는 하이퍼바이저의 오류로 인한 VM 장애
  • 커널 패닉
  • 클러스터 네트워크 파티션의 발생으로 클러스터에서 노드가 사라짐
  • 노드의 리소스 부족으로 파드가 축출됨

자발적 중단은 다음과 같다.

  • 어플리케이션 소유자의 작업
    • deployment 제거 혹은 다른 pod를 관리하는 controller의 제거
    • 재시작을 유발하는 deployment의 pod template 업데이트
    • pod를 직접 삭제
  • 클러스터 관리자의 작업 (관리자가 직접 수행 or 자동화를 통한 수행 or 클러스터 호스팅 공급자에 의한 수행)
    • 복구 혹은 업그레이드를 위한 Draining a node
    • 클러스터의 스케일 축소를 위한 Draining a node
    • 노드에 다른 무언가를 추가하기 위해 pod를 제거

중단 다루기

비자발적 중단으로 인한 영향을 감소시키기 위한 몇 가지 방법을 먼저 살펴보자

  • 파드가 필요한 리소스를 요청하는지 확인한다.
  • 고가용성이 필요한 경우, 미리 어플리케이션을 복제한다.
  • 복제된 어플리케이션의 더 높은 가용성을 위해선 랙 전체 혹은 영역간 어플리케이션 분산을 적용하도록 한다.

Pod disruption budgets

쿠버네티스는 자발적 중단이 자주 발생하는 경우에도 고가용성 어플리케이션을 실행할 수 있도록 도움을 주고 있다.

어플리케이션 소유자는 각 어플리케이션에 대해 PodDisruptionBudget(PDB)를 만들 수 있다. 이는 자발적 중단으로인해 일시에 중지되는 (복제된) 어플리케이션 파드의 수를 제한한다. quorum 기반의 어플리케이션은 실행 중인 레플리카의 수가 quorum 이하로 떨어지지 않도록 한다. 웹 프런트엔드는 부하를 처리하는 레플리카의 수가 일정 비율 이하로 떨어지지 않도록 보장할 수 있다.

따라서 클러스터 관리자와 호스팅 공급자는 직접적으로 파드나 Deployment를 제거하는 대신 PDB를 준수하는 도구(Eviction API)를 사용해야한다.

kubectl drain 명령을 사용하면 노드를 서비스 중단으로 표시 할 수 있는데, 이 때 Eviction API는 사용자가 서비스를 중단시키려는 노드의 모든 파드를 제거하려고 한다. kubectl은 사용자를 대신해 제출되는 제거 요청을 거부 할 수 있으므로 Eviction API는 대상 노드의 모든 파드가 종료되거나 타임아웃 될 때까지 실패한 요청에 대해 주기적으로 재시도한다.

PDB는 어플리케이션이 필요로하는 레플리카 수에 상대적으로, 용인 가능한 레플리카 수를 지정한다. 예를 들어,.spec.replicas: 5를 갖는 Deployment는 어느 시점에든 5개의 파드를 유지해야한다. 해당 Deployment의 PDB가 특정 시점에 파드 4개를 허용한다면, Eviction API는 한 번에 1(spec-PDB)개의 파드에 대한 자발적 중단을 허용한다.

파드가 의도한 수량은 워크로드 리소스의 .spec.replicas를 기반으로 계산되며, 컨트롤 플레인은 파드의 .metadata.ownerReferences를 기반으로 소유하는 워크로드 리소스를 찾아낸다.

PDB는 비자발적 중단이 발생하는 것을 막을 수는 없지만, Budget은 차감된다.

어플리케이션의 롤링 업그레이드로 파드가 삭제되거나 사용 불가 상태가 되는 경우 중단 Budget에 영향을 주나, 워크로드 리소스(Deployment, StatefulSet 등)는 롤링 업데이트 시 PDB의 제한을 받지않는다. 단, 어플리케이션 업데이트 중 실패 처리는 특정 워크로드 리소스에 대한 명세가 정의한다.

Eviction API를 사용해 파드를 제거하는 경우, PodSpec의 termicationGracePeriodSeconds 설정을 준수하여 정상적으로 종료된 상태가 된다.

PodDisruptionBudget Example

현재 노드는 3개가 존재한다.

  • node-1
  • node-2
  • node-3

클러스터에서는 여러 어플리케이션을 실행하고 있는데, 이 중 하나는 3개의 레플리카를 가진다.

  • pod-a
  • pod-b
  • pod-c

pod-x라는 PDB와 무관한 파드도 존재한다.

초기에 파드는 다음처럼 배치된다.

node-1node-2node-3
pod-a availablepod-b availablepod-c available
pod-x available

PDB는 다음처럼 정의되어 있다.

  • Deployment의 일부분으로써 항상 3개의 파드 중 최소 2개 파드는 사용가능해야한다.

클러스터 관리자가 어떤 작업을 위해 node-1kubectl drain 명령을 사용해 비우려고 한다. 이에 따라 kubectlpod-apod-x를 제거하려 할 것이다.

이는 즉시 성공할 것이고, 두 파드는 동시에 terminating상태로 진입한다.

node-1 drainingnode-2node-3
pod-a terminatingpod-b availablepod-c available
pod-x terminating

Deployment는 (a, b, c) 세 개의 파드 중 하나가 중지(pod-a)됨을 알게되고 pod-d라는 대체 파드를 생성하나 node-1이 차단되어 있기에 다른 노드에 위치시킨다.

pod-x의 대체 파드인 pod-y 또한 생성된다.

node-1 drainingnode-2node-3
pod-a terminatingpod-b availablepod-c available
pod-x terminatingpod-d startingpod-y

pod-apod-x가 완전히 종료되는 순간 클러스터는 다음 상태가 된다.

node-1 drainednode-2node-3
 pod-b availablepod-c available
 pod-d startingpod-y

pod-d가 아직 available한 상태가 아닌 경우 node-2 혹은 node-3를 비우려고하면 Deployment에 available한 파드가 2개이기 때문에 PDB에 의해 drain 명령은 차단된다. (PDB는 최소 2개의 pod를 유지하기로 했기 때문이다.)

이어서 pod-d가 available 상태가 되었다고 치자.

node-1 drainednode-2node-3
 pod-b availablepod-c available
 pod-d availablepod-y

클러스터 관리자는 node-1에 이어 node-2도 비우고자 한다.
kubectl drain 명령에 의해 pod-bpod-d를 제거하려하나 (둘 중 어떤 순서에 의해서든) 하나가 제거(pod-b가 제거되었다고 치자.)되면 pod는 2개가 남고, 남은 하나(pod-d)를 더 제거하려하는 순간 이는 거부된다.
따라서 Deployment는 pod-d를 대체한 pod-e를 생성한다.

클러스터에 pod-e를 스케줄하기 위한 충분한 리소스가 없으므로 braining 명령은 차단된다. 따라서 최종적으로 클러스터는 다음과 같은 상태로 끝나게 된다.

node-1 drainednode-2node-3no node
 pod-b terminatingpod-c availablepod-e pending
 pod-d availablepod-y

이 시점에서 클러스터 관리자는 노드를 추가해야(pod-e를 available 상태로 만들어야한다.) 원하는 작업을 이어갈 수 있을 것이다.

쿠버네티스에 중단이 발생할 수 있는 비율을 변화시키는 요소는 다음과 같은 것들이 있다.

  • 어플리케이션에 필요한 레플리카의 수
  • 인스턴스를 정상적으로 종료하는데 소요되는 시간
  • 새 인스턴스를 시작하는데 소요되는 시간
  • 컨트롤러의 유형
  • 클러스터의 리소스 용량

클러스터 매니저와 어플리케이션 소유자의 역할 분리

둘은 서로에 대한 지식이 부족한 별도 역할로 생각하는 것이 유용하다. 그 이유는 다음과 같은 경우에 타당하다.

  1. 쿠버네티스 클러스터를 공유하는 어플리케이션 팀이 많고, 자연스럽게 역할이 나눠진 경우
  2. 타사 도구 또는 타사 서비스를 이용해서 클러스터 관리를 자동화하는 경우

PDB는 역할 분리에 따라 알맞는 인터페이스를 제공한다.
만약 조직에 역할 분리에 따른 책임의 분리가 없다면 PDB를 사용할 필요가 없다.

클러스터에서 중단이 발생할 수 있는 작업을 (억지로) 하는 방법

클러스터 전체 노드에 대해 노드 혹은 시스템 소프트웨어 업그레이드로 인한 중단이 발생할 수 있는 작업을 수행하는 경우 다음과 같은 옵션을 선택한다.

  • 업그레이드 하는 동안 다운타임을 허용한다.
  • 다른 레플리카 클러스터로 장애조치를 한다.
    • 다운타임은 없지만, 노드 사본과 전환 작업을 조정하기 위한 인력 비용이 많이 발생할 수 있다.
  • PDB를 이용해 어플리케이션의 중단에 견디도록 작성한다.
    • 다운타임 없음
    • 최소한의 리소스 중복
    • 클러스터 관리의 자동화 확대 적용
    • 내결함성이 있는 어플리케이션의 작성을 까다로우나, 자발적 중단을 허용하는 작업의 대부분은 오토스케일링과 비자발적 중단을 지원하는 작업과 겹친다.

임시(Ephemeral) 컨테이너

이 컨테이너 유형은 트러블 슈팅 등과 같은 작업을 위해 기존 파드에서 임시적으로 실행된다.
사용자는 어플리케이션 빌드보다는 서비스 점검 시에 임시 컨테이너를 사용한다.

❗️ 주의
임시 컨테이너는 초기의 alpha 상태이며, Production 컨테이너에는 적합하지 않다. 쿠버네티스 사용 중단 정책에 따라 이 alpha 기능은 향후 크게 변경되거나 완전히 제거될 가능성이 있다.

임시컨테이너란?

파드는 일회용이고, 교체 가능한 것으로 의도되었으므로 사용자는 파드가 한 번 생성된 이후에는 컨테이너를 추가할 수 없다. 대신, 사용자는 보통 Deployment를 사용해 제어하는 방식으로 파드를 삭제하고 교체한다.

그러나 때때로 재현하기 어려운 버그의 문제 해결을 위해 기존 파드의 상태를 검사해야 할 수 있다. 이 경우 사용자는 기존 파드에서 임시컨테이너를 실행해 상태를 검사하고, 임의의 명령을 실행할 수 있다.

✔️ 임시 컨테이너는 리소스/실행에 대한 보증이 없다는 점에서 실 컨테이너와 다르며, 결코 자동으로 재시작되지 않는다. 그래서 (당연히) 어플리케이션을 만드는데 적합하지 않다.

임시 컨테이너 또한 일반 컨테이너와 동일하게 ContainerSpec으로 구성하지만, 많은 필드가 호환되지 않으며 허용되지 않는다.

  • 포트를 가지지 않을 수 있으므로, ports, livenessProbe, readinessProbe와 같은 필드가 허용되지 않음
  • 파드에 할당된 리소스는 변경 불가하므로 resources 설정이 허용되지 않음
  • 허용되는 필드의 전체 목록은 여기을 참조한다.

또한, pod.spec에 직접 추가하는 대신 특별한 API인 ephemeralcontainers 핸들러를 사용해 만들어지므로 kubectl edit을 사용해 추가할 수 없다.

일반 컨테이너와 동일하게 사용자는 임시 컨테이너를 파드에 추가한 이후에 변경 혹은 제거 할 수 없다.

임시 컨테이너의 사용

컨테이너가 충돌되거나 이미 배포된 컨테이너에 디버깅 도구가 포함되지 않은 이유로 kubectl exec이 불충분할 때 문제 해결에 유용하다.

예를들어, distroless 이미지를 사용해 attack surface, 버그 및 취약점 노출을 줄이는 최소한의 컨테이너 이미지 배포가 가능하나 해당 이미지는 셸이나 다른 디버깅 도구를 포함하지 않으므로 kubectl exec 만으로 문제 해결이 어려울 때도 사용 가능하다.

임시 컨테이너를 사용 시 프로세스 네임스페이스 공유를 활성화하여 다른 컨테이너 내부의 프로세스를 확인할 수도 있다.

임시 컨테이너 API

일반적으로 API를 직접 호출하지 않고 kubectl debug 이나 다른 kubectl 플러그인을 사용해 이런 단계들을 자동화한다.

임시 컨테이너는 파드의 ephemeralcontainers 하위 리소스를 사용해 생성되며, kubectl --raw를 사용해서 볼 수 있다.

{
    "apiVersion": "v1",
    "kind": "EphemeralContainers",
    "metadata": {
        "name": "example-pod"
    },
    "ephemeralContainers": [{
        "command": [
            "sh"
        ],
        "image": "busybox",
        "imagePullPolicy": "IfNotPresent",
        "name": "debugger",
        "stdin": true,
        "tty": true,
        "terminationMessagePolicy": "File"
    }]
}

이미 실행중인 example-pod에 임시 컨테이너를 업데이트 한다.

> kubectl replace --raw /api/v1/namespaces/default/pods/example-pod/ephemeralcontainers  -f ec.json

# 반환된 새로운 임시컨테이너 목록
{
   "kind":"EphemeralContainers",
   "apiVersion":"v1",
   "metadata":{
      "name":"example-pod",
      "namespace":"default",
      "selfLink":"/api/v1/namespaces/default/pods/example-pod/ephemeralcontainers",
      "uid":"a14a6d9b-62f2-4119-9d8e-e2ed6bc3a47c",
      "resourceVersion":"15886",
      "creationTimestamp":"2019-08-29T06:41:42Z"
   },
   "ephemeralContainers":[
      {
         "name":"debugger",
         "image":"busybox",
         "command":[
            "sh"
         ],
         "resources":{

         },
         "terminationMessagePolicy":"File",
         "imagePullPolicy":"IfNotPresent",
         "stdin":true,
         "tty":true
      }
   ]
}

kubectl describe를 사용해 임시 컨테이너의 상태를 볼 수 있다.

> kubectl describe pod example-pod

...
Ephemeral Containers:
  debugger:
    Container ID:  docker://cf81908f149e7e9213d3c3644eda55c72efaff67652a2685c1146f0ce151e80f
    Image:         busybox
    Image ID:      docker-pullable://busybox@sha256:9f1003c480699be56815db0f8146ad2e22efea85129b5b5983d0e0fb52d9ab70
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
    State:          Running
      Started:      Thu, 29 Aug 2019 06:42:21 +0000
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:         <none>
...

kubectl attach, kubectl exec, kubectl logs 를 사용해서 다른 컨테이너와 같은 방식으로 임시 컨테이너와 상호작용할 수 있다.

> kubectl attach -it example-pod -c debugger
profile
🌱 😈💻 🌱

0개의 댓글