This the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

파드

파드(Pod) 는 쿠버네티스에서 생성하고 관리할 수 있는 배포 가능한 가장 작은 컴퓨팅 단위이다.

파드 (고래 떼(pod of whales)나 콩꼬투리(pea pod)와 마찬가지로)는 하나 이상의 컨테이너의 그룹이다. 이 그룹은 스토리지 및 네트워크를 공유하고, 해당 컨테이너를 구동하는 방식에 대한 명세를 갖는다. 파드의 콘텐츠는 항상 함께 배치되고, 함께 스케줄되며, 공유 콘텍스트에서 실행된다. 파드는 애플리케이션 별 "논리 호스트"를 모델링한다. 여기에는 상대적으로 밀접하게 결합된 하나 이상의 애플리케이션 컨테이너가 포함된다. 클라우드가 아닌 콘텍스트에서, 동일한 물리 또는 가상 머신에서 실행되는 애플리케이션은 동일한 논리 호스트에서 실행되는 클라우드 애플리케이션과 비슷하다.

애플리케이션 컨테이너와 마찬가지로, 파드에는 파드 시작 중에 실행되는 초기화 컨테이너가 포함될 수 있다. 클러스터가 제공하는 경우, 디버깅을 위해 임시 컨테이너를 삽입할 수도 있다.

파드란 무엇인가?

참고: 도커가 가장 일반적으로 잘 알려진 런타임이지만, 쿠버네티스는 도커보다 컨테이너 런타임을 더 많이 지원하며, 도커의 일부 용어를 사용하면 파드를 설명하는 데 도움이 된다.

파드의 공유 콘텍스트는 리눅스 네임스페이스, 컨트롤 그룹(cgroup) 및 도커 컨테이너를 격리하는 것과 같이 잠재적으로 다른 격리 요소들이다. 파드의 콘텍스트 내에서 개별 애플리케이션은 추가적으로 하위 격리가 적용된다.

도커 개념 측면에서, 파드는 공유 네임스페이스와 공유 파일시스템 볼륨이 있는 도커 컨테이너 그룹과 비슷하다.

파드의 사용

일반적으로 싱글톤(singleton) 파드를 포함하여 파드를 직접 만들 필요가 없다. 대신, 디플로이먼트(Deployment) 또는 잡(Job)과 같은 워크로드 리소스를 사용하여 생성한다. 파드가 상태를 추적해야 한다면, 스테이트풀셋(StatefulSet) 리소스를 고려한다.

쿠버네티스 클러스터의 파드는 두 가지 주요 방식으로 사용된다.

  • 단일 컨테이너를 실행하는 파드. "파드 당 하나의 컨테이너" 모델은 가장 일반적인 쿠버네티스 유스케이스이다. 이 경우, 파드를 단일 컨테이너를 둘러싼 래퍼(wrapper)로 생각할 수 있다. 쿠버네티스는 컨테이너를 직접 관리하는 대신 파드를 관리한다.

  • 함께 작동해야 하는 여러 컨테이너를 실행하는 파드. 파드는 밀접하게 결합되어 있고 리소스를 공유해야 하는 함께 배치된 여러 개의 컨테이너로 구성된 애플리케이션을 캡슐화할 수 있다. 이런 함께 배치된 컨테이너는 하나의 결합된 서비스 단위를 형성한다. 예를 들어, 하나의 컨테이너는 공유 볼륨에 저장된 데이터를 퍼블릭에 제공하는 반면, 별도의 사이드카 컨테이너는 해당 파일을 새로 고치거나 업데이트한다. 파드는 이러한 컨테이너, 스토리지 리소스, 임시 네트워크 ID를 단일 단위로 함께 래핑한다.

    참고: 단일 파드에서 함께 배치된 또는 함께 관리되는 여러 컨테이너를 그룹화하는 것은 비교적 고급 유스케이스이다. 이 패턴은 컨테이너가 밀접하게 결합된 특정 인스턴스에서만 사용해야 한다.

각 파드는 특정 애플리케이션의 단일 인스턴스를 실행하기 위한 것이다. 더 많은 인스턴스를 실행하여 더 많은 전체 리소스를 제공하기 위해 애플리케이션을 수평적으로 확장하려면, 각 인스턴스에 하나씩, 여러 파드를 사용해야 한다. 쿠버네티스에서는 이를 일반적으로 레플리케이션 이라고 한다. 복제된 파드는 일반적으로 워크로드 리소스와 해당 컨트롤러에 의해 그룹으로 생성되고 관리된다.

쿠버네티스가 워크로드 리소스와 해당 컨트롤러를 사용하여 애플리케이션 스케일링과 자동 복구를 구현하는 방법에 대한 자세한 내용은 파드와 컨트롤러를 참고한다.

파드가 여러 컨테이너를 관리하는 방법

파드는 응집력있는 서비스 단위를 형성하는 여러 협력 프로세스(컨테이너)를 지원하도록 설계되었다. 파드의 컨테이너는 클러스터의 동일한 물리 또는 가상 머신에서 자동으로 같은 위치에 배치되고 함께 스케줄된다. 컨테이너는 리소스와 의존성을 공유하고, 서로 통신하고, 종료 시기와 방법을 조정할 수 있다.

예를 들어, 다음 다이어그램에서와 같이 공유 볼륨의 파일에 대한 웹 서버 역할을 하는 컨테이너와, 원격 소스에서 해당 파일을 업데이트하는 별도의 "사이드카" 컨테이너가 있을 수 있다.

예제 파드 다이어그램

일부 파드에는 앱 컨테이너 뿐만 아니라 초기화 컨테이너를 갖고 있다. 초기화 컨테이너는 앱 컨테이너가 시작되기 전에 실행되고 완료된다.

파드는 기본적으로 파드에 속한 컨테이너에 네트워킹스토리지라는 두 가지 종류의 공유 ​​리소스를 제공한다.

파드 작업

사용자가 쿠버네티스에서 직접 개별 파드를 만드는 경우는 거의 없다. 싱글톤 파드도 마찬가지이다. 이는 파드가 상대적으로 일시적인, 일회용 엔티티로 설계되었기 때문이다. 파드가 생성될 때(사용자가 직접 또는 컨트롤러가 간접적으로), 새 파드는 클러스터의 노드에서 실행되도록 스케줄된다. 파드는 파드 실행이 완료되거나, 파드 오브젝트가 삭제되거나, 리소스 부족으로 인해 파드가 축출 되거나, 노드가 실패할 때까지 해당 노드에 남아있다.

참고: 파드에서 컨테이너를 다시 시작하는 것과 파드를 다시 시작하는 것을 혼동해서는 안된다. 파드는 프로세스가 아니라 컨테이너를 실행하기 위한 환경이다. 파드는 삭제될 때까지 유지된다.

파드 오브젝트에 대한 매니페스트를 만들 때, 지정된 이름이 유효한 DNS 서브도메인 이름인지 확인한다.

파드와 컨트롤러

워크로드 리소스를 사용하여 여러 파드를 만들고 관리할 수 있다. 리소스에 대한 컨트롤러는 파드 장애 시 복제 및 롤아웃과 자동 복구를 처리한다. 예를 들어, 노드가 실패하면, 컨트롤러는 해당 노드의 파드가 작동을 중지했음을 인식하고 대체 파드를 생성한다. 스케줄러는 대체 파드를 정상 노드에 배치한다.

다음은 하나 이상의 파드를 관리하는 워크로드 리소스의 몇 가지 예시이다.

파드 템플릿

워크로드 리소스에 대한 컨트롤러는 파드 템플릿 에서 파드를 생성하고 사용자 대신 해당 파드를 관리한다.

파드템플릿(PodTemplate)은 파드를 생성하기 위한 명세이며, 디플로이먼트, 데몬셋과 같은 워크로드 리소스에 포함된다.

워크로드 리소스의 각 컨트롤러는 워크로드 오브젝트 내부의 PodTemplate 을 사용하여 실제 파드를 생성한다. PodTemplate 은 앱을 실행하는 데 사용되는 워크로드 리소스가 무엇이든지 원하는 상태의 일부이다.

아래 샘플은 하나의 컨테이너를 시작하는 template 이 있는 간단한 잡의 매니페스트이다. 해당 파드의 컨테이너는 메시지를 출력한 다음 일시 중지한다.

apiVersion: batch/v1
kind: Job
metadata:
  name: hello
spec:
  template:
    # 여기서부터 파드 템플릿이다
    spec:
      containers:
      - name: hello
        image: busybox
        command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
      restartPolicy: OnFailure
    # 여기까지 파드 템플릿이다

파드 템플릿을 수정하거나 새로운 파드 템플릿으로 바꿔도 이미 존재하는 파드에는 직접적인 영향을 주지 않는다. 워크로드 리소스의 파드 템플릿을 변경하는 경우, 해당 리소스는 수정된 템플릿을 사용하는 대체 파드를 생성해야 한다.

예를 들어, 스테이트풀셋 컨트롤러는 실행 중인 파드가 각 스테이트풀셋 오브젝트에 대한 현재 파드 템플릿과 일치하는지 확인한다. 스테이트풀셋을 수정하여 파드 템플릿을 변경하면, 스테이트풀셋이 업데이트된 템플릿을 기반으로 새로운 파드를 생성하기 시작한다. 결국, 모든 이전의 파드가 새로운 파드로 교체되고, 업데이트가 완료된다.

각 워크로드 리소스는 파드 템플릿의 변경 사항을 처리하기 위한 자체 규칙을 구현한다. 스테이트풀셋에 대해 자세히 알아 보려면, 스테이트풀셋 기본 튜토리얼에서 업데이트 전략을 읽어본다.

노드에서 kubelet은 파드 템플릿과 업데이트에 대한 상세 정보를 직접 관찰하거나 관리하지 않는다. 이러한 상세 내용은 추상화된다. 이러한 추상화와 관심사 분리(separation of concerns)는 시스템 시맨틱을 단순화하고, 기존 코드를 변경하지 않고도 클러스터의 동작을 확장할 수 있게 한다.

파드 갱신 및 교체

이전 섹션에서 언급한 바와 같이, 워크로드 리소스의 파드 템플릿이 바뀌면, 컨트롤러는 기존의 파드를 갱신하거나 패치하는 대신 갱신된 템플릿을 기반으로 신규 파드를 생성한다.

쿠버네티스는 사용자가 파드를 직접 관리하는 것을 막지는 않는다. 동작 중인 파드의 필드를 갱신하는 것도 가능하다. 그러나, patchreplace와 같은 파드 갱신 작업에는 다음과 같은 제약이 있다.

  • 파드에 대한 대부분의 메타데이터는 불변(immutable)이다. 예를 들면, 사용자는 namespace, name, uid, 또는 creationTimestamp 필드를 변경할 수 없다. 그리고 generation 필드는 고유하다. 이 필드는 필드의 현재 값을 증가시키는 갱신만 허용한다.

  • metadata.deletionTimestamp 가 설정된 경우, metadata.finalizers 리스트에 새로운 항목이 추가될 수 없다.

  • 파드 갱신은 spec.containers[*].image, spec.initContainers[*].image, spec.activeDeadlineSeconds, 또는 spec.tolerations 이외의 필드는 변경하지 않을 것이다. spec.tolerations 에 대해서만 새로운 항목을 추가할 수 있다.

  • spec.activeDeadlineSeconds 필드를 추가할 때는, 다음의 두 가지 형태의 갱신만 허용한다.

    1. 지정되지 않은 필드를 양수로 설정;
    2. 필드의 양수를 음수가 아닌 더 작은 숫자로 갱신.

리소스 공유와 통신

파드는 파드에 속한 컨테이너 간의 데이터 공유와 통신을 지원한다.

파드 스토리지

파드는 공유 스토리지 볼륨의 집합을 지정할 수 있다. 파드의 모든 컨테이너는 공유 볼륨에 접근할 수 있으므로, 해당 컨테이너가 데이터를 공유할 수 있다. 또한 볼륨은 내부 컨테이너 중 하나를 다시 시작해야 하는 경우 파드의 영구 데이터를 유지하도록 허용한다. 쿠버네티스가 공유 스토리지를 구현하고 파드에서 사용할 수 있도록 하는 방법에 대한 자세한 내용은 스토리지를 참고한다.

파드 네트워킹

각 파드에는 각 주소 패밀리에 대해 고유한 IP 주소가 할당된다. 파드의 모든 컨테이너는 IP 주소와 네트워크 포트를 포함하여 네트워크 네임스페이스를 공유한다. 파드 내부(그때 해당)에서, 파드에 속한 컨테이너는 localhost 를 사용하여 서로 통신할 수 있다. 파드의 컨테이너가 파드 외부의 엔티티와 통신할 때, 공유 네트워크 리소스(포트와 같은)를 사용하는 방법을 조정해야 한다. 파드 내에서 컨테이너는 IP 주소와 포트 공간을 공유하며, localhost 를 통해 서로를 찾을 수 있다. 파드의 컨테이너는 SystemV 세마포어 또는 POSIX 공유 메모리와 같은 표준 프로세스 간 통신을 사용하여 서로 통신할 수도 있다. 다른 파드의 컨테이너는 고유한 IP 주소를 가지며 특별한 구성 없이 IPC로 통신할 수 없다. 다른 파드에서 실행되는 컨테이너와 상호 작용하려는 컨테이너는 IP 네트워킹을 사용하여 통신할 수 있다.

파드 내의 컨테이너는 시스템 호스트명이 파드에 대해 구성된 name 과 동일한 것으로 간주한다. 네트워킹 섹션에 이에 대한 자세한 내용이 있다.

컨테이너에 대한 특권 모드

파드의 모든 컨테이너는 컨테이너 명세의 보안 콘텍스트에 있는 privileged 플래그를 사용하여 특권 모드를 활성화할 수 있다. 이는 네트워크 스택 조작이나 하드웨어 장치 접근과 같은 운영 체제 관리 기능을 사용하려는 컨테이너에 유용하다. 특권이 있는 컨테이너 내의 프로세스는 컨테이너 외부의 프로세스가 가지는 거의 동일한 권한을 가진다.

참고: 이 설정을 사용하려면 사용자의 컨테이너 런타임이 특권이 있는 컨테이너의 개념을 지원해야 한다.

정적 파드

정적 파드API 서버가 관찰하는 대신 특정 노드의 kubelet 데몬에 의해 직접 관리된다. 대부분의 파드는 컨트롤 플레인(예를 들어, 디플로이먼트)에 의해 관리되고, 정적 파드의 경우, kubelet이 각 정적 파드를 직접 감독한다(실패하면 다시 시작한다).

정적 파드는 항상 특정 노드의 Kubelet 하나에 바인딩된다. 정적 파드의 주요 용도는 자체 호스팅 컨트롤 플레인을 실행하는 것이다. 즉, kubelet을 사용하여 개별 컨트롤 플레인 컴포넌트를 감독한다.

kubelet은 자동으로 각 정적 파드에 대한 쿠버네티스 API 서버에서 미러 파드를 생성하려고 한다. 즉, 노드에서 실행되는 파드는 API 서버에서 보이지만, 여기에서 제어할 수는 없다는 의미이다.

다음 내용

쿠버네티스가 다른 리소스(스테이트풀셋이나 디플로이먼트와 같은)에서 공통 파드 API를 래핑하는 이유에 대한 콘텍스트를 이해하기 위해서, 다음과 같은 선행 기술에 대해 읽어볼 수 있다.

1 - 파드 라이프사이클

이 페이지에서는 파드의 라이프사이클을 설명한다. 파드는 정의된 라이프사이클을 따른다. Pending 단계에서 시작해서, 기본 컨테이너 중 적어도 하나 이상이 OK로 시작하면 Running 단계를 통과하고, 그런 다음 파드의 컨테이너가 실패로 종료되었는지 여부에 따라 Succeeded 또는 Failed 단계로 이동한다.

파드가 실행되는 동안, kubelet은 일종의 오류를 처리하기 위해 컨테이너를 다시 시작할 수 있다. 파드 내에서, 쿠버네티스는 다양한 컨테이너 상태를 추적하고 파드를 다시 정상 상태로 만들기 위해 취할 조치를 결정한다.

쿠버네티스 API에서 파드는 명세와 실제 상태를 모두 가진다. 파드 오브젝트의 상태는 일련의 파드 조건으로 구성된다. 사용자의 애플리케이션에 유용한 경우, 파드의 조건 데이터에 사용자 정의 준비성 정보를 삽입할 수도 있다.

파드는 파드의 수명 중 한 번만 스케줄된다. 파드가 노드에 스케줄(할당)되면, 파드는 중지되거나 종료될 때까지 해당 노드에서 실행된다.

파드의 수명

개별 애플리케이션 컨테이너와 마찬가지로, 파드는 비교적 임시(계속 이어지는 것이 아닌) 엔티티로 간주된다. 파드가 생성되고, 고유 ID(UID)가 할당되고, 종료(재시작 정책에 따라) 또는 삭제될 때까지 남아있는 노드에 스케줄된다. 만약 노드가 종료되면, 해당 노드에 스케줄된 파드는 타임아웃 기간 후에 삭제되도록 스케줄된다.

파드는 자체적으로 자가 치유되지 않는다. 파드가 노드에 스케줄된 후에 해당 노드가 실패하면, 파드는 삭제된다. 마찬가지로, 파드는 리소스 부족 또는 노드 유지 관리 작업으로 인해 축출되지 않는다. 쿠버네티스는 컨트롤러라 부르는 하이-레벨 추상화를 사용하여 상대적으로 일회용인 파드 인스턴스를 관리하는 작업을 처리한다.

UID로 정의된 특정 파드는 다른 노드로 절대 "다시 스케줄"되지 않는다. 대신, 해당 파드는 사용자가 원한다면 이름은 같지만, UID가 다른, 거의 동일한 새 파드로 대체될 수 있다.

볼륨과 같은 어떤 것이 파드와 동일한 수명을 갖는다는 것은, 특정 파드(정확한 UID 포함)가 존재하는 한 그것이 존재함을 의미한다. 어떤 이유로든 해당 파드가 삭제되고, 동일한 대체 파드가 생성되더라도, 관련된 그것(이 예에서는 볼륨)도 폐기되고 새로 생성된다.

Pod diagram

컨테이너 간의 공유 스토리지에 퍼시스턴트 볼륨을 사용하는 웹 서버와 파일 풀러(puller)가 포함된 다중 컨테이너 파드이다.

파드의 단계

파드의 status 필드는 phase 필드를 포함하는 PodStatus 오브젝트로 정의된다.

파드의 phase는 파드가 라이프사이클 중 어느 단계에 해당하는지 표현하는 간단한 고수준의 요약이다. Phase는 컨테이너나 파드의 관측 정보에 대한 포괄적인 롤업이나, 포괄적인 상태 머신을 표현하도록 의도되지는 않았다.

파드 phase 값에서 숫자와 의미는 엄격하게 지켜진다. 여기에 문서화된 내용 이외에는, 파드와 파드에 주어진 phase 값에 대해서 어떤 사항도 가정되어서는 안 된다.

phase에 가능한 값은 다음과 같다.

의미
Pending파드가 쿠버네티스 클러스터에서 승인되었지만, 하나 이상의 컨테이너가 설정되지 않았고 실행할 준비가 되지 않았다. 여기에는 파드가 스케줄되기 이전까지의 시간 뿐만 아니라 네트워크를 통한 컨테이너 이미지 다운로드 시간도 포함된다.
Running파드가 노드에 바인딩되었고, 모든 컨테이너가 생성되었다. 적어도 하나의 컨테이너가 아직 실행 중이거나, 시작 또는 재시작 중에 있다.
Succeeded파드에 있는 모든 컨테이너들이 성공적으로 종료되었고, 재시작되지 않을 것이다.
Failed파드에 있는 모든 컨테이너가 종료되었고, 적어도 하나 이상의 컨테이너가 실패로 종료되었다. 즉, 해당 컨테이너는 non-zero 상태로 빠져나왔거나(exited) 시스템에 의해서 종료(terminated)되었다.
Unknown어떤 이유에 의해서 파드의 상태를 얻을 수 없다. 이 단계는 일반적으로 파드가 실행되어야 하는 노드와의 통신 오류로 인해 발생한다.
참고: 파드가 삭제될 때, 일부 kubectl 커맨드에서 Terminating 이 표시된다. 이 Terminating 상태는 파드의 단계에 해당하지 않는다. 파드에는 그레이스풀하게(gracefully) 종료되도록 기간이 부여되며, 그 기본값은 30초이다. 강제로 파드를 종료하려면 --force 플래그를 설정하면 된다.

노드가 죽거나 클러스터의 나머지와의 연결이 끊어지면, 쿠버네티스는 손실된 노드의 모든 파드의 phase 를 Failed로 설정하는 정책을 적용한다.

컨테이너 상태

전체 파드의 단계뿐 아니라, 쿠버네티스는 파드 내부의 각 컨테이너 상태를 추적한다. 컨테이너 라이프사이클 훅(hook)을 사용하여 컨테이너 라이프사이클의 특정 지점에서 실행할 이벤트를 트리거할 수 있다.

일단 스케줄러가 노드에 파드를 할당하면, kubelet은 컨테이너 런타임을 사용하여 해당 파드에 대한 컨테이너 생성을 시작한다. 표시될 수 있는 세 가지 컨테이너 상태는 Waiting, Running 그리고 Terminated 이다.

파드의 컨테이너 상태를 확인하려면, kubectl describe pod <name-of-pod> 를 사용할 수 있다. 출력 결과는 해당 파드 내의 각 컨테이너 상태가 표시된다.

각 상태에는 특정한 의미가 있다.

Waiting

만약 컨테이너가 Running 또는 Terminated 상태가 아니면, Waiting 상태이다. Waiting 상태의 컨테이너는 시작을 완료하는 데 필요한 작업(예를 들어, 컨테이너 이미지 레지스트리에서 컨테이너 이미지 가져오거나, 시크릿(Secret) 데이터를 적용하는 작업)을 계속 실행하고 있는 중이다. kubectl 을 사용하여 컨테이너가 Waiting 인 파드를 쿼리하면, 컨테이너가 해당 상태에 있는 이유를 요약하는 Reason 필드도 표시된다.

Running

Running 상태는 컨테이너가 문제없이 실행되고 있음을 나타낸다. postStart 훅이 구성되어 있었다면, 이미 실행되고 완료되었다. kubectl 을 사용하여 컨테이너가 Running 인 파드를 쿼리하면, 컨테이너가 Running 상태에 진입한 시기에 대한 정보도 볼 수 있다.

Terminated

Terminated 상태의 컨테이너는 실행을 시작한 다음 완료될 때까지 실행되었거나 어떤 이유로 실패했다. kubectl 을 사용하여 컨테이너가 Terminated 인 파드를 쿼리하면, 이유와 종료 코드 그리고 해당 컨테이너의 실행 기간에 대한 시작과 종료 시간이 표시된다.

컨테이너에 구성된 preStop 훅이 있는 경우, 컨테이너가 Terminated 상태에 들어가기 전에 실행된다.

컨테이너 재시작 정책

파드의 spec 에는 restartPolicy 필드가 있다. 사용 가능한 값은 Always, OnFailure 그리고 Never이다. 기본값은 Always이다.

restartPolicy 는 파드의 모든 컨테이너에 적용된다. restartPolicy 는 동일한 노드에서 kubelet에 의한 컨테이너 재시작만을 의미한다. 파드의 컨테이너가 종료된 후, kubelet은 5분으로 제한되는 지수 백오프 지연(10초, 20초, 40초, …)으로 컨테이너를 재시작한다. 컨테이너가 10분 동안 아무런 문제없이 실행되면, kubelet은 해당 컨테이너의 재시작 백오프 타이머를 재설정한다.

파드의 조건

파드는 하나의 PodStatus를 가지며, 그것은 파드가 통과했거나 통과하지 못한 조건에 대한 PodConditions 배열을 가진다.

  • PodScheduled: 파드가 노드에 스케줄되었다.
  • ContainersReady: 파드의 모든 컨테이너가 준비되었다.
  • Initialized: 모든 초기화 컨테이너가 성공적으로 시작되었다.
  • Ready: 파드는 요청을 처리할 수 있으며 일치하는 모든 서비스의 로드 밸런싱 풀에 추가되어야 한다.
필드 이름설명
type이 파드 조건의 이름이다.
status가능한 값이 "True", "False", 또는 "Unknown"으로, 해당 조건이 적용 가능한지 여부를 나타낸다.
lastProbeTime파드 조건이 마지막으로 프로브된 시간의 타임스탬프이다.
lastTransitionTime파드가 한 상태에서 다른 상태로 전환된 마지막 시간에 대한 타임스탬프이다.
reason조건의 마지막 전환에 대한 이유를 나타내는 기계가 판독 가능한 UpperCamelCase 텍스트이다.
message마지막 상태 전환에 대한 세부 정보를 나타내는 사람이 읽을 수 있는 메시지이다.

파드의 준비성(readiness)

FEATURE STATE: Kubernetes v1.14 [stable]

애플리케이션은 추가 피드백 또는 신호를 PodStatus: Pod readiness 와 같이 주입할 수 있다. 이를 사용하기 위해, kubelet이 파드의 준비성을 평가하기 위한 추가적인 조건들을 파드의 specreadinessGate 필드를 통해서 지정할 수 있다.

준비성 게이트는 파드에 대한 status.condition 필드의 현재 상태에 따라 결정된다. 만약 쿠버네티스가 status.conditions 필드에서 해당하는 조건을 찾지 못한다면, 그 조건의 상태는 기본 값인 "False"가 된다.

여기 예제가 있다.

kind: Pod
...
spec:
  readinessGates:
    - conditionType: "www.example.com/feature-1"
status:
  conditions:
    - type: Ready                              # 내장된 PodCondition이다
      status: "False"
      lastProbeTime: null
      lastTransitionTime: 2018-01-01T00:00:00Z
    - type: "www.example.com/feature-1"        # 추가적인 PodCondition
      status: "False"
      lastProbeTime: null
      lastTransitionTime: 2018-01-01T00:00:00Z
  containerStatuses:
    - containerID: docker://abcd...
      ready: true
...

추가하는 파드 상태에는 쿠버네티스 레이블 키 포맷을 충족하는 이름이 있어야 한다.

파드 준비성 상태

kubectl patch 명령어는 아직 오브젝트 상태 패치(patching)를 지원하지 않는다. 이러한 status.conditions 을 파드에 설정하려면 애플리케이션과 오퍼레이터PATCH 액션을 필요로 한다. 쿠버네티스 클라이언트 라이브러리를 사용해서 파드 준비성에 대한 사용자 지정 파드 조건을 설정하는 코드를 작성할 수 있다.

사용자 지정 조건을 사용하는 파드의 경우, 다음 두 조건이 모두 적용되는 경우에 해당 파드가 준비된 것으로 평가된다.

  • 파드 내의 모든 컨테이너들이 준비 상태이다.
  • readinessGates에 지정된 모든 조건들이 True 이다.

파드의 컨테이너가 Ready 이나 적어도 한 개의 사용자 지정 조건이 빠졌거나 False 이면, kubelet은 파드의 조건ContainerReady 로 설정한다.

컨테이너 프로브(probe)

프로브는 컨테이너에서 kubelet에 의해 주기적으로 수행되는 진단(diagnostic)이다. 진단을 수행하기 위해서, kubelet은 컨테이너에 의해서 구현된 핸들러를 호출한다. 핸들러에는 다음과 같이 세 가지 타입이 있다.

  • ExecAction은 컨테이너 내에서 지정된 명령어를 실행한다. 명령어가 상태 코드 0으로 종료되면 진단이 성공한 것으로 간주한다.

  • TCPSocketAction은 지정된 포트에서 컨테이너의 IP주소에 대해 TCP 검사를 수행한다. 포트가 활성화되어 있다면 진단이 성공한 것으로 간주한다.

  • HTTPGetAction은 지정한 포트 및 경로에서 컨테이너의 IP주소에 대한 HTTP GET 요청을 수행한다. 응답의 상태 코드가 200 이상 400 미만이면 진단이 성공한 것으로 간주한다.

각 probe는 다음 세 가지 결과 중 하나를 가진다.

  • Success: 컨테이너가 진단을 통과함.
  • Failure: 컨테이너가 진단에 실패함.
  • Unknown: 진단 자체가 실패하였으므로 아무런 액션도 수행되면 안됨.

kubelet은 실행 중인 컨테이너들에 대해서 선택적으로 세 가지 종류의 프로브를 수행하고 그에 반응할 수 있다.

  • livenessProbe: 컨테이너가 동작 중인지 여부를 나타낸다. 만약 활성 프로브(liveness probe)에 실패한다면, kubelet은 컨테이너를 죽이고, 해당 컨테이너는 재시작 정책의 대상이 된다. 만약 컨테이너가 활성 프로브를 제공하지 않는 경우, 기본 상태는 Success이다.

  • readinessProbe: 컨테이너가 요청을 처리할 준비가 되었는지 여부를 나타낸다. 만약 준비성 프로브(readiness probe)가 실패한다면, 엔드포인트 컨트롤러는 파드에 연관된 모든 서비스들의 엔드포인트에서 파드의 IP주소를 제거한다. 준비성 프로브의 초기 지연 이전의 기본 상태는 Failure이다. 만약 컨테이너가 준비성 프로브를 지원하지 않는다면, 기본 상태는 Success이다.

  • startupProbe: 컨테이너 내의 애플리케이션이 시작되었는지를 나타낸다. 스타트업 프로브(startup probe)가 주어진 경우, 성공할 때까지 다른 나머지 프로브는 활성화되지 않는다. 만약 스타트업 프로브가 실패하면, kubelet이 컨테이너를 죽이고, 컨테이너는 재시작 정책에 따라 처리된다. 컨테이너에 스타트업 프로브가 없는 경우, 기본 상태는 Success이다.

활성, 준비성 및 스타트업 프로브를 설정하는 방법에 대한 추가적인 정보는, 활성, 준비성 및 스타트업 프로브 설정하기를 참조하면 된다.

언제 활성 프로브를 사용해야 하는가?

FEATURE STATE: Kubernetes v1.0 [stable]

만약 컨테이너 속 프로세스가 어떠한 이슈에 직면하거나 건강하지 못한 상태(unhealthy)가 되는 등 프로세스 자체의 문제로 중단될 수 있더라도, 활성 프로브가 반드시 필요한 것은 아니다. 그 경우에는 kubelet이 파드의 restartPolicy에 따라서 올바른 대처를 자동적으로 수행할 것이다.

프로브가 실패한 후 컨테이너가 종료되거나 재시작되길 원한다면, 활성 프로브를 지정하고, restartPolicy를 항상(Always) 또는 실패 시(OnFailure)로 지정한다.

언제 준비성 프로브를 사용해야 하는가?

FEATURE STATE: Kubernetes v1.0 [stable]

프로브가 성공한 경우에만 파드에 트래픽 전송을 시작하려고 한다면, 준비성 프로브를 지정하길 바란다. 이 경우에서는, 준비성 프로브가 활성 프로브와 유사해 보일 수도 있지만, 스팩에 준비성 프로브가 존재한다는 것은 파드가 트래픽을 받지 않는 상태에서 시작되고 프로브가 성공하기 시작한 이후에만 트래픽을 받는다는 뜻이다. 만약 컨테이너가 대량의 데이터, 설정 파일들, 또는 시동 중 마그레이션을 처리해야 한다면, 준비성 프로브를 지정하길 바란다.

만약 당신의 컨테이너가 유지 관리를 위해서 자체 중단되게 하려면, 준비성 프로브를 지정하길 바란다. 준비성 프로브는 활성 프로브와는 다르게 준비성에 특정된 엔드포인트를 확인한다.

참고: 파드가 삭제될 때 요청들을 흘려 보내기(drain) 위해 준비성 프로브가 꼭 필요한 것은 아니다. 삭제 시에, 파드는 프로브의 존재 여부와 무관하게 자동으로 스스로를 준비되지 않은 상태(unready)로 변경한다. 파드는 파드 내의 모든 컨테이너들이 중지될 때까지 준비되지 않은 상태로 남아 있다.

언제 스타트업 프로브를 사용해야 하는가?

FEATURE STATE: Kubernetes v1.20 [stable]

스타트업 프로브는 서비스를 시작하는 데 오랜 시간이 걸리는 컨테이너가 있는 파드에 유용하다. 긴 활성 간격을 설정하는 대신, 컨테이너가 시작될 때 프로브를 위한 별도의 구성을 설정하여, 활성 간격보다 긴 시간을 허용할 수 있다.

컨테이너가 보통 initialDelaySeconds + failureThreshold × periodSeconds 이후에 기동된다면, 스타트업 프로브가 활성화 프로브와 같은 엔드포인트를 확인하도록 지정해야 한다. periodSeconds의 기본값은 10s 이다. 이 때 컨테이너가 활성화 프로브의 기본값 변경 없이 기동되도록 하려면, failureThreshold 를 충분히 높게 설정해주어야 한다. 그래야 데드락(deadlocks)을 방지하는데 도움이 된다.

파드의 종료

파드는 클러스터의 노드에서 실행되는 프로세스를 나타내므로, 해당 프로세스가 더 이상 필요하지 않을 때 정상적으로 종료되도록 하는 것이 중요하다(KILL 시그널로 갑자기 중지되고 정리할 기회가 없는 것 보다).

디자인 목표는 삭제를 요청하고 프로세스가 종료되는 시기를 알 수 있을 뿐만 아니라, 삭제가 결국 완료되도록 하는 것이다. 사용자가 파드의 삭제를 요청하면, 클러스터는 파드가 강제로 종료되기 전에 의도한 유예 기간을 기록하고 추적한다. 강제 종료 추적이 적용되면, kubelet은 정상 종료를 시도한다.

일반적으로, 컨테이너 런타임은 각 컨테이너의 기본 프로세스에 TERM 신호를 전송한다. 많은 컨테이너 런타임은 컨테이너 이미지에 정의된 STOPSIGNAL 값을 존중하며 TERM 대신 이 값을 보낸다. 일단 유예 기간이 만료되면, KILL 시그널이 나머지 프로세스로 전송되고, 그런 다음 파드는 API 서버로부터 삭제된다. 프로세스가 종료될 때까지 기다리는 동안 kubelet 또는 컨테이너 런타임의 관리 서비스가 다시 시작되면, 클러스터는 전체 원래 유예 기간을 포함하여 처음부터 다시 시도한다.

플로우의 예는 다음과 같다.

  1. kubectl 도구를 사용하여 기본 유예 기간(30초)으로 특정 파드를 수동으로 삭제한다.
  2. API 서버의 파드는 유예 기간과 함께 파드가 "dead"로 간주되는 시간으로 업데이트된다. kubectl describe 를 사용하여 삭제하려는 파드를 확인하면, 해당 파드가 "Terminating"으로 표시된다. 파드가 실행 중인 노드에서, kubelet이 파드가 종료된 것(terminating)으로 표시되었음을 확인하는 즉시(정상적인 종료 기간이 설정됨), kubelet은 로컬 파드의 종료 프로세스를 시작한다.
    1. 파드의 컨테이너 중 하나가 preStop 을 정의한 경우, kubelet은 컨테이너 내부에서 해당 훅을 실행한다. 유예 기간이 만료된 후 preStop 훅이 계속 실행되면, kubelet은 2초의 작은 일회성 유예 기간 연장을 요청한다.
      참고: preStop 훅을 완료하는 데 기본 유예 기간이 허용하는 것보다 오랜 시간이 필요한 경우, 이에 맞게 terminationGracePeriodSeconds 를 수정해야 한다.
    2. kubelet은 컨테이너 런타임을 트리거하여 각 컨테이너 내부의 프로세스 1에 TERM 시그널을 보낸다.
      참고: 파드의 컨테이너는 서로 다른 시간에 임의의 순서로 TERM 시그널을 수신한다. 종료 순서가 중요한 경우, preStop 훅을 사용하여 동기화하는 것이 좋다.
  3. kubelet이 정상 종료를 시작하는 동시에, 컨트롤 플레인은 구성된 셀렉터가 있는 서비스를 나타내는 엔드포인트(Endpoint)(그리고, 활성화된 경우, 엔드포인트슬라이스(EndpointSlice)) 오브젝트에서 종료된 파드를 제거한다. 레플리카셋(ReplicaSet)과 기타 워크로드 리소스는 더 이상 종료된 파드를 유효한 서비스 내 복제본으로 취급하지 않는다. 로드 밸런서(서비스 프록시와 같은)가 종료 유예 기간이 시작되는 즉시 엔드포인트 목록에서 파드를 제거하므로 느리게 종료되는 파드는 트래픽을 계속 제공할 수 없다.
  4. 유예 기간이 만료되면, kubelet은 강제 종료를 트리거한다. 컨테이너 런타임은 SIGKILL 을 파드의 모든 컨테이너에서 여전히 실행 중인 모든 프로세스로 전송한다. kubelet은 해당 컨테이너 런타임이 하나를 사용하는 경우 숨겨진 pause 컨테이너도 정리한다.
  5. kubelet은 유예 기간을 0(즉시 삭제)으로 설정하여, API 서버에서 파드 오브젝트의 강제 삭제를 트리거한다.
  6. API 서버가 파드의 API 오브젝트를 삭제하면, 더 이상 클라이언트에서 볼 수 없다.

강제 파드 종료

주의: 강제 삭제는 일부 워크로드와 해당 파드에 대해서 잠재적으로 중단될 수 있다.

기본적으로, 모든 삭제는 30초 이내에는 정상적으로 수행된다. kubectl delete 명령은 기본값을 재정의하고 사용자의 고유한 값을 지정할 수 있는 --grace-period=<seconds> 옵션을 지원한다.

유예 기간을 0 로 강제로 즉시 설정하면 API 서버에서 파드가 삭제된다. 파드가 노드에서 계속 실행 중인 경우, 강제 삭제는 kubelet을 트리거하여 즉시 정리를 시작한다.

참고: 강제 삭제를 수행하려면 --grace-period=0 와 함께 추가 플래그 --force 를 지정해야 한다.

강제 삭제가 수행되면, API 서버는 실행 중인 노드에서 파드가 종료되었다는 kubelet의 확인을 기다리지 않는다. API에서 즉시 파드를 제거하므로 동일한 이름으로 새로운 파드를 생성할 수 있다. 노드에서 즉시 종료되도록 설정된 파드는 강제 종료되기 전에 작은 유예 기간이 계속 제공된다.

스테이트풀셋(StatefulSet)의 일부인 파드를 강제 삭제해야 하는 경우, 스테이트풀셋에서 파드를 삭제하기에 대한 태스크 문서를 참고한다.

실패한 파드의 가비지 콜렉션

실패한 파드의 경우, API 오브젝트는 사람이나 컨트롤러 프로세스가 명시적으로 파드를 제거할 때까지 클러스터의 API에 남아 있다.

컨트롤 플레인은 파드 수가 구성된 임계값(kube-controller-manager에서 terminated-pod-gc-threshold 에 의해 결정됨)을 초과할 때 종료된 파드(Succeeded 또는 Failed 단계 포함)를 정리한다. 이렇게 하면 시간이 지남에 따라 파드가 생성되고 종료될 때 리소스 유출이 방지된다.

다음 내용

2 - 초기화 컨테이너

이 페이지는 초기화 컨테이너에 대한 개요를 제공한다. 초기화 컨테이너는 파드의 앱 컨테이너들이 실행되기 전에 실행되는 특수한 컨테이너이며, 앱 이미지에는 없는 유틸리티 또는 설정 스크립트 등을 포함할 수 있다.

초기화 컨테이너는 containers 배열(앱 컨테이너를 기술하는)과 나란히 파드 스펙에 명시할 수 있다.

초기화 컨테이너 이해하기

파드는 앱들을 실행하는 다수의 컨테이너를 포함할 수 있고, 또한 앱 컨테이너 실행 전에 동작되는 하나 이상의 초기화 컨테이너도 포함할 수 있다.

다음의 경우를 제외하면, 초기화 컨테이너는 일반적인 컨테이너와 매우 유사하다.

  • 초기화 컨테이너는 항상 완료를 목표로 실행된다.
  • 각 초기화 컨테이너는 다음 초기화 컨테이너가 시작되기 전에 성공적으로 완료되어야 한다.

만약 파드의 초기화 컨테이너가 실패하면, kubelet은 초기화 컨테이너가 성공할 때까지 반복적으로 재시작한다. 그러나, 만약 파드의 restartPolicy 를 절대 하지 않음(Never)으로 설정하고, 해당 파드를 시작하는 동안 초기화 컨테이너가 실패하면, 쿠버네티스는 전체 파드를 실패한 것으로 처리한다.

컨테이너를 초기화 컨테이너로 지정하기 위해서는, 파드 스펙에 앱 containers 배열과 나란히 initContainers 필드를 컨테이너 타입 오브젝트들의 JSON 배열로서 추가한다. 초기화 컨테이너의 상태는 .status.initContainerStatuses 필드를 통해서 컨테이너 상태 배열로 반환된다 (.status.containerStatuses 필드와 유사하게).

일반적인 컨테이너와의 차이점

초기화 컨테이너는 앱 컨테이너의 리소스 상한(limit), 볼륨, 보안 세팅을 포함한 모든 필드와 기능을 지원한다. 그러나, 초기화 컨테이너를 위한 리소스 요청량과 상한은 리소스에 문서화된 것처럼 다르게 처리된다.

또한, 초기화 컨테이너는 lifecycle, livenessProbe, readinessProbe 또는 startupProbe 를 지원하지 않는다. 왜냐하면 초기화 컨테이너는 파드가 준비 상태가 되기 전에 완료를 목표로 실행되어야 하기 때문이다.

만약 다수의 초기화 컨테이너가 파드에 지정되어 있다면, kubelet은 해당 초기화 컨테이너들을 한 번에 하나씩 실행한다. 각 초기화 컨테이너는 다음 컨테이너를 실행하기 전에 꼭 성공해야 한다. 모든 초기화 컨테이너들이 실행 완료되었을 때, kubelet은 파드의 애플리케이션 컨테이너들을 초기화하고 평소와 같이 실행한다.

초기화 컨테이너 사용하기

초기화 컨테이너는 앱 컨테이너와는 별도의 이미지를 가지고 있기 때문에, 시동(start-up)에 관련된 코드로서 몇 가지 이점을 가진다.

  • 앱 이미지에는 없는 셋업을 위한 유틸리티 또는 맞춤 코드를 포함할 수 있다. 예를 들어, 셋업 중에 단지 sed, awk, python, 또는 dig와 같은 도구를 사용하기 위해서 다른 이미지로부터(FROM) 새로운 이미지를 만들 필요가 없다.
  • 애플리케이션 이미지 빌더와 디플로이어 역할은 독립적으로 동작될 수 있어서 공동의 단일 앱 이미지 형태로 빌드될 필요가 없다.
  • 초기화 컨테이너는 앱 컨테이너와 다른 파일 시스템 뷰를 가지도록 리눅스 네임스페이스를 사용한다. 결과적으로, 초기화 컨테이너에는 앱 컨테이너가 가질 수 없는 시크릿에 접근 권한이 주어질 수 있다.
  • 앱 컨테이너들은 병렬로 실행되는 반면, 초기화 컨테이너들은 어떠한 앱 컨테이너라도 시작되기 전에 실행 완료되어야 하므로, 초기화 컨테이너는 사전 조건들이 충족될 때까지 앱 컨테이너가 시동되는 것을 막거나 지연시키는 간편한 방법을 제공한다.
  • 초기화 컨테이너는 앱 컨테이너 이미지의 보안성을 떨어뜨릴 수도 있는 유틸리티 혹은 커스텀 코드를 안전하게 실행할 수 있다. 불필요한 툴들을 분리한 채로 유지함으로써 앱 컨테이너 이미지의 공격에 대한 노출을 제한할 수 있다.

예제

초기화 컨테이너를 사용하는 방법에 대한 몇 가지 아이디어는 다음과 같다.

  • 다음과 같은 셸 커맨드로, 서비스가 생성될 때까지 기다리기.

    for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; done; exit 1
    
  • 다음과 같은 커맨드로, 다운워드 API(Downward API)를 통한 원격 서버에 해당 파드를 등록하기.

    curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
    
  • 다음과 같은 커맨드로 앱 컨테이너가 시작되기 전에 일정 시간 기다리기.

    sleep 60
    
  • Git 저장소를 볼륨 안에 클론하기.

  • 설정 파일에 값을 지정하고 메인 앱 컨테이너를 위한 설정 파일을 동적으로 생성하기 위한 템플릿 도구를 실행하기. 예를 들어, 설정에 POD_IP 값을 지정하고 메인 앱 설정 파일을 Jinja를 통해서 생성.

사용 중인 초기화 컨테이너

쿠버네티스 1.5에 대한 다음의 yaml 파일은 두 개의 초기화 컨테이너를 포함한 간단한 파드에 대한 개요를 보여준다. 첫 번째는 myservice 를 기다리고 두 번째는 mydb 를 기다린다. 두 컨테이너들이 완료되면, 파드가 시작될 것이다.

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
  - name: init-mydb
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]

다음 커맨드들을 이용하여 파드를 시작하거나 디버깅할 수 있다.

kubectl apply -f myapp.yaml

출력 결과는 다음과 같다.

pod/myapp-pod created

그리고 파드의 상태를 확인한다.

kubectl get -f myapp.yaml

출력 결과는 다음과 같다.

NAME        READY     STATUS     RESTARTS   AGE
myapp-pod   0/1       Init:0/2   0          6m

혹은 좀 더 자세히 살펴본다.

kubectl describe -f myapp.yaml

출력 결과는 다음과 같다.

Name:          myapp-pod
Namespace:     default
[...]
Labels:        app=myapp
Status:        Pending
[...]
Init Containers:
  init-myservice:
[...]
    State:         Running
[...]
  init-mydb:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Containers:
  myapp-container:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Events:
  FirstSeen    LastSeen    Count    From                      SubObjectPath                           Type          Reason        Message
  ---------    --------    -----    ----                      -------------                           --------      ------        -------
  16s          16s         1        {default-scheduler }                                              Normal        Scheduled     Successfully assigned myapp-pod to 172.17.4.201
  16s          16s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulling       pulling image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulled        Successfully pulled image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Created       Created container with docker id 5ced34a04634; Security:[seccomp=unconfined]
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Started       Started container with docker id 5ced34a04634

파드의 초기화 컨테이너의 상태를 보기 위해, 다음을 실행한다.

kubectl logs myapp-pod -c init-myservice # Inspect the first init container
kubectl logs myapp-pod -c init-mydb      # Inspect the second init container

mydbmyservice 서비스를 시작하고 나면, 초기화 컨테이너가 완료되고 myapp-pod 가 생성된 것을 볼 수 있다.

여기에 이 서비스를 보이기 위해 사용할 수 있는 구성이 있다.

---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
---
apiVersion: v1
kind: Service
metadata:
  name: mydb
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9377

mydbmyservice 서비스 생성하기.

kubectl apply -f services.yaml

출력 결과는 다음과 같다.

service/myservice created
service/mydb created

초기화 컨테이너들이 완료되는 것과 myapp-pod 파드가 Running 상태로 변경되는 것을 볼 것이다.

kubectl get -f myapp.yaml

출력 결과는 다음과 같다.

NAME        READY     STATUS    RESTARTS   AGE
myapp-pod   1/1       Running   0          9m

이 간단한 예제는 사용자만의 초기화 컨테이너를 생성하는데 영감을 줄 것이다. 다음 순서에는 더 자세한 예제의 링크가 있다.

자세한 동작

파드 시작 시에 kubelet은 네트워크와 스토리지가 준비될 때까지 초기화 컨테이너의 실행을 지연시킨다. 그런 다음 kubelet은 파드 사양에 나와있는 순서대로 파드의 초기화 컨테이너를 실행한다.

각 초기화 컨테이너는 다음 컨테이너가 시작되기 전에 성공적으로 종료되어야 한다. 만약 런타임 문제나 실패 상태로 종료되는 문제로인하여 초기화 컨테이너의 시작이 실패된다면, 초기화 컨테이너는 파드의 restartPolicy 에 따라서 재시도 된다. 다만, 파드의 restartPolicy 가 항상(Always)으로 설정된 경우, 해당 초기화 컨테이너는 restartPolicy 를 실패 시(OnFailure)로 사용한다.

파드는 모든 초기화 컨테이너가 성공되기 전까지 Ready 될 수 없다. 초기화 컨테이너의 포트는 서비스 하에 합쳐지지 않는다. 초기화 중인 파드는 Pending 상태이지만 Initialized 가 거짓이 되는 조건을 가져야 한다.

만약 파드가 재시작되었다면, 모든 초기화 컨테이너는 반드시 다시 실행된다.

초기화 컨테이너 스펙 변경은 컨테이너 이미지 필드에서만 한정적으로 가능하다. 초기화 컨테이너 이미지 필드를 변경하는 것은 파드를 재시작하는 것과 같다.

초기화 컨테이너는 재시작되거나, 재시도, 또는 재실행 될 수 있기 때문에, 초기화 컨테이너 코드는 멱등성(idempotent)을 유지해야 한다. 특히, EmptyDirs 에 있는 파일에 쓰기를 수행하는 코드는 출력 파일이 이미 존재할 가능성에 대비해야 한다.

초기화 컨테이너는 앱 컨테이너의 필드를 모두 가지고 있다. 그러나, 쿠버네티스는 readinessProbe 가 사용되는 것을 금지한다. 초기화 컨테이너가 완료 상태와 준비성을 구분해서 정의할 수 없기 때문이다. 이것은 유효성 검사 중에 시행된다.

초기화 컨테이너들이 실패를 영원히 지속하는 상황을 방지하기 위해서 파드의 activeDeadlineSeconds 와 컨테이너의 livenessProbe 를 사용한다.

파드 내의 각 앱과 초기화 컨테이너의 이름은 유일해야 한다. 어떤 컨테이너가 다른 컨테이너와 같은 이름을 공유하는 경우 유효성 오류가 발생한다.

리소스

초기화 컨테이너에게 명령과 실행이 주어진 경우, 리소스 사용에 대한 다음의 규칙이 적용된다.

  • 모든 컨테이너에 정의된 특정 리소스 요청량 또는 상한 중 가장 높은 것은 유효한 초기화 요청량/상한 이다.
  • 리소스를 위한 파드의 유효한 초기화 요청량/상한 은 다음 보다 더 높다.
    • 모든 앱 컨테이너의 리소스에 대한 요청량/상한의 합계
    • 리소스에 대한 유효한 초기화 요청량/상한
  • 스케줄링은 유효한 요청/상한에 따라 이루어진다. 즉, 초기화 컨테이너는 파드의 삶에서는 사용되지 않는 초기화를 위한 리소스를 예약할 수 있다.
  • 파드의 유효한 QoS 계층 에서 QoS(서비스의 품질) 계층은 초기화 컨테이너들과 앱 컨테이너들의 QoS 계층과 같다.

쿼터 및 상한은 유효한 파드의 요청량 및 상한에 따라 적용된다.

파드 레벨 cgroup은 유효한 파드 요청량 및 상한을 기반으로 한다. 이는 스케줄러와 같다.

파드 재시작 이유

파드는 다음과 같은 사유로, 초기화 컨테이너들의 재-실행을 일으키는, 재시작을 수행할 수 있다.

  • 파드 인프라스트럭처 컨테이너가 재시작된 상황. 이는 일반적인 상황이 아니며 노드에 대해서 root 접근 권한을 가진 누군가에 의해서 수행됐을 것이다.
  • 초기화 컨테이너의 완료 기록이 가비지 수집 때문에 유실된 상태에서, restartPolicy가 Always로 설정된 파드의 모든 컨테이너가 종료되어 모든 컨테이너를 재시작해야 하는 상황

초기화 컨테이너 이미지가 변경되거나 초기화 컨테이너의 완료 기록이 가비지 수집 때문에 유실된 상태이면 파드는 재시작되지 않는다. 이는 쿠버네티스 버전 1.20 이상에 적용된다. 이전 버전의 쿠버네티스를 사용하는 경우 해당 쿠버네티스 버전의 문서를 참고한다.

다음 내용

3 - 파드 토폴로지 분배 제약 조건

FEATURE STATE: Kubernetes v1.19 [stable]

사용자는 토폴로지 분배 제약 조건 을 사용해서 지역, 영역, 노드 그리고 기타 사용자-정의 토폴로지 도메인과 같이 장애-도메인으로 설정된 클러스터에 걸쳐 파드가 분산되는 방식을 제어할 수 있다. 이를 통해 고가용성뿐만 아니라, 효율적인 리소스 활용의 목적을 이루는 데 도움이 된다.

참고: v1.19 이전 버전의 쿠버네티스에서는 파드 토폴로지 분배 제약조건을 사용하려면 API 서버스케줄러에서 EvenPodsSpread기능 게이트를 활성화해야 한다

필수 구성 요소

노드 레이블

토폴로지 분배 제약 조건은 노드 레이블을 의지해서 각 노드가 속한 토폴로지 도메인(들)을 인식한다. 예를 들어, 노드에 다음과 같은 레이블을 가지고 있을 수 있다. node=node1,zone=us-east-1a,region=us-east-1

다음 레이블이 있고, 4개 노드를 가지는 클러스터가 있다고 가정한다.

NAME    STATUS   ROLES    AGE     VERSION   LABELS
node1   Ready    <none>   4m26s   v1.16.0   node=node1,zone=zoneA
node2   Ready    <none>   3m58s   v1.16.0   node=node2,zone=zoneA
node3   Ready    <none>   3m17s   v1.16.0   node=node3,zone=zoneB
node4   Ready    <none>   2m43s   v1.16.0   node=node4,zone=zoneB

그러면 클러스터는 논리적으로 다음과 같이 보이게 된다.

graph TB subgraph "zoneB" n3(Node3) n4(Node4) end subgraph "zoneA" n1(Node1) n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4 k8s; class zoneA,zoneB cluster;

레이블을 수동으로 적용하는 대신에, 사용자는 대부분의 클러스터에서 자동으로 생성되고 채워지는 잘-알려진 레이블을 재사용할 수 있다.

파드의 분배 제약 조건

API

API 필드 pod.spec.topologySpreadConstraints 는 다음과 같이 정의된다.

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  topologySpreadConstraints:
    - maxSkew: <integer>
      topologyKey: <string>
      whenUnsatisfiable: <string>
      labelSelector: <object>

사용자는 하나 또는 다중 topologySpreadConstraint 를 정의해서 kube-scheduler 에게 클러스터에 걸쳐 있는 기존 파드와 시작하는 각각의 파드와 연관하여 배치하는 방법을 명령할 수 있다. 필드는 다음과 같다.

  • maxSkew 는 파드가 균등하지 않게 분산될 수 있는 정도를 나타낸다. 이것은 주어진 토폴로지 유형의 임의의 두 토폴로지 도메인에 일치하는 파드의 수 사이에서 허용되는 차이의 최댓값이다. 이것은 0보다는 커야 한다. 그 의미는 whenUnsatisfiable 의 값에 따라 다르다.
    • whenUnsatisfiable 이 "DoNotSchedule"과 같을 때, maxSkew 는 대상 토폴로지에서 일치하는 파드 수와 전역 최솟값 사이에 허용되는 최대 차이이다.
    • whenUnsatisfiable 이 "ScheduleAnyway"와 같으면, 스케줄러는 왜곡을 줄이는데 도움이 되는 토폴로지에 더 높은 우선 순위를 부여한다.
  • topologyKey 는 노드 레이블의 키다. 만약 두 노드가 이 키로 레이블이 지정되고, 레이블이 동일한 값을 가진다면 스케줄러는 두 노드를 같은 토폴로지에 있는것으로 여기게 된다. 스케줄러는 각 토폴로지 도메인에 균형잡힌 수의 파드를 배치하려고 시도한다.
  • whenUnsatisfiable 는 분산 제약 조건을 만족하지 않을 경우에 처리하는 방법을 나타낸다.
    • DoNotSchedule (기본값)은 스케줄러에 스케줄링을 하지 말라고 알려준다.
    • ScheduleAnyway 는 스케줄러에게 차이(skew)를 최소화하는 노드에 높은 우선 순위를 부여하면서, 스케줄링을 계속하도록 지시한다.
  • labelSelector 는 일치하는 파드를 찾는데 사용된다. 이 레이블 셀렉터와 일치하는 파드의 수를 계산하여 해당 토폴로지 도메인에 속할 파드의 수를 결정한다. 자세한 내용은 레이블 셀렉터를 참조한다.

사용자는 kubectl explain Pod.spec.topologySpreadConstraints 를 실행해서 이 필드에 대한 자세한 내용을 알 수 있다.

예시: 단수 토폴로지 분배 제약 조건

4개 노드를 가지는 클러스터에 foo:bar 가 레이블된 3개의 파드가 node1, node2 그리고 node3에 각각 위치한다고 가정한다.

graph BT subgraph "zoneB" p3(Pod) --> n3(Node3) n4(Node4) end subgraph "zoneA" p1(Pod) --> n1(Node1) p2(Pod) --> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3 k8s; class zoneA,zoneB cluster;

신규 파드가 기존 파드와 함께 영역에 걸쳐서 균등하게 분배되도록 하려면, 스펙(spec)은 다음과 같이 주어질 수 있다.

kind: Pod
apiVersion: v1
metadata:
  name: mypod
  labels:
    foo: bar
spec:
  topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        foo: bar
  containers:
  - name: pause
    image: k8s.gcr.io/pause:3.1

topologyKey: zone 는 "zone:<any value>" 레이블 쌍을 가지는 노드에 대해서만 균등한 분배를 적용하는 것을 의미한다. whenUnsatisfiable: DoNotSchedule 은 만약 들어오는 파드가 제약 조건을 만족시키지 못하면 스케줄러에게 pending 상태를 유지하도록 지시한다.

만약 스케줄러가 이 신규 파드를 "zoneA"에 배치하면 파드 분포는 [3, 1]이 되며, 따라서 실제 차이(skew)는 2 (3 - 1)가 되어 maxSkew: 1 를 위반하게 된다. 이 예시에서는 들어오는 파드는 오직 "zoneB"에만 배치할 수 있다.

graph BT subgraph "zoneB" p3(Pod) --> n3(Node3) p4(mypod) --> n4(Node4) end subgraph "zoneA" p1(Pod) --> n1(Node1) p2(Pod) --> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3 k8s; class p4 plain; class zoneA,zoneB cluster;

OR

graph BT subgraph "zoneB" p3(Pod) --> n3(Node3) p4(mypod) --> n3 n4(Node4) end subgraph "zoneA" p1(Pod) --> n1(Node1) p2(Pod) --> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3 k8s; class p4 plain; class zoneA,zoneB cluster;

사용자는 파드 스펙을 조정해서 다음과 같은 다양한 요구사항을 충족할 수 있다.

  • maxSkew 를 "2" 보다 큰 값으로 변경해서 들어오는 파드들이 "zoneA"에도 배치할 수 있도록 한다.
  • topologyKey 를 "node"로 변경해서 파드가 영역이 아닌, 노드에 걸쳐 고르게 분산할 수 있게 한다. 위의 예시에서 만약 maxSkew 가 "1"로 유지되면 들어오는 파드는 오직 "node4"에만 배치할 수 있다.
  • whenUnsatisfiable: DoNotSchedule 에서 whenUnsatisfiable: ScheduleAnyway 로 변경하면 들어오는 파드는 항상 다른 스케줄링 API를 충족한다는 가정하에 스케줄할 수 있도록 보장한다. 그러나 일치하는 파드가 적은 토폴로지 도메인에 배치되는 것이 좋다. (이 선호도는 리소스 사용 비율 등과 같은 다른 내부 스케줄링 우선순위와 공동으로 정규화 된다는 것을 알아두자.)

예시: 다중 토폴로지 분배 제약 조건

4개 노드를 가지는 클러스터에 foo:bar 가 레이블된 3개의 파드가 node1, node2 그리고 node3에 각각 위치한다고 가정한다.

graph BT subgraph "zoneB" p3(Pod) --> n3(Node3) n4(Node4) end subgraph "zoneA" p1(Pod) --> n1(Node1) p2(Pod) --> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3 k8s; class p4 plain; class zoneA,zoneB cluster;

사용자는 2개의 TopologySpreadConstraints를 사용해서 영역과 노드에 파드를 분배하는 것을 제어할 수 있다.

kind: Pod
apiVersion: v1
metadata:
  name: mypod
  labels:
    foo: bar
spec:
  topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        foo: bar
  - maxSkew: 1
    topologyKey: node
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        foo: bar
  containers:
  - name: pause
    image: k8s.gcr.io/pause:3.1

이 경우에는, 첫 번째 제약 조건에 부합시키려면, 신규 파드는 오직 "zoneB"에만 배치할 수 있다. 두 번째 제약 조건에서는 신규 파드는 오직 "node4"에만 배치할 수 있다. 그런 다음 두 가지 제약 조건의 결과는 AND 가 되므로, 실행 가능한 유일한 옵션은 "node4"에 배치하는 것이다.

다중 제약 조건은 충돌로 이어질 수 있다. 3개의 노드를 가지는 클러스터 하나가 2개의 영역에 걸쳐 있다고 가정한다.

graph BT subgraph "zoneB" p4(Pod) --> n3(Node3) p5(Pod) --> n3 end subgraph "zoneA" p1(Pod) --> n1(Node1) p2(Pod) --> n1 p3(Pod) --> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3,p4,p5 k8s; class zoneA,zoneB cluster;

만약 사용자가 "two-constraints.yaml" 을 이 클러스터에 적용하면, "mypod"가 Pending 상태로 유지되는 것을 알게 된다. 이러한 이유는, 첫 번째 제약 조건을 충족하기 위해 "mypod"는 오직 "zoneB"에만 놓을 수 있다. 두 번째 제약 조건에서는 "mypod"는 오직 "node2"에만 놓을 수 있다. 그러면 "zoneB"와 "node2"의 공동 결과는 아무것도 반환되지 않는다.

이 상황을 극복하기 위해서는 사용자가 maxSkew 의 증가 또는 whenUnsatisfiable: ScheduleAnyway 를 사용하도록 제약 조건 중 하나를 수정할 수 있다.

규칙

여기에 주목할만한 몇 가지 암묵적인 규칙이 있다.

  • 신규 파드와 같은 네임스페이스를 갖는 파드만이 매칭의 후보가 된다.

  • topologySpreadConstraints[*].topologyKey 가 없는 노드는 무시된다. 이것은 다음을 의미한다.

    1. 이러한 노드에 위치한 파드는 "maxSkew" 계산에 영향을 미치지 않는다. - 위의 예시에서, "node1"은 "zone" 레이블을 가지고 있지 않다고 가정하면, 파드 2개는 무시될 것이고, 이런 이유로 신규 파드는 "zoneA"로 스케줄된다.
    2. 신규 파드는 이런 종류의 노드에 스케줄 될 기회가 없다. - 위의 예시에서, 레이블로 {zone-typo: zoneC} 를 가지는 "node5"가 클러스터에 편입한다고 가정하면, 레이블 키에 "zone"이 없기 때문에 무시하게 된다.
  • 들어오는 파드의 topologySpreadConstraints[*].labelSelector 와 자체 레이블과 일치하지 않을 경우 어떻게 되는지 알고 있어야 한다. 위의 예시에서, 만약 들어오는 파드의 레이블을 제거하더라도 여전히 제약 조건이 충족하기 때문에 "zoneB"에 배치할 수 있다. 그러나, 배치 이후에도 클러스터의 불균형 정도는 변경되지 않는다. - 여전히 zoneA는 {foo:bar} 레이블을 가지고 있는 2개의 파드를 가지고 있고, zoneB 도 {foo:bar}를 레이블로 가지는 파드 1개를 가지고 있다. 따라서 만약 예상과 다르면, 워크로드의 topologySpreadConstraints[*].labelSelector 가 자체 레이블과 일치하도록 하는 것을 권장한다.

  • 만약 신규 파드에 spec.nodeSelector 또는 spec.affinity.nodeAffinity 가 정의되어 있으면, 일치하지 않는 노드는 무시하게 된다.

    zoneA 에서 zoneC에 걸쳐있고, 5개의 노드를 가지는 클러스터가 있다고 가정한다.

    graph BT subgraph "zoneB" p3(Pod) --> n3(Node3) n4(Node4) end subgraph "zoneA" p1(Pod) --> n1(Node1) p2(Pod) --> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3 k8s; class p4 plain; class zoneA,zoneB cluster;
    graph BT subgraph "zoneC" n5(Node5) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n5 k8s; class zoneC cluster;

    그리고 알다시피 "zoneC"는 제외해야 한다. 이 경우에, "mypod"가 "zoneC"가 아닌 "zoneB"에 배치되도록 yaml을 다음과 같이 구성할 수 있다. 마찬가지로 spec.nodeSelector 도 존중된다.

    kind: Pod
    apiVersion: v1
    metadata:
      name: mypod
      labels:
        foo: bar
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            foo: bar
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: zone
                operator: NotIn
                values:
                - zoneC
      containers:
      - name: pause
        image: k8s.gcr.io/pause:3.1

클러스터 수준의 기본 제약 조건

클러스터에 대한 기본 토폴로지 분배 제약 조건을 설정할 수 있다. 기본 토폴로지 분배 제약 조건은 다음과 같은 경우에만 파드에 적용된다.

  • .spec.topologySpreadConstraints 에는 어떠한 제약도 정의되어 있지 않는 경우.
  • 서비스, 레플리케이션컨트롤러(ReplicationController), 레플리카셋(ReplicaSet) 또는 스테이트풀셋(StatefulSet)에 속해있는 경우.

기본 제약 조건은 스케줄링 프로파일에서 PodTopologySpread 플러그인의 일부로 설정할 수 있다. 제약 조건은 labelSelector 가 비어 있어야 한다는 점을 제외하고, 위와 동일한 API로 제약 조건을 지정한다. 셀렉터는 파드가 속한 서비스, 레플리케이션 컨트롤러, 레플리카셋 또는 스테이트풀셋에서 계산한다.

예시 구성은 다음과 같다.

apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration

profiles:
  - pluginConfig:
      - name: PodTopologySpread
        args:
          defaultConstraints:
            - maxSkew: 1
              topologyKey: topology.kubernetes.io/zone
              whenUnsatisfiable: ScheduleAnyway
          defaultingType: List
참고: 기본 스케줄링 제약 조건에 의해 생성된 점수는 SelectorSpread 플러그인에 의해 생성된 점수와 충돌 할 수 있다. PodTopologySpread 에 대한 기본 제약 조건을 사용할 때 스케줄링 프로파일에서 이 플러그인을 비활성화 하는 것을 권장한다.

내부 기본 제약

FEATURE STATE: Kubernetes v1.20 [beta]

기본적으로 활성화된 DefaultPodTopologySpread 기능 게이트를 사용하면, 기존 SelectorSpread 플러그인이 비활성화된다. kube-scheduler는 PodTopologySpread 플러그인 구성에 다음과 같은 기본 토폴로지 제약 조건을 사용한다.

defaultConstraints:
  - maxSkew: 3
    topologyKey: "kubernetes.io/hostname"
    whenUnsatisfiable: ScheduleAnyway
  - maxSkew: 5
    topologyKey: "topology.kubernetes.io/zone"
    whenUnsatisfiable: ScheduleAnyway

또한, 같은 동작을 제공하는 레거시 SelectorSpread 플러그인이 비활성화된다.

참고:

노드에 kubernetes.io/hostnametopology.kubernetes.io/zone 레이블 세트 둘 다가 설정되지 않을 것으로 예상되는 경우, 쿠버네티스 기본값을 사용하는 대신 자체 제약 조건을 정의한다.

PodTopologySpread 플러그인은 분배 제약 조건에 지정된 토폴로지 키가 없는 노드에 점수를 매기지 않는다.

클러스터에 기본 파드 분배 제약 조건을 사용하지 않으려면, PodTopologySpread 플러그인 구성에서 defaultingTypeList 로 설정하고 defaultConstraints 를 비워두어 기본값을 비활성화할 수 있다.

apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration

profiles:
  - pluginConfig:
      - name: PodTopologySpread
        args:
          defaultConstraints: []
          defaultingType: List

파드어피니티(PodAffinity)/파드안티어피니티(PodAntiAffinity)와의 비교

쿠버네티스에서 "어피니티(Affinity)"와 관련된 지침은 파드가 더 많이 채워지거나 더 많이 분산되는 방식으로 스케줄 되는 방법을 제어한다.

  • PodAffinity 는, 사용자가 자격이 충족되는 토폴로지 도메인에 원하는 수의 파드를 얼마든지 채울 수 있다.
  • PodAntiAffinity 로는, 단일 토폴로지 도메인에 단 하나의 파드만 스케줄 될 수 있다.

더 세밀한 제어를 위해, 토폴로지 분배 제약 조건을 지정하여 다양한 토폴로지 도메인에 파드를 분배해서 고 가용성 또는 비용 절감을 달성할 수 있는 유연한 옵션을 제공한다. 또한 워크로드의 롤링 업데이트와 레플리카의 원활한 스케일링 아웃에 도움이 될 수 있다. 더 자세한 내용은 모티베이션(Motivation)를 참고한다.

알려진 제한사항

  • 디플로이먼트를 스케일링 다운하면 그 결과로 파드의 분포가 불균형이 될 수 있다.
  • 파드와 일치하는 테인트(taint)가 된 노드가 존중된다. 이슈 80921을 본다.

다음 내용

4 - 중단(disruption)

이 가이드는 고가용성 애플리케이션을 구성하려는 소유자와 파드에서 발생하는 장애 유형을 이해하기 원하는 애플리케이션 소유자를 위한 것이다.

또한 클러스터의 업그레이드와 오토스케일링과 같은 클러스터의 자동화 작업을 하려는 관리자를 위한 것이다.

자발적 중단과 비자발적 중단

파드는 누군가(사람 또는 컨트롤러)가 파괴하거나 불가피한 하드웨어 오류 또는 시스템 소프트웨어 오류가 아니면 사라지지 않는다.

우리는 이런 불가피한 상황을 애플리케이션의 비자발적 중단 으로 부른다. 예시:

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

리소스 부족을 제외한 나머지 조건은 대부분의 사용자가 익숙할 것이다. 왜냐하면 그 조건은 쿠버네티스에 국한되지 않기 때문이다.

우리는 다른 상황을 자발적인 중단 으로 부른다. 여기에는 애플리케이션 소유자의 작업과 클러스터 관리자의 작업이 모두 포함된다. 다음은 대표적인 애플리케이션 소유자의 작업이다.

  • 디플로이먼트 제거 또는 다른 파드를 관리하는 컨트롤러의 제거
  • 재시작을 유발하는 디플로이먼트의 파드 템플릿 업데이트
  • 파드를 직접 삭제(예: 우연히)

클러스터 관리자의 작업은 다음을 포함한다.

위 작업은 클러스터 관리자가 직접 수행하거나 자동화를 통해 수행하며, 클러스터 호스팅 공급자에 의해서도 수행된다.

클러스터에 자발적인 중단을 일으킬 수 있는 어떤 원인이 있는지 클러스터 관리자에게 문의하거나 클라우드 공급자에게 문의하고, 배포 문서를 참조해서 확인해야 한다. 만약 자발적인 중단을 일으킬 수 있는 원인이 없다면 Pod Disruption Budget의 생성을 넘길 수 있다.

주의: 모든 자발적인 중단이 Pod Disruption Budget에 연관되는 것은 아니다. 예를 들어 디플로이먼트 또는 파드의 삭제는 Pod Disruption Budget을 무시한다.

중단 다루기

비자발적인 중단으로 인한 영향을 경감하기 위한 몇 가지 방법은 다음과 같다.

자발적 중단의 빈도는 다양하다. 기본적인 쿠버네티스 클러스터에서는 자발적인 운영 중단이 전혀 없다. 그러나 클러스터 관리자 또는 호스팅 공급자가 자발적 중단이 발생할 수 있는 일부 부가 서비스를 운영할 수 있다. 예를 들어 노드 소프트웨어의 업데이트를 출시하는 경우 자발적 중단이 발생할 수 있다. 또한 클러스터(노드) 오토스케일링의 일부 구현에서는 단편화를 제거하고 노드의 효율을 높이는 과정에서 자발적 중단을 야기할 수 있다. 클러스터 관리자 또는 호스팅 공급자는 예측 가능한 자발적 중단 수준에 대해 문서화해야 한다.

파드 disruption budgets

FEATURE STATE: Kubernetes v1.21 [stable]

쿠버네티스는 자발적인 중단이 자주 발생하는 경우에도 고 가용성 애플리케이션을 실행하는 데 도움이 되는 기능을 제공한다.

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

클러스터 관리자와 호스팅 공급자는 직접적으로 파드나 디플로이먼트를 제거하는 대신 Eviction API로 불리는 PodDisruptionBudget을 준수하는 도구를 이용해야 한다.

예를 들어, kubectl drain 하위 명령을 사용하면 노드를 서비스 중단으로 표시할 수 있다. kubectl drain 을 실행하면, 도구는 사용자가 서비스를 중단하는 노드의 모든 파드를 축출하려고 한다. kubectl 이 사용자를 대신하여 수행하는 축출 요청은 일시적으로 거부될 수 있으며, 도구는 대상 노드의 모든 파드가 종료되거나 설정 가능한 타임아웃이 도래할 때까지 주기적으로 모든 실패된 요청을 다시 시도한다.

PDB는 애플리케이션이 필요로 하는 레플리카의 수에 상대적으로, 용인할 수 있는 레플리카의 수를 지정한다. 예를 들어 .spec.replicas: 5 의 값을 갖는 디플로이먼트는 어느 시점에든 5개의 파드를 가져야 한다. 만약 해당 디플로이먼트의 PDB가 특정 시점에 파드를 4개 허용한다면, Eviction API는 한 번에 1개(2개의 파드가 아닌)의 파드의 자발적인 중단을 허용한다.

파드 그룹은 레이블 셀렉터를 사용해서 지정한 애플리케이션으로 구성되며 애플리케이션 컨트롤러(디플로이먼트, 스테이트풀셋 등)를 사용한 것과 같다.

파드의 "의도"하는 수량은 해당 파드를 관리하는 워크로드 리소스의 .spec.replicas 를 기반으로 계산한다. 컨트롤 플레인은 파드의 .metadata.ownerReferences 를 검사하여 소유하는 워크로드 리소스를 발견한다.

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

애플리케이션의 롤링 업그레이드로 파드가 삭제되거나 사용할 수 없는 경우 중단 버짓에 영향을 준다. 그러나 워크로드 리소스(디플로이먼트, 스테이트풀셋과 같은)는 롤링 업데이트 시 PDB의 제한을 받지 않는다. 대신, 애플리케이션 업데이트 중 실패 처리는 특정 워크로드 리소스에 대한 명세에서 구성된다.

Eviction API를 사용하여 파드를 축출하면, PodSpecterminationGracePeriodSeconds 설정을 준수하여 정상적으로 종료됨 상태가 된다.)

PodDisruptionBudget 예시

node-1 부터 node-3 까지 3개의 노드가 있는 클러스터가 있다고 하자. 클러스터에는 여러 애플리케이션을 실행하고 있다. 여러 애플리케이션 중 하나는 pod-a, pod-b, pod-c 로 부르는 3개의 레플리카가 있다. 여기에 pod-x 라고 부르는 PDB와 무관한 파드가 보인다. 초기에 파드는 다음과 같이 배치된다.

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

전체 3개 파드는 디플로이먼트의 일부분으로 전체적으로 항상 3개의 파드 중 최소 2개의 파드를 사용할 수 있도록 하는 PDB를 가지고 있다.

예를 들어, 클러스터 관리자가 커널 버그를 수정하기위해 새 커널 버전으로 재부팅하려는 경우를 가정해보자. 클러스터 관리자는 첫째로 node-1kubectl drain 명령어를 사용해서 비우려 한다. kubectlpod-apod-x 를 축출하려고 한다. 이는 즉시 성공한다. 두 파드는 동시에 terminating 상태로 진입한다. 이렇게 하면 클러스터는 다음의 상태가 된다.

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

디플로이먼트는 한 개의 파드가 중지되는 것을 알게되고, pod-d 라는 대체 파드를 생성한다. node-1 은 차단되어 있어 다른 노드에 위치한다. 무언가가 pod-x 의 대체 파드로 pod-y 도 생성했다.

(참고: 스테이트풀셋은 pod-0 처럼 불릴, pod-a 를 교체하기 전에 완전히 중지해야 하며, pod-0 로 불리지만, 다른 UID로 생성된다. 그렇지 않으면 이 예시는 스테이트풀셋에도 적용된다.)

이제 클러스터는 다음과 같은 상태이다.

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

어느 순간 파드가 종료되고, 클러스터는 다음과 같은 상태가 된다.

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

이 시점에서 만약 성급한 클러스터 관리자가 node-2 또는 node-3 을 비우려고 하는 경우 디플로이먼트에 available 상태의 파드가 2개 뿐이고, PDB에 필요한 최소 파드는 2개이기 때문에 drain 명령이 차단된다. 약간의 시간이 지나면 pod-d 가 available 상태가 된다.

이제 클러스터는 다음과 같은 상태이다.

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

이제 클러스터 관리자는 node-2 를 비우려고 한다. drain 커멘드는 pod-b 에서 pod-d 와 같이 어떤 순서대로 두 파드를 축출하려 할 것이다. drain 커멘드는 pod-b 를 축출하는데 성공했다. 그러나 drain 커멘드가 pod-d 를 축출하려 하는 경우 디플로이먼트에 available 상태의 파드는 1개로 축출이 거부된다.

디플로이먼트는pod-b 를 대체할 pod-e 라는 파드를 생성한다. 클러스터에 pod-e 를 스케줄하기 위한 충분한 리소스가 없기 때문에 드레이닝 명령어는 차단된다. 클러스터는 다음 상태로 끝나게 된다.

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

이 시점에서 클러스터 관리자는 클러스터에 노드를 추가해서 업그레이드를 진행해야 한다.

쿠버네티스에 중단이 발생할 수 있는 비율을 어떻게 변화시키는지 다음의 사례를 통해 알 수 있다.

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

클러스터 소유자와 애플리케이션 소유자의 역할 분리

보통 클러스터 매니저와 애플리케이션 소유자는 서로에 대한 지식이 부족한 별도의 역할로 생각하는 것이 유용하다. 이와 같은 책임의 분리는 다음의 시나리오에서 타당할 수 있다.

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

Pod Disruption Budget은 역할 분리에 따라 역할에 맞는 인터페이스를 제공한다.

만약 조직에 역할 분리에 따른 책임의 분리가 없다면 Pod Disruption Budget을 사용할 필요가 없다.

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

만약 클러스터 관리자라면, 그리고 클러스터 전체 노드에 노드 또는 시스템 소프트웨어 업그레이드와 같은 중단이 발생할 수 있는 작업을 수행하는 경우 다음과 같은 옵션을 선택한다.

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

다음 내용

5 - 임시(Ephemeral) 컨테이너

FEATURE STATE: Kubernetes v1.16 [alpha]

이 페이지는 임시 컨테이너에 대한 개요를 제공한다: 이 특별한 유형의 컨테이너는 트러블 슈팅과 같은 사용자가 시작한 작업을 완료하기위해 기존 파드 에서 임시적으로 실행된다. 사용자는 애플리케이션 빌드보다는 서비스를 점검할 때 임시 컨테이너를 사용한다.

경고: 임시 컨테이너는 초기 알파 상태이며, 프로덕션 클러스터에는 적합하지 않다. 쿠버네티스 사용 중단(deprecation) 정책에 따라 이 알파 기능은 향후 크게 변경되거나, 완전히 제거될 수 있다.

임시 컨테이너 이해하기

파드 는 쿠버네티스 애플리케이션의 기본 구성 요소이다. 파드는 일회용이고, 교체 가능한 것으로 의도되었기 때문에, 사용자는 파드가 한번 생성되면, 컨테이너를 추가할 수 없다. 대신, 사용자는 보통 디플로이먼트 를 사용해서 제어하는 방식으로 파드를 삭제하고 교체한다.

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

임시 컨테이너는 무엇인가?

임시 컨테이너는 리소스 또는 실행에 대한 보증이 없다는 점에서 다른 컨테이너와 다르며, 결코 자동으로 재시작되지 않는다. 그래서 애플리케이션을 만드는데 적합하지 않다. 임시 컨테이너는 일반 컨테이너와 동일한 ContainerSpec 을 사용해서 명시하지만, 많은 필드가 호환되지 않으며 임시 컨테이너에는 허용되지 않는다.

  • 임시 컨테이너는 포트를 가지지 않을 수 있으므로, ports, livenessProbe, readinessProbe 와 같은 필드는 허용되지 않는다.
  • 파드에 할당된 리소스는 변경할 수 없으므로, resources 설정이 허용되지 않는다.
  • 허용되는 필드의 전체 목록은 임시컨테이너 참조 문서를 본다.

임시 컨테이너는 pod.spec 에 직접 추가하는 대신 API에서 특별한 ephemeralcontainers 핸들러를 사용해서 만들어지기 때문에 kubectl edit을 사용해서 임시 컨테이너를 추가할 수 없다.

일반 컨테이너와 마찬가지로, 사용자는 임시 컨테이너를 파드에 추가한 이후에 변경하거나 제거할 수 없다.

임시 컨테이너의 사용

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

특히, distroless 이미지 를 사용하면 공격 표면(attack surface)과 버그 및 취약점의 노출을 줄이는 최소한의 컨테이너 이미지를 배포할 수 있다. distroless 이미지는 셸 또는 어떤 디버깅 도구를 포함하지 않기 때문에, kubectl exec 만으로는 distroless 이미지의 문제 해결이 어렵다.

임시 컨테이너 사용 시 프로세스 네임스페이스 공유를 활성화하면 다른 컨테이너 안의 프로세스를 보는데 도움이 된다.

임시 컨테이너를 사용해서 문제를 해결하는 예시는 [임시 디버깅 컨테이너로 디버깅하기] (/docs/tasks/debug-application-cluster/debug-running-pod/#ephemeral-container)를 참조한다.

임시 컨테이너 API

참고: 이 섹션의 예시는 EphemeralContainers 기능 게이트의 활성화를 필요로 하고, 쿠버네티스 클라이언트와 서버는 v1.16 또는 이후의 버전이어야 한다.

이 섹션의 예시는 임시 컨테이너가 어떻게 API에 나타나는지 보여준다. 일반적으로 kubectl debug 또는 다른 kubectl 플러그인을 사용해서 API를 직접 호출하지 않고 이런 단계들을 자동화 한다.

임시 컨테이너는 파드의 ephemeralcontainers 하위 리소스를 사용해서 생성되며, kubectl --raw 를 사용해서 보여준다. 먼저 EphemeralContainers 목록으로 추가하는 임시 컨테이너를 명시한다.

{
    "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