파드

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

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

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

파드란 무엇인가?

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

파드는 공유 네임스페이스와 공유 파일시스템 볼륨이 있는 컨테이너들의 집합과 비슷하다.

파드의 사용

다음은 nginx:1.14.2 이미지를 실행하는 컨테이너로 구성되는 파드의 예시이다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80

위에서 설명한 파드를 생성하려면, 다음 명령을 실행한다.

kubectl apply -f https://k8s.io/examples/pods/simple-pod.yaml

일반적으로 파드는 직접 생성하지는 않으며, 대신 워크로드 리소스를 사용하여 생성한다. 파드 작업 섹션에서 파드와 워크로드 리소스의 관계에 대한 더 많은 정보를 확인한다.

Workload resources for managing pods

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

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

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

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

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

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

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

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

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

파드 생성 다이어그램

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

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

파드 작업

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

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

파드 OS

기능 상태: Kubernetes v1.25 [stable]

파드를 실행할 때 OS를 표시하려면 .spec.os.name 필드를 windows 또는 linux로 설정해야 한다. 이 두 가지 운영체제는 현재 쿠버네티스에서 지원되는 유일한 운영체제이다. 앞으로 이 목록이 확장될 수 있다.

쿠버네티스 v1.29에서, 이 필드에 대해 설정한 값은 파드의 스케줄링에 영향을 미치지 않는다. .spec.os.name을 설정하면 파드 검증 시 OS를 식별하는 데 도움이 된다. kubelet은 자신이 실행되고 있는 노드의 운영체제와 동일하지 않은 파드 OS가 명시된 파드의 실행을 거부한다. 파드 시큐리티 스탠다드도 이 필드를 사용하여 해당 운영체제와 관련이 없는 정책을 시행하지 않도록 한다.

파드와 컨트롤러

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

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

파드 템플릿

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

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

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

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

apiVersion: batch/v1
kind: Job
metadata:
  name: hello
spec:
  template:
    # 여기서부터 파드 템플릿이다
    spec:
      containers:
      - name: hello
        image: busybox:1.28
        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 주소를 가지며 특별한 구성 없이 OS 수준의 IPC로 통신할 수 없다. 다른 파드에서 실행되는 컨테이너와 상호 작용하려는 컨테이너는 IP 네트워킹을 사용하여 통신할 수 있다.

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

컨테이너에 대한 특권 모드

리눅스에서, 파드의 모든 컨테이너는 컨테이너 명세의 보안 컨텍스트에 있는 privileged (리눅스) 플래그를 사용하여 특권 모드를 활성화할 수 있다. 이는 네트워크 스택 조작이나 하드웨어 장치 접근과 같은 운영 체제 관리 기능을 사용하려는 컨테이너에 유용하다.

클러스터가 WindowsHostProcessContainers 기능을 활성화하였다면, 파드 스펙의 보안 컨텍스트의 windowsOptions.hostProcess 에 의해 윈도우 HostProcess 파드를 생성할 수 있다. 이러한 모든 컨테이너는 윈도우 HostProcess 컨테이너로 실행해야 한다. HostProcess 파드는 직접적으로 호스트에서 실행하는 것으로, 리눅스 특권있는 컨테이너에서 수행되는 관리 태스크 수행에도 사용할 수 있다. 파드의 모든 컨테이너는 윈도우 HostProcess 컨테이너로 반드시 실행해야 한다. HostProcess 파드는 호스트에서 직접 실행되며 리눅스 특권있는 컨테이너에서 수행되는 것과 같은 관리 작업을 수행하는데도 사용할 수 있다.

정적 파드

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

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

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

컨테이너 프로브

_프로브_는 컨테이너의 kubelet에 의해 주기적으로 실행되는 진단이다. 진단을 수행하기 위하여 kubelet은 다음과 같은 작업을 호출할 수 있다.

  • ExecAction (컨테이너 런타임의 도움을 받아 수행)
  • TCPSocketAction (kubelet에 의해 직접 검사)
  • HTTPGetAction (kubelet에 의해 직접 검사)

프로브에 대한 자세한 내용은 파드 라이프사이클 문서를 참고한다.

다음 내용

쿠버네티스가 다른 리소스(스테이트풀셋이나 디플로이먼트와 같은)에서 공통 파드 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어떤 이유에 의해서 파드의 상태를 얻을 수 없다. 이 단계는 일반적으로 파드가 실행되어야 하는 노드와의 통신 오류로 인해 발생한다.

노드가 죽거나 클러스터의 나머지와의 연결이 끊어지면, 쿠버네티스는 손실된 노드의 모든 파드의 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 배열을 가진다. kubelet은 다음 PodConditions를 관리한다.

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

파드의 준비성(readiness)

기능 상태: 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 로 설정한다.

파드 네트워크 준비성(readiness)

기능 상태: Kubernetes v1.25 [alpha]

파드가 노드에 스케줄링되면, kubelet이 이 파드를 승인해야 하고 모든 볼륨이 마운트되어야 한다. 이러한 단계가 완료되면 kubelet은 (컨테이너 런타임 인터페이스(Container runtime interface, CRI)를 사용하여) 컨테이너 런타임과 통신하여 런타임 샌드박스를 설정하고 파드에 대한 네트워킹을 구성한다. 만약 PodHasNetworkCondition 기능 게이트가 활성화되면, Kubelet은 파드의 status.conditions 필드에 있는 PodHasNetwork 컨디션을 통해 파드가 초기화 마일스톤에 도달했는지 여부를 보고한다.

Kubelet이 파드에 네트워킹이 구성된 런타임 샌드박스가 없음을 탐지했을 때 PodHasNetwork 컨디션은 False로 설정된다. 이것은 다음 시나리오에서 발생한다.

  • 파드 라이프사이클 초기에, kubelet이 컨테이너 런타임을 사용하여 파드를 위한 샌드박스 생성을 아직 ​​시작하지 않은 때.
  • 파드 라이프사이클 후기에, 파드 샌드박스가 다음중 하나의 이유로 파괴되었을 때.
    • 파드 축출 없이, 노드가 재부팅됨
    • 격리를 위해 가상 머신을 사용하는 컨테이너 런타임을 사용하는 경우, 파드 샌드박스 가상 머신이 재부팅됨(이후, 새로운 샌드박스 및 새로운 컨테이너 네트워크 구성 생성이 필요함)

런타임 플러그인이 파드를 위한 샌드박스 생성 및 네트워크 구성을 성공적으로 완료하면 kubelet이 PodHasNetwork 컨디션을 True로 설정한다. PodHasNetwork 컨디션이 True로 설정되면 kubelet이 컨테이너 이미지를 풀링하고 컨테이너를 생성할 수 있다.

초기화 컨테이너가 있는 파드의 경우, kubelet은 초기화 컨테이너가 성공적으로 완료(런타임 플러그인에 의한 성공적인 샌드박스 생성 및 네트워크 구성이 완료되었음을 의미)된 후 Initialized 컨디션을 True로 설정한다. 초기화 컨테이너가 없는 파드의 경우, kubelet은 샌드박스 생성 및 네트워크 구성이 시작되기 전에 Initialized 컨디션을 True로 설정한다.

컨테이너 프로브(probe)

프로브 는 컨테이너에서 kubelet에 의해 주기적으로 수행되는 진단(diagnostic)이다. 진단을 수행하기 위해서, kubelet은 컨테이너 안에서 코드를 실행하거나, 또는 네트워크 요청을 전송한다.

체크 메커니즘

프로브를 사용하여 컨테이너를 체크하는 방법에는 4가지가 있다. 각 프로브는 다음의 4가지 메커니즘 중 단 하나만을 정의해야 한다.

exec
컨테이너 내에서 지정된 명령어를 실행한다. 명령어가 상태 코드 0으로 종료되면 진단이 성공한 것으로 간주한다.
grpc
gRPC를 사용하여 원격 프로시저 호출을 수행한다. 체크 대상이 gRPC 헬스 체크를 구현해야 한다. 응답의 statusSERVING 이면 진단이 성공했다고 간주한다. gRPC 프로브는 알파 기능이며 GRPCContainerProbe 기능 게이트를 활성화해야 사용할 수 있다.
httpGet
지정한 포트 및 경로에서 컨테이너의 IP주소에 대한 HTTP GET 요청을 수행한다. 응답의 상태 코드가 200 이상 400 미만이면 진단이 성공한 것으로 간주한다.
tcpSocket
지정된 포트에서 컨테이너의 IP주소에 대해 TCP 검사를 수행한다. 포트가 활성화되어 있다면 진단이 성공한 것으로 간주한다. 원격 시스템(컨테이너)가 연결을 연 이후 즉시 닫는다면, 이 또한 진단이 성공한 것으로 간주한다.

프로브 결과

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

Success
컨테이너가 진단을 통과함.
Failure
컨테이너가 진단에 실패함.
Unknown
진단 자체가 실패함(아무런 조치를 수행해서는 안 되며, kubelet이 추가 체크를 수행할 것이다)

프로브 종류

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

livenessProbe
컨테이너가 동작 중인지 여부를 나타낸다. 만약 활성 프로브(liveness probe)에 실패한다면, kubelet은 컨테이너를 죽이고, 해당 컨테이너는 재시작 정책의 대상이 된다. 만약 컨테이너가 활성 프로브를 제공하지 않는 경우, 기본 상태는 Success 이다.
readinessProbe
컨테이너가 요청을 처리할 준비가 되었는지 여부를 나타낸다. 만약 준비성 프로브(readiness probe)가 실패한다면, 엔드포인트 컨트롤러는 파드에 연관된 모든 서비스들의 엔드포인트에서 파드의 IP주소를 제거한다. 준비성 프로브의 초기 지연 이전의 기본 상태는 Failure 이다. 만약 컨테이너가 준비성 프로브를 지원하지 않는다면, 기본 상태는 Success 이다.
startupProbe
컨테이너 내의 애플리케이션이 시작되었는지를 나타낸다. 스타트업 프로브(startup probe)가 주어진 경우, 성공할 때까지 다른 나머지 프로브는 활성화되지 않는다. 만약 스타트업 프로브가 실패하면, kubelet이 컨테이너를 죽이고, 컨테이너는 재시작 정책에 따라 처리된다. 컨테이너에 스타트업 프로브가 없는 경우, 기본 상태는 Success 이다.

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

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

기능 상태: Kubernetes v1.0 [stable]

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

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

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

기능 상태: Kubernetes v1.0 [stable]

프로브가 성공한 경우에만 파드에 트래픽 전송을 시작하려고 한다면, 준비성 프로브를 지정하길 바란다. 이 경우에서는, 준비성 프로브가 활성 프로브와 유사해 보일 수도 있지만, 스팩에 준비성 프로브가 존재한다는 것은 파드가 트래픽을 받지 않는 상태에서 시작되고 프로브가 성공하기 시작한 이후에만 트래픽을 받는다는 뜻이다.

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

만약 애플리케이션이 백엔드 서비스에 엄격한 의존성이 있다면, 활성 프로브와 준비성 프로브 모두 활용할 수도 있다. 활성 프로브는 애플리케이션 스스로가 건강한 상태면 통과하지만, 준비성 프로브는 추가적으로 요구되는 각 백-엔드 서비스가 가용한지 확인한다. 이를 이용하여, 오류 메시지만 응답하는 파드로 트래픽이 가는 것을 막을 수 있다.

만약 컨테이너가 시동 시 대량 데이터의 로딩, 구성 파일, 또는 마이그레이션에 대한 작업을 수행해야 한다면, 스타트업 프로브를 사용하면 된다. 그러나, 만약 failed 애플리케이션과 시동 중에 아직 데이터를 처리하고 있는 애플리케이션을 구분하여 탐지하고 싶다면, 준비성 프로브를 사용하는 것이 더 적합할 것이다.

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

기능 상태: 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초의 작은 일회성 유예 기간 연장을 요청한다.
    2. kubelet은 컨테이너 런타임을 트리거하여 각 컨테이너 내부의 프로세스 1에 TERM 시그널을 보낸다.
  3. kubelet이 정상 종료를 시작하는 동시에, 컨트롤 플레인은 구성된 셀렉터가 있는 서비스를 나타내는 엔드포인트슬라이스(EndpointSplice)(그리고 엔드포인트) 오브젝트에서 종료된 파드를 제거한다. 레플리카셋(ReplicaSet)과 기타 워크로드 리소스는 더 이상 종료된 파드를 유효한 서비스 내 복제본으로 취급하지 않는다. 로드 밸런서(서비스 프록시와 같은)가 종료 유예 기간이 시작되는 즉시 엔드포인트 목록에서 파드를 제거하므로 느리게 종료되는 파드는 트래픽을 계속 제공할 수 없다.
  4. 유예 기간이 만료되면, kubelet은 강제 종료를 트리거한다. 컨테이너 런타임은 SIGKILL 을 파드의 모든 컨테이너에서 여전히 실행 중인 모든 프로세스로 전송한다. kubelet은 해당 컨테이너 런타임이 하나를 사용하는 경우 숨겨진 pause 컨테이너도 정리한다.
  5. kubelet은 유예 기간을 0(즉시 삭제)으로 설정하여, API 서버에서 파드 오브젝트의 강제 삭제를 트리거한다.
  6. API 서버가 파드의 API 오브젝트를 삭제하면, 더 이상 클라이언트에서 볼 수 없다.

강제 파드 종료

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

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

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

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

파드의 가비지 콜렉션

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

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

추가적으로 PodGC는 다음과 같은 조건들 중 하나라도 만족하는 파드들을 정리한다.

  1. 고아 파드 - 더 이상 존재하지 않는 노드에 종속되어있는 파드이거나,
  2. 스케줄되지 않은 종료 중인 파드이거나,
  3. NodeOutOfServiceVolumeDetach 기능 게이트가 활성화되어 있을 때, node.kubernetes.io/out-of-service에 테인트된 준비되지 않은 노드에 속한 종료 중인 파드인 경우에 적용된다.

PodDisruptionConditions 기능 게이트가 활성화된 경우, PodGC는 파드를 정리하는 것 뿐만 아니라 해당 파드들이 non-terminal 단계에 있는 경우 그들을 실패했다고 표시하기도 한다. 또한, PodGC는 고아 파드를 정리할 때 파드 중단 조건을 추가하기도 한다. (자세한 내용은 파드 중단 조건을 확인한다.)

다음 내용

2 - 초기화 컨테이너

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

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

초기화 컨테이너 이해하기

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

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

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

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

컨테이너를 초기화 컨테이너로 지정하기 위해서는, 파드 스펙initContainers 필드를 container 항목(앱 container 필드 및 내용과 유사한)들의 배열로서 추가한다. 컨테이너에 대한 더 상세한 사항은 API 레퍼런스를 참고한다.

초기화 컨테이너의 상태는 컨테이너 상태의 배열(.status.containerStatuses 필드와 유사)로 .status.initContainerStatuses 필드에 반환된다.

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

초기화 컨테이너는 앱 컨테이너의 리소스 상한(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.kubernetes.io/name: 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.kubernetes.io/name=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 init-myservice
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Started       Started container init-myservice

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

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를 사용한다. Active deadline은 초기화 컨테이너를 포함한다. 그러나 팀에서 애플리케이션을 잡(job)으로 배포한 경우에만 activeDeadlineSeconds를 사용하길 추천한다. 왜냐하면, activeDeadlineSeconds는 초기화 컨테이너가 완료된 이후에도 영향을 주기 때문이다. 이미 정상적으로 동작하고 있는 파드도 activeDeadlineSeconds를 설정한 경우 종료(killed)될 수 있다.

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

리소스

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

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

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

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

파드 재시작 이유

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

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

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

다음 내용

3 - 중단(disruption)

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

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

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

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

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

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

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

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

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

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

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

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

중단 다루기

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

자발적 중단의 빈도는 다양하다. 기본적인 쿠버네티스 클러스터에서는 자동화된 자발적 중단은 발생하지 않는다(사용자가 지시한 자발적 중단만 발생한다). 그러나 클러스터 관리자 또는 호스팅 공급자가 자발적 중단이 발생할 수 있는 일부 부가 서비스를 운영할 수 있다. 예를 들어 노드 소프트웨어의 업데이트를 출시하는 경우 자발적 중단이 발생할 수 있다. 또한 클러스터(노드) 오토스케일링의 일부 구현에서는 단편화를 제거하고 노드의 효율을 높이는 과정에서 자발적 중단을 야기할 수 있다. 클러스터 관리자 또는 호스팅 공급자는 예측 가능한 자발적 중단 수준에 대해 문서화해야 한다. 파드 스펙 안에 프라이어리티클래스 사용하기와 같은 특정 환경설정 옵션 또한 자발적(+ 비자발적) 중단을 유발할 수 있다.

파드 disruption budgets

기능 상태: 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

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

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

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

파드 중단 조건

기능 상태: Kubernetes v1.26 [beta]

이 기능이 활성화되면, 파드 전용 DisruptionTarget 컨디션이 추가되어 중단(disruption)으로 인해 파드가 삭제될 예정임을 나타낸다. 추가로 컨디션의 reason 필드는 파드 종료에 대한 다음 원인 중 하나를 나타낸다.

PreemptionByKubeScheduler
파드는 더 높은 우선순위를 가진 새 파드를 수용하기 위해 스케줄러에 의해 선점(preempted)된다. 자세한 내용은 파드 우선순위(priority)와 선점(preemption)을 참조해보자.
DeletionByTaintManager
허용하지 않는 NoExecute 테인트(taint) 때문에 파드가 테인트 매니저(kube-controller-manager 내의 노드 라이프사이클 컨트롤러의 일부)에 의해 삭제될 예정이다. taint 기반 축출을 참조해보자.
EvictionByEvictionAPI
파드에 쿠버네티스 API를 이용한 축출이 표시되었다.
DeletionByPodGC
더 이상 존재하지 않는 노드에 바인딩된 파드는 파드의 가비지 콜렉션에 의해 삭제될 예정이다.
TerminationByKubelet
노드 압박-축출 또는 그레이스풀 노드 셧다운으로 인해 kubelet이 파드를 종료시켰다.

잡(또는 크론잡(CronJob))을 사용할 때, 이러한 파드 중단 조건을 잡의 파드 실패 정책의 일부로 사용할 수 있다.

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

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

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

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

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

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

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

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

다음 내용

4 - 임시(Ephemeral) 컨테이너

기능 상태: Kubernetes v1.25 [stable]

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

임시 컨테이너 이해하기

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

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

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

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

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

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

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

임시 컨테이너의 사용

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

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

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

다음 내용

5 - 사용자 네임스페이스

기능 상태: Kubernetes v1.25 [alpha]

이 페이지에서는 사용자 네임스페이스가 쿠버네티스 파드에서 사용되는 방법에 대해 설명한다. 사용자 네임스페이스는 컨테이너 내에서 실행 중인 사용자와 호스트의 사용자를 분리할 수 있도록 한다.

컨테이너에서 루트(root)로 실행되는 프로세스는 호스트에서 다른 (루트가 아닌) 사용자로 실행될 수 있다. 즉, 프로세스는 사용자 네임스페이스 내부 작업에 대해 특권을 갖지만(privileged) 네임스페이스 외부의 작업에서는 특권을 갖지 않는다.

이러한 기능을 사용하여 훼손(compromised)된 컨테이너가 호스트나 동일한 노드의 다른 파드에 미칠 수 있는 손상을 줄일 수 있다. HIGH 또는 CRITICAL 등급의 일부 보안 취약점은 사용자 네임스페이스가 Active 상태일 때 사용할 수 없다. 사용자 네임스페이스는 일부 미래의 취약점들도 완화시킬 수 있을 것으로 기대된다.

시작하기 전에

이것은 리눅스 전용 기능이다. 또한 컨테이너 런타임에서 이 기능을 쿠버네티스 스테이트리스(stateless) 파드와 함께 사용하려면 지원이 필요하다.

  • CRI-O: v1.25에는 사용자 네임스페이스가 지원된다.

  • containerd: 1.7 릴리즈에서 지원이 계획돼 있다. 자세한 내용은 containerd 이슈 #7063를 참조한다.

cri-dockerd에서의 지원은 아직 계획되지 않았다.

개요

사용자 네임스페이스는 컨테이너의 사용자를 호스트의 다른 사용자에게 매핑할 수 있는 리눅스 기능이다. 또한 사용자 네임스페이스의 파드에 부여된 기능은 네임스페이스 내부에서만 유효하고 외부에서는 무효하다.

파드는 pod.spec.hostUsers 필드를 false로 설정해 사용자 네임스페이스를 사용하도록 선택할 수 있다.

kubelet은 동일 노드에 있는 두 개의 스테이트리스 파드가 동일한 매핑을 사용하지 않도록 보장하는 방식으로 파드가 매핑된 호스트 UID나 GID를 선택한다.

pod.specrunAsUser, runAsGroup, fsGroup 등의 필드는 항상 컨테이너 내의 사용자를 가리킨다.

이 기능을 활성화한 경우 유효한 UID/GID의 범위는 0부터 65535까지이다. 이는 파일 및 프로세스(runAsUser, runAsGroup 등)에 적용된다.

이 범위를 벗어나는 UID/GID를 사용하는 파일은 오버플로 ID, 주로 65534(/proc/sys/kernel/overflowuid/proc/sys/kernel/overflowgid에서 설정됨)에 속하는 것으로 간주된다. 그러나 65534 사용자/그룹으로 실행하더라도 이러한 파일을 수정할 수 없다.

루트로 실행돼야 하지만 다른 호스트 네임스페이스나 리소스에 접근하지 않는 대부분의 애플리케이션은 사용자 네임스페이스를 활성화한 경우 변경할 필요 없이 계속 정상적으로 실행돼야 한다.

스테이트리스 파드의 사용자 네임스페이스 이해하기

기본 설정의 여러 컨테이너 런타임(Docker Engine, containerd, CRI-O 등)은 격리를 위해 리눅스 네임스페이스를 사용한다. 다른 기술도 존재하며 위와 같은 런타임에도 사용할 수 있다. (예를 들어, Kata Container는 리눅스 네임스페이스 대신 VM을 사용한다.) 이 페이지는 격리를 위해 리눅스 네임스페이스를 사용하는 컨테이너 런타임에 주안을 둔다.

파드를 생성할 때 기본적으로 컨테이너의 네트워크를 분리하는 네임스페이스, 프로세스 보기를 분리하는 PID 네임스페이스 등 여러 새로운 네임스페이스가 격리에 사용된다. 사용자 네임스페이스를 사용하면 컨테이너의 사용자와 노드의 사용자가 격리된다.

이는 컨테이너가 루트로 실행될 수 있고 호스트의 루트가 아닌 사용자에게 매핑될 수 있다는 것을 의미한다. 컨테이너 내부에서 프로세스는 자신이 루트로 실행된다고 생각하지만 (따라서 apt, yum 등과 같은 도구가 정상적으로 작동) 실제로 프로세스는 호스트에 대한 권한이 없다. 예를 들어, 호스트에서 ps aux를 실행함으로써 컨테이너 프로세스가 어느 사용자에 의해 실행 중인지 확인할 수 있다. 사용자 ps는 컨테이너 안에서 명령 id를 실행하면 나타나는 사용자와 동일하지 않다.

이 추상화는 컨테이너가 호스트로 탈출하는 것과 같은 상황을 제한한다. 컨테이너가 호스트에서 권한이 없는 사용자로 실행 중이기 때문에 호스트에게 수행할 수 있는 작업은 제한된다.

또한, 각 파드의 사용자는 호스트의 서로 겹치지 않는 다른 사용자에게 매핑되므로 다른 파드에 대해서도 수행할 수 있는 작업이 제한된다.

파드에 부여된 기능 또한 파드 사용자 네임스페이스로 제한되는데 대부분 유효하지 않고 심지어 일부는 완전히 무효하다. 다음은 두 가지 예시이다.

  • CAP_SYS_MODULE은 사용자 네임스페이스를 사용하여 파드에 부여돼도 아무런 효과가 없으며 파드는 커널 모듈을 로드할 수 없다.
  • CAP_SYS_ADMIN은 파드의 사용자 네임스페이스로 제한되며 네임스페이스 외부에서는 유효하지 않다.

사용자 네임스페이스를 사용하지 않고 루트로 실행되는 컨테이너는 컨테이너 브레이크아웃(container breakout)의 경우 노드에 대한 루트 권한을 가진다. 컨테이너에 일부 기능이 부여된 경우 호스트에서도 해당 기능이 유효하다. 사용자 네임스페이스를 사용할 경우 이 중 어느 것도 사실이 아니다.

사용자 네임스페이스 사용 시 변경사항에 대한 자세한 내용은 man 7 user_namespace를 참조한다.

사용자 네임스페이스를 지원하도록 노드 설정하기

호스트의 파일 및 프로세스에서 0 ~ 65535 범위의 UID/GID를 사용하는 것이 좋다.

kubelet은 파드에 그것보다 더 높은 UID/GID를 할당한다. 따라서 가능한 많은 격리를 보장하려면 호스트의 파일 및 호스트의 프로세스에 사용되는 UID/GID가 0-65535 범위여야 한다.

이 권장 사항은 파드가 호스트의 임의의 파일을 잠재적으로 읽을 수 있는 CVE-2021-25741과 같은 CVE의 영향을 완화하기 위해 중요하다. 파드와 호스트의 UID/GID가 겹치지 않으면 파드가 수행할 수 있는 작업이 제한된다. 즉, 파드 UID/GID가 호스트의 파일 소유자/그룹과 일치하지 않게 된다.

제약 사항

파드에 사용자 네임스페이스를 사용하는 경우 다른 호스트 네임스페이스를 사용할 수 없다. 특히 hostUsers: false를 설정하면 다음 중 하나를 설정할 수 없다.

  • hostNetwork: true
  • hostIPC: true
  • hostPID: true

파드는 볼륨을 전혀 사용하지 않거나 볼륨을 사용하는 경우에는 다음 볼륨 유형만 사용할 수 있다.

  • configmap
  • secret
  • projected
  • downwardAPI
  • emptyDir

파드가 이러한 볼륨의 파일을 읽을 수 있도록 하기 위해 파드에 .spec.securityContext.fsGroup0으로 지정한 것처럼 볼륨이 생성된다. 이 값이 다른 값으로 지정되면 당연히 이 다른 값이 대신 적용된다.

이로 인해 볼륨의 특정 항목에 대한 defaultModemode가 그룹에 대한 권한 없이 지정되었더라도 이러한 볼륨에 대한 폴더와 파일은 그룹에 대한 권한을 갖는다. 예를 들어 파일에 소유자에 대한 권한만 있는 방식으로 이러한 볼륨을 마운트할 수 없다.

6 - 다운워드(Downward) API

실행 중인 컨테이너에 파드 및 컨테이너 필드를 노출하는 두 가지 방법이 있다. 환경 변수를 활용하거나, 그리고 특수한 볼륨 타입으로 채워진 파일을 이용한다. 파드 및 컨테이너 필드를 노출하는 이 두 가지 방법을 다운워드 API라고 한다.

컨테이너가 쿠버네티스에 지나치게 종속되지 않으면서도 자기 자신에 대한 정보를 알고 있으면 유용할 때가 있다. 다운워드 API는 컨테이너가 자기 자신 혹은 클러스터에 대한 정보를, 쿠버네티스 클라이언트나 API 서버 없이도 사용할 수 있게 한다.

예를 들어, 잘 알려진 특정 환경 변수에다가 고유한 식별자를 넣어 사용하는 애플리케이션이 있다고 하자. 해당 애플리케이션에 맞게 작업할 수도 있겠지만, 이는 지루하고 오류가 나기 쉬울뿐더러, 낮은 결합이라는 원칙에도 위배된다. 대신, 파드의 이름을 식별자로 사용하고 잘 알려진 환경 변수에 파드의 이름을 넣는 것도 괜찮은 방법이다.

쿠버네티스에는 실행 중인 컨테이너에 파드 및 컨테이너 필드를 노출하는 두 가지 방법이 있다.

파드 및 컨테이너 필드를 노출하는 이 두 가지 방법을 다운워드 API라고 한다.

사용 가능한 필드

쿠버네티스 API 필드 중 일부만이 다운워드 API를 통해 접근 가능하다. 이 페이지에서는 사용 가능한 필드를 나열한다.

사용 가능한 파드 필드에 대한 정보는 fieldRef를 통해 넘겨줄 수 있다. API 레벨에서, 파드의 spec은 항상 하나 이상의 컨테이너를 정의한다. 사용 가능한 컨테이너 필드에 대한 정보는 resourceFiledRef를 통해 넘겨줄 수 있다.

fieldRef를 통해 접근 가능한 정보

대부분의 파드 필드는 환경 변수로써, 또는 다운워드 API 볼륨을 사용하여 컨테이너에 제공할 수 있다. 이런 두 가지 방법을 통해 사용 가능한 필드는 다음과 같다.

metadata.name
파드의 이름
metadata.namespace
파드가 속한 네임스페이스
metadata.uid
파드의 고유 ID
metadata.annotations['<KEY>']
파드의 어노테이션에서 <KEY>에 해당하는 값 (예를 들어, metadata.annotations['myannotation'])
metadata.labels['<KEY>']
파드의 레이블에서 <KEY>에 해당하는 문자열 (예를 들어, metadata.labels['mylabel'])
spec.serviceAccountName
파드의 서비스 어카운트
spec.nodeName
파드가 실행중인 노드
status.hostIP
파드가 할당된 노드의 기본 IP 주소
status.podIP
파드의 기본 IP 주소 (일반적으로 IPv4 주소)

추가적으로 아래 필드는 환경 변수가 아닌, 다운워드 API 볼륨의 fieldRef로만 접근 가능하다.

metadata.labels
파드의 모든 레이블로, 한 줄마다 하나의 레이블을 갖는(label-key="escaped-label-value") 형식을 취함
metadata.annotations
파드의 모든 어노테이션으로, 한 줄마다 하나의 어노테이션을 갖는(annotation-key="escaped-annotation-value") 형식을 취함

resourceFieldRef를 통해 접근 가능한 정보

컨테이너 필드는 CPU와 메모리 같은 리소스에 대한 요청 및 제한 값을 제공한다.

resource: limits.cpu
컨테이너의 CPU 제한
resource: requests.cpu
컨테이너의 CPU 요청
resource: limits.memory
컨테이너의 메모리 제한
resource: requests.memory
컨테이너의 메모리 요청
resource: limits.hugepages-*
컨테이너의 hugepage 제한 (DownwardAPIHugePages 기능 게이트가 활성화 된 경우)
resource: requests.hugepages-*
컨테이너의 hugepage 요청 (DownwardAPIHugePages 기능 게이트가 활성화 된 경우)
resource: limits.ephemeral-storage
컨테이너의 임시 스토리지 제한
resource: requests.ephemeral-storage
컨테이너의 임시 스토리지 요청

리소스 제한에 대한 참고 정보

컨테이너의 CPU와 메모리 제한을 명시하지 않고 다운워드 API로 이 정보들을 제공하려고 할 경우, kubelet은 기본적으로 노드의 할당 가능량에 기반하여 CPU와 메모리에 할당 가능한 최댓값을 노출시킨다.

다음 내용

자세한 정보는 다운워드API 볼륨를 참고한다.

다운워드 API를 사용하여 파드 및 컨테이너 정보를 노출시켜보자.