본문 바로가기

Infrastructure/Kubernetes

[Pod] 초기화 컨테이너

초기화 컨테이너

쿠버네티스 공식문서를 확인하며 초기화 컨테이너에 대해서 기억해야 하는 부분을 기록한다.

  • 초기화 컨테이너는 파드의 앱 컨테이너들이 실행되기 전에 실행되는 특수한 컨테이너로 앱 이미지에는 없는 유틸리티 또는 설정 스크립트 등을 포함할 수 있다.
  • 초기화 컨테이너는 containers 배열에 과 나란히 파드 스펙에 명시할 수 있다.
  • 초기화 컨테이너는 일반 컨테이너와 비교하여 아래와 같은 차이가 있다.
    • 초기화 컨테이너는 항상 완료를 목표로 실행된다.
    • 각 초기화 컨테이너는 다음 초기화 컨테이너가 시작되기 전에 성공적으로 완료되어야 한다.
  • 파드의 초기화 컨테이너가 실패하면, kubelet은 초기화 컨테이너가 성공할 때까지 반복적으로 재시작한다.
  • 만약 파드의 restartPolicyNever로 설정하고, 해당 파드를 시작하는 동안 초기화 컨테이너가 실패하면, 쿠버네티스는 전체 파드를 실패한 것으로 처리한다.
  • 컨테이너를 초기화 컨테이너로 지정하기 위해서는, 파드 스펙에 initContainers 필드를 container 항목들의 배열로서 추가한다.
  • 초기화 컨테이너의 상태는 컨테이너 상태의 배열로 .status.initContainerStatuses 필드에 반환된다.

[정리]

초기화 컨테이너는 애플리케이션 컨테이너를 실행시키기 위한 특수한 컨테이너로 애플리케이션 컨테이너가 실행되기 전에 성공적으로 완료되는 것을 목표로 한다. 여러 초기화 컨테이너가 존재할 수 있으며 애플리케이션 컨테이너를 실행시키기 위해 순차적으로 실행된다.


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

  • 초기화 컨테이너는 앱 컨테이너의 리소스 상한, 볼륨, 보안 세팅을 포함한 모든 필드와 기능을 지원하지만 초기화 컨테이너를 위한 리소스 요청량과 상한은 다르게 처리된다.
  • 초기화 컨테이너는 lifecycle, livenessProbe, readinessProbe 또는 startupProbe를 지원하지 않는다. 파드의 준비 상태가 되기 전에 완료를 목표로 실행되어야 하기 때문이다.
  • 다수의 초기화 컨테이너가 파드에 지정되어 있다면, kubelet은 한 번에 하나씩 실행한다. 각 초기화 컨테이너는 다음 컨테이너를 실행하기 전에 꼭 성공해야 한다. 모든 컨테이너들이 실행 완료되었을 때, kubelet은 파드의 애플리케이션 컨테이너들을 초기화하고 평소와 같이 실행한다.

[정리]

초기화 컨테이너는 일반 컨테이너와 대부분의 필드를 공유하지만 리소스 요청 및 사용량은 다르게 처리된다. 컨테이너의 목적에 따라 라이프사이클, 일부 프로브와 같은 특정 기능은 제공하지 않는다.


초기화 컨테이너 사용하기

  • 초기화 컨테이너는 앱 컨테이너와는 별도의 이미지를 가지고 있기 때문에, 시동(start-up)에 관련된 코드로서 몇 가지 이점을 가진다.
    • 앱 이미지에는 없는 셋업을 위한 유틸리티 또는 맞춤 코드를 포함할 수 있다. FROM에서 다른 이미지를 명시하고 셋업을 위한 작업을 진행할 수 있다는 것을 의미한다.
    • 애플리케이션 이미지 빌더와 디플로이 역할은 독립적으로 동작될 수 있어서 공동의 단일 앱 이미지 형태로 빌드될 필요가 없다.
    • 앱 컨테이너들은 병렬로 실행되는 반면, 초기화 컨테이너들은 어떠한 앱 컨테이너라도 시작되기 전에 실행 완료되어야 하므로, 초기화 컨테이너는 사전 조건들이 충족될 때까지 앱 컨테이너가 시동되는 것을 막거나 지연시키는 간편한 방법을 제공한다.
    • 초기화 컨테이너는 앱 컨테이너 이미지의 보안성을 떨어뜨릴 수도 있는 유틸리티 혹은 커스텀 코드를 안전하게 실행할 수 있다. 불필요한 툴들을 분리한 채로 유지함으로써 앱 컨테이너 이미지의 공격에 대한 노출을 제한할 수 있다.
  • 아래와 같은 Shell 커맨드로 서비스가 생성될 때까지 기다릴 수 있다.
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
  • 이외에도 깃 저장소를 볼륨안에 복사, 설정 파일에 값을 지정하고 메인 앱 컨테이너를 위한 설정 파일들을 동적으로 생성과 같은 작업을 진행할 수 있다.

[정리]

초기화 컨테이너는 앱 컨테이너와는 별도의 이미지를 가져있기 때문에 몇가지 이점을 가지고 있다. 특히 초기화 컨테이너가 전부 완료되기 전에 앱 컨테이너가 실행되는 것을 제어해야 하기 때문에 이를 위한 여러가지 간편한 방법을 제공한다.

사용 중인 초기화 컨테이너

  • v1.5 기준으로 아래와 같은 yaml 파일이 있을 때 처음으로 myservice를 기다리고 두 번째는 mydb를 기다릴 것이다. 두 컨테이너들이 완료되면 파드가 시작된다.
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app.kubernetes.io/name: MyApp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
  - name: init-mydb
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]
  • kubectl describe -f myapp.yaml 커맨드를 입력하여 파드가 실행된 순서를 확인해보면 아래와 같다.
    mydbmyservice 서비스를 시작하고 초기화 컨테이너가 완료되면 myapp-pod가 생성된다.
Name:          myapp-pod
Namespace:     default
[...]
Labels:        app.kubernetes.io/name=MyApp
Status:        Pending
[...]
Init Containers:
  init-myservice:
[...]
    State:         Running
[...]
  init-mydb:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Containers:
  myapp-container:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Events:
  FirstSeen    LastSeen    Count    From                      SubObjectPath                           Type          Reason        Message
  ---------    --------    -----    ----                      -------------                           --------      ------        -------
  16s          16s         1        {default-scheduler }                                              Normal        Scheduled     Successfully assigned myapp-pod to 172.17.4.201
  16s          16s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulling       pulling image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulled        Successfully pulled image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Created       Created container 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

자세한 동작

  • 파드 시작 시에 kubelet은 네트워크와 스토리지가 준비될 때까지 초기화 컨테이너의 실행을 지연시키고, 파드 사양에 나와있는 순서대로 파드의 초기화 컨테이너를 실행한다.
  • 각 초기화 컨테이너는 다음 컨테이너가 시작되기 전에 성공적으로 종료되어야 하며, 런타임 문제나 실패 상태로 종료되는 문제로 인하여 초기화 컨테이너가 실패한다면, 초기화 컨테이너는 파드의 restartPolicy에 따라서 재시도 된다.
  • 만약, 파드의 restartPolicy가 항상(Always)로 설정된 경우, 해당 초기화 컨테이너는 restartPolicy를 실패 시(OnFailure)로 사용한다.
  • 파드는 모든 초기화 컨테이너가 성공되기 전까지 Ready 될 수 없다. 초기화 컨테이너의 포트는 서비스 하에 합쳐지지 않고 초기화 중인 파드는 Pending 상태이지만 Initialized가 거짓이 되는 조건을 가져야 한다.
  • 파드가 재시작되는 경우에 모든 초기화 컨테이너도 반드시 다시 실행된다.
  • 초기화 컨테이너 스펙 변경은 컨테이너 이미지 필드에서만 한정적으로 가능하며 초기화 컨테이너 이미지 필드를 변경하는 것은 파드를 재시작하는 것과 같다.
  • 초기화 컨테이너는 재시작되거나, 재시도 또는 재실행 될 수 있기 때문에 초기화 컨테이너 멱등성(idempotent)을 유지해야 한다. 특히, EmptyDirs에 있는 파일에 쓰기를 수행하는 코드는 출력 파일이 이미 존재할 가능성에 대비해야 한다.
  • 초기화 컨테이너는 앱 컨테이너의 모든 필드를 가지고 있지만 **readinessProbe를 사용하는 것을 금지한다. 초기화 컨테이너가 **완료 상태와 준비성을 구분해서 정의할 수 없기 때문이다.
  • 초기화 컨테이너들이 실패를 영원히 지속하는 상황을 방지하기 위해서 파드의 activeDeadlineSeconds를 사용하고 Active deadline은 초기화 컨테이너를 포함한다.
  • activeDeadlineSeconds는 초기화 컨테이너가 완료된 이후에도 영향을 주기 때문에 애플리케이션을 잡(job)으로 배포된 경우에만 사용되는 것이 추천된다. 이미 정상적으로 동작하고 있는 파드도 activeDeadlineSeconds를 설정한 경우 종료될 수 있다.

[정리]

kubelet은 초기화 컨테이너가 시작되기 전에 네트워크와 스토리지 준비를 하고 준비가 완료되면 순서대로 실행시킨다. 순서대로라고 하면 사용자가 의도한 순서를 의미한다. 트랜잭션과 같이 모든 초기화 컨테이너가 성공적으로 실행되어야 애플리케이션 컨테이너가 실행될 수 있고 파드가 재실행되는 경우에도 모든 초기화 컨테이너는 정해진 순서대로 재실행된다. 초기화 컨테이너는 몇 번이고 재실행될 수 있기 때문에 항상 같은 결과를 반환해야 한다.

리소스

  • 초기화 컨테이너에게 명령과 실행이 주어진 경우, 리소스 사용에 대해 아래의 규칙이 적용된다.
    • 모든 컨테이너에 정의된 특정 리소스 요청량과 상한 중 가장 높은 것은 유효 초기화 요청량과 상한이다. 리소스 제한이 지정되지 않은 리소스는 이 유효 초기화 요청량과 상한을 가장 높은 요청량 상한으로 간주한다.
    • 리소스를 위한 파드의 유효한 초기화 요청량과 상한은 아래보다 더 높다.
      • 모든 앱 컨테이너의 리소스에 대한 요청량과 상한의 합계
      • 리소스에 대한 유효한 초기화 요청량과 상한
    • 스케줄링은 유효한 요청과 상한에 따라 이루어진다. 즉, 초기화 컨테이너는 파드의 삶에서 사용되지 않는 초기화를 위한 리소스를 예약할 수 있다.
    • 파드의 유효한 QoS 계층에서 QoS(서비스의 품질) 계층은 초기화 컨테이너들과 앱 컨테이너들의 QoS 계층과 같다.
  • 쿼터 및 상한은 유효한 파드의 요청량 및 상한에 따라 적용된다.
  • 파드 레벨 cgroup은 유효한 파드 요청량 및 상한을 기반으로 하고 이것은 스케줄러와 같다.

파드 재시작 이유

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

[정리]

초기화 컨테이너의 경우 애플리케이션 컨테이너와 다르게 리소스 사용 규칙이 적용되기 때문에 이 부분을 인지해야 한다. 파드는 일반적인 상황이 아니면 노드에 대해서 root 접근 권한을 가진 누군가에 의해 재실행되거나 가비지 수집에 의해 초기화 컨테이너 완료기록이 유실된 상태로 restartPolicy가 Always로 설정되어 재시작해야 하는 상황에서 재시작을 수행할 수 있다.


참고 자료

'Infrastructure > Kubernetes' 카테고리의 다른 글

[Pod] 임시(Ephemeral) 컨테이너  (0) 2022.10.18
[Pod] 중단(Disruption)  (0) 2022.10.18
[Pod] 라이프사이클  (0) 2022.10.18
[Pod] 개념  (0) 2022.10.14
[워크로드] 개념  (0) 2022.10.14