워크로드

쿠버네티스에서 배포할 수 있는 가장 작은 컴퓨트 오브젝트인 파드와, 이를 실행하는 데 도움이 되는 하이-레벨(higher-level) 추상화

워크로드는 쿠버네티스에서 구동되는 애플리케이션이다. 워크로드가 단일 컴포넌트이거나 함께 작동하는 여러 컴포넌트이든 관계없이, 쿠버네티스에서는 워크로드를 일련의 파드 집합 내에서 실행한다. 쿠버네티스에서 Pod 는 클러스터에서 실행 중인 컨테이너 집합을 나타낸다.

쿠버네티스 파드에는 정의된 라이프사이클이 있다. 예를 들어, 일단 파드가 클러스터에서 실행되고 나서 해당 파드가 동작 중인 노드에 심각한 오류가 발생하면 해당 노드의 모든 파드가 실패한다. 쿠버네티스는 이 수준의 실패를 최종(final)으로 취급한다. 사용자는 향후 노드가 복구되는 것과 상관 없이 Pod 를 새로 생성해야 한다.

그러나, 작업이 훨씬 쉽도록, 각 Pod 를 직접 관리할 필요는 없도록 만들었다. 대신, 사용자를 대신하여 파드 집합을 관리하는 워크로드 리소스 를 사용할 수 있다. 이러한 리소스는 지정한 상태와 일치하도록 올바른 수의 올바른 파드 유형이 실행되고 있는지 확인하는 컨트롤러를 구성한다.

쿠버네티스는 다음과 같이 여러 가지 빌트인(built-in) 워크로드 리소스를 제공한다.

  • DeploymentReplicaSet (레거시 리소스 레플리케이션컨트롤러(ReplicationController)를 대체). DeploymentDeployment 의 모든 Pod 가 필요 시 교체 또는 상호 교체 가능한 경우, 클러스터의 스테이트리스 애플리케이션 워크로드를 관리하기에 적합하다.
  • StatefulSet는 어떻게든 스테이트(state)를 추적하는 하나 이상의 파드를 동작하게 해준다. 예를 들면, 워크로드가 데이터를 지속적으로 기록하는 경우, 사용자는 PodPersistentVolume을 연계하는 StatefulSet 을 실행할 수 있다. 전체적인 회복력 향상을 위해서, StatefulSetPods 에서 동작 중인 코드는 동일한 StatefulSet 의 다른 Pods 로 데이터를 복제할 수 있다.
  • DaemonSet은 노드-로컬 기능(node-local facilities)을 제공하는 Pods 를 정의한다. 이러한 기능들은 클러스터를 운용하는 데 기본적인 것일 것이다. 예를 들면, 네트워킹 지원 도구 또는 add-on 등이 있다. DaemonSet 의 명세에 맞는 노드를 클러스터에 추가할 때마다, 컨트롤 플레인은 해당 신규 노드에 DaemonSet 을 위한 Pod 를 스케줄한다.
  • JobCronJob은 실행 완료 후 중단되는 작업을 정의한다. CronJobs 이 스케줄에 따라 반복되는 반면, 잡은 단 한 번의 작업을 나타낸다.

더 넓은 쿠버네티스 에코시스템 내에서는 추가적인 동작을 제공하는 제 3자의 워크로드 리소스도 찾을 수 있다. 커스텀 리소스 데피니션을 사용하면, 쿠버네티스 코어에서 제공하지 않는 특별한 동작을 원하는 경우 제 3자의 워크로드 리소스를 추가할 수 있다. 예를 들어, 사용자 애플리케이션을 위한 Pods 의 그룹을 실행하되 모든 파드가 가용한 경우가 아닌 경우 멈추고 싶다면(아마도 높은 처리량의 분산 처리를 하는 상황 같은), 사용자는 해당 기능을 제공하는 확장을 구현하거나 설치할 수 있다.

다음 내용

각 리소스에 대해 읽을 수 있을 뿐만 아니라, 리소스와 관련된 특정 작업에 대해서도 알아볼 수 있다.

코드를 구성(configuration)에서 분리하는 쿠버네티스의 메커니즘을 배우기 위해서는, 구성을 참고하길 바란다.

다음은 쿠버네티스가 애플리케이션의 파드를 어떻게 관리하는지를 알 수 있게 해주는 두 가지 개념이다.

일단 애플리케이션이 실행되면, 인터넷에서 서비스로 사용하거나, 웹 애플리케이션의 경우에만 인그레스(Ingress)를 이용하여 사용할 수 있다.

1 - 파드

파드(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 서브도메인 이름인지 확인한다.

파드와 컨트롤러

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

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

파드 템플릿

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

파드템플릿(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 (리눅스) 플래그를 사용하여 특권 모드를 활성화할 수 있다. 이는 네트워크 스택 조작이나 하드웨어 장치 접근과 같은 운영 체제 관리 기능을 사용하려는 컨테이너에 유용하다.

클러스터가 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.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 배열을 가진다.

  • PodScheduled: 파드가 노드에 스케줄되었다.
  • ContainersReady: 파드의 모든 컨테이너가 준비되었다.
  • Initialized: 모든 초기화 컨테이너가 성공적으로 완료(completed)되었다.
  • 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은 컨테이너 안에서 코드를 실행하거나, 또는 네트워크 요청을 전송한다.

체크 메커니즘

프로브를 사용하여 컨테이너를 체크하는 방법에는 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 이다.

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

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

FEATURE STATE: Kubernetes v1.0 [stable]

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

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

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

FEATURE STATE: Kubernetes v1.0 [stable]

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

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

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

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

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

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초의 작은 일회성 유예 기간 연장을 요청한다.
    2. kubelet은 컨테이너 런타임을 트리거하여 각 컨테이너 내부의 프로세스 1에 TERM 시그널을 보낸다.
  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을 트리거하여 즉시 정리를 시작한다.

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

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

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

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

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

다음 내용

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

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

리소스

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

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

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

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

파드 재시작 이유

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

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

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

다음 내용

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

FEATURE STATE: Kubernetes v1.19 [stable]

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

필수 구성 요소

노드 레이블

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

파드에 2개 이상의 topologySpreadConstraint가 정의되어 있으면, 각 제약 조건은 AND로 연결된다 - kube-scheduler는 새로운 파드의 모든 제약 조건을 만족하는 노드를 찾는다.

사용자는 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 를 사용하도록 제약 조건 중 하나를 수정할 수 있다.

노드 어피니티(Affinity) 및 노드 셀렉터(Selector)와의 상호 작용

스케줄러는 신규 파드에 spec.nodeSelector 또는 spec.affinity.nodeAffinity가 정의되어 있는 경우, 부합하지 않는 노드들을 차이(skew) 계산에서 생략한다.

예시: TopologySpreadConstraints와 노드 어피니티

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

스케줄러는 클러스터에 있는 모든 영역(zone) 또는 다른 토폴로지 도메인에 대한 사전 지식이 없다. 스케줄링은 클러스터의 기존 노드에서 결정된다. 노드 풀(또는 노드 그룹)이 0개의 노드로 스케일(scale)되고 사용자는 노드가 확장될 것으로 예상하는 경우, 자동 스케일되는 클러스터에서 문제가 발생할 수 있다. 이러한 토폴로지 도메인은 스케줄링에서 해당 도메인에 노드가 하나 이상 있을 때까지 고려되지 않을 것이기 때문이다.

기타 눈에 띄는 의미(semantics)

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

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

  • 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.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

내부 기본 제약

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 플러그인이 비활성화된다.

클러스터에 기본 파드 분배 제약 조건을 사용하지 않으려면, 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)를 참고한다.

알려진 제한사항

  • 파드가 제거된 이후에도 제약 조건이 계속 충족된다는 보장은 없다. 예를 들어 디플로이먼트를 스케일링 다운하면 그 결과로 파드의 분포가 불균형해질 수 있다. Descheduler를 사용하여 파드 분포를 다시 균형있게 만들 수 있다.
  • 파드와 일치하는 테인트(taint)가 된 노드가 존중된다. 이슈 80921을 본다.

다음 내용

1.4 - 중단(disruption)

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

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

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

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

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

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

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

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

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

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

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

클러스터에 자발적인 중단을 일으킬 수 있는 어떤 원인이 있는지 클러스터 관리자에게 문의하거나 클라우드 공급자에게 문의하고, 배포 문서를 참조해서 확인해야 한다. 만약 자발적인 중단을 일으킬 수 있는 원인이 없다면 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를 이용해서 애플리케이션의 중단에 견디도록 작성한다.
    • 다운타임 없음
    • 최소한의 리소스 중복
    • 클러스터 관리의 자동화 확대 적용
    • 내결함성이 있는 애플리케이션의 작성은 까다롭지만 자발적 중단를 허용하는 작업의 대부분은 오토스케일링과 비자발적 중단를 지원하는 작업과 겹친다.

다음 내용

1.5 - 임시(Ephemeral) 컨테이너

FEATURE STATE: Kubernetes v1.23 [beta]

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

임시 컨테이너 이해하기

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

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

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

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

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

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

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

임시 컨테이너의 사용

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

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

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

다음 내용

2 - 워크로드 리소스

2.1 - 디플로이먼트

디플로이먼트(Deployment)파드레플리카셋(ReplicaSet)에 대한 선언적 업데이트를 제공한다.

디플로이먼트에서 의도하는 상태 를 설명하고, 디플로이먼트 컨트롤러(Controller)는 현재 상태에서 의도하는 상태로 비율을 조정하며 변경한다. 새 레플리카셋을 생성하는 디플로이먼트를 정의하거나 기존 디플로이먼트를 제거하고, 모든 리소스를 새 디플로이먼트에 적용할 수 있다.

유스케이스

다음은 디플로이먼트의 일반적인 유스케이스이다.

디플로이먼트 생성

다음은 디플로이먼트의 예시이다. 예시는 3개의 nginx 파드를 불러오기 위한 레플리카셋을 생성한다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

이 예시에 대한 설명은 다음과 같다.

  • .metadata.name 필드에 따라 nginx-deployment 이름으로 디플로이먼트가 생성된다.

  • .spec.replicas 필드에 따라 디플로이먼트는 3개의 레플리카 파드를 생성한다.

  • .spec.selector 필드는 디플로이먼트가 관리할 파드를 찾는 방법을 정의한다. 이 사례에서는 파드 템플릿에 정의된 레이블(app: nginx)을 선택한다. 그러나 파드 템플릿 자체의 규칙이 만족되는 한, 보다 정교한 선택 규칙의 적용이 가능하다.

  • template 필드에는 다음 하위 필드가 포함되어 있다.

    • 파드는 .metadata.labels 필드를 사용해서 app: nginx 라는 레이블을 붙인다.
    • 파드 템플릿의 사양 또는 .template.spec 필드는 파드가 도커 허브nginx 1.14.2 버전 이미지를 실행하는 nginx 컨테이너 1개를 실행하는 것을 나타낸다.
    • 컨테이너 1개를 생성하고, .spec.template.spec.containers[0].name 필드를 사용해서 nginx 이름을 붙인다.

시작하기 전에, 쿠버네티스 클러스터가 시작되고 실행 중인지 확인한다. 위의 디플로이먼트를 생성하려면 다음 단계를 따른다.

  1. 다음 명령어를 실행해서 디플로이먼트를 생성한다.
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
  1. kubectl get deployments 을 실행해서 디플로이먼트가 생성되었는지 확인한다.

만약 디플로이먼트가 여전히 생성 중이면, 다음과 유사하게 출력된다.

NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   0/3     0            0           1s

클러스터에서 디플로이먼트를 점검할 때, 다음 필드가 표시된다.

  • NAME 은 네임스페이스에 있는 디플로이먼트 이름의 목록이다.
  • READY 는 사용자가 사용할 수 있는 애플리케이션의 레플리카의 수를 표시한다. ready/desired 패턴을 따른다.
  • UP-TO-DATE 는 의도한 상태를 얻기 위해 업데이트된 레플리카의 수를 표시한다.
  • AVAILABLE 은 사용자가 사용할 수 있는 애플리케이션 레플리카의 수를 표시한다.
  • AGE 는 애플리케이션의 실행된 시간을 표시한다.

.spec.replicas 필드에 따라 의도한 레플리카의 수가 3개인지 알 수 있다.

  1. 디플로이먼트의 롤아웃 상태를 보려면, kubectl rollout status deployment/nginx-deployment 를 실행한다.

    다음과 유사하게 출력된다.

    Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
    deployment "nginx-deployment" successfully rolled out
    
  2. 몇 초 후 kubectl get deployments 를 다시 실행한다. 다음과 유사하게 출력된다.

    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment   3/3     3            3           18s
    

    디플로이먼트에서 3개의 레플리카가 생성되었고, 모든 레플리카는 최신 상태(최신 파드 템플릿을 포함)이며 사용 가능한 것을 알 수 있다.

  3. 디플로이먼트로 생성된 레플리카셋(rs)을 보려면, kubectl get rs 를 실행한다. 다음과 유사하게 출력된다.

    NAME                          DESIRED   CURRENT   READY   AGE
    nginx-deployment-75675f5897   3         3         3       18s
    

    레플리카셋의 출력에는 다음 필드가 표시된다.

    • NAME 은 네임스페이스에 있는 레플리카셋 이름의 목록이다.
    • DESIRED 는 디플로이먼트의 생성 시 정의된 의도한 애플리케이션 레플리카 의 수를 표시한다. 이것이 의도한 상태 이다.
    • CURRENT 는 현재 실행 중인 레플리카의 수를 표시한다.
    • READY 는 사용자가 사용할 수 있는 애플리케이션의 레플리카의 수를 표시한다.
    • AGE 는 애플리케이션의 실행된 시간을 표시한다.

    레플리카셋의 이름은 항상 [DEPLOYMENT-NAME]-[RANDOM-STRING] 형식으로 된 것을 알 수 있다. 무작위 문자열은 무작위로 생성되며, pod-template-hash 를 시드(seed)로 사용한다.

  4. 각 파드에 자동으로 생성된 레이블을 보려면, kubectl get pods --show-labels 를 실행한다. 다음과 유사하게 출력된다.

    NAME                                READY     STATUS    RESTARTS   AGE       LABELS
    nginx-deployment-75675f5897-7ci7o   1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
    nginx-deployment-75675f5897-kzszj   1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
    nginx-deployment-75675f5897-qqcnn   1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
    

    만들어진 레플리카셋은 실행 중인 3개의 nginx 파드를 보장한다.

Pod-template-hash 레이블

pod-template-hash 레이블은 디플로이먼트 컨트롤러에 의해서 디플로이먼트가 생성 또는 채택한 모든 레플리카셋에 추가된다.

이 레이블은 디플로이먼트의 자식 레플리카셋이 겹치지 않도록 보장한다. 레플리카셋의 PodTemplate 을 해싱하고, 해시 결과를 레플리카셋 셀렉터, 파드 템플릿 레이블 및 레플리카셋 이 가질 수 있는 기존의 모든 파드에 레이블 값으로 추가해서 사용하도록 생성한다.

디플로이먼트 업데이트

다음 단계에 따라 디플로이먼트를 업데이트한다.

  1. nginx:1.14.2 이미지 대신 nginx:1.16.1 이미지를 사용하도록 nginx 파드를 업데이트 한다.

    kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.16.1
    

    또는 다음의 명령어를 사용한다.

    kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
    

    다음과 유사하게 출력된다.

    deployment.apps/nginx-deployment image updated
    

    대안으로 디플로이먼트를 edit 해서 .spec.template.spec.containers[0].imagenginx:1.14.2 에서 nginx:1.16.1 로 변경한다.

    kubectl edit deployment/nginx-deployment
    

    다음과 유사하게 출력된다.

    deployment.apps/nginx-deployment edited
    
  2. 롤아웃 상태를 보려면 다음을 실행한다.

    kubectl rollout status deployment/nginx-deployment
    

    이와 유사하게 출력된다.

    Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
    

    또는

    deployment "nginx-deployment" successfully rolled out
    

업데이트된 디플로이먼트에 대해 자세한 정보 보기

  • 롤아웃이 성공하면 kubectl get deployments 를 실행해서 디플로이먼트를 볼 수 있다. 이와 유사하게 출력된다.

    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment   3/3     3            3           36s
    
  • kubectl get rs 를 실행해서 디플로이먼트가 새 레플리카셋을 생성해서 파드를 업데이트 했는지 볼 수 있고, 새 레플리카셋을 최대 3개의 레플리카로 스케일 업, 이전 레플리카셋을 0개의 레플리카로 스케일 다운한다.

    kubectl get rs
    

    이와 유사하게 출력된다.

    NAME                          DESIRED   CURRENT   READY   AGE
    nginx-deployment-1564180365   3         3         3       6s
    nginx-deployment-2035384211   0         0         0       36s
    
  • get pods 를 실행하면 새 파드만 표시된다.

    kubectl get pods
    

    이와 유사하게 출력된다.

    NAME                                READY     STATUS    RESTARTS   AGE
    nginx-deployment-1564180365-khku8   1/1       Running   0          14s
    nginx-deployment-1564180365-nacti   1/1       Running   0          14s
    nginx-deployment-1564180365-z9gth   1/1       Running   0          14s
    

    다음에 이러한 파드를 업데이트 하려면 디플로이먼트의 파드 템플릿만 다시 업데이트 하면 된다.

    디플로이먼트는 업데이트되는 동안 일정한 수의 파드만 중단되도록 보장한다. 기본적으로 적어도 의도한 파드 수의 75% 이상이 동작하도록 보장한다(최대 25% 불가).

    또한 디플로이먼트는 의도한 파드 수 보다 더 많이 생성되는 파드의 수를 제한한다. 기본적으로, 의도한 파드의 수 기준 최대 125%까지만 추가 파드가 동작할 수 있도록 제한한다(최대 25% 까지).

    예를 들어, 위 디플로이먼트를 자세히 살펴보면 먼저 새로운 파드를 생성한 다음 이전 파드를 삭제하고, 새로운 파드를 만든 것을 볼 수 있다. 충분한 수의 새로운 파드가 나올 때까지 이전 파드를 죽이지 않으며, 충분한 수의 이전 파드들이 죽기 전까지 새로운 파드를 만들지 않는다. 이것은 최소 2개의 파드를 사용할 수 있게 하고, 최대 4개의 파드를 사용할 수 있게 한다.

  • 디플로이먼트의 세부 정보 가져오기

    kubectl describe deployments
    

    이와 유사하게 출력된다.

    Name:                   nginx-deployment
    Namespace:              default
    CreationTimestamp:      Thu, 30 Nov 2017 10:56:25 +0000
    Labels:                 app=nginx
    Annotations:            deployment.kubernetes.io/revision=2
    Selector:               app=nginx
    Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
    StrategyType:           RollingUpdate
    MinReadySeconds:        0
    RollingUpdateStrategy:  25% max unavailable, 25% max surge
    Pod Template:
      Labels:  app=nginx
       Containers:
        nginx:
          Image:        nginx:1.16.1
          Port:         80/TCP
          Environment:  <none>
          Mounts:       <none>
        Volumes:        <none>
      Conditions:
        Type           Status  Reason
        ----           ------  ------
        Available      True    MinimumReplicasAvailable
        Progressing    True    NewReplicaSetAvailable
      OldReplicaSets:  <none>
      NewReplicaSet:   nginx-deployment-1564180365 (3/3 replicas created)
      Events:
        Type    Reason             Age   From                   Message
        ----    ------             ----  ----                   -------
        Normal  ScalingReplicaSet  2m    deployment-controller  Scaled up replica set nginx-deployment-2035384211 to 3
        Normal  ScalingReplicaSet  24s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 1
        Normal  ScalingReplicaSet  22s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 2
        Normal  ScalingReplicaSet  22s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 2
        Normal  ScalingReplicaSet  19s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 1
        Normal  ScalingReplicaSet  19s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 3
        Normal  ScalingReplicaSet  14s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 0
    

    처음 디플로이먼트를 생성했을 때, 디플로이먼트가 레플리카셋(nginx-deployment-2035384211)을 생성해서 3개의 레플리카로 직접 스케일 업한 것을 볼 수 있다. 디플로이먼트를 업데이트할 때 새 레플리카셋(nginx-deployment-1564180365)을 생성하고, 1개로 스케일 업한 다음 이전 레플리카셋을 2개로 스케일 다운해서, 최소 2개의 파드를 사용할 수 있고 최대 4개의 파드가 항상 생성되어 있도록 하였다. 이후 지속해서 같은 롤링 업데이트 정책으로 새 레플리카셋은 스케일 업하고 이전 레플리카셋은 스케일 다운한다. 마지막으로 새로운 레플리카셋에 3개의 사용 가능한 레플리카가 구성되며, 이전 레플리카셋은 0개로 스케일 다운된다.

롤오버(일명 인-플라이트 다중 업데이트)

디플로이먼트 컨트롤러는 각 시간마다 새로운 디플로이먼트에서 레플리카셋이 의도한 파드를 생성하고 띄우는 것을 주시한다. 만약 디플로이먼트가 업데이트되면, 기존 레플리카셋에서 .spec.selector 레이블과 일치하는 파드를 컨트롤 하지만, 템플릿과 .spec.template 이 불일치하면 스케일 다운이 된다. 결국 새로운 레플리카셋은 .spec.replicas 로 스케일되고, 모든 기존 레플리카셋은 0개로 스케일된다.

만약 기존 롤아웃이 진행되는 중에 디플로이먼트를 업데이트하는 경우 디플로이먼트가 업데이트에 따라 새 레플리카셋을 생성하고, 스케일 업하기 시작한다. 그리고 이전에 스케일 업 하던 레플리카셋에 롤오버 한다. --이것은 기존 레플리카셋 목록에 추가하고 스케일 다운을 할 것이다.

예를 들어 디플로이먼트로 nginx:1.14.2 레플리카를 5개 생성을 한다. 하지만 nginx:1.14.2 레플리카 3개가 생성되었을 때 디플로이먼트를 업데이트해서 nginx:1.16.1 레플리카 5개를 생성성하도록 업데이트를 한다고 가정한다. 이 경우 디플로이먼트는 즉시 생성된 3개의 nginx:1.14.2 파드 3개를 죽이기 시작하고 nginx:1.16.1 파드를 생성하기 시작한다. 이것은 과정이 변경되기 전 nginx:1.14.2 레플리카 5개가 생성되는 것을 기다리지 않는다.

레이블 셀렉터 업데이트

일반적으로 레이블 셀렉터를 업데이트 하는 것을 권장하지 않으며 셀렉터를 미리 계획하는 것을 권장한다. 어떤 경우든 레이블 셀렉터의 업데이트를 해야하는 경우 매우 주의하고, 모든 영향을 파악했는지 확인해야 한다.

  • 셀렉터 추가 시 디플로이먼트의 사양에 있는 파드 템플릿 레이블도 새 레이블로 업데이트해야 한다. 그렇지 않으면 유효성 검사 오류가 반환된다. 이 변경은 겹치지 않는 변경으로 새 셀렉터가 이전 셀렉터로 만든 레플리카셋과 파드를 선택하지 않게 되고, 그 결과로 모든 기존 레플리카셋은 고아가 되며, 새로운 레플리카셋을 생성하게 된다.
  • 셀렉터 업데이트는 기존 셀렉터 키 값을 변경하며, 결과적으로 추가와 동일한 동작을 한다.
  • 셀렉터 삭제는 디플로이먼트 셀렉터의 기존 키를 삭제하며 파드 템플릿 레이블의 변경을 필요로 하지 않는다. 기존 레플리카셋은 고아가 아니고, 새 레플리카셋은 생성되지 않는다. 그러나 제거된 레이블은 기존 파드와 레플리카셋에 여전히 존재한다는 점을 참고해야 한다.

디플로이먼트 롤백

때때로 디플로이먼트의 롤백을 원할 수도 있다. 예를 들어 디플로이먼트가 지속적인 충돌로 안정적이지 않은 경우. 기본적으로 모든 디플로이먼트의 롤아웃 기록은 시스템에 남아있어 언제든지 원할 때 롤백이 가능하다 (이 사항은 수정 기록에 대한 상한 수정을 통해서 변경할 수 있다).

  • 디플로이먼트를 업데이트하는 동안 이미지 이름을 nginx:1.16.1 이 아닌 nginx:1.161 로 입력해서 오타를 냈다고 가정한다.

    kubectl set image deployment/nginx-deployment nginx=nginx:1.161 
    

    이와 유사하게 출력된다.

    deployment.apps/nginx-deployment image updated
    
  • 롤아웃이 고착 된다. 고착된 롤아웃 상태를 확인할 수 있다.

    kubectl rollout status deployment/nginx-deployment
    

    이와 유사하게 출력된다.

    Waiting for rollout to finish: 1 out of 3 new replicas have been updated...
    
  • Ctrl-C 를 눌러 위의 롤아웃 상태 보기를 중지한다. 고착된 롤아웃 상태에 대한 자세한 정보는 이 것을 더 읽어본다.

  • 이전 레플리카는 2개(nginx-deployment-1564180365nginx-deployment-2035384211), 새 레플리카는 1개(nginx-deployment-3066724191)임을 알 수 있다.

    kubectl get rs
    

    이와 유사하게 출력된다.

    NAME                          DESIRED   CURRENT   READY   AGE
    nginx-deployment-1564180365   3         3         3       25s
    nginx-deployment-2035384211   0         0         0       36s
    nginx-deployment-3066724191   1         1         0       6s
    
  • 생성된 파드를 보면, 새로운 레플리카셋에 생성된 1개의 파드가 이미지 풀 루프(pull loop)에서 고착된 것을 볼 수 있다.

    kubectl get pods
    

    이와 유사하게 출력된다.

    NAME                                READY     STATUS             RESTARTS   AGE
    nginx-deployment-1564180365-70iae   1/1       Running            0          25s
    nginx-deployment-1564180365-jbqqo   1/1       Running            0          25s
    nginx-deployment-1564180365-hysrc   1/1       Running            0          25s
    nginx-deployment-3066724191-08mng   0/1       ImagePullBackOff   0          6s
    
  • 디플로이먼트에 대한 설명 보기

    kubectl describe deployment
    

    이와 유사하게 출력된다.

    Name:           nginx-deployment
    Namespace:      default
    CreationTimestamp:  Tue, 15 Mar 2016 14:48:04 -0700
    Labels:         app=nginx
    Selector:       app=nginx
    Replicas:       3 desired | 1 updated | 4 total | 3 available | 1 unavailable
    StrategyType:       RollingUpdate
    MinReadySeconds:    0
    RollingUpdateStrategy:  25% max unavailable, 25% max surge
    Pod Template:
      Labels:  app=nginx
      Containers:
       nginx:
        Image:        nginx:1.161
        Port:         80/TCP
        Host Port:    0/TCP
        Environment:  <none>
        Mounts:       <none>
      Volumes:        <none>
    Conditions:
      Type           Status  Reason
      ----           ------  ------
      Available      True    MinimumReplicasAvailable
      Progressing    True    ReplicaSetUpdated
    OldReplicaSets:     nginx-deployment-1564180365 (3/3 replicas created)
    NewReplicaSet:      nginx-deployment-3066724191 (1/1 replicas created)
    Events:
      FirstSeen LastSeen    Count   From                    SubObjectPath   Type        Reason              Message
      --------- --------    -----   ----                    -------------   --------    ------              -------
      1m        1m          1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-2035384211 to 3
      22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 1
      22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 2
      22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 2
      21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 1
      21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 3
      13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 0
      13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-3066724191 to 1
    

    이 문제를 해결하려면 디플로이먼트를 안정적인 이전 수정 버전으로 롤백해야 한다.

디플로이먼트의 롤아웃 기록 확인

다음 순서에 따라 롤아웃 기록을 확인한다.

  1. 먼저 이 디플로이먼트의 수정 사항을 확인한다.

    kubectl rollout history deployment/nginx-deployment
    

    이와 유사하게 출력된다.

    deployments "nginx-deployment"
    REVISION    CHANGE-CAUSE
    1           kubectl apply --filename=https://k8s.io/examples/controllers/nginx-deployment.yaml
    2           kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
    3           kubectl set image deployment/nginx-deployment nginx=nginx:1.161
    

    CHANGE-CAUSE 는 수정 생성시 디플로이먼트 주석인 kubernetes.io/change-cause 에서 복사한다. 다음에 대해 CHANGE-CAUSE 메시지를 지정할 수 있다.

    • 디플로이먼트에 kubectl annotate deployment/nginx-deployment kubernetes.io/change-cause="image updated to 1.16.1" 로 주석을 단다.
    • 수동으로 리소스 매니페스트 편집.
  2. 각 수정 버전의 세부 정보를 보려면 다음을 실행한다.

    kubectl rollout history deployment/nginx-deployment --revision=2
    

    이와 유사하게 출력된다.

    deployments "nginx-deployment" revision 2
      Labels:       app=nginx
              pod-template-hash=1159050644
      Annotations:  kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
      Containers:
       nginx:
        Image:      nginx:1.16.1
        Port:       80/TCP
         QoS Tier:
            cpu:      BestEffort
            memory:   BestEffort
        Environment Variables:      <none>
      No volumes.
    

이전 수정 버전으로 롤백

다음 단계에 따라 디플로이먼트를 현재 버전에서 이전 버전인 버전 2로 롤백한다.

  1. 이제 현재 롤아웃의 실행 취소 및 이전 수정 버전으로 롤백 하기로 결정했다.

    kubectl rollout undo deployment/nginx-deployment
    

    이와 유사하게 출력된다.

    deployment.apps/nginx-deployment rolled back
    

    Alternatively, you can rollback to a specific revision by specifying it with --to-revision:

    kubectl rollout undo deployment/nginx-deployment --to-revision=2
    

    이와 유사하게 출력된다.

    deployment.apps/nginx-deployment rolled back
    

    롤아웃 관련 명령에 대한 자세한 내용은 kubectl rollout을 참조한다.

    이제 디플로이먼트가 이전 안정 수정 버전으로 롤백 된다. 버전 2로 롤백하기 위해 DeploymentRollback 이벤트가 디플로이먼트 컨트롤러에서 생성되는 것을 볼 수 있다.

  2. 만약 롤백에 성공하고, 디플로이먼트가 예상대로 실행되는지 확인하려면 다음을 실행한다.

    kubectl get deployment nginx-deployment
    

    이와 유사하게 출력된다.

    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment   3/3     3            3           30m
    
  3. 디플로이먼트의 설명 가져오기.

    kubectl describe deployment nginx-deployment
    

    이와 유사하게 출력된다.

    Name:                   nginx-deployment
    Namespace:              default
    CreationTimestamp:      Sun, 02 Sep 2018 18:17:55 -0500
    Labels:                 app=nginx
    Annotations:            deployment.kubernetes.io/revision=4
                            kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
    Selector:               app=nginx
    Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
    StrategyType:           RollingUpdate
    MinReadySeconds:        0
    RollingUpdateStrategy:  25% max unavailable, 25% max surge
    Pod Template:
      Labels:  app=nginx
      Containers:
       nginx:
        Image:        nginx:1.16.1
        Port:         80/TCP
        Host Port:    0/TCP
        Environment:  <none>
        Mounts:       <none>
      Volumes:        <none>
    Conditions:
      Type           Status  Reason
      ----           ------  ------
      Available      True    MinimumReplicasAvailable
      Progressing    True    NewReplicaSetAvailable
    OldReplicaSets:  <none>
    NewReplicaSet:   nginx-deployment-c4747d96c (3/3 replicas created)
    Events:
      Type    Reason              Age   From                   Message
      ----    ------              ----  ----                   -------
      Normal  ScalingReplicaSet   12m   deployment-controller  Scaled up replica set nginx-deployment-75675f5897 to 3
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 1
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 2
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 2
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 1
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 3
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 0
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-595696685f to 1
      Normal  DeploymentRollback  15s   deployment-controller  Rolled back deployment "nginx-deployment" to revision 2
      Normal  ScalingReplicaSet   15s   deployment-controller  Scaled down replica set nginx-deployment-595696685f to 0
    

디플로이먼트 스케일링

다음 명령어를 사용해서 디플로이먼트의 스케일을 할 수 있다.

kubectl scale deployment/nginx-deployment --replicas=10

이와 유사하게 출력된다.

deployment.apps/nginx-deployment scaled

가령 클러스터에서 horizontal Pod autoscaling를 설정 한 경우 디플로이먼트에 대한 오토스케일러를 설정할 수 있다. 그리고 기존 파드의 CPU 사용률을 기준으로 실행할 최소 파드 및 최대 파드의 수를 선택할 수 있다.

kubectl autoscale deployment/nginx-deployment --min=10 --max=15 --cpu-percent=80

이와 유사하게 출력된다.

deployment.apps/nginx-deployment scaled

비례적 스케일링(Proportional Scaling)

디플로이먼트 롤링업데이트는 여러 버전의 애플리케이션을 동시에 실행할 수 있도록 지원한다. 사용자 또는 오토스케일러가 롤아웃 중에 있는 디플로이먼트 롤링 업데이트를 스케일링 하는 경우(진행중 또는 일시 중지 중), 디플로이먼트 컨트롤러는 위험을 줄이기 위해 기존 활성화된 레플리카셋(파드와 레플리카셋)의 추가 레플리카의 균형을 조절 한다. 이것을 proportional scaling 라 부른다.

예를 들어, 10개의 레플리카를 디플로이먼트로 maxSurge=3, 그리고 maxUnavailable=2 로 실행 한다.

  • 디플로이먼트에 있는 10개의 레플리카가 실행되는지 확인한다.

    kubectl get deploy
    

    이와 유사하게 출력된다.

    NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment     10        10        10           10          50s
    
  • 클러스터 내부에서 확인할 수 없는 새 이미지로 업데이트 된다.

    kubectl set image deployment/nginx-deployment nginx=nginx:sometag
    

    이와 유사하게 출력된다.

    deployment.apps/nginx-deployment image updated
    
  • 이미지 업데이트는 레플리카셋 nginx-deployment-1989198191 으로 새로운 롤 아웃이 시작하지만, 위에서 언급한 maxUnavailable 의 요구 사항으로 인해 차단된다. 롤아웃 상태를 확인한다.

    kubectl get rs
    
    이와 유사하게 출력된다.
    
    NAME                          DESIRED   CURRENT   READY     AGE
    nginx-deployment-1989198191   5         5         0         9s
    nginx-deployment-618515232    8         8         8         1m
    
  • 그 다음 디플로이먼트에 대한 새로운 스케일링 요청이 함께 따라온다. 오토스케일러는 디플로이먼트 레플리카를 15로 증가시킨다. 디플로이먼트 컨트롤러는 새로운 5개의 레플리카의 추가를 위한 위치를 결정해야 한다. 만약 비례적 스케일링을 사용하지 않으면 5개 모두 새 레플리카셋에 추가된다. 비례적 스케일링으로 추가 레플리카를 모든 레플리카셋에 걸쳐 분산할 수 있다. 비율이 높을수록 가장 많은 레플리카가 있는 레플리카셋으로 이동하고, 비율이 낮을 수록 적은 레플리카가 있는 레플리카셋으로 이동한다. 남은 것들은 대부분의 레플리카가 있는 레플리카셋에 추가된다. 0개의 레플리카가 있는 레플리카셋은 스케일 업 되지 않는다.

위의 예시에서 기존 레플리카셋에 3개의 레플리카가 추가되고, 2개의 레플리카는 새 레플리카에 추가된다. 결국 롤아웃 프로세스는 새 레플리카가 정상이라고 가정하면 모든 레플리카를 새 레플리카셋으로 이동시킨다. 이를 확인하려면 다음을 실행한다.

kubectl get deploy

이와 유사하게 출력된다.

NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment     15        18        7            8           7m

롤아웃 상태는 레플리카가 각 레플리카셋에 어떻게 추가되었는지 확인한다.

kubectl get rs

이와 유사하게 출력된다.

NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   7         7         0         7m
nginx-deployment-618515232    11        11        11        7m

디플로이먼트 롤아웃 일시 중지와 재개

디플로이먼트를 업데이트할 때 (또는 계획할 때), 하나 이상의 업데이트를 트리거하기 전에 해당 디플로이먼트에 대한 롤아웃을 일시 중지할 수 있다. 변경 사항을 적용할 준비가 되면, 디플로이먼트 롤아웃을 재개한다. 이러한 방법으로, 불필요한 롤아웃을 트리거하지 않고 롤아웃 일시 중지와 재개 사이에 여러 수정 사항을 적용할 수 있다.

  • 예를 들어, 생성된 디플로이먼트의 경우

    디플로이먼트 상세 정보를 가져온다.

    kubectl get deploy
    

    이와 유사하게 출력된다.

    NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    nginx     3         3         3            3           1m
    

    롤아웃 상태를 가져온다.

    kubectl get rs
    

    이와 유사하게 출력된다.

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   3         3         3         1m
    
  • 다음 명령을 사용해서 일시 중지한다.

    kubectl rollout pause deployment/nginx-deployment
    

    이와 유사하게 출력된다.

    deployment.apps/nginx-deployment paused
    
  • 그런 다음 디플로이먼트의 이미지를 업데이트 한다.

    kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
    

    이와 유사하게 출력된다.

    deployment.apps/nginx-deployment image updated
    
  • 새로운 롤아웃이 시작되지 않는다.

    kubectl rollout history deployment/nginx-deployment
    

    이와 유사하게 출력된다.

    deployments "nginx"
    REVISION  CHANGE-CAUSE
    1   <none>
    
  • 기존 레플리카셋이 변경되지 않았는지 확인하기 위해 롤아웃 상태를 출력한다.

    kubectl get rs
    

    이와 유사하게 출력된다.

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   3         3         3         2m
    
  • 예를 들어 사용할 리소스를 업데이트하는 것처럼 원하는 만큼 업데이트할 수 있다.

    kubectl set resources deployment/nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi
    

    이와 유사하게 출력된다.

    deployment.apps/nginx-deployment resource requirements updated
    

    디플로이먼트 롤아웃을 일시 중지하기 전 디플로이먼트의 초기 상태는 해당 기능을 지속한다. 그러나 디플로이먼트 롤아웃이 일시 중지한 상태에서는 디플로이먼트의 새 업데이트에 영향을 주지 않는다.

  • 결국, 디플로이먼트 롤아웃을 재개하고 새로운 레플리카셋이 새로운 업데이트를 제공하는 것을 관찰한다.

    kubectl rollout resume deployment/nginx-deployment
    

    이와 유사하게 출력된다.

    deployment.apps/nginx-deployment resumed
    
  • 롤아웃이 완료될 때까지 상태를 관찰한다.

    kubectl get rs -w
    

    이와 유사하게 출력된다.

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   2         2         2         2m
    nginx-3926361531   2         2         0         6s
    nginx-3926361531   2         2         1         18s
    nginx-2142116321   1         2         2         2m
    nginx-2142116321   1         2         2         2m
    nginx-3926361531   3         2         1         18s
    nginx-3926361531   3         2         1         18s
    nginx-2142116321   1         1         1         2m
    nginx-3926361531   3         3         1         18s
    nginx-3926361531   3         3         2         19s
    nginx-2142116321   0         1         1         2m
    nginx-2142116321   0         1         1         2m
    nginx-2142116321   0         0         0         2m
    nginx-3926361531   3         3         3         20s
    
  • 롤아웃 최신 상태를 가져온다.

    kubectl get rs
    

    이와 유사하게 출력된다.

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   0         0         0         2m
    nginx-3926361531   3         3         3         28s
    

디플로이먼트 상태

디플로이먼트는 라이프사이클 동안 다양한 상태로 전환된다. 이는 새 레플리카셋을 롤아웃하는 동안 진행 중이 될 수 있고, 완료이거나 진행 실패일 수 있다.

디플로이먼트 진행 중

쿠버네티스는 다음 작업중 하나를 수행할 때 디플로이먼트를 진행 중 으로 표시한다.

  • 디플로이먼트로 새 레플리카셋을 생성.
  • 디플로이먼트로 새로운 레플리카셋을 스케일 업.
  • 디플로이먼트로 기존 레플리카셋을 스케일 다운.
  • 새 파드가 준비되거나 이용할 수 있음(최소 준비 시간(초) 동안 준비됨).

롤아웃이 "진행 중" 상태가 되면, 디플로이먼트 컨트롤러는 디플로이먼트의 .status.conditions에 다음 속성을 포함하는 컨디션을 추가한다.

  • type: Progressing
  • status: "True"
  • reason: NewReplicaSetCreated | reason: FoundNewReplicaSet | reason: ReplicaSetUpdated

kubectl rollout status 를 사용해서 디플로이먼트의 진행사황을 모니터할 수 있다.

디플로이먼트 완료

쿠버네티스는 다음과 같은 특성을 가지게 되면 디플로이먼트를 완료 로 표시한다.

  • 디플로이먼트과 관련된 모든 레플리카가 지정된 최신 버전으로 업데이트 되었을 때. 즉, 요청한 모든 업데이트가 완료되었을 때.
  • 디플로이먼트와 관련한 모든 레플리카를 사용할 수 있을 때.
  • 디플로이먼트에 대해 이전 복제본이 실행되고 있지 않을 때.

롤아웃이 "완료" 상태가 되면, 디플로이먼트 컨트롤러는 디플로이먼트의 .status.conditions에 다음 속성을 포함하는 컨디션을 추가한다.

  • type: Progressing
  • status: "True"
  • reason: NewReplicaSetAvailable

Progressing 컨디션은 새로운 롤아웃이 시작되기 전까지는 "True" 상태값을 유지할 것이다. 레플리카의 가용성이 변경되는 경우에도(이 경우 Available 컨디션에 영향을 미침) 컨디션은 유지된다.

kubectl rollout status 를 사용해서 디플로이먼트가 완료되었는지 확인할 수 있다. 만약 롤아웃이 성공적으로 완료되면 kubectl rollout status 는 종료 코드로 0이 반환된다.

kubectl rollout status deployment/nginx-deployment

이와 유사하게 출력된다.

Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deployment" successfully rolled out

그리고 kubectl rollout 의 종료 상태는 0(success)이다.

echo $?
0

디플로이먼트 실패

디플로이먼트시 새 레플리카셋인 완료되지 않은 상태에서는 배포를 시도하면 고착될 수 있다. 이 문제는 다음 몇 가지 요인으로 인해 발생한다.

  • 할당량 부족
  • 준비성 프로브(readiness probe)의 실패
  • 이미지 풀 에러
  • 권한 부족
  • 범위 제한
  • 애플리케이션 런타임의 잘못된 구성

이 조건을 찾을 수 있는 한 가지 방법은 디플로이먼트 스펙에서 데드라인 파라미터를 지정하는 것이다 (.spec.progressDeadlineSeconds). .spec.progressDeadlineSeconds 는 (디플로이먼트 상태에서) 디플로이먼트의 진행이 정지되었음을 나타내는 디플로이먼트 컨트롤러가 대기하는 시간(초)를 나타낸다.

다음 kubectl 명령어로 progressDeadlineSeconds 를 설정해서 컨트롤러가 10분 후 디플로이먼트 롤아웃에 대한 진행 상태의 부족에 대한 리포트를 수행하게 한다.

kubectl patch deployment/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'

이와 유사하게 출력된다.

deployment.apps/nginx-deployment patched

만약 데드라인을 넘어서면 디플로이먼트 컨트롤러는 디플로이먼트의 .status.conditions 속성에 다음의 디플로이먼트 컨디션(DeploymentCondition)을 추가한다.

  • type: Progressing
  • status: "False"
  • reason: ProgressDeadlineExceeded

이 컨디션은 일찍 실패할 수도 있으며 이러한 경우 ReplicaSetCreateError를 이유로 상태값을 "False"로 설정한다. 또한, 디플로이먼트 롤아웃이 완료되면 데드라인은 더 이상 고려되지 않는다.

컨디션 상태에 대한 자세한 내용은 쿠버네티스 API 규칙을 참고한다.

설정한 타임아웃이 낮거나 일시적으로 처리될 수 있는 다른 종료의 에러로 인해 디플로이먼트에 일시적인 에러가 발생할 수 있다. 예를 들어, 할당량이 부족하다고 가정해보자. 만약 디플로이먼트를 설명하려면 다음 섹션을 확인한다.

kubectl describe deployment nginx-deployment

이와 유사하게 출력된다.

<...>
Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     True    ReplicaSetUpdated
  ReplicaFailure  True    FailedCreate
<...>

만약 kubectl get deployment nginx-deployment -o yaml 을 실행하면 디플로이먼트 상태는 다음과 유사하다.

status:
  availableReplicas: 2
  conditions:
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: Replica set "nginx-deployment-4262182780" is progressing.
    reason: ReplicaSetUpdated
    status: "True"
    type: Progressing
  - lastTransitionTime: 2016-10-04T12:25:42Z
    lastUpdateTime: 2016-10-04T12:25:42Z
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota:
      object-counts, requested: pods=1, used: pods=3, limited: pods=2'
    reason: FailedCreate
    status: "True"
    type: ReplicaFailure
  observedGeneration: 3
  replicas: 2
  unavailableReplicas: 2

결국, 디플로이먼트 진행 데드라인을 넘어서면, 쿠버네티스는 진행 컨디션의 상태와 이유를 업데이트한다.

Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     False   ProgressDeadlineExceeded
  ReplicaFailure  True    FailedCreate

디플로이먼트를 스케일 다운하거나, 실행 중인 다른 컨트롤러를 스케일 다운하거나, 네임스페이스에서 할당량을 늘려서 할당량이 부족한 문제를 해결할 수 있다. 만약 할당량 컨디션과 디플로이먼트 롤아웃이 완료되어 디플로이먼트 컨트롤러를 만족한다면 성공한 컨디션의 디플로이먼트 상태가 업데이트를 볼 수 있다(status: "True"reason: NewReplicaSetAvailable).

Conditions:
  Type          Status  Reason
  ----          ------  ------
  Available     True    MinimumReplicasAvailable
  Progressing   True    NewReplicaSetAvailable

type: Availablestatus: "True" 는 디플로이먼트가 최소한의 가용성을 가지고 있는 것을 의미한다. 최소한의 가용성은 디플로이먼트 계획에 명시된 파라미터에 의해 결정된다. type: Progressingstatus: "True" 는 디플로이먼트가 롤아웃 도중에 진행 중 이거나, 성공적으로 완료되었으며, 진행 중 최소한으로 필요한 새로운 레플리카를 이용 가능하다는 것이다. (자세한 내용은 특정 조건의 이유를 참조한다. 이 경우 reason: NewReplicaSetAvailable 는 배포가 완료되었음을 의미한다.)

kubectl rollout status 를 사용해서 디플로이먼트의 진행이 실패되었는지 확인할 수 있다. kubectl rollout status 는 디플로이먼트의 진행 데드라인을 초과하면 0이 아닌 종료 코드를 반환한다.

kubectl rollout status deployment/nginx-deployment

이와 유사하게 출력된다.

Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
error: deployment "nginx" exceeded its progress deadline

그리고 kubectl rollout 의 종료 상태는 1(error를 의미함)이다.

echo $?
1

실패한 디플로이먼트에서의 운영

완료된 디플로이먼트에 적용되는 모든 행동은 실패한 디플로이먼트에도 적용된다. 디플로이먼트 파드 템플릿에서 여러 개의 수정사항을 적용해야하는 경우 스케일 업/다운 하거나, 이전 수정 버전으로 롤백하거나, 일시 중지할 수 있다.

정책 초기화

디플로이먼트의 .spec.revisionHistoryLimit 필드를 설정해서 디플로이먼트에서 유지해야 하는 이전 레플리카셋의 수를 명시할 수 있다. 나머지는 백그라운드에서 가비지-수집이 진행된다. 기본적으로 10으로 되어 있다.

카나리 디플로이먼트

만약 디플로이먼트를 이용해서 일부 사용자 또는 서버에 릴리스를 롤아웃 하기 위해서는 리소스 관리에 설명된 카나리 패던에 따라 각 릴리스 마다 하나씩 여러 디플로이먼트를 생성할 수 있다.

디플로이먼트 사양 작성

다른 모든 쿠버네티스 설정과 마찬가지로 디플로이먼트에는 .apiVersion, .kind 그리고 .metadata 필드가 필요하다. 설정 파일 작업에 대한 일반적인 내용은 애플리케이션 배포하기, 컨테이너 구성하기 그리고 kubectl을 사용해서 리소스 관리하기 문서를 참조한다. 디플로이먼트 오브젝트의 이름은 유효한 DNS 서브도메인 이름이어야 한다.

디플로이먼트에는 .spec 섹션도 필요하다.

파드 템플릿

.spec.template.spec.selector.spec 에서 유일한 필수 필드이다.

.spec.template파드 템플릿이다. 이것은 파드와 정확하게 동일한 스키마를 가지고 있고, 중첩된 것을 제외하면 apiVersionkind 를 가지고 있지 않는다.

파드에 필요한 필드 외에 디플로이먼트 파드 템플릿은 적절한 레이블과 적절한 재시작 정책을 명시해야 한다. 레이블의 경우 다른 컨트롤러와 겹치지 않도록 해야 한다. 자세한 것은 셀렉터를 참조한다.

.spec.template.spec.restartPolicy 에는 오직 Always 만 허용되고, 명시되지 않으면 기본값이 된다.

레플리카

.spec.replicas 은 필요한 파드의 수를 지정하는 선택적 필드이다. 이것의 기본값은 1이다.

예를 들어 kubectl scale deployment deployment --replicas=X 명령으로 디플로이먼트의 크기를 수동으로 조정한 뒤, 매니페스트를 이용하여 디플로이먼트를 업데이트하면(예: kubectl apply -f deployment.yaml 실행), 수동으로 설정했던 디플로이먼트의 크기가 오버라이드된다.

HorizontalPodAutoscaler(또는 수평 스케일링을 위한 유사 API)가 디플로이먼트 크기를 관리하고 있다면, .spec.replicas 를 설정해서는 안 된다.

대신, 쿠버네티스 컨트롤 플레인.spec.replicas 필드를 자동으로 관리한다.

셀렉터

.spec.selector 는 디플로이먼트의 대상이 되는 파드에 대해 레이블 셀렉터를 지정하는 필수 필드이다.

.spec.selector.spec.template.metadata.labels 과 일치해야 하며, 그렇지 않으면 API에 의해 거부된다.

API 버전 apps/v1 에서는 .spec.selector.metadata.labels 이 설정되지 않으면 .spec.template.metadata.labels 은 기본 설정되지 않는다. 그래서 이것들은 명시적으로 설정되어야 한다. 또한 apps/v1 에서는 디플로이먼트를 생성한 후에는 .spec.selector 이 변경되지 않는 점을 참고한다.

디플로이먼트는 템플릿의 .spec.template 와 다르거나 파드의 수가 .spec.replicas 를 초과할 경우 셀렉터와 일치하는 레이블을 가진 파드를 종료할 수 있다. 파드의 수가 의도한 수량보다 적을 경우 .spec.template 에 맞는 새 파드를 띄운다.

만약 셀렉터가 겹치는 컨트롤러가 어러 개 있는 경우, 컨트롤러는 서로 싸우고 올바르게 작동하지 않는다.

전략

.spec.strategy 는 이전 파드를 새로운 파드로 대체하는 전략을 명시한다. .spec.strategy.type 은 "재생성" 또는 "롤링업데이트"가 될 수 있다. "롤링업데이트"가 기본값이다.

디플로이먼트 재생성

기존의 모든 파드는 .spec.strategy.type==Recreate 이면 새 파드가 생성되기 전에 죽는다.

디플로이먼트 롤링 업데이트

디플로이먼트는 .spec.strategy.type==RollingUpdate 이면 파드를 롤링 업데이트 방식으로 업데이트 한다. maxUnavailablemaxSurge 를 명시해서 롤링 업데이트 프로세스를 제어할 수 있다.

최대 불가(Max Unavailable)

.spec.strategy.rollingUpdate.maxUnavailable 은 업데이트 프로세스 중에 사용할 수 없는 최대 파드의 수를 지정하는 선택적 필드이다. 이 값은 절대 숫자(예: 5) 또는 의도한 파드 비율(예: 10%)이 될 수 있다. 절대 값은 내림해서 백분율로 계산한다. 만약 .spec.strategy.rollingUpdate.maxSurge 가 0이면 값이 0이 될 수 없다. 기본 값은 25% 이다.

예를 들어 이 값을 30%로 설정하면 롤링업데이트 시작시 즉각 이전 레플리카셋의 크기를 의도한 파드 중 70%를 스케일 다운할 수 있다. 새 파드가 준비되면 기존 레플리카셋을 스케일 다운할 수 있으며, 업데이트 중에 항상 사용 가능한 전체 파드의 수는 의도한 파드의 수의 70% 이상이 되도록 새 레플리카셋을 스케일 업할 수 있다.

최대 서지(Max Surge)

.spec.strategy.rollingUpdate.maxSurge 는 의도한 파드의 수에 대해 생성할 수 있는 최대 파드의 수를 지정하는 선택적 필드이다. 이 값은 절대 숫자(예: 5) 또는 의도한 파드 비율(예: 10%)이 될 수 있다. MaxUnavailable 값이 0이면 이 값은 0이 될 수 없다. 절대 값은 올림해서 백분율로 계산한다. 기본 값은 25% 이다.

예를 들어 이 값을 30%로 설정하면 롤링업데이트 시작시 새 레플리카셋의 크기를 즉시 조정해서 기존 및 새 파드의 전체 갯수를 의도한 파드의 130%를 넘지 않도록 한다. 기존 파드가 죽으면 새로운 래플리카셋은 스케일 업할 수 있으며, 업데이트하는 동안 항상 실행하는 총 파드의 수는 최대 의도한 파드의 수의 130%가 되도록 보장한다.

진행 기한 시간(초)

.spec.progressDeadlineSeconds 는 디플로어먼트가 표면적으로 type: Progressing, status: "False"의 상태 그리고 리소스가 reason: ProgressDeadlineExceeded 상태로 진행 실패를 보고하기 전에 디플로이먼트가 진행되는 것을 대기시키는 시간(초)를 명시하는 선택적 필드이다. 디플로이먼트 컨트롤러는 디플로이먼트를 계속 재시도 한다. 기본값은 600(초)이다. 미래에 자동화된 롤백이 구현된다면 디플로이먼트 컨트롤러는 상태를 관찰하고, 그 즉시 디플로이먼트를 롤백할 것이다.

만약 명시된다면 이 필드는 .spec.minReadySeconds 보다 커야 한다.

최소 대기 시간(초)

.spec.minReadySeconds 는 새롭게 생성된 파드의 컨테이너가 어떤 것과도 충돌하지 않고 사 용할 수 있도록 준비되어야 하는 최소 시간(초)을 지정하는 선택적 필드이다. 이 기본 값은 0이다(파드는 준비되는 즉시 사용할 수 있는 것으로 간주됨). 파드가 준비되었다고 간주되는 시기에 대한 자세한 내용은 컨테이너 프로브를 참조한다.

수정 버전 기록 제한

디플로이먼트의 수정 버전 기록은 자신이 컨트롤하는 레플리카셋에 저장된다.

.spec.revisionHistoryLimit 은 롤백을 허용하기 위해 보존할 이전 레플리카셋의 수를 지정하는 선택적 필드이다. 이 이전 레플리카셋은 etcd 의 리소스를 소비하고, kubectl get rs 의 결과를 가득차게 만든다. 각 디플로이먼트의 구성은 디플로이먼트의 레플리카셋에 저장된다. 이전 레플리카셋이 삭제되면 해당 디플로이먼트 수정 버전으로 롤백할 수 있는 기능이 사라진다. 기본적으로 10개의 기존 레플리카셋이 유지되지만 이상적인 값은 새로운 디플로이먼트의 빈도와 안정성에 따라 달라진다.

더욱 구체적으로 이 필드를 0으로 설정하면 레플리카가 0이 되며 이전 레플리카셋이 정리된다. 이 경우, 새로운 디플로이먼트 롤아웃을 취소할 수 없다. 새로운 디플로이먼트 롤아웃은 수정 버전 이력이 정리되기 때문이다.

일시 정지

.spec.paused 는 디플로이먼트를 일시 중지나 재개하기 위한 선택적 부울 필드이다. 일시 중지 된 디플로이먼트와 일시 중지 되지 않은 디플로이먼트 사이의 유일한 차이점은 일시 중지된 디플로이먼트는 PodTemplateSpec에 대한 변경 사항이 일시중지 된 경우 새 롤아웃을 트리거 하지 않는다. 디플로이먼트는 생성시 기본적으로 일시 중지되지 않는다.

다음 내용

2.2 - 레플리카셋

레플리카셋의 목적은 레플리카 파드 집합의 실행을 항상 안정적으로 유지하는 것이다. 이처럼 레플리카셋은 보통 명시된 동일 파드 개수에 대한 가용성을 보증하는데 사용한다.

레플리카셋의 작동 방식

레플리카셋을 정의하는 필드는 획득 가능한 파드를 식별하는 방법이 명시된 셀렉터, 유지해야 하는 파드 개수를 명시하는 레플리카의 개수, 그리고 레플리카 수 유지를 위해 생성하는 신규 파드에 대한 데이터를 명시하는 파드 템플릿을 포함한다. 그러면 레플리카셋은 필드에 지정된 설정을 충족하기 위해 필요한 만큼 파드를 만들고 삭제한다. 레플리카셋이 새로운 파드를 생성해야 할 경우, 명시된 파드 템플릿을 사용한다.

레플리카셋은 파드의 metadata.ownerReferences 필드를 통해 파드에 연결되며, 이는 현재 오브젝트가 소유한 리소스를 명시한다. 레플리카셋이 가지고 있는 모든 파드의 ownerReferences 필드는 해당 파드를 소유한 레플리카셋을 식별하기 위한 소유자 정보를 가진다. 이 링크를 통해 레플리카셋은 자신이 유지하는 파드의 상태를 확인하고 이에 따라 관리 한다.

레플리카셋은 셀렉터를 이용해서 필요한 새 파드를 식별한다. 만약 파드에 OwnerReference이 없거나 OwnerReference가 컨트롤러(Controller) 가 아니고 레플리카셋의 셀렉터와 일치한다면 레플리카셋이 즉각 파드를 가지게 될 것이다.

레플리카셋을 사용하는 시기

레플리카셋은 지정된 수의 파드 레플리카가 항상 실행되도록 보장한다. 그러나 디플로이먼트는 레플리카셋을 관리하고 다른 유용한 기능과 함께 파드에 대한 선언적 업데이트를 제공하는 상위 개념이다. 따라서 우리는 사용자 지정 오케스트레이션이 필요하거나 업데이트가 전혀 필요하지 않은 경우라면 레플리카셋을 직접적으로 사용하기 보다는 디플로이먼트를 사용하는 것을 권장한다.

이는 레플리카셋 오브젝트를 직접 조작할 필요가 없다는 것을 의미한다. 대신 디플로이먼트를 이용하고 사양 부분에서 애플리케이션을 정의하면 된다.

예시

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # 케이스에 따라 레플리카를 수정한다.
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3

이 매니페스트를 frontend.yaml에 저장하고 쿠버네티스 클러스터에 적용하면 정의되어 있는 레플리카셋이 생성되고 레플리카셋이 관리하는 파드가 생성된다.

kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml

현재 배포된 레플리카셋을 확인할 수 있다.

kubectl get rs

그리고 생성된 프런트엔드를 볼 수 있다.

NAME       DESIRED   CURRENT   READY   AGE
frontend   3         3         3       6s

또한 레플리카셋의 상태를 확인할 수 있다.

kubectl describe rs/frontend

출력은 다음과 유사할 것이다.

Name:         frontend
Namespace:    default
Selector:     tier=frontend
Labels:       app=guestbook
              tier=frontend
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"apps/v1","kind":"ReplicaSet","metadata":{"annotations":{},"labels":{"app":"guestbook","tier":"frontend"},"name":"frontend",...
Replicas:     3 current / 3 desired
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  tier=frontend
  Containers:
   php-redis:
    Image:        gcr.io/google_samples/gb-frontend:v3
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  117s  replicaset-controller  Created pod: frontend-wtsmm
  Normal  SuccessfulCreate  116s  replicaset-controller  Created pod: frontend-b2zdv
  Normal  SuccessfulCreate  116s  replicaset-controller  Created pod: frontend-vcmts

마지막으로 파드가 올라왔는지 확인할 수 있다.

kubectl get pods

다음과 유사한 파드 정보를 볼 수 있다.

NAME             READY   STATUS    RESTARTS   AGE
frontend-b2zdv   1/1     Running   0          6m36s
frontend-vcmts   1/1     Running   0          6m36s
frontend-wtsmm   1/1     Running   0          6m36s

또한 파드들의 소유자 참조 정보가 해당 프런트엔드 레플리카셋으로 설정되어 있는지 확인할 수 있다. 확인을 위해서는 실행 중인 파드 중 하나의 yaml을 확인한다.

kubectl get pods frontend-b2zdv -o yaml

메타데이터의 ownerReferences 필드에 설정되어 있는 프런트엔드 레플리카셋의 정보가 다음과 유사하게 나오는 것을 볼 수 있다.

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2020-02-12T07:06:16Z"
  generateName: frontend-
  labels:
    tier: frontend
  name: frontend-b2zdv
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: frontend
    uid: f391f6db-bb9b-4c09-ae74-6a1f77f3d5cf
...

템플릿을 사용하지 않는 파드의 획득

단독(bare) 파드를 생성하는 것에는 문제가 없지만, 단독 파드가 레플리카셋의 셀렉터와 일치하는 레이블을 가지지 않도록 하는 것을 강력하게 권장한다. 그 이유는 레플리카셋이 소유하는 파드가 템플릿에 명시된 파드에만 국한되지 않고, 이전 섹션에서 명시된 방식에 의해서도 다른 파드의 획득이 가능하기 때문이다.

이전 프런트엔드 레플리카셋 예제와 다음의 매니페스트에 명시된 파드를 가져와 참조한다.

apiVersion: v1
kind: Pod
metadata:
  name: pod1
  labels:
    tier: frontend
spec:
  containers:
  - name: hello1
    image: gcr.io/google-samples/hello-app:2.0

---

apiVersion: v1
kind: Pod
metadata:
  name: pod2
  labels:
    tier: frontend
spec:
  containers:
  - name: hello2
    image: gcr.io/google-samples/hello-app:1.0

기본 파드는 소유자 관련 정보에 컨트롤러(또는 오브젝트)를 가지지 않기 때문에 프런트엔드 레플리카셋의 셀렉터와 일치하면 즉시 레플리카셋에 소유된다.

프런트엔드 레플리카셋이 배치되고 초기 파드 레플리카가 셋업된 이후에, 레플리카 수 요구 사항을 충족시키기 위해서 신규 파드를 생성한다고 가정해보자.

kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml

새로운 파드는 레플리카셋에 의해 인식되며 레플리카셋이 필요한 수량을 초과하면 즉시 종료된다.

파드를 가져온다.

kubectl get pods

결과에는 새로운 파드가 이미 종료되었거나 종료가 진행 중인 것을 보여준다.

NAME             READY   STATUS        RESTARTS   AGE
frontend-b2zdv   1/1     Running       0          10m
frontend-vcmts   1/1     Running       0          10m
frontend-wtsmm   1/1     Running       0          10m
pod1             0/1     Terminating   0          1s
pod2             0/1     Terminating   0          1s

파드를 먼저 생성한다.

kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml

그 다음 레플리카셋을 생성한다.

kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml

레플리카셋이 해당 파드를 소유한 것을 볼 수 있으며 새 파드 및 기존 파드의 수가 레플리카셋이 필요로 하는 수와 일치할 때까지 사양에 따라 신규 파드만 생성한다. 파드를 가져온다.

kubectl get pods

다음 출력에서 볼 수 있다.

NAME             READY   STATUS    RESTARTS   AGE
frontend-hmmj2   1/1     Running   0          9s
pod1             1/1     Running   0          36s
pod2             1/1     Running   0          36s

이러한 방식으로 레플리카셋은 템플릿을 사용하지 않는 파드를 소유하게 된다.

레플리카셋 매니페스트 작성하기

레플리카셋은 모든 쿠버네티스 API 오브젝트와 마찬가지로 apiVersion, kind, metadata 필드가 필요하다. 레플리카셋에 대한 kind 필드의 값은 항상 레플리카셋이다. 쿠버네티스 1.9에서의 레플리카셋의 kind에 있는 API 버전 apps/v1은 현재 버전이며, 기본으로 활성화 되어 있다. API 버전 apps/v1beta2은 사용 중단(deprecated)되었다. API 버전에 대해서는 frontend.yaml 예제의 첫 번째 줄을 참고한다.

레플리카셋 오브젝트의 이름은 유효한 DNS 서브도메인 이름이어야 한다.

레플리카셋도 .spec 섹션이 필요하다.

파드 템플릿

.spec.template은 레이블을 붙이도록 되어 있는 파드 템플릿이다. 우리는 frontend.yaml 예제에서 tier: frontend이라는 레이블을 하나 가지고 있다. 이 파드를 다른 컨트롤러가 취하지 않도록 다른 컨트롤러의 셀렉터와 겹치지 않도록 주의해야 한다.

템플릿의 재시작 정책 필드인 .spec.template.spec.restartPolicy는 기본값인 Always만 허용된다.

파드 셀렉터

.spec.selector 필드는 레이블 셀렉터이다. 앞서 논의한 것처럼 이 레이블은 소유될 가능성이 있는 파드를 식별하는데 사용된다. 우리 frontend.yaml 예제에서의 셀렉터는 다음과 같다.

matchLabels:
  tier: frontend

레플리카셋에서 .spec.template.metadata.labelsspec.selector과 일치해야 하며 그렇지 않으면 API에 의해 거부된다.

레플리카

.spec.replicas를 설정해서 동시에 동작하는 파드의 수를 지정할 수 있다. 레플리카셋은 파드의 수가 일치하도록 생성 및 삭제한다.

만약 .spec.replicas를 지정하지 않으면 기본값은 1이다.

레플리카셋 작업

레플리카셋과 해당 파드 삭제

레플리카셋 및 모든 파드를 삭제하려면 kubectl delete를 사용한다. 가비지 수집기는 기본적으로 종속되어 있는 모든 파드를 자동으로 삭제한다.

REST API또는 client-go 라이브러리를 이용할 때는 -d 옵션으로 propagationPolicyBackground또는 Foreground로 설정해야 한다. 예시:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
> -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
> -H "Content-Type: application/json"

레플리카셋만 삭제하기

레플리카셋을 --cascade=orphan 옵션과 함께 kubectl delete를 사용하면 연관 파드에 영향을 주지 않고 삭제할 수 있다. REST API 또는 client-go 라이브러리를 이용할 때는 propagationPolicyOrphan을 설정해야 한다. 예시:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
> -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
> -H "Content-Type: application/json"

원본이 삭제되면 새 레플리카셋을 생성해서 대체할 수 있다. 기존 .spec.selector와 신규 .spec.selector가 같으면 새 레플리카셋은 기존 파드를 선택한다. 하지만 신규 레플리카셋은 기존 파드를 신규 레플리카셋의 새롭고 다른 파드 템플릿에 일치시키는 작업을 수행하지는 않는다. 컨트롤 방식으로 파드를 새로운 사양으로 업데이트 하기 위해서는 디플로이먼트를 이용하면 된다. 이는 레플리카셋이 롤링 업데이트를 직접적으로 지원하지 않기 때문이다.

레플리카셋에서 파드 격리

레이블을 변경하면 레플리카셋에서 파드를 제거할 수 있다. 이 방식은 디버깅과 데이터 복구 등을 위해 서비스에서 파드를 제거하는 데 사용할 수 있다. 이 방식으로 제거된 파드는 자동으로 교체된다( 레플리카의 수가 변경되지 않는다고 가정한다).

레플리카셋의 스케일링

레플리카셋을 손쉽게 스케일 업 또는 다운하는 방법은 단순히 .spec.replicas 필드를 업데이트하면 된다. 레플리카셋 컨트롤러는 일치하는 레이블 셀렉터가 있는 파드가 의도한 수 만큼 가용하고 운영 가능하도록 보장한다.

스케일 다운할 때, 레플리카셋 컨트롤러는 스케일 다운할 파드의 우선순위를 정하기 위해 다음의 기준으로 가용 파드를 정렬하여 삭제할 파드를 결정한다.

  1. Pending 상태인 (+ 스케줄링할 수 없는) 파드가 먼저 스케일 다운된다.
  2. controller.kubernetes.io/pod-deletion-cost 어노테이션이 설정되어 있는 파드에 대해서는, 낮은 값을 갖는 파드가 먼저 스케일 다운된다.
  3. 더 많은 레플리카가 있는 노드의 파드가 더 적은 레플리카가 있는 노드의 파드보다 먼저 스케일 다운된다.
  4. 파드 생성 시간이 다르면, 더 최근에 생성된 파드가 이전에 생성된 파드보다 먼저 스케일 다운된다. (LogarithmicScaleDown 기능 게이트가 활성화되어 있으면 생성 시간이 정수 로그 스케일로 버킷화된다)

모든 기준에 대해 동등하다면, 스케일 다운할 파드가 임의로 선택된다.

파드 삭제 비용

FEATURE STATE: Kubernetes v1.22 [beta]

controller.kubernetes.io/pod-deletion-cost 어노테이션을 이용하여, 레플리카셋을 스케일 다운할 때 어떤 파드부터 먼저 삭제할지에 대한 우선순위를 설정할 수 있다.

이 어노테이션은 파드에 설정되어야 하며, [-2147483647, 2147483647] 범위를 갖는다. 이 어노테이션은 하나의 레플리카셋에 있는 다른 파드와의 상대적 삭제 비용을 나타낸다. 삭제 비용이 낮은 파드는 삭제 비용이 높은 파드보다 삭제 우선순위가 높다.

파드에 대해 이 값을 명시하지 않으면 기본값은 0이다. 음수로도 설정할 수 있다. 유효하지 않은 값은 API 서버가 거부한다.

이 기능은 베타 상태이며 기본적으로 활성화되어 있다. kube-apiserver와 kube-controller-manager에 대해 PodDeletionCost 기능 게이트를 이용하여 비활성화할 수 있다.

사용 예시

한 애플리케이션 내의 여러 파드는 각각 사용률이 다를 수 있다. 스케일 다운 시, 애플리케이션은 사용률이 낮은 파드를 먼저 삭제하고 싶을 수 있다. 파드를 자주 업데이트하는 것을 피하기 위해, 애플리케이션은 controller.kubernetes.io/pod-deletion-cost 값을 스케일 다운하기 전에 1회만 업데이트해야 한다 (파드 사용률에 비례하는 값으로 설정). 이 방식은 Spark 애플리케이션의 드라이버 파드처럼 애플리케이션이 스스로 다운스케일링을 수행하는 경우에 유효하다.

레플리카셋을 Horizontal Pod Autoscaler 대상으로 설정

레플리카셋은 Horizontal Pod Autoscalers (HPA)의 대상이 될 수 있다. 즉, 레플리카셋은 HPA에 의해 오토스케일될 수 있다. 다음은 이전에 만든 예시에서 만든 레플리카셋을 대상으로 하는 HPA 예시이다.

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: frontend-scaler
spec:
  scaleTargetRef:
    kind: ReplicaSet
    name: frontend
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50

이 매니페스트를 hpa-rs.yaml로 저장한 다음 쿠버네티스 클러스터에 적용하면 CPU 사용량에 따라 파드가 복제되는 오토스케일 레플리카셋 HPA가 생성된다.

kubectl apply -f https://k8s.io/examples/controllers/hpa-rs.yaml

또는 kubectl autoscale 커맨드을 사용해서 동일한 작업을 할 수 있다. (그리고 더 쉽다!)

kubectl autoscale rs frontend --max=10 --min=3 --cpu-percent=50

레플리카셋의 대안

디플로이먼트(권장)

디플로이먼트는 레플리카셋을 소유하거나 업데이트를 하고, 파드의 선언적인 업데이트와 서버측 롤링 업데이트를 할 수 있는 오브젝트이다. 레플리카셋은 단독으로 사용할 수 있지만, 오늘날에는 주로 디플로이먼트로 파드의 생성과 삭제 그리고 업데이트를 오케스트레이션하는 메커니즘으로 사용한다. 디플로이먼트를 이용해서 배포할 때 생성되는 레플리카셋을 관리하는 것에 대해 걱정하지 않아도 된다. 디플로이먼트는 레플리카셋을 소유하거나 관리한다. 따라서 레플리카셋을 원한다면 디플로이먼트를 사용하는 것을 권장한다.

기본 파드

사용자가 직접 파드를 생성하는 경우와는 다르게, 레플리카셋은 노드 장애 또는 노드의 커널 업그레이드와 같은 관리 목적의 중단 등 어떤 이유로든 종료되거나 삭제된 파드를 교체한다. 이런 이유로 애플리케이션이 단일 파드가 필요하더라도 레플리카셋을 이용하는 것을 권장한다. 레플리카셋을 프로세스 관리자와 비교해서 생각해본다면, 레플리카셋은 단일 노드에서의 개별 프로세스들이 아닌 다수의 노드에 걸쳐있는 다수의 파드를 관리하는 것이다. 레플리카셋은 로컬 컨테이너의 재시작을 노드에 있는 어떤 에이전트에게 위임한다(예를들어 Kubelet 또는 도커).

스스로 종료되는 것이 예상되는 파드의 경우에는 레플리카셋 대신 을 이용한다 (즉, 배치 잡).

데몬셋

머신 모니터링 또는 머신 로깅과 같은 머신-레벨의 기능을 제공하는 파드를 위해서는 레플리카셋 대신 데몬셋을 사용한다. 이러한 파드의 수명은 머신의 수명과 연관되어 있고, 머신에서 다른 파드가 시작하기 전에 실행되어야 하며, 머신의 재부팅/종료가 준비되었을 때, 해당 파드를 종료하는 것이 안전하다.

레플리케이션 컨트롤러

레플리카셋은 레플리케이션 컨트롤러를 계승하였다. 이 두 개의 용도는 동일하고, 유사하게 동작하며, 레플리케이션 컨트롤러가 레이블 사용자 가이드에 설명된 설정-기반의 셀렉터의 요건을 지원하지 않는다는 점을 제외하면 유사하다. 따라서 레플리카셋이 레플리케이션 컨트롤러보다 선호된다.

다음 내용

2.3 - 스테이트풀셋

스테이트풀셋은 애플리케이션의 스테이트풀을 관리하는데 사용하는 워크로드 API 오브젝트이다.

파드 집합의 디플로이먼트와 스케일링을 관리하며, 파드들의 순서 및 고유성을 보장한다 .

디플로이먼트와 유사하게, 스테이트풀셋은 동일한 컨테이너 스펙을 기반으로 둔 파드들을 관리한다. 디플로이먼트와는 다르게, 스테이트풀셋은 각 파드의 독자성을 유지한다. 이 파드들은 동일한 스팩으로 생성되었지만, 서로 교체는 불가능하다. 다시 말해, 각각은 재스케줄링 간에도 지속적으로 유지되는 식별자를 가진다.

스토리지 볼륨을 사용해서 워크로드에 지속성을 제공하려는 경우, 솔루션의 일부로 스테이트풀셋을 사용할 수 있다. 스테이트풀셋의 개별 파드는 장애에 취약하지만, 퍼시스턴트 파드 식별자는 기존 볼륨을 실패한 볼륨을 대체하는 새 파드에 더 쉽게 일치시킬 수 있다.

스테이트풀셋 사용

스테이트풀셋은 다음 중 하나 또는 이상이 필요한 애플리케이션에 유용하다.

  • 안정된, 고유한 네트워크 식별자.
  • 안정된, 지속성을 갖는 스토리지.
  • 순차적인, 정상 배포(graceful deployment)와 스케일링.
  • 순차적인, 자동 롤링 업데이트.

위의 안정은 파드의 (재)스케줄링 전반에 걸친 지속성과 같은 의미이다. 만약 애플리케이션이 안정적인 식별자 또는 순차적인 배포, 삭제 또는 스케일링이 필요하지 않으면, 스테이트리스 레플리카셋(ReplicaSet)을 제공하는 워크로드 오브젝트를 사용해서 애플리케이션을 배포해야 한다. 디플로이먼트 또는 레플리카셋과 같은 컨트롤러가 스테이트리스 요구에 더 적합할 수 있다.

제한사항

  • 파드에 지정된 스토리지는 관리자에 의해 퍼시스턴트 볼륨 프로비저너를 기반으로 하는 storage class 를 요청해서 프로비전하거나 사전에 프로비전이 되어야 한다.
  • 스테이트풀셋을 삭제 또는 스케일 다운해도 스테이트풀셋과 연관된 볼륨이 삭제되지 않는다. 이는 일반적으로 스테이트풀셋과 연관된 모든 리소스를 자동으로 제거하는 것보다 더 중요한 데이터의 안전을 보장하기 위함이다.
  • 스테이트풀셋은 현재 파드의 네트워크 신원을 책임지고 있는 헤드리스 서비스가 필요하다. 사용자가 이 서비스를 생성할 책임이 있다.
  • 스테이트풀셋은 스테이트풀셋의 삭제 시 파드의 종료에 대해 어떠한 보증을 제공하지 않는다. 스테이트풀셋에서는 파드가 순차적이고 정상적으로 종료(graceful termination)되도록 하려면, 삭제 전 스테이트풀셋의 스케일을 0으로 축소할 수 있다.
  • 롤링 업데이트와 기본 파드 매니지먼트 폴리시 (OrderedReady)를 함께 사용시 복구를 위한 수동 개입이 필요한 파손 상태로 빠질 수 있다.

구성 요소

아래의 예시에서는 스테이트풀셋의 구성요소를 보여 준다.

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # .spec.template.metadata.labels 와 일치해야 한다
  serviceName: "nginx"
  replicas: 3 # 기본값은 1
  minReadySeconds: 10 # 기본값은 0
  template:
    metadata:
      labels:
        app: nginx # .spec.selector.matchLabels 와 일치해야 한다
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

위의 예시에서:

  • 이름이 nginx라는 헤드리스 서비스는 네트워크 도메인을 컨트롤하는데 사용 한다.
  • 이름이 web인 스테이트풀셋은 3개의 nginx 컨테이너의 레플리카가 고유의 파드에서 구동될 것이라 지시하는 Spec을 갖는다.
  • volumeClaimTemplates은 퍼시스턴트 볼륨 프로비저너에서 프로비전한 퍼시스턴트 볼륨을 사용해서 안정적인 스토리지를 제공한다.

스테이트풀셋 오브젝트의 이름은 유효한 DNS 서브도메인 이름이어야 한다.

파드 셀렉터

스테이트풀셋의 .spec.selector 필드는 .spec.template.metadata.labels 레이블과 일치하도록 설정해야 한다. 1.8 버전 이상에서는, 해당되는 파드 셀렉터를 찾지 못하면 스테이트풀셋 생성 과정에서 검증 오류가 발생한다.

볼륨 클레임 템플릿

.spec.volumeClaimTemplates 를 설정하여, 퍼시스턴트볼륨 프로비저너에 의해 프로비전된 퍼시스턴트볼륨을 이용하는 안정적인 스토리지를 제공할 수 있다.

최소 준비 시간 초

FEATURE STATE: Kubernetes v1.23 [beta]

.spec.minReadySeconds 는 파드가 '사용 가능(available)'이라고 간주될 수 있도록 파드의 모든 컨테이너가 문제 없이 실행되어야 하는 최소 시간(초)을 나타내는 선택적인 필드이다. 이 기능은 베타이며 기본적으로 활성화되어 있음에 유의한다. 이 기능을 사용하지 않으려면 StatefulSetMinReadySeconds 플래그를 설정 해제한다. 이 필드의 기본값은 0이다(이 경우, 파드가 Ready 상태가 되면 바로 사용 가능하다고 간주된다.) 파드가 언제 사용 가능하다고 간주되는지에 대한 자세한 정보는 컨테이너 프로브(probe)를 참고한다.

파드 신원

스테이트풀셋 파드는 순서, 안정적인 네트워크 신원 그리고 안정적인 스토리지로 구성되는 고유한 신원을 가진다. 신원은 파드가 어떤 노드에 있고, (재)스케줄과도 상관없이 파드에 붙어있다.

순서 색인

N개의 레플리카가 있는 스테이트풀셋은 스테이트풀셋에 있는 각 파드에 0에서 N-1 까지의 정수가 순서대로 할당되며 해당 스테이트풀셋 내에서 고유 하다.

안정적인 네트워크 신원

스테이트풀셋의 각 파드는 스테이트풀셋의 이름과 파드의 순번에서 호스트 이름을 얻는다. 호스트 이름을 구성하는 패턴은 $(statefulset name)-$(ordinal) 이다. 위의 예시에서 생성된 3개 파드의 이름은 web-0,web-1,web-2 이다. 스테이트풀셋은 스테이트풀셋에 있는 파드의 도메인을 제어하기위해 헤드리스 서비스를 사용할 수 있다. 이 서비스가 관리하는 도메인은 $(service name).$(namespace).svc.cluster.local 의 형식을 가지며, 여기서 "cluster.local"은 클러스터 도메인이다. 각 파드는 생성되면 $(podname).$(governing service domain) 형식을 가지고 일치되는 DNS 서브도메인을 가지며, 여기서 거버닝 서비스(governing service)는 스테이트풀셋의 serviceName 필드에 의해 정의된다.

클러스터에서 DNS가 구성된 방식에 따라, 새로 실행된 파드의 DNS 이름을 즉시 찾지 못할 수 있다. 이 동작은 클러스터의 다른 클라이언트가 파드가 생성되기 전에 파드의 호스트 이름에 대한 쿼리를 이미 보낸 경우에 발생할 수 있다. 네거티브 캐싱(DNS에서 일반적)은 이전에 실패한 조회 결과가 파드가 실행된 후에도 적어도 몇 초 동안 기억되고 재사용됨을 의미한다.

파드를 생성한 후 즉시 파드를 검색해야 하는 경우, 몇 가지 옵션이 있다.

  • DNS 조회에 의존하지 않고 쿠버네티스 API를 직접(예를 들어 watch 사용) 쿼리한다.
  • 쿠버네티스 DNS 공급자의 캐싱 시간(일반적으로 CoreDNS의 컨피그맵을 편집하는 것을 의미하며, 현재 30초 동안 캐시함)을 줄인다.

제한사항 섹션에서 언급한 것처럼 사용자는 파드의 네트워크 신원을 책임지는 헤드리스 서비스를 생성할 책임이 있다.

여기 클러스터 도메인, 서비스 이름, 스테이트풀셋 이름을 선택을 하고, 그 선택이 스테이트풀셋 파드의 DNS이름에 어떻게 영향을 주는지에 대한 약간의 예시가 있다.

클러스터 도메인서비스 (ns/이름)스테이트풀셋 (ns/이름)스테이트풀셋 도메인파드 DNS파드 호스트 이름
cluster.localdefault/nginxdefault/webnginx.default.svc.cluster.localweb-{0..N-1}.nginx.default.svc.cluster.localweb-{0..N-1}
cluster.localfoo/nginxfoo/webnginx.foo.svc.cluster.localweb-{0..N-1}.nginx.foo.svc.cluster.localweb-{0..N-1}
kube.localfoo/nginxfoo/webnginx.foo.svc.kube.localweb-{0..N-1}.nginx.foo.svc.kube.localweb-{0..N-1}

안정된 스토리지

쿠버네티스는 각 VolumeClaimTemplate마다 하나의 퍼시스턴트 볼륨을 생성한다. 위의 nginx 예시에서 각 파드는 my-storage-class 라는 스토리지 클래스와 1 Gib의 프로비전된 스토리지를 가지는 단일 퍼시스턴트 볼륨을 받게 된다. 만약 스토리지 클래스가 명시되지 않은 경우, 기본 스토리지 클래스가 사용된다. 파드가 노드에서 스케줄 혹은 재스케줄이 되면 파드의 volumeMounts 는 퍼시스턴트 볼륨 클레임과 관련된 퍼시스턴트 볼륨이 마운트 된다. 참고로, 파드 퍼시스턴트 볼륨 클레임과 관련된 퍼시스턴트 볼륨은 파드 또는 스테이트풀셋이 삭제되더라도 삭제되지 않는다. 이것은 반드시 수동으로 해야 한다.

파드 이름 레이블

스테이트풀셋 컨트롤러(Controller) 가 파드를 생성할 때 파드 이름으로 statefulset.kubernetes.io/pod-name 레이블이 추가된다. 이 레이블로 스테이트풀셋의 특정 파드에 서비스를 연결할 수 있다.

디플로이먼트와 스케일링 보증

  • N개의 레플리카가 있는 스테이트풀셋이 파드를 배포할 때 연속해서 {0..N-1}의 순서로 생성한다.
  • 파드가 삭제될 때는 {N-1..0}의 순서인 역순으로 종료된다.
  • 파드에 스케일링 작업을 적용하기 전에 모든 선행 파드가 Running 및 Ready 상태여야 한다.
  • 파드가 종료되기 전에 모든 후속 파드가 완전히 종료 되어야 한다.

스테이트풀셋은 pod.Spec.TerminationGracePeriodSeconds 을 0으로 명시해서는 안된다. 이 방법은 안전하지 않으며, 사용하지 않기를 강권한다. 자세한 설명은 스테이트풀셋 파드 강제 삭제를 참고한다.

위의 nginx 예시가 생성될 때 web-0, web-1, web-2 순서로 3개 파드가 배포된다. web-1은 web-0이 Running 및 Ready 상태가 되기 전에는 배포되지 않으며, web-2 도 web-1이 Running 및 Ready 상태가 되기 전에는 배포되지 않는다. 만약 web-1이 Running 및 Ready 상태가 된 이후, web-2가 시작되기 전에 web-0이 실패하게 된다면, web-2는 web-0이 성공적으로 재시작이되고, Running 및 Ready 상태가 되기 전까지 시작되지 않는다.

만약 사용자가 배포된 예제의 스테이트풀셋을 replicas=1 으로 패치해서 스케일한 경우 web-2가 먼저 종료된다. web-1은 web-2가 완전히 종료 및 삭제되기 전까지 정지되지 않는다. 만약 web-2의 종료 및 완전히 중지되고, web-1이 종료되기 전에 web-0이 실패할 경우 web-1은 web-0이 Running 및 Ready 상태가 되기 전까지 종료되지 않는다.

파드 관리 정책

쿠버네티스 1.7 및 이후에는 스테이트풀셋의 .spec.podManagementPolicy 필드를 통해 고유성 및 신원 보증을 유지하면서 순차 보증을 완화한다.

OrderedReady 파드 관리

OrderedReady 파드 관리는 스테이트풀셋의 기본이다. 이것은 위에서 설명한 행위를 구현한다.

Parallel 파드 관리

Parallel 파드 관리는 스테이트풀셋 컨트롤러에게 모든 파드를 병렬로 실행 또는 종료하게 한다. 그리고 다른 파드의 실행이나 종료에 앞서 파드가 Running 및 Ready 상태가 되거나 완전히 종료되기를 기다리지 않는다. 이 옵션은 오직 스케일링 작업에 대한 동작에만 영향을 미친다. 업데이트는 영향을 받지 않는다.

업데이트 전략

스테이트풀셋의 .spec.updateStrategy 필드는 스테이트풀셋의 파드에 대한 컨테이너, 레이블, 리소스의 요청/제한 그리고 주석에 대한 자동화된 롤링 업데이트를 구성하거나 비활성화할 수 있다. 두 가지 가능한 전략이 있다.

OnDelete(삭제시)
스테이트풀셋의 .spec.updateStrategy.typeOnDelete 를 설정하며, 스테이트풀셋 컨트롤러는 스테이트풀셋의 파드를 자동으로 업데이트하지 않는다. 사용자는 컨트롤러가 스테이트풀셋의 .spec.template를 반영하는 수정된 새로운 파드를 생성하도록 수동으로 파드를 삭제해야 한다.
RollingUpdate(롤링 업데이트)
롤링 업데이트 의 업데이트 전략은 스테이트풀셋의 파드에 대한 롤링 업데이트를 구현한다. 롤링 업데이트는 .spec.updateStrategy 가 지정되지 않으면 기본 전략이 된다.

롤링 업데이트

스테이트풀셋에 롤링 업데이트.spec.updateStrategy.type 에 설정되면 스테이트풀셋 컨트롤러는 스테이트풀셋의 각 파드를 삭제 및 재생성한다. 이 과정에서 똑같이 순차적으로 파드가 종료되고(가장 큰 순서 색인에서부터에서 작은 순서 색인쪽으로), 각 파드의 업데이트는 한 번에 하나씩 한다.

쿠버네티스 컨트롤 플레인은 이전 버전을 업데이트 하기 전에, 업데이트된 파드가 실행 및 준비될 때까지 기다린다. .spec.minReadySeconds(최소 준비 시간 초 참조)를 설정한 경우, 컨트롤 플레인은 파드가 준비 상태로 전환된 후 해당 시간을 추가로 기다린 후 이동한다.

파티션 롤링 업데이트

롤링 업데이트 의 업데이트 전략은 .spec.updateStrategy.rollingUpdate.partition 를 명시해서 파티션 할 수 있다. 만약 파티션을 명시하면 스테이트풀셋의 .spec.template 가 업데이트 될 때 부여된 수가 파티션보다 크거나 같은 모든 파드가 업데이트 된다. 파티션보다 작은 수를 가진 모든 파드는 업데이트 되지 않으며, 삭제 된 경우라도 이전 버전에서 재생성된다. 만약 스테이트풀셋의 .spec.updateStrategy.rollingUpdate.partition.spec.replicas 보다 큰 경우 .spec.template 의 업데이트는 해당 파드에 전달하지 않는다. 대부분의 케이스는 파티션을 사용할 필요가 없지만 업데이트를 준비하거나, 카나리의 롤 아웃 또는 단계적인 롤 아웃을 행하려는 경우에는 유용하다.

강제 롤백

기본 파드 관리 정책 (OrderedReady)과 함께 롤링 업데이트를 사용할 경우 직접 수동으로 복구를 해야하는 고장난 상태가 될 수 있다.

만약 파드 템플릿을 Running 및 Ready 상태가 되지 않는 구성으로 업데이트하는 경우(예시: 잘못된 바이너리 또는 애플리케이션-레벨 구성 오류로 인한) 스테이트풀셋은 롤아웃을 중지하고 기다린다.

이 상태에서는 파드 템플릿을 올바른 구성으로 되돌리는 것으로 충분하지 않다. 알려진 이슈로 인해 스테이트풀셋은 손상된 파드가 준비(절대 되지 않음)될 때까지 기다리며 작동하는 구성으로 되돌아가는 시도를 하기 전까지 기다린다.

템플릿을 되돌린 이후에는 스테이트풀셋이 이미 잘못된 구성으로 실행하려고 시도한 모든 파드를 삭제해야 한다. 그러면 스테이트풀셋은 되돌린 템플릿을 사용해서 파드를 다시 생성하기 시작한다.

퍼시스턴트볼륨클레임 유보

FEATURE STATE: Kubernetes v1.23 [alpha]

선택적 필드인 .spec.persistentVolumeClaimRetentionPolicy 는 스테이트풀셋의 생애주기동안 PVC를 삭제할 것인지, 삭제한다면 어떻게 삭제하는지를 관리한다. 이 필드를 사용하려면 StatefulSetAutoDeletePVC 기능 게이트를 활성화해야 한다. 활성화 시, 각 스테이트풀셋에 대해 두 가지 정책을 설정할 수 있다.

whenDeleted
스테이트풀셋이 삭제될 때 적용될 볼륨 유보 동작을 설정한다.
whenScaled
스테이트풀셋의 레플리카 수가 줄어들 때, 예를 들면 스테이트풀셋을 스케일 다운할 때 적용될 볼륨 유보 동작을 설정한다.

설정 가능한 각 정책에 대해, 그 값을 Delete 또는 Retain 으로 설정할 수 있다.

Delete
volumeClaimTemplate 스테이트풀셋으로부터 생성된 PVC는 정책에 영향을 받는 각 파드에 대해 삭제된다. whenDeleted 가 이 값으로 설정되어 있으면 volumeClaimTemplate 으로부터 생성된 모든 PVC는 파드가 삭제된 뒤에 삭제된다. whenScaled 가 이 값으로 설정되어 있으면 스케일 다운된 파드 레플리카가 삭제된 뒤, 삭제된 파드에 해당되는 PVC만 삭제된다.
Retain (기본값)
파드가 삭제되어도 volumeClaimTemplate 으로부터 생성된 PVC는 영향을 받지 않는다. 이는 이 신기능이 도입되기 전의 기본 동작이다.

이러한 정책은 파드의 삭제가 스테이트풀셋 삭제 또는 스케일 다운으로 인한 것일 때에만 적용됨에 유의한다. 예를 들어, 스테이트풀셋의 파드가 노드 실패로 인해 실패했고, 컨트롤 플레인이 대체 파드를 생성했다면, 스테이트풀셋은 기존 PVC를 유지한다. 기존 볼륨은 영향을 받지 않으며, 새 파드가 실행될 노드에 클러스터가 볼륨을 연결(attach)한다.

정책의 기본값은 Retain 이며, 이는 이 신기능이 도입되기 전의 스테이트풀셋 기본 동작이다.

다음은 정책 예시이다.

apiVersion: apps/v1
kind: StatefulSet
...
spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain
    whenScaled: Delete
...

스테이트풀셋 컨트롤러는 자신이 소유한 PVC에 소유자 정보(reference)를 추가하며, 파드가 종료된 이후에는 가비지 콜렉터가 이 정보를 삭제한다. 이로 인해 PVC가 삭제되기 전에 (그리고 유보 정책에 따라, 매칭되는 기반 PV와 볼륨이 삭제되기 전에) 파드가 모든 볼륨을 깨끗하게 언마운트할 수 있다. whenDeleted 정책을 Delete 로 설정하면, 해당 스테이트풀셋에 연결된 모든 PVC에 스테이트풀셋 인스턴스의 소유자 정보가 기록된다.

whenScaled 정책은 파드가 스케일 다운되었을 때에만 PVC를 삭제하며, 파드가 다른 원인으로 삭제되면 PVC를 삭제하지 않는다. 조정 상황 발생 시, 스테이트풀셋 컨트롤러는 목표 레플리카 수와 클러스터 상의 실제 파드 수를 비교한다. 레플리카 카운트보다 큰 ID를 갖는 스테이트풀셋 파드는 부적격 판정을 받으며 삭제 대상으로 표시된다. whenScaled 정책이 Delete 이면, 부적격 파드는 삭제되기 전에, 연결된 스테이트풀셋 템플릿 PVC의 소유자로 지정된다. 이로 인해, 부적격 파드가 종료된 이후에만 PVC가 가비지 콜렉트된다.

이는 곧 만약 컨트롤러가 강제 종료되어 재시작되면, 파드의 소유자 정보가 정책에 적합하게 업데이트되기 전에는 어떤 파드도 삭제되지 않을 것임을 의미한다. 만약 컨트롤러가 다운된 동안 부적격 파드가 강제로 삭제되면, 컨트롤러가 강제 종료된 시점에 따라 소유자 정보가 설정되었을 수도 있고 설정되지 않았을 수도 있다. 소유자 정보가 업데이트되기까지 몇 번의 조정 절차가 필요할 수 있으며, 따라서 일부 부적격 파드는 소유자 정보 설정을 완료하고 나머지는 그러지 못했을 수 있다. 이러한 이유로, 컨트롤러가 다시 켜져서 파드를 종료하기 전에 소유자 정보를 검증할 때까지 기다리는 것을 추천한다. 이것이 불가능하다면, 관리자는 PVC의 소유자 정보를 확인하여 파드가 강제 삭제되었을 때 해당되는 오브젝트가 삭제되도록 해야 한다.

레플리카

.spec.replicas 은 필요한 파드의 수를 지정하는 선택적 필드이다. 기본값은 1이다.

예를 들어 kubectl scale deployment deployment --replicas=X 명령으로 디플로이먼트의 크기를 수동으로 조정한 뒤, 매니페스트를 이용하여 디플로이먼트를 업데이트하면(예: kubectl apply -f deployment.yaml 실행), 수동으로 설정했던 디플로이먼트의 크기가 오버라이드된다.

HorizontalPodAutoscaler(또는 수평 스케일링을 위한 유사 API)가 디플로이먼트 크기를 관리하고 있다면, .spec.replicas 를 설정해서는 안 된다. 대신, 쿠버네티스 컨트롤 플레인.spec.replicas 필드를 자동으로 관리한다.

다음 내용

2.4 - 데몬셋

데몬셋 은 모든(또는 일부) 노드가 파드의 사본을 실행하도록 한다. 노드가 클러스터에 추가되면 파드도 추가된다. 노드가 클러스터에서 제거되면 해당 파드는 가비지(garbage)로 수집된다. 데몬셋을 삭제하면 데몬셋이 생성한 파드들이 정리된다.

데몬셋의 일부 대표적인 용도는 다음과 같다.

  • 모든 노드에서 클러스터 스토리지 데몬 실행
  • 모든 노드에서 로그 수집 데몬 실행
  • 모든 노드에서 노드 모니터링 데몬 실행

단순한 케이스에서는, 각 데몬 유형의 처리를 위해서 모든 노드를 커버하는 하나의 데몬셋이 사용된다. 더 복잡한 구성에서는 단일 유형의 데몬에 여러 데몬셋을 사용할 수 있지만, 각기 다른 하드웨어 유형에 따라 서로 다른 플래그, 메모리, CPU 요구가 달라진다.

데몬셋 사양 작성

데몬셋 생성

YAML 파일에 데몬셋 명세를 작성할 수 있다. 예를 들어 아래 daemonset.yaml 파일은 fluentd-elasticsearch 도커 이미지를 실행하는 데몬셋을 설명한다.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      # this toleration is to have the daemonset runnable on master nodes
      # remove it if your masters can't run pods
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

YAML 파일을 기반으로 데몬셋을 생성한다.

kubectl apply -f https://k8s.io/examples/controllers/daemonset.yaml

필수 필드

다른 모든 쿠버네티스 설정과 마찬가지로 데몬셋에는 apiVersion, kind 그리고 metadata 필드가 필요하다. 일반적인 설정파일 작업에 대한 정보는 스테이트리스 애플리케이션 실행하기kubectl을 사용한 오브젝트 관리를 참고한다.

데몬셋 오브젝트의 이름은 유효한 DNS 서브도메인 이름이어야 한다.

데몬셋에는 .spec 섹션도 필요하다.

파드 템플릿

.spec.template.spec 의 필수 필드 중 하나이다.

.spec.template파드 템플릿이다. 이것은 중첩되어 있다는 점과 apiVersion 또는 kind 를 가지지 않는 것을 제외하면 파드와 정확히 같은 스키마를 가진다.

데몬셋의 파드 템플릿에는 파드의 필수 필드 외에도 적절한 레이블이 명시되어야 한다(파드 셀렉터를 본다).

데몬셋의 파드 템플릿의 RestartPolicyAlways 를 가져야 하며, 명시되지 않은 경우 기본으로 Always가 된다.

파드 셀렉터

.spec.selector 필드는 파드 셀렉터이다. 이것은 .spec.selector 와 같은 동작을 한다.

쿠버네티스 1.8 부터는 레이블이 .spec.template 와 일치하는 파드 셀렉터를 명시해야 한다. 파드 셀렉터는 비워두면 더 이상 기본 값이 설정이 되지 않는다. 셀렉터의 기본 값은 kubectl apply 과 호환되지 않는다. 또한, 한 번 데몬셋이 만들어지면 .spec.selector 의 변형은 가능하지 않다. 파드 셀렉터를 변형하면 의도하지 않게 파드는 고아가 되거나 사용자에게 혼란을 주는 것으로 밝혀졌다.

.spec.selector 는 다음 2개의 필드로 구성된 오브젝트이다.

  • matchLabels - 레플리케이션 컨트롤러.spec.selector 와 동일하게 작동한다.
  • matchExpressions - 키, 값 목록 그리고 키 및 값에 관련된 연산자를 명시해서 보다 정교한 셀렉터를 만들 수 있다.

2개의 필드가 명시되면 두 필드를 모두 만족하는 것(ANDed)이 결과가 된다.

만약 .spec.selector 를 명시하면, 이것은 .spec.template.metadata.labels 와 일치해야 한다. 일치하지 않는 구성은 API에 의해 거부된다.

오직 일부 노드에서만 파드 실행

만약 .spec.template.spec.nodeSelector 를 명시하면 데몬셋 컨트롤러는 노드 셀렉터와 일치하는 노드에 파드를 생성한다. 마찬가지로 .spec.template.spec.affinity 를 명시하면 데몬셋 컨트롤러는 노드 어피니티와 일치하는 노드에 파드를 생성한다. 만약 둘 중 하나를 명시하지 않으면 데몬셋 컨트롤러는 모든 노드에서 파드를 생성한다.

데몬 파드가 스케줄 되는 방법

기본 스케줄러로 스케줄

FEATURE STATE: Kubernetes v1.24 [stable]

데몬셋은 자격이 되는 모든 노드에서 파드 사본이 실행하도록 보장한다. 일반적으로 쿠버네티스 스케줄러에 의해 파드가 실행되는 노드가 선택된다. 그러나 데몬셋 파드는 데몬셋 컨트롤러에 의해 생성되고 스케줄된다. 이에 대한 이슈를 소개한다.

  • 파드 동작의 불일치: 스케줄 되기 위해서 대기 중인 일반 파드는 Pending 상태로 생성된다. 그러나 데몬셋 파드는 Pending 상태로 생성되지 않는다. 이것은 사용자에게 혼란을 준다.
  • 파드 선점은 기본 스케줄러에서 처리한다. 선점이 활성화되면 데몬셋 컨트롤러는 파드 우선순위와 선점을 고려하지 않고 스케줄 한다.

ScheduleDaemonSetPods 로 데몬셋 파드에 .spec.nodeName 용어 대신 NodeAffinity 용어를 추가해서 데몬셋 컨트롤러 대신 기본 스케줄러를 사용해서 데몬셋을 스케줄할 수 있다. 이후에 기본 스케줄러를 사용해서 대상 호스트에 파드를 바인딩한다. 만약 데몬셋 파드에 이미 노드 선호도가 존재한다면 교체한다(대상 호스트를 선택하기 전에 원래 노드의 어피니티가 고려된다). 데몬셋 컨트롤러는 데몬셋 파드를 만들거나 수정할 때만 이런 작업을 수행하며, 데몬셋의 spec.template 은 변경되지 않는다.

nodeAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
    - matchFields:
      - key: metadata.name
        operator: In
        values:
        - target-host-name

또한, 데몬셋 파드에 node.kubernetes.io/unschedulable:NoSchedule 이 톨러레이션(toleration)으로 자동으로 추가된다. 기본 스케줄러는 데몬셋 파드를 스케줄링시 unschedulable 노드를 무시한다.

테인트(taints)와 톨러레이션(tolerations)

데몬 파드는 테인트와 톨러레이션을 존중하지만, 다음과 같이 관련 기능에 따라 자동적으로 데몬셋 파드에 톨러레이션을 추가한다.

톨러레이션 키영향버전설명
node.kubernetes.io/not-readyNoExecute1.13+네트워크 파티션과 같은 노드 문제가 발생해도 데몬셋 파드는 축출되지 않는다.
node.kubernetes.io/unreachableNoExecute1.13+네트워크 파티션과 같은 노드 문제가 발생해도 데몬셋 파드는 축출되지 않는다.
node.kubernetes.io/disk-pressureNoSchedule1.8+데몬셋 파드는 기본 스케줄러에서 디스크-압박(disk-pressure) 속성을 허용한다.
node.kubernetes.io/memory-pressureNoSchedule1.8+데몬셋 파드는 기본 스케줄러에서 메모리-압박(memory-pressure) 속성을 허용한다.
node.kubernetes.io/unschedulableNoSchedule1.12+데몬셋 파드는 기본 스케줄러의 스케줄할 수 없는(unschedulable) 속성을 극복한다.
node.kubernetes.io/network-unavailableNoSchedule1.12+호스트 네트워크를 사용하는 데몬셋 파드는 기본 스케줄러에 의해 이용할 수 없는 네트워크(network-unavailable) 속성을 극복한다.

데몬 파드와 통신

데몬셋의 파드와 통신할 수 있는 몇 가지 패턴은 다음과 같다.

  • 푸시(Push): 데몬셋의 파드는 통계 데이터베이스와 같은 다른 서비스로 업데이트를 보내도록 구성되어 있다. 그들은 클라이언트들을 가지지 않는다.
  • 노드IP와 알려진 포트: 데몬셋의 파드는 호스트 포트를 사용할 수 있으며, 노드IP를 통해 파드에 접근할 수 있다. 클라이언트는 노드IP를 어떻게든지 알고 있으며, 관례에 따라 포트를 알고 있다.
  • DNS: 동일한 파드 셀렉터로 헤드리스 서비스를 만들고, 그 다음에 엔드포인트 리소스를 사용해서 데몬셋을 찾거나 DNS에서 여러 A레코드를 검색한다.
  • 서비스: 동일한 파드 셀렉터로 서비스를 생성하고, 서비스를 사용해서 임의의 노드의 데몬에 도달한다(특정 노드에 도달할 방법이 없다).

데몬셋 업데이트

만약 노드 레이블이 변경되면, 데몬셋은 새로 일치하는 노드에 즉시 파드를 추가하고, 새로 일치하지 않는 노드에서 파드를 삭제한다.

사용자는 데몬셋이 생성하는 파드를 수정할 수 있다. 그러나 파드는 모든 필드가 업데이트 되는 것을 허용하지 않는다. 또한 데몬셋 컨트롤러는 다음에 노드(동일한 이름으로)가 생성될 때 원본 템플릿을 사용한다.

사용자는 데몬셋을 삭제할 수 있다. 만약 kubectl 에서 --cascade=orphan 를 명시하면 파드는 노드에 남게 된다. 이후에 동일한 셀렉터로 새 데몬셋을 생성하면, 새 데몬셋은 기존 파드를 채택한다. 만약 파드를 교체해야 하는 경우 데몬셋은 updateStrategy 에 따라 파드를 교체한다.

사용자는 데몬셋에서 롤링 업데이트를 수행할 수 있다.

데몬셋의 대안

초기화 스크립트

데몬 프로세스를 직접 노드에서 시작해서 실행하는 것도 당연히 가능하다. (예: init, upstartd 또는 systemd 를 사용). 이 방법도 문제는 전혀 없다. 그러나 데몬셋을 통해 데몬 프로세스를 실행하면 몇 가지 이점 있다.

  • 애플리케이션과 동일한 방법으로 데몬을 모니터링하고 로그 관리를 할 수 있다.
  • 데몬 및 애플리케이션과 동일한 구성 언어와 도구(예: 파드 템플릿, kubectl).
  • 리소스 제한이 있는 컨테이너에서 데몬을 실행하면 앱 컨테이너에서 데몬간의 격리를 증가시킨다. 그러나 이것은 파드가 아닌 컨테이너에서 데몬을 실행해서 이루어진다 (예: 도커에서 직접적으로 시작).

베어(Bare) 파드

직접적으로 파드를 실행할 특정한 노드를 명시해서 파드를 생성할 수 있다. 그러나 데몬셋은 노드 장애 또는 커널 업그레이드와 같이 변경사항이 많은 노드 유지보수의 경우를 비롯하여 어떠한 이유로든 삭제되거나 종료된 파드를 교체한다. 따라서 개별 파드를 생성하는 것보다는 데몬 셋을 사용해야 한다.

스태틱(static) 파드

Kubelet이 감시하는 특정 디렉터리에 파일을 작성하는 파드를 생성할 수 있다. 이것을 스태틱 파드라고 부른다. 데몬셋과는 다르게 스태틱 파드는 kubectl 또는 다른 쿠버네티스 API 클라이언트로 관리할 수 없다. 스태틱 파드는 API 서버에 의존하지 않기 때문에 클러스터 부트스트랩(bootstraping)하는 경우에 유용하다. 또한 스태틱 파드는 향후에 사용 중단될 수 있다.

디플로이먼트

데몬셋은 파드를 생성한다는 점에서 디플로이먼트와 유사하고, 해당 파드에서는 프로세스가 종료되지 않을 것으로 예상한다(예: 웹 서버).

파드가 실행되는 호스트를 정확하게 제어하는 것보다 레플리카의 수를 스케일링 업 및 다운 하고, 업데이트 롤아웃이 더 중요한 프런트 엔드와 같은 것은 스테이트리스 서비스의 디플로이먼트를 사용한다. 데몬셋이 특정 노드에서 다른 파드가 올바르게 실행되도록 하는 노드 수준 기능을 제공한다면, 파드 사본이 항상 모든 호스트 또는 특정 호스트에서 실행되는 것이 중요한 경우에 데몬셋을 사용한다.

예를 들어, 네트워크 플러그인은 데몬셋으로 실행되는 컴포넌트를 포함할 수 있다. 데몬셋 컴포넌트는 작동 중인 노드가 정상적인 클러스터 네트워킹을 할 수 있도록 한다.

다음 내용

2.5 - 잡

잡에서 하나 이상의 파드를 생성하고 지정된 수의 파드가 성공적으로 종료될 때까지 계속해서 파드의 실행을 재시도한다. 파드가 성공적으로 완료되면, 성공적으로 완료된 잡을 추적한다. 지정된 수의 성공 완료에 도달하면, 작업(즉, 잡)이 완료된다. 잡을 삭제하면 잡이 생성한 파드가 정리된다. 작업을 일시 중지하면 작업이 다시 재개될 때까지 활성 파드가 삭제된다.

간단한 사례는 잡 오브젝트를 하나 생성해서 파드 하나를 안정적으로 실행하고 완료하는 것이다. 첫 번째 파드가 실패 또는 삭제된 경우(예로는 노드 하드웨어의 실패 또는 노드 재부팅) 잡 오브젝트는 새로운 파드를 기동시킨다.

잡을 사용하면 여러 파드를 병렬로 실행할 수도 있다.

잡을 스케줄에 따라 구동하고 싶은 경우(단일 작업이든, 여러 작업의 병렬 수행이든), 크론잡(CronJob)을 참고한다.

예시 잡 실행하기

다음은 잡 설정 예시이다. 예시는 파이(π)의 2000 자리까지 계산해서 출력한다. 이를 완료하는 데 약 10초가 소요된다.

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

이 명령으로 예시를 실행할 수 있다.

kubectl apply -f https://kubernetes.io/examples/controllers/job.yaml

출력 결과는 다음과 같다.

job.batch/pi created

kubectl 을 사용해서 잡 상태를 확인한다.

kubectl describe jobs/pi

출력 결과는 다음과 같다.

Name:           pi
Namespace:      default
Selector:       controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
Labels:         controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
                job-name=pi
Annotations:    kubectl.kubernetes.io/last-applied-configuration:
                  {"apiVersion":"batch/v1","kind":"Job","metadata":{"annotations":{},"name":"pi","namespace":"default"},"spec":{"backoffLimit":4,"template":...
Parallelism:    1
Completions:    1
Start Time:     Mon, 02 Dec 2019 15:20:11 +0200
Completed At:   Mon, 02 Dec 2019 15:21:16 +0200
Duration:       65s
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
           job-name=pi
  Containers:
   pi:
    Image:      perl
    Port:       <none>
    Host Port:  <none>
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  14m   job-controller  Created pod: pi-5rwd7

kubectl get pods 를 사용해서 잡의 완료된 파드를 본다.

잡에 속하는 모든 파드를 기계적으로 읽을 수 있는 양식으로 나열하려면, 다음과 같은 명령을 사용할 수 있다.

pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods

출력 결과는 다음과 같다.

pi-5rwd7

여기서 셀렉터는 잡의 셀렉터와 동일하다. --output=jsonpath 옵션은 반환된 목록에 있는 각 파드의 이름으로 표현식을 지정한다.

파드 중 하나를 표준 출력으로 본다.

kubectl logs $pods

출력 결과는 다음과 같다.

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901

잡 사양 작성하기

다른 쿠버네티스의 설정과 마찬가지로 잡에는 apiVersion, kind 그리고 metadata 필드가 필요하다. 잡의 이름은 유효한 DNS 서브도메인 이름이어야 한다.

잡에는 .spec 섹션도 필요하다.

파드 템플릿

.spec.template.spec 의 유일한 필수 필드이다.

.spec.template파드 템플릿이다. 이것은 apiVersion 또는 kind 가 없다는 것을 제외한다면 파드와 정확하게 같은 스키마를 가지고 있다.

추가로 파드의 필수 필드 외에도 잡의 파드 템플릿은 적절한 레이블(파드 셀렉터를 본다)과 적절한 재시작 정책을 명시해야 한다.

Never 또는 OnFailure 와 같은 RestartPolicy만 허용된다.

파드 셀렉터

.spec.selector 필드는 선택 사항이다. 대부분의 케이스에서 지정해서는 안된다. 자신의 파드 셀렉터를 지정하기 섹션을 참고한다.

잡에 대한 병렬 실행

잡으로 실행하기에 적합한 작업 유형은 크게 세 가지가 있다.

  1. 비-병렬(Non-parallel) 잡:
    • 일반적으로, 파드가 실패하지 않은 한, 하나의 파드만 시작된다.
    • 파드가 성공적으로 종료하자마자 즉시 잡이 완료된다.
  2. 고정적(fixed)인 완료 횟수 를 가진 병렬 잡:
    • .spec.completions 에 0이 아닌 양수 값을 지정한다.
    • 잡은 전체 작업을 나타내며, .spec.completions 성공한 파드가 있을 때 완료된다.
    • .spec.completionMode="Indexed" 를 사용할 때, 각 파드는 0에서 .spec.completions-1 범위 내의 서로 다른 인덱스를 가져온다.
  3. 작업 큐(queue) 가 있는 병렬 잡:
    • .spec.completions 를 지정하지 않고, .spec.parallelism 를 기본으로 한다.
    • 파드는 각자 또는 외부 서비스 간에 조정을 통해 각각의 작업을 결정해야 한다. 예를 들어 파드는 작업 큐에서 최대 N 개의 항목을 일괄로 가져올(fetch) 수 있다.
    • 각 파드는 모든 피어들의 작업이 완료되었는지 여부를 독립적으로 판단할 수 있으며, 결과적으로 전체 잡이 완료되게 한다.
    • 잡의 모든 파드가 성공적으로 종료되면, 새로운 파드는 생성되지 않는다.
    • 하나 이상의 파드가 성공적으로 종료되고, 모든 파드가 종료되면 잡은 성공적으로 완료된다.
    • 성공적으로 종료된 파드가 하나라도 생긴 경우, 다른 파드들은 해당 작업을 지속하지 않아야 하며 어떠한 출력도 작성하면 안 된다. 파드들은 모두 종료되는 과정에 있어야 한다.

비-병렬 잡은 .spec.completions.spec.parallelism 모두를 설정하지 않은 채로 둘 수 있다. 이때 둘 다 설정하지 않은 경우 1이 기본으로 설정된다.

고정적인 완료 횟수 잡은 .spec.completions 을 필요한 완료 횟수로 설정해야 한다. .spec.parallelism 을 설정할 수 있고, 설정하지 않으면 1이 기본으로 설정된다.

작업 큐 잡은 .spec.completions 를 설정하지 않은 상태로 두고, .spec.parallelism 을 음수가 아닌 정수로 설정해야 한다.

다른 유형의 잡을 사용하는 방법에 대한 더 자세한 정보는 잡 패턴 섹션을 본다.

병렬 처리 제어하기

요청된 병렬 처리(.spec.parallelism)는 음수가 아닌 값으로 설정할 수 있다. 만약 지정되지 않은 경우에는 1이 기본이 된다. 만약 0으로 지정되면 병렬 처리가 증가할 때까지 사실상 일시 중지된다.

실제 병렬 처리(모든 인스턴스에서 실행되는 파드의 수)는 여러가지 이유로 요청된 병렬 처리보다 많거나 적을 수 있다.

  • 고정적인 완료 횟수(fixed completion count) 잡의 경우, 병렬로 실행 중인 파드의 수는 남은 완료 수를 초과하지 않는다. .spec.parallelism 의 더 큰 값은 사실상 무시된다.
  • 작업 큐 잡은 파드가 성공한 이후에 새로운 파드가 시작되지 않는다. 그러나 나머지 파드는 완료될 수 있다.
  • 만약 잡 컨트롤러 가 반응할 시간이 없는 경우
  • 만약 잡 컨트롤러가 어떤 이유(ResourceQuota 의 부족, 권한 부족 등)로든 파드 생성에 실패한 경우, 요청한 것보다 적은 수의 파드가 있을 수 있다.
  • 잡 컨트롤러는 동일한 잡에서 과도하게 실패한 이전 파드들로 인해 새로운 파드의 생성을 조절할 수 있다.
  • 파드가 정상적으로(gracefully) 종료되면, 중지하는데 시간이 소요된다.

완료 모드

FEATURE STATE: Kubernetes v1.22 [beta]

완료 횟수가 고정적인 완료 횟수 즉, null이 아닌 .spec.completions 가 있는 잡은 .spec.completionMode 에 지정된 완료 모드를 가질 수 있다.

  • NonIndexed (기본값): .spec.completions 가 성공적으로 완료된 파드가 있는 경우 작업이 완료된 것으로 간주된다. 즉, 각 파드 완료는 서로 상동하다(homologous). null .spec.completions 가 있는 잡은 암시적으로 NonIndexed 이다.

  • Indexed: 잡의 파드는 연결된 완료 인덱스를 0에서 .spec.completions-1 까지 가져온다. 이 인덱스는 다음의 세 가지 메카니즘으로 얻을 수 있다.

    • 파드 어노테이션 batch.kubernetes.io/job-completion-index.
    • 파드 호스트네임 중 일부($(job-name)-$(index) 형태). 인덱스된(Indexed) 잡과 서비스를 결합하여 사용하고 있다면, 잡에 속한 파드는 DNS를 이용하여 서로를 디스커버 하기 위해 사전에 결정된 호스트네임을 사용할 수 있다.
    • 컨테이너화된 태스크의 경우, JOB_COMPLETION_INDEX 환경 변수.

    각 인덱스에 대해 성공적으로 완료된 파드가 하나 있으면 작업이 완료된 것으로 간주된다. 이 모드를 사용하는 방법에 대한 자세한 내용은 정적 작업 할당을 사용한 병렬 처리를 위해 인덱싱된 잡을 참고한다. 참고로, 드물기는 하지만, 동일한 인덱스에 대해 둘 이상의 파드를 시작할 수 있지만, 그 중 하나만 완료 횟수에 포함된다.

파드와 컨테이너 장애 처리하기

파드내 컨테이너의 프로세스가 0이 아닌 종료 코드로 종료되었거나 컨테이너 메모리 제한을 초과해서 죽는 등의 여러가지 이유로 실패할 수 있다. 만약 이런 일이 발생하고 .spec.template.spec.restartPolicy = "OnFailure" 라면 파드는 노드에 그대로 유지되지만, 컨테이너는 다시 실행된다. 따라서 프로그램은 로컬에서 재시작될 때의 케이스를 다루거나 .spec.template.spec.restartPolicy = "Never" 로 지정해야 한다. 더 자세한 정보는 파드 라이프사이클restartPolicy 를 본다.

파드가 노드에서 내보내지는 경우(노드 업그레이드, 재부팅, 삭제 등) 또는 파드의 컨테이너가 실패 되고 .spec.template.spec.restartPolicy = "Never" 로 설정됨과 같은 여러 이유로 전체 파드가 실패할 수 있다. 파드가 실패하면 잡 컨트롤러는 새 파드를 시작한다. 이 의미는 애플리케이션이 새 파드에서 재시작될 때 이 케이스를 처리해야 한다는 점이다. 특히, 이전 실행으로 인한 임시파일, 잠금, 불완전한 출력 그리고 이와 유사한 것들을 처리해야 한다.

.spec.parallelism = 1, .spec.completions = 1 그리고 .spec.template.spec.restartPolicy = "Never" 를 지정하더라도 같은 프로그램을 두 번 시작하는 경우가 있다는 점을 참고한다.

.spec.parallelism 그리고 .spec.completions 를 모두 1보다 크게 지정한다면 한번에 여러 개의 파드가 실행될 수 있다. 따라서 파드는 동시성에 대해서도 관대(tolerant)해야 한다.

파드 백오프(backoff) 실패 정책

구성 등의 논리적 오류로 인해 약간의 재시도 이후에 잡을 실패하게 만들려는 경우가 있다. 이렇게 하려면 .spec.backoffLimit 에 잡을 실패로 간주하기 이전에 재시도할 횟수를 설정한다. 백오프 제한은 기본적으로 6으로 설정되어 있다. 잡과 관련한 실패한 파드는 최대 6분안에서 기하급수적으로 증가하는 백-오프 지연 (10초, 20초, 40초 ...) 한도가 되어 잡 컨트롤러에 의해 재생성된다. 잡의 파드가 삭제되거나 해당 시간 동안 잡에 대한 다른 파드가 실패 없이 성공했을 때 백 오프 카운트가 재설정된다.

잡의 종료와 정리

잡이 완료되면 파드가 더 이상 생성되지도 않지만, 일반적으로는 삭제되지도 않는다.
이를 유지하면 완료된 파드의 로그를 계속 보며 에러, 경고 또는 다른 기타 진단 출력을 확인할 수 있다. 잡 오브젝트는 완료된 후에도 상태를 볼 수 있도록 남아 있다. 상태를 확인한 후 이전 잡을 삭제하는 것은 사용자의 몫이다. kubectl 로 잡을 삭제할 수 있다 (예: kubectl delete jobs/pi 또는 kubectl delete -f ./job.yaml). kubectl 을 사용해서 잡을 삭제하면 생성된 모든 파드도 함께 삭제된다.

기본적으로 파드의 실패(restartPolicy=Never) 또는 컨테이너가 오류(restartPolicy=OnFailure)로 종료되지 않는 한, 잡은 중단되지 않고 실행되고 이때 위에서 설명했던 .spec.backoffLimit 까지 연기된다. .spec.backoffLimit 에 도달하면 잡은 실패로 표기되고 실행 중인 모든 파드는 종료된다.

잡을 종료하는 또 다른 방법은 유효 데드라인을 설정하는 것이다. 잡의 .spec.activeDeadlineSeconds 필드를 초 단위로 설정하면 된다. activeDeadlineSeconds 는 생성된 파드의 수에 관계 없이 잡의 기간에 적용된다. 잡이 activeDeadlineSeconds 에 도달하면, 실행 중인 모든 파드가 종료되고 잡의 상태는 reason: DeadlineExceeded 와 함께 type: Failed 가 된다.

잡의 .spec.activeDeadlineSeconds.spec.backoffLimit 보다 우선한다는 점을 참고한다. 따라서 하나 이상 실패한 파드를 재시도하는 잡은 backoffLimit 에 도달하지 않은 경우에도 activeDeadlineSeconds 에 지정된 시간 제한에 도달하면 추가 파드를 배포하지 않는다.

예시:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-timeout
spec:
  backoffLimit: 5
  activeDeadlineSeconds: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

잡의 사양과 잡의 파드 템플릿 사양에는 모두 activeDeadlineSeconds 필드가 있다는 점을 참고한다. 이 필드를 적절한 레벨로 설정해야 한다.

restartPolicy 는 잡 자체에 적용되는 것이 아니라 파드에 적용된다는 점을 유념한다. 잡의 상태가 type: Failed 이 되면, 잡의 자동 재시작은 없다. 즉, .spec.activeDeadlineSeconds.spec.backoffLimit 로 활성화된 잡의 종료 메커니즘은 영구적인 잡의 실패를 유발하며 이를 해결하기 위해 수동 개입이 필요하다.

완료된 잡을 자동으로 정리

완료된 잡은 일반적으로 시스템에서 더 이상 필요로 하지 않는다. 시스템 내에 이를 유지한다면 API 서버에 부담이 된다. 만약 크론잡과 같은 상위 레벨 컨트롤러가 잡을 직접 관리하는 경우, 지정된 용량 기반 정리 정책에 따라 크론잡이 잡을 정리할 수 있다.

완료된 잡을 위한 TTL 메커니즘

FEATURE STATE: Kubernetes v1.21 [beta]

완료된 잡 (Complete 또는 Failed)을 자동으로 정리하는 또 다른 방법은 잡의 .spec.ttlSecondsAfterFinished 필드를 지정해서 완료된 리소스에 대해 TTL 컨트롤러에서 제공하는 TTL 메커니즘을 사용하는 것이다.

TTL 컨트롤러는 잡을 정리하면 잡을 계단식으로 삭제한다. 즉, 잡과 함께 파드와 같은 종속 오브젝트를 삭제한다. 잡을 삭제하면 finalizer와 같은 라이프사이클 보증이 보장되는 것을 참고한다.

예시:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-ttl
spec:
  ttlSecondsAfterFinished: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

pi-with-ttl 잡은 완료 후 100 초 이후에 자동으로 삭제될 수 있다.

만약 필드를 0 으로 설정하면, 잡이 완료된 직후에 자동으로 삭제되도록 할 수 있다. 만약 필드를 설정하지 않으면, 이 잡이 완료된 후에 TTL 컨트롤러에 의해 정리되지 않는다.

잡 패턴

잡 오브젝트를 사용해서 신뢰할 수 있는 파드의 병렬 실행을 지원할 수 있다. 잡 오브젝트는 과학 컴퓨팅(scientific computing)에서 일반적으로 사용되는 밀접하게 통신하는 병렬 프로세스를 지원하도록 설계되지 않았다. 잡 오브젝트는 독립적이지만 관련된 작업 항목 집합의 병렬 처리를 지원한다. 여기에는 전송할 이메일들, 렌더링할 프레임, 코드 변환이 필요한 파일, NoSQL 데이터베이스에서의 키 범위 스캔 등이 있다.

복잡한 시스템에는 여러 개의 다른 작업 항목 집합이 있을 수 있다. 여기서는 사용자와 함께 관리하려는 하나의 작업 항목 집합 — 배치 잡 을 고려하고 있다.

병렬 계산에는 몇몇 다른 패턴이 있으며 각각의 장단점이 있다. 트레이드오프는 다음과 같다.

  • 각 작업 항목에 대한 하나의 잡 오브젝트 vs 모든 작업 항목에 대한 단일 잡 오브젝트. 후자는 작업 항목 수가 많은 경우 더 적합하다. 전자는 사용자와 시스템이 많은 수의 잡 오브젝트를 관리해야 하는 약간의 오버헤드를 만든다.
  • 작업 항목과 동일한 개수의 파드 생성 vs 각 파드에서 다수의 작업 항목을 처리. 전자는 일반적으로 기존 코드와 컨테이너를 거의 수정할 필요가 없다. 후자는 이전 글 머리표(-)와 비슷한 이유로 많은 수의 작업 항목에 적합하다.
  • 여러 접근 방식이 작업 큐를 사용한다. 이를 위해서는 큐 서비스를 실행하고, 작업 큐를 사용하도록 기존 프로그램이나 컨테이너를 수정해야 한다. 다른 접근 방식들은 기존에 컨테이너화된 애플리케이션에 보다 쉽게 적용할 수 있다.

여기에 트레이드오프가 요약되어 있고, 2열에서 4열까지가 위의 트레이드오프에 해당한다. 패턴 이름은 예시와 더 자세한 설명을 위한 링크이다.

패턴단일 잡 오브젝트작업 항목보다 파드가 적은가?수정되지 않은 앱을 사용하는가?
작업 항목 당 파드가 있는 큐때때로
가변 파드 수를 가진 큐
정적 작업 할당을 사용한 인덱싱된 잡
잡 템플릿 확장

.spec.completions 로 완료를 지정할 때, 잡 컨트롤러에 의해 생성된 각 파드는 동일한 사양을 갖는다. 이 의미는 작업의 모든 파드는 동일한 명령 줄과 동일한 이미지, 동일한 볼륨, (거의) 동일한 환경 변수를 가진다는 점이다. 이 패턴은 파드가 다른 작업을 수행하도록 배열하는 다른 방법이다.

이 표는 각 패턴에 필요한 .spec.parallelism 그리고 .spec.completions 설정을 보여준다. 여기서 W 는 작업 항목의 수이다.

패턴.spec.completions.spec.parallelism
작업 항목 당 파드가 있는 큐Wany
가변 파드 수를 가진 큐nullany
정적 작업 할당을 사용한 인덱싱된 잡Wany
잡 템플릿 확장11이어야 함

고급 사용법

잡 일시 중지

FEATURE STATE: Kubernetes v1.22 [beta]

잡이 생성되면, 잡 컨트롤러는 잡의 요구 사항을 충족하기 위해 즉시 파드 생성을 시작하고 잡이 완료될 때까지 계속한다. 그러나, 잡의 실행을 일시적으로 중단하고 나중에 재개하거나, 잡을 중단 상태로 생성하고 언제 시작할지를 커스텀 컨트롤러가 나중에 결정하도록 하고 싶을 수도 있다.

잡을 일시 중지하려면, 잡의 .spec.suspend 필드를 true로 업데이트할 수 있다. 이후에, 다시 재개하려면, false로 업데이트한다. .spec.suspend 로 설정된 잡을 생성하면 일시 중지된 상태로 생성된다.

잡이 일시 중지에서 재개되면, 해당 .status.startTime 필드가 현재 시간으로 재설정된다. 즉, 잡이 일시 중지 및 재개되면 .spec.activeDeadlineSeconds 타이머가 중지되고 재설정된다.

잡을 일시 중지하면 모든 활성 파드가 삭제된다. 잡이 일시 중지되면, SIGTERM 시그널로 파드가 종료된다. 파드의 정상 종료 기간이 적용되며 사용자의 파드는 이 기간 동안에 이 시그널을 처리해야 한다. 나중에 진행 상황을 저장하거나 변경 사항을 취소하는 작업이 포함될 수 있다. 이 방법으로 종료된 파드는 잡의 completions 수에 포함되지 않는다.

일시 중지된 상태의 잡 정의 예시는 다음과 같다.

kubectl get job myjob -o yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  suspend: true
  parallelism: 1
  completions: 5
  template:
    spec:
      ...

잡의 상태를 사용하여 잡이 일시 중지되었는지 또는 과거에 일시 중지되었는지 확인할 수 있다.

kubectl get jobs/myjob -o yaml
apiVersion: batch/v1
kind: Job
# .metadata and .spec omitted
status:
  conditions:
  - lastProbeTime: "2021-02-05T13:14:33Z"
    lastTransitionTime: "2021-02-05T13:14:33Z"
    status: "True"
    type: Suspended
  startTime: "2021-02-05T13:13:48Z"

"True" 상태인 "Suspended" 유형의 잡의 컨디션은 잡이 일시 중지되었음을 의미한다. 이 lastTransitionTime 필드는 잡이 일시 중지된 기간을 결정하는 데 사용할 수 있다. 해당 컨디션의 상태가 "False"이면, 잡이 이전에 일시 중지되었다가 현재 실행 중이다. 이러한 컨디션이 잡의 상태에 없으면, 잡이 중지되지 않은 것이다.

잡이 일시 중지 및 재개될 때에도 이벤트가 생성된다.

kubectl describe jobs/myjob
Name:           myjob
...
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  12m   job-controller  Created pod: myjob-hlrpl
  Normal  SuccessfulDelete  11m   job-controller  Deleted pod: myjob-hlrpl
  Normal  Suspended         11m   job-controller  Job suspended
  Normal  SuccessfulCreate  3s    job-controller  Created pod: myjob-jvb44
  Normal  Resumed           3s    job-controller  Job resumed

마지막 4개의 이벤트, 특히 "Suspended" 및 "Resumed" 이벤트는 .spec.suspend 필드를 전환한 결과이다. 이 두 이벤트 사이의 시간동안 파드가 생성되지 않았지만, 잡이 재개되자마자 파드 생성이 다시 시작되었음을 알 수 있다.

가변적 스케줄링 지시

FEATURE STATE: Kubernetes v1.23 [beta]

병렬 잡에서 대부분의 경우 파드를 특정 제약 조건 하에서 실행하고 싶을 것이다. (예를 들면 동일 존에서 실행하거나, 또는 GPU 모델 x 또는 y를 사용하지만 둘을 혼합하지는 않는 등)

suspend 필드는 이러한 목적을 달성하기 위한 첫 번째 단계이다. 이 필드를 사용하면 커스텀 큐(queue) 컨트롤러가 잡이 언제 시작될지를 결정할 수 있다. 그러나, 잡이 재개된 이후에는, 커스텀 큐 컨트롤러는 잡의 파드가 실제로 어디에 할당되는지에 대해서는 영향을 주지 않는다.

이 기능을 이용하여 잡이 실행되기 전에 잡의 스케줄링 지시를 업데이트할 수 있으며, 이를 통해 커스텀 큐 컨트롤러가 파드 배치에 영향을 줌과 동시에 노드로의 파드 실제 할당 작업을 kube-scheduler로부터 경감시켜 줄 수 있도록 한다. 이는 이전에 재개된 적이 없는 중지된 잡에 대해서만 허용된다.

잡의 파드 템플릿 필드 중, 노드 어피니티(node affinity), 노드 셀렉터(node selector), 톨러레이션(toleration), 레이블(label), 어노테이션(annotation)은 업데이트가 가능하다.

자신의 파드 셀렉터를 지정하기

일반적으로 잡 오브젝트를 생성할 때 .spec.selector 를 지정하지 않는다. 시스템의 기본적인 로직은 잡이 생성될 때 이 필드를 추가한다. 이것은 다른 잡과 겹치지 않는 셀렉터 값을 선택한다.

그러나, 일부 케이스에서는 이 자동화된 설정 셀렉터를 재정의해야 할 수도 있다. 이를 위해 잡의 .spec.selector 를 설정할 수 있다.

이 것을 할 때는 매우 주의해야 한다. 만약 해당 잡의 파드에 고유하지 않고 연관이 없는 파드와 일치하는 레이블 셀렉터를 지정하면, 연관이 없는 잡의 파드가 삭제되거나, 해당 잡이 다른 파드가 완료한 것으로 수를 세거나, 하나 또는 양쪽 잡 모두 파드 생성이나 실행 완료를 거부할 수도 있다. 만약 고유하지 않은 셀렉터가 선택된 경우, 다른 컨트롤러(예: 레플리케이션 컨트롤러)와 해당 파드는 예측할 수 없는 방식으로 작동할 수 있다. 쿠버네티스는 당신이 .spec.selector 를 지정할 때 발생하는 실수를 막을 수 없을 것이다.

다음은 이 기능을 사용하려는 경우의 예시이다.

old 가 이미 실행 중이다. 기존 파드가 계속 실행되기를 원하지만, 잡이 생성한 나머지 파드에는 다른 파드 템플릿을 사용하고 잡으로 하여금 새 이름을 부여하기를 원한다. 그러나 관련된 필드들은 업데이트가 불가능하기 때문에 잡을 업데이트할 수 없다. 따라서 kubectl delete jobs/old --cascade=orphan 명령을 사용해서 잡 old 를 삭제하지만, 파드를 실행 상태로 둔다. 삭제하기 전에 어떤 셀렉터를 사용하는지 기록한다.

kubectl get job old -o yaml

출력 결과는 다음과 같다.

kind: Job
metadata:
  name: old
  ...
spec:
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

그런 이후에 이름이 new 인 새 잡을 생성하고, 동일한 셀렉터를 명시적으로 지정한다. 기존 파드에는 controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002 레이블이 있기에 잡 new 에 의해서도 제어된다.

시스템이 일반적으로 자동 생성하는 셀렉터를 사용하지 않도록 하기 위해 새 잡에서 manualSelector: true 를 지정해야 한다.

kind: Job
metadata:
  name: new
  ...
spec:
  manualSelector: true
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

새 잡 자체는 a8f3d00d-c6d2-11e5-9f87-42010af00002 와 다른 uid 를 가지게 될 것이다. manualSelector: true 를 설정하면 시스템에게 사용자가 무엇을 하는지 알고 있으며 이런 불일치를 허용한다고 알릴 수 있다.

종료자(finalizers)를 이용한 잡 추적

FEATURE STATE: Kubernetes v1.23 [beta]

이 기능이 활성화되지 않으면, 잡 컨트롤러succeededfailed 파드의 수를 세어 잡 상태를 추적한다. 그런데, 파드는 다음과 같은 이유로 제거될 수 있다.

  • 노드가 다운되었을 때 가비지 콜렉터가 버려진(orphan) 파드를 제거
  • 가비지 콜렉터가 (Succeeded 또는 Failed 단계에 있는) 완료된 파드를 일정 임계값 이후에 제거
  • 잡에 속한 파드를 사용자가 임의로 제거
  • (쿠버네티스에 속하지 않는) 외부 컨트롤러가 파드를 제거하거나 교체

클러스터에서 JobTrackingWithFinalizers 기능을 활성화하면, 컨트롤 플레인은 잡에 속하는 파드의 상태를 추적하고 API 서버에서 파드가 제거되면 이를 알아챈다. 이를 위해, 잡 컨트롤러는 batch.kubernetes.io/job-tracking 종료자를 갖는 파드를 생성한다. 컨트롤러는 파드의 상태 변화가 잡 상태에 반영된 후에만 종료자를 제거하므로, 이후 다른 컨트롤러나 사용자가 파드를 제거할 수 있다.

잡 컨트롤러는 새로운 잡에 대해서만 새로운 알고리즘을 적용한다. 이 기능이 활성화되기 전에 생성된 잡은 영향을 받지 않는다. 잡에 batch.kubernetes.io/job-tracking 어노테이션이 있는지 확인하여, 잡 컨트롤러가 파드 종료자를 이용하여 잡을 추적하고 있는지 여부를 확인할 수 있다. 이 어노테이션을 잡에 수동으로 추가하거나 제거해서는 안 된다.

대안

베어(Bare) 파드

파드가 실행 중인 노드가 재부팅되거나 실패하면 파드가 종료되고 다시 시작되지 않는다. 그러나 잡은 종료된 항목을 대체하기 위해 새 파드를 생성한다. 따라서, 애플리케이션에 단일 파드만 필요한 경우에도 베어 파드 대신 잡을 사용하는 것을 권장한다.

레플리케이션 컨트롤러

잡은 레플리케이션 컨트롤러를 보완한다. 레플리케이션 컨트롤러는 종료하지 않을 파드(예: 웹 서버)를 관리하고, 잡은 종료될 것으로 예상되는 파드(예: 배치 작업)를 관리한다.

파드 라이프사이클에서 설명한 것처럼, 오직 OnFailure 또는 Never 와 같은 RestartPolicy 를 사용하는 파드에만 적절하다. (참고: RestartPolicy 가 설정되지 않은 경우에는 기본값은 Always 이다.)

단일 잡으로 컨트롤러 파드 시작

또 다른 패턴은 단일 잡이 파드를 생성한 후 다른 파드들을 생성해서 해당 파드들에 일종의 사용자 정의 컨트롤러 역할을 하는 것이다. 이를 통해 최대한의 유연성을 얻을 수 있지만, 시작하기에는 다소 복잡할 수 있으며 쿠버네티스와의 통합성이 낮아진다.

이 패턴의 한 예시는 파드를 시작하는 잡이다. 파드는 스크립트를 실행해서 스파크(Spark) 마스터 컨트롤러 (스파크 예시를 본다)를 시작하고, 스파크 드라이버를 실행한 다음, 정리한다.

이 접근 방식의 장점은 전체 프로세스가 잡 오브젝트의 완료를 보장하면서도, 파드 생성과 작업 할당 방법을 완전히 제어하고 유지한다는 것이다.

다음 내용

2.6 - 완료된 잡 자동 정리

FEATURE STATE: Kubernetes v1.23 [stable]

완료-이후-TTL(TTL-after-finished) 컨트롤러는 실행이 완료된 리소스 오브젝트의 수명을 제한하는 TTL (time to live) 메커니즘을 제공한다. TTL 컨트롤러는 만을 제어한다.

완료-이후-TTL 컨트롤러

완료-이후-TTL 컨트롤러는 잡만을 지원한다. 클러스터 운영자는 예시 와 같이 .spec.ttlSecondsAfterFinished 필드를 명시하여 완료된 잡(완료 또는 실패)을 자동으로 정리하기 위해 이 기능을 사용할 수 있다. 잡의 작업이 완료된 TTL 초(sec) 후 (다른 말로는, TTL이 만료되었을 때), 완료-이후-TTL 컨트롤러는 해당 잡이 정리될 수 있다고 가정한다. 완료-이후-TTL 컨트롤러가 잡을 정리할때 잡을 연속적으로 삭제한다. 이는 의존하는 오브젝트도 해당 잡과 함께 삭제되는 것을 의미한다. 잡이 삭제되면 완료자(finalizers)와 같은 라이프 사이클 보증이 적용 된다.

TTL 초(sec)는 언제든지 설정이 가능하다. 여기에 잡 필드 중 .spec.ttlSecondsAfterFinished 를 설정하는 몇 가지 예시가 있다.

  • 작업이 완료된 다음, 일정 시간 후에 자동으로 잡이 정리될 수 있도록 잡 메니페스트에 이 필드를 지정한다.
  • 이미 완료된 기존 잡에 이 새 기능을 적용하기 위해서 이 필드를 설정한다.
  • 어드미션 웹후크 변형 을 사용해서 잡 생성시 이 필드를 동적으로 설정 한다. 클러스터 관리자는 이것을 사용해서 완료된 잡에 대해 TTL 정책을 적용할 수 있다.
  • 잡이 완료된 이후에 어드미션 웹후크 변형 을 사용해서 이 필드를 동적으로 설정하고, 잡의 상태, 레이블 등에 따라 다른 TTL 값을 선택한다.

경고

TTL 초(sec) 업데이트

TTL 기간은, 예를 들어 잡의 .spec.ttlSecondsAfterFinished 필드는 잡을 생성하거나 완료한 후에 수정할 수 있다. 그러나, 잡을 삭제할 수 있게 되면(TTL이 만료된 경우) 시스템은 TTL을 연장하기 위한 업데이트가 성공적인 API 응답을 리턴하더라도 작업이 유지되도록 보장하지 않는다.

시간 차이(Skew)

완료-이후-TTL 컨트롤러는 쿠버네티스 잡에 저장된 타임스탬프를 사용해서 TTL의 만료 여부를 결정하기 때문에, 이 기능은 클러스터 간의 시간 차이에 민감하며, 시간 차이에 의해서 완료-이후-TTL 컨트롤러가 잘못된 시간에 잡 오브젝트를 정리하게 될 수 있다.

시계가 항상 정확한 것은 아니지만, 그 차이는 아주 작아야 한다. 0이 아닌 TTL을 설정할때는 이 위험에 대해 유의해야 한다.

다음 내용

2.7 - 크론잡

FEATURE STATE: Kubernetes v1.21 [stable]

크론잡은 반복 일정에 따라 을 만든다.

하나의 크론잡 오브젝트는 크론탭 (크론 테이블) 파일의 한 줄과 같다. 크론잡은 잡을 크론 형식으로 쓰여진 주어진 일정에 따라 주기적으로 동작시킨다.

크론잡 리소스에 대한 매니페스트를 생성할 때에는 제공하는 이름이 유효한 DNS 서브도메인 이름이어야 한다. 이름은 52자 이하여야 한다. 이는 크론잡 컨트롤러는 제공된 잡 이름에 11자를 자동으로 추가하고, 작업 이름의 최대 길이는 63자라는 제약 조건이 있기 때문이다.

크론잡

크론잡은 백업, 리포트 생성 등의 정기적 작업을 수행하기 위해 사용된다. 각 작업은 무기한 반복되도록 구성해야 한다(예: 1일/1주/1달마다 1회). 작업을 시작해야 하는 해당 간격 내 특정 시점을 정의할 수 있다.

예시

이 크론잡 매니페스트 예제는 현재 시간과 hello 메시지를 1분마다 출력한다.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

(크론잡으로 자동화된 작업 실행하기는 이 예시를 더 자세히 설명한다.)

크론 스케줄 문법

# ┌───────────── 분 (0 - 59)
# │ ┌───────────── 시 (0 - 23)
# │ │ ┌───────────── 일 (1 - 31)
# │ │ │ ┌───────────── 월 (1 - 12)
# │ │ │ │ ┌───────────── 요일 (0 - 6) (일요일부터 토요일까지;
# │ │ │ │ │                                   특정 시스템에서는 7도 일요일)
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
항목설명상응 표현
@yearly (or @annually)매년 1월 1일 자정에 실행0 0 1 1 *
@monthly매월 1일 자정에 실행0 0 1 * *
@weekly매주 일요일 자정에 실행0 0 * * 0
@daily (or @midnight)매일 자정에 실행0 0 * * *
@hourly매시 0분에 시작0 * * * *

예를 들면, 다음은 해당 작업이 매주 금요일 자정에 시작되어야 하고, 매월 13일 자정에도 시작되어야 한다는 뜻이다.

0 0 13 * 5

크론잡 스케줄 표현을 생성하기 위해서 crontab.guru와 같은 웹 도구를 사용할 수도 있다.

크론잡의 한계

크론잡은 일정의 실행시간 마다 한 번의 잡 오브젝트를 생성한다. "약" 이라고 하는 이유는 특정 환경에서는 두 개의 잡이 만들어지거나, 잡이 생성되지 않기도 하기 때문이다. 보통 이렇게 하지 않도록 해야겠지만, 완벽히 그럴 수는 없다. 따라서 잡은 멱등원 이 된다.

만약 startingDeadlineSeconds 가 큰 값으로 설정되거나, 설정되지 않고(디폴트 값), concurrencyPolicyAllow 로 설정될 경우, 잡은 항상 적어도 한 번은 실행될 것이다.

모든 크론잡에 대해 크론잡 컨트롤러 는 마지막 일정부터 지금까지 얼마나 많은 일정이 누락되었는지 확인한다. 만약 100회 이상의 일정이 누락되었다면, 잡을 실행하지 않고 아래와 같은 에러 로그를 남긴다.

Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.

중요한 것은 만약 startingDeadlineSeconds 필드가 설정이 되면(nil 이 아닌 값으로), 컨트롤러는 마지막 일정부터 지금까지 대신 startingDeadlineSeconds 값에서 몇 개의 잡이 누락되었는지 카운팅한다. 예를 들면, startingDeadlineSeconds200 이면, 컨트롤러는 최근 200초 내 몇 개의 잡이 누락되었는지 카운팅한다.

크론잡은 정해진 일정에 잡 실행을 실패하면 놓쳤다고 카운팅된다. 예를 들면, concurrencyPolicyForbid 로 설정되었고, 크론잡이 이전 일정이 스케줄되어 여전히 시도하고 있을 때, 그 때 누락되었다고 판단한다.

즉, 크론잡이 08:30:00 에 시작하여 매 분마다 새로운 잡을 실행하도록 설정이 되었고, startingDeadlineSeconds 값이 설정되어 있지 않는다고 가정해보자. 만약 크론잡 컨트롤러가 08:29:00 부터 10:21:00 까지 고장이 나면, 일정을 놓친 작업 수가 100개를 초과하여 잡이 실행되지 않을 것이다.

이 개념을 더 자세히 설명하자면, 크론잡이 08:30:00 부터 매 분 실행되는 일정으로 설정되고, startingDeadlineSeconds 이 200이라고 가정한다. 크론잡 컨트롤러가 전의 예시와 같이 고장났다고 하면 (08:29:00 부터 10:21:00 까지), 잡은 10:22:00 부터 시작될 것이다. 이 경우, 컨트롤러가 마지막 일정부터 지금까지가 아니라, 최근 200초 안에 얼마나 놓쳤는지 체크하기 때문이다. (여기서는 3번 놓쳤다고 체크함)

크론잡은 오직 그 일정에 맞는 잡 생성에 책임이 있고, 잡은 그 잡이 대표하는 파드 관리에 책임이 있다.

컨트롤러 버전

쿠버네티스 v1.21부터 크론잡 컨트롤러의 두 번째 버전이 기본 구현이다. 기본 크론잡 컨트롤러를 비활성화하고 대신 원래 크론잡 컨트롤러를 사용하려면, CronJobControllerV2 기능 게이트 플래그를 kube-controller-manager에 전달하고, 이 플래그를 false 로 설정한다. 예를 들면, 다음과 같다.

--feature-gates="CronJobControllerV2=false"

다음 내용

  • 크론잡이 의존하고 있는 파드 두 개념에 대해 배운다.
  • 크론잡 .spec.schedule 필드의 형식에 대해서 읽는다.
  • 크론잡을 생성하고 다루기 위한 지침 및 크론잡 매니페스트의 예제로 크론잡으로 자동화된 작업 실행를 읽는다.
  • 실패했거나 완료된 잡을 자동으로 정리하도록 하려면, 완료된 잡을 자동으로 정리를 확인한다.
  • CronJob은 쿠버네티스 REST API의 일부이다. CronJob 오브젝트 정의를 읽고 쿠버네티스 크론잡 API에 대해 이해한다.

2.8 - 레플리케이션 컨트롤러

레플리케이션컨트롤러 는 언제든지 지정된 수의 파드 레플리카가 실행 중임을 보장한다. 다시 말하면, 레플리케이션 컨트롤러는 파드 또는 동일 종류의 파드의 셋이 항상 기동되고 사용 가능한지 확인한다.

레플리케이션 컨트롤러의 동작방식

파드가 너무 많으면 레플리케이션 컨트롤러가 추가적인 파드를 제거한다. 너무 적으면 레플리케이션 컨트롤러는 더 많은 파드를 시작한다. 수동으로 생성된 파드와 달리 레플리케이션 컨트롤러가 유지 관리하는 파드는 실패하거나 삭제되거나 종료되는 경우 자동으로 교체된다. 예를 들어, 커널 업그레이드와 같이 파괴적인 유지 보수 작업을 하고 난 이후의 노드에서 파드가 다시 생성된다. 따라서 애플리케이션에 하나의 파드만 필요한 경우에도 레플리케이션 컨트롤러를 사용해야 한다. 레플리케이션 컨트롤러는 프로세스 감시자(supervisor)와 유사하지만 단일 노드에서 개별 프로세스를 감시하는 대신 레플리케이션 컨트롤러는 여러 노드에서 여러 파드를 감시한다.

레플리케이션 컨트롤러는 디스커션에서 종종 "rc"로 축약되며 kubectl 명령에서 숏컷으로 사용된다.

간단한 경우는 하나의 레플리케이션 컨트롤러 오브젝트를 생성하여 한 개의 파드 인스턴스를 영구히 안정적으로 실행하는 것이다. 보다 복잡한 사용 사례는 웹 서버와 같이 복제된 서비스의 동일한 레플리카를 여러 개 실행하는 것이다.

레플리케이션 컨트롤러 예제 실행

레플리케이션 컨트롤러 예제의 config는 nginx 웹서버의 복사본 세 개를 실행한다.

apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

예제 파일을 다운로드한 후 다음 명령을 실행하여 예제 작업을 실행하라.

kubectl apply -f https://k8s.io/examples/controllers/replication.yaml

출력 결과는 다음과 같다.

replicationcontroller/nginx created

다음 명령을 사용하여 레플리케이션 컨트롤러의 상태를 확인하자.

kubectl describe replicationcontrollers/nginx

출력 결과는 다음과 같다.

Name:        nginx
Namespace:   default
Selector:    app=nginx
Labels:      app=nginx
Annotations:    <none>
Replicas:    3 current / 3 desired
Pods Status: 0 Running / 3 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:       app=nginx
  Containers:
   nginx:
    Image:              nginx
    Port:               80/TCP
    Environment:        <none>
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen       LastSeen     Count    From                        SubobjectPath    Type      Reason              Message
  ---------       --------     -----    ----                        -------------    ----      ------              -------
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-qrm3m
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-3ntk0
  20s             20s          1        {replication-controller }                    Normal    SuccessfulCreate    Created pod: nginx-4ok8v

이제 세 개의 파드가 생성되었으나 아직 이미지가 풀(pull)되지 않아서 어떤 파드도 시작되지 않았다. 조금 지난 후에 같은 명령이 다음과 같이 보일 것이다.

Pods Status:    3 Running / 0 Waiting / 0 Succeeded / 0 Failed

레플리케이션 컨트롤러에 속한 모든 파드를 머신이 읽을 수 있는 형식으로 나열하기 위해 다음과 같은 명령을 사용할 수 있다.

pods=$(kubectl get pods --selector=app=nginx --output=jsonpath={.items..metadata.name})
echo $pods

출력 결과는 다음과 같다.

nginx-3ntk0 nginx-4ok8v nginx-qrm3m

여기서 셀렉터는 레플리케이션컨트롤러(kubectl describe 의 출력에서 보인)의 셀렉터와 같고, 다른 형식의 파일인 replication.yaml 의 것과 동일하다. --output=jsonpath 은 반환된 목록의 각 파드의 이름을 출력하도록 하는 옵션이다.

레플리케이션 컨트롤러의 Spec 작성

다른 모든 쿠버네티스 컨피그와 마찬가지로 레플리케이션 컨트롤러는 apiVersion, kind, metadata 와 같은 필드가 필요하다. 레플리케이션 컨트롤러 오브젝트의 이름은 유효한 DNS 서브도메인 이름이어야 한다. 환경설정 파일의 동작에 관련된 일반적인 정보는 쿠버네티스 오브젝트 관리를 참고한다.

레플리케이션 컨트롤러는 또한 .spec section도 필요하다.

파드 템플릿

.spec.template 는 오직 .spec 필드에서 요구되는 것이다.

.spec.template파드 템플릿이다. 정확하게 파드 스키마와 동일하나, 중첩되어 있고 apiVersion 혹은 kind를 갖지 않는다.

파드에 필요한 필드 외에도 레플리케이션 컨트롤러의 파드 템플릿은 적절한 레이블과 적절한 재시작 정책을 지정해야 한다. 레이블의 경우 다른 컨트롤러와 중첩되지 않도록 하라. 파드 셀렉터를 참조하라.

오직 Always 와 동일한 .spec.template.spec.restartPolicy만 허용되며, 특별히 지정되지 않으면 기본값이다.

로컬 컨테이너의 재시작의 경우, 레플리케이션 컨트롤러는 노드의 에이전트에게 위임한다. 예를 들어 Kubelet 혹은 도커이다.

레플리케이션 컨트롤러에서 레이블

레플리케이션 컨트롤러 자체는 레이블 (.metadata.labels) 을 가질 수 있다. 일반적으로 이것을 .spec.template.metadata.labels 와 동일하게 설정할 것이다. .metadata.labels 가 지정되어 있지 않은 경우, 기본은 .spec.template.metadata.labels 이다. 하지만 레이블은 다른 것이 허용되며, .metadata.labels 라벨은 레플리케이션 컨트롤러의 동작에 영향을 미치지 않는다.

파드 셀렉터

.spec.selector 필드는 레이블 셀렉터이다. 레플리케이션 컨트롤러는 셀렉터와 일치하는 레이블이 있는 모든 파드를 관리한다. 직접 생성하거나 삭제된 파드와 다른 사람이나 프로세스가 생성하거나 삭제한 파드를 구분하지 않는다. 이렇게 하면 실행중인 파드에 영향을 주지 않고 레플리케이션 컨트롤러를 교체할 수 있다.

지정된 경우 .spec.template.metadata.labels.spec.selector 와 동일해야 하며 그렇지 않으면 API에 의해 거부된다. .spec.selector 가 지정되지 않은 경우 기본값은 .spec.template.metadata.labels 이다.

또한 일반적으로 이 셀렉터와 레이블이 일치하는 파드를 직접 다른 레플리케이션 컨트롤러 또는 잡과 같은 다른 컨트롤러로 작성해서는 안된다. 그렇게 하면 레플리케이션 컨트롤러는 다른 파드를 생성했다고 생각한다. 쿠버네티스는 이런 작업을 중단해 주지 않는다.

중첩된 셀렉터들을 갖는 다수의 컨트롤러들을 종료하게 되면, 삭제된 것들은 스스로 관리를 해야 한다 (아래를 참조).

다수의 레플리카

.spec.replicas 를 동시에 실행하고 싶은 파드의 수로 설정함으로써 실행할 파드의 수를 지정할 수 있다. 레플리카가 증가 또는 감소한 경우 또는 파드가 정상적으로 종료되고 교체가 일찍 시작되는 경우라면 언제든지 실행중인 수가 더 높거나 낮을 수 있다.

.spec.replicas 를 지정하지 않으면 기본값은 1이다.

레플리케이션 컨트롤러 사용하기

레플리케이션 컨트롤러와 레플리케이션 컨트롤러의 파드 삭제

레플리케이션 컨트롤러와 레플리케이션의 모든 파드를 삭제하려면 kubectl delete 를 사용하라. Kubectl은 레플리케이션 컨트롤러를 0으로 스케일하고 레플리케이션 컨트롤러 자체를 삭제하기 전에 각 파드를 삭제하기를 기다린다. 이 kubectl 명령이 인터럽트되면 다시 시작할 수 있다.

REST API나 Go 클라이언트 라이브러리를 사용하는 경우 명시적으로 단계를 수행해야 한다(레플리카를 0으로 스케일하고 파드의 삭제를 기다린 이후, 레플리케이션 컨트롤러를 삭제).

레플리케이션 컨트롤러만 삭제

해당 파드에 영향을 주지 않고 레플리케이션 컨트롤러를 삭제할 수 있다.

kubectl을 사용하여, kubectl delete에 옵션으로 --cascade=orphan을 지정하라.

REST API나 Go 클라이언트 라이브러리를 사용하는 경우 레플리케이션 컨트롤러 오브젝트를 삭제하라.

원본이 삭제되면 대체할 새로운 레플리케이션 컨트롤러를 생성하여 교체할 수 있다. 오래된 파드와 새로운 파드의 .spec.selector 가 동일하다면, 새로운 레플리케이션 컨트롤러는 오래된 파드를 채택할 것이다. 그러나 기존 파드를 새로운 파드 템플릿과 일치시키려는 노력은 하지 않을 것이다. 새로운 spec에 대한 파드를 제어된 방법으로 업데이트하려면 롤링 업데이트를 사용하라.

레플리케이션 컨트롤러에서 파드 격리

파드는 레이블을 변경하여 레플리케이션 컨트롤러의 대상 셋에서 제거될 수 있다. 이 기술은 디버깅과 데이터 복구를 위해 서비스에서 파드를 제거하는 데 사용될 수 있다. 이 방법으로 제거된 파드는 자동으로 교체된다 (레플리카 수가 변경되지 않는다고 가정).

일반적인 사용법 패턴

다시 스케줄하기

위에서 언급했듯이, 실행하려는 파드가 한 개 혹은 1000개이든 관계없이 레플리케이션 컨트롤러는 노드 실패 또는 파드 종료시 지정된 수의 파드가 존재하도록 보장한다 (예 : 다른 제어 에이전트에 의한 동작으로 인해).

스케일링

레플리케이션컨트롤러는 replicas 필드를 업데이트하여, 수동으로 또는 오토 스케일링 제어 에이전트를 통해, 레플리카의 수를 늘리거나 줄일 수 있다.

롤링 업데이트

레플리케이션 컨트롤러는 파드를 하나씩 교체함으로써 서비스에 대한 롤링 업데이트를 쉽게 하도록 설계되었다.

#1353에서 설명한 것처럼, 권장되는 접근법은 1 개의 레플리카를 가진 새로운 레플리케이션 컨트롤러를 생성하고 새로운 (+1) 컨트롤러 및 이전 (-1) 컨트롤러를 차례대로 스케일한 후 0개의 레플리카가 되면 이전 컨트롤러를 삭제하는 것이다. 예상치 못한 오류와 상관없이 파드 세트를 예측 가능하게 업데이트한다.

이상적으로 롤링 업데이트 컨트롤러는 애플리케이션 준비 상태를 고려하며 주어진 시간에 충분한 수의 파드가 생산적으로 제공되도록 보장할 것이다.

두 레플리케이션 컨트롤러는 일반적으로 롤링 업데이트를 동기화 하는 이미지 업데이트이기 때문에 파드의 기본 컨테이너 이미지 태그와 같이 적어도 하나의 차별화된 레이블로 파드를 생성해야 한다.

다수의 릴리스 트랙

롤링 업데이트가 진행되는 동안 다수의 애플리케이션 릴리스를 실행하는 것 외에도 다수의 릴리스 트랙을 사용하여 장기간에 걸쳐 또는 연속적으로 실행하는 것이 일반적이다. 트랙은 레이블 별로 구분된다.

예를 들어, 서비스는 tier in (frontend), environment in (prod) 이 있는 모든 파드를 대상으로 할 수 있다. 이제 이 계층을 구성하는 10 개의 복제된 파드가 있다고 가정해 보자. 하지만 이 구성 요소의 새로운 버전을 '카나리' 하기를 원한다. 대량의 레플리카에 대해 replicas 를 9로 설정하고 tier=frontend, environment=prod, track=stable 레이블을 설정한 레플리케이션 컨트롤러와, 카나리에 replicas 가 1로 설정된 다른 레플리케이션 컨트롤러에 tier=frontend, environment=prod, track=canary 라는 레이블을 설정할 수 있다. 이제 이 서비스는 카나리와 카나리 이외의 파드 모두를 포함한다. 그러나 레플리케이션 컨트롤러를 별도로 조작하여 테스트하고 결과를 모니터링하는 등의 작업이 혼란스러울 수 있다.

서비스와 레플리케이션컨트롤러 사용

하나의 서비스 뒤에 여러 개의 레플리케이션컨트롤러가 있을 수 있다. 예를 들어 일부 트래픽은 이전 버전으로 이동하고 일부는 새 버전으로 이동한다.

레플리케이션컨트롤러는 자체적으로 종료되지 않지만, 서비스만큼 오래 지속될 것으로 기대되지는 않는다. 서비스는 여러 레플리케이션컨트롤러에 의해 제어되는 파드로 구성될 수 있으며, 서비스 라이프사이클 동안(예를 들어, 서비스를 실행하는 파드 업데이트 수행을 위해) 많은 레플리케이션컨트롤러가 생성 및 제거될 것으로 예상된다. 서비스 자체와 클라이언트 모두 파드를 유지하는 레플리케이션컨트롤러를 의식하지 않는 상태로 남아 있어야 한다.

레플리케이션을 위한 프로그램 작성

레플리케이션 컨트롤러에 의해 생성된 파드는 해당 구성이 시간이 지남에 따라 이질적이 될 수 있지만 균일하고 의미상 동일하도록 설계되었다. 이는 레플리카된 상태 스테이트리스 서버에 적합하지만 레플리케이션 컨트롤러를 사용하여 마스터 선출, 샤드 및 워크-풀 애플리케이션의 가용성을 유지할 수도 있다. RabbitMQ work queues와 같은 애플리케이션은 안티패턴으로 간주되는 각 파드의 구성에 대한 정적/일회성 사용자 정의와 반대로 동적 작업 할당 메커니즘을 사용해야 한다. 리소스의 수직 자동 크기 조정 (예 : CPU 또는 메모리)과 같은 수행된 모든 파드 사용자 정의는 레플리케이션 컨트롤러 자체와 달리 다른 온라인 컨트롤러 프로세스에 의해 수행되어야 한다.

레플리케이션 컨트롤러의 책임

레플리케이션 컨트롤러는 의도한 수의 파드가 해당 레이블 셀렉터와 일치하고 동작하는지를 확인한다. 현재, 종료된 파드만 해당 파드의 수에서 제외된다. 향후 시스템에서 사용할 수 있는 readiness 및 기타 정보가 고려될 수 있으며 교체 정책에 대한 통제를 더 추가 할 수 있고 외부 클라이언트가 임의로 정교한 교체 또는 스케일 다운 정책을 구현하기 위해 사용할 수 있는 이벤트를 내보낼 계획이다.

레플리케이션 컨트롤러는 이 좁은 책임에 영원히 제약을 받는다. 그 자체로는 준비성 또는 활성 프로브를 실행하지 않을 것이다. 오토 스케일링을 수행하는 대신, 외부 오토 스케일러 (#492에서 논의된)가 레플리케이션 컨트롤러의 replicas 필드를 변경함으로써 제어되도록 의도되었다. 레플리케이션 컨트롤러에 스케줄링 정책 (예를 들어 spreading)을 추가하지 않을 것이다. 오토사이징 및 기타 자동화 된 프로세스를 방해할 수 있으므로 제어된 파드가 현재 지정된 템플릿과 일치하는지 확인해야 한다. 마찬가지로 기한 완료, 순서 종속성, 구성 확장 및 기타 기능은 다른 곳에 속한다. 대량의 파드 생성 메커니즘 (#170)까지도 고려해야 한다.

레플리케이션 컨트롤러는 조합 가능한 빌딩-블록 프리미티브가 되도록 고안되었다. 향후 사용자의 편의를 위해 더 상위 수준의 API 및/또는 도구와 그리고 다른 보완적인 기본 요소가 그 위에 구축 될 것으로 기대한다. 현재 kubectl이 지원하는 "매크로" 작업 (실행, 스케일)은 개념 증명의 예시이다. 예를 들어 Asgard와 같이 레플리케이션 컨트롤러, 오토 스케일러, 서비스, 정책 스케줄링, 카나리 등을 관리할 수 있다.

API 오브젝트

레플리케이션 컨트롤러는 쿠버네티스 REST API의 최상위 수준의 리소스이다. API 오브젝트에 대한 더 자세한 것은 ReplicationController API object 에서 찾을 수 있다.

레플리케이션 컨트롤러의 대안

레플리카셋

ReplicaSet은 새로운 집합성 기준 레이블 셀렉터이다. 이것은 주로 디플로이먼트에 의해 파드의 생성, 삭제 및 업데이트를 오케스트레이션 하는 메커니즘으로 사용된다. 사용자 지정 업데이트 조정이 필요하거나 업데이트가 필요하지 않은 경우가 아니면 레플리카셋을 직접 사용하는 대신 디플로이먼트를 사용하는 것이 좋다.

디플로이먼트 (권장됨)

Deployment는 기본 레플리카셋과 그 파드를 업데이트하는 상위 수준의 API 오브젝트이다. 선언적이며, 서버 사이드이고, 추가 기능이 있기 때문에 롤링 업데이트 기능을 원한다면 디플로이먼트를 권장한다.

베어 파드

사용자가 직접 파드를 만든 경우와 달리 레플리케이션 컨트롤러는 노드 오류 또는 커널 업그레이드와 같은 장애가 발생하는 노드 유지 관리의 경우와 같이 어떤 이유로든 삭제되거나 종료된 파드를 대체한다. 따라서 애플리케이션에 하나의 파드만 필요한 경우에도 레플리케이션 컨트롤러를 사용하는 것이 좋다. 프로세스 관리자와 비슷하게 생각하면, 단지 단일 노드의 개별 프로세스가 아닌 여러 노드에서 여러 파드를 감독하는 것이다. 레플리케이션 컨트롤러는 로컬 컨테이너가 노드의 에이전트로 (예를 들어 Kubelet 또는 도커 ) 재시작하도록 위임한다.

자체적으로 제거될 것으로 예상되는 파드 (즉, 배치 잡)의 경우 레플리케이션 컨트롤러 대신 Job을 사용하라.

데몬셋

머신 모니터링이나 머신 로깅과 같은 머신 레벨 기능을 제공하는 파드에는 레플리케이션 컨트롤러 대신 DaemonSet을 사용하라. 이런 파드들의 수명은 머신의 수명에 달려 있다. 다른 파드가 시작되기 전에 파드가 머신에서 실행되어야 하며, 머신이 재부팅/종료 준비가 되어 있을 때 안전하게 종료된다.

다음 내용

  • 파드에 대해 배운다.
  • 레플리케이션 컨트롤러를 대신하는 디플로이먼트에 대해 배운다.
  • ReplicationController는 쿠버네티스 REST API의 일부이다. 레플리케이션 컨트롤러 API에 대해 이해하기 위해 ReplicationController 오브젝트 정의를 읽는다.