본문 바로가기

Infrastructure/Kubernetes

[컨테이너] 환경변수와 라이프사이클 훅(Hook)

환경변수와 라이프사이클 훅(Hook)

쿠버네티스 공식문서를 확인하며 환경변수와 라이프사이클 훅(Hook)에 대해서 기억해야 하는 부분을 기록한다.


환경변수

컨테이너 환경

  • 컨테이너 환경은 컨테이너에 몇 가지 중요한 리소스를 제공한다.
    • 하나의 이미지와 하나 이상의 볼륨이 결합된 파일 시스템
    • 컨테이너 자신에 대한 정보
    • 클러스터 내의 다른 오브젝트에 대한 정보

컨테이너 정보

  • 컨테이너의 “호스트네임”은 컨테이너가 동작 중인 파드의 이름과 같다. **hostname 커맨드 또는 libc의 gethostname 함수 호출을 통해서 구할 수** 있다.
  • 파드 이름과 네임스페이스는 다운워드(Downward) API를 통해 환경 변수로 구할 수 있다.
  • 컨테이너 이미지에 정적으로 명시된 환경 변수와 마찬가지로, 파드 정의에서의 사용자 정의 환경 변수도 컨테이너가 사용할 수 있다.

클러스터 정보

  • 컨테이너가 생성될 때 실행 중이던 모든 서비스의 목록은 환경 변수로 해당 컨테이너에서 사용할 수 있다.
  • 서비스 목록은 새로운 컨테이너의 파드 및 쿠버네티스 컨트롤 플레인 서비스와 동일한 네임스페이스 내에 있는 서비스로 한정된다.
  • bar라는 이름의 컨테이너에 매핑되는 foo라는 이름의 서비스에 대해서는, 아래의 형태로 변수가 정의된다.
FOO_SERVICE_HOST=<서비스가 동작 중인 호스트>
FOO_SERVICE_PORT=<서비스가 동작 중인 포트>
  • 서비스에 지정된 IP 주소가 있고 DNS 애드온이 활성화된 경우, DNS를 통해서 컨테이너가 서비스를 사용할 수 있다.

[정리]

컨테이너 환경은 컨테이너에게 환경변수로 몇가지 리소스를 제공한다.

여기에는 컨테이너 정보, 클러스터 정보, 동일 네임스페이스 내에 있는 서비스 목록 등이 있다.


컨테이너 라이프사이클 훅(Hook)

  • kubelet이 관리하는 컨테이너가 관리 라이프사이클 동안 이벤트에 의해 발동되는 코드를 실행하기 위해 컨테이너 라이프사이클 훅 프레임워크를 사용한다.
  • Angular와 같이, 컴포넌트 라이프사이클 훅을 가진 많은 프로그래밍 언어 프레임워크와 유사하게, 쿠버네티스도 컨테이너에 라이프사이클 훅을 제공한다.
  • 훅은 컨테이너가 관리 라이프사이클의 이벤트를 인지하고 상응하는 라이프사이클 훅이 실행될 때 핸들러에 구현된 코드를 실행할 수 있게 한다.

컨테이너 훅

  • 컨테이너 훅은 아래와 총 두 가지가 있다.

PostStart

  • PostStart 훅은 컨테이너가 생성된 직후에 실행되지만, 훅이 컨테이너 엔트리포인트에 앞서서 실행된다는 보장은 없으며, 파라미터는 핸들러에 전달되지 않는다.

PreStop

  • PreStop 훅은 API 요청이나 활성 프로브(liveness probe) 실패, 선점, 자원 경합 등의 관리 이벤트로 인해 컨테이너가 종료되기 직전에 호출된다.
  • 컨테이너가 이미 terminated 또는 completed 상태인 경우에는 PreStop 훅 요청이 실패하며, 훅은 컨테이너를 중지하기 위한 TERM 신호가 보내지기 이전에 완료되어야 한다.
  • 파드의 그레이스 종료 기간(termination grace period)의 초읽기는 PreStop 훅이 실행되기 전에 시작되어, 핸들러의 결과에 상관없이 컨테이너가 파드의 그레이스 종료 기간 내에 결국 종료되도록 한다. 파라미터는 핸들러에 전달되지 않는다.

[정리]

라이프사이클 훅 프레임워크 컨테이너의 라이프사이클을 관리하기 위해서 사용된다. 훅은 컨테이너가 관리 라이프사이클의 이벤트를 인지하고 상응하는 라이프사이클 훅이 실행될 때 핸들러에 구현된 코드를 실행할 수 있게 한다.

PostStart는 컨테이너 생성 직후 실행되며, PreStop은 컨테이너가 종료되기 직전에 호출된다.


훅 핸들러 구현

  • 컨테이너는 훅의 핸들러를 구현하고 등록함으로써 해당 훅에 접근할 수 있다.
  • 구현될 수 있는 컨테이너의 훅 핸들러에는 두 가지 유형이 있다
    • Exec: 컨테이너의 cgroups와 네임스페이스 안에서, pre-stop.sh 와 같은, 특정 커맨드를 실행. 커맨드에 의해 소비된 리소스는 해당 컨테이너에 대해 계산된다.
    • HTTP: 컨테이너의 특정 엔드포인트에 대해서 HTTP 요청을 실행.

훅 핸들러 실행

  • 컨테이너 라이프사이클 관리 훅이 호출되면, 쿠버네티스 관리 시스템은 훅 동작에 따라 핸들러를 실행하고, httpGettcpSocket은 kubelet 프로세스에 의해 실행되고, exec은 컨테이너에서 실행된다.
  • 훅 핸들러 호출은 해당 컨테이너를 포함하고 있는 파드의 컨텍스트와 동기적으로 동작한다. 이것은 PostStart 훅에 대해서, 훅이 컨테이너 엔트리포인트와는 비동기적으로 동작함을 의미한다. 파드와 동기이기 때문에 파드 내부의 컨테이너와는 비동기로 작동할 수 밖에 없다.
  • 그러나, 만약 해당 훅이 너무 오래 동작하거나 어딘가에 걸려 있다면, 컨테이너는 running 상태에 이르지 못한다.
  • PreStop 훅은 컨테이너 중지 신호에서 비동기적으로 실행되지 않는다. 훅은 TERM 신호를 보내기 전에 실행을 완료해야 한다.
  • 실행 중에 PreStop 훅이 중단되면, 파드의 단계는 Terminating이며 terminationGracePeriodSeconds가 만료된 후 파드가 종료될 때까지 남아 있다. 이때의 유예 기간은 PreStop 훅이 실행되고 컨테이너가 정상적으로 중지되는 데 걸리는 총 시간에 적용된다.
  • 만약 terminationGracePeriodSeconds가 60이고, 훅이 완료되는 데 55초가 걸리고, 컨테이너가 신호를 수신한 후 정상적으로 중지하는 데 10초가 걸린다면, terminationGracePeriodSeconds 이후 컨테이너가 정상적으로 중지되기 전에 종료된다. 이 두 가지 일이 발생하는 데 걸리는 총 시간(55 + 10)보다 적다.
  • 만약 PostStart 또는 PreStop 훅이 실패하면, 컨테이너를 종료시킨다.

훅 전달 보장

  • 훅 전달은 “한 번 이상”으로 의도되어 있는데, 이는 PostStart 또는 PreStop과 같은 특정 이벤트에 대해서, 훅이 여러 번 호출될 수 있다는 것을 의미한다.
  • 일반적으로 훅의 전달은 단 한 번만 이루어지기 때문에 HTTP 훅 수신기가 다운되어 트래픽을 받을 수 없는 경우에도, 재전송을 시도하지 않는다.
  • 훅을 전송하는 도중에 kubelet이 재시작된다면 kubelet이 구동된 후에 해당 훅이 재전송되기 때문에 적은 확룔로 이중 전달이 발생할 수 있다.

훅 핸들러 디버깅

  • 훅 핸들러의 로그는 파드 이벤트로 노출되지 않는다. 핸들러가 어떠한 이유로 실패하면, 핸들러는 이벤트를 방송한다.
  • PostStart의 경우, 이것은 FailedPostStartHook 이벤트이며, PreStop의 경우, 이것은 FailedPreStopHook 이벤트다.
  • 실패한 FailedPreStopHook 이벤트를 직접 생성하려면, lifecycle-events.yaml 파일을 수정하여 postStart 명령을 badcommend로 변경하고 이를 적용한다.
  • 아래는 kubectl describe pod lifecycle-demo를 실행하여 볼 수 있는 이벤트 출력 예시다.
Events:
  Type     Reason               Age              From               Message
  ----     ------               ----             ----               -------
  Normal   Scheduled            7s               default-scheduler  Successfully assigned default/lifecycle-demo to ip-XXX-XXX-XX-XX.us-east-2...
  Normal   Pulled               6s               kubelet            Successfully pulled image "nginx" in 229.604315ms
  Normal   Pulling              4s (x2 over 6s)  kubelet            Pulling image "nginx"
  Normal   Created              4s (x2 over 5s)  kubelet            Created container lifecycle-demo-container
  Normal   Started              4s (x2 over 5s)  kubelet            Started container lifecycle-demo-container
  Warning  FailedPostStartHook  4s (x2 over 5s)  kubelet            Exec lifecycle hook ([badcommand]) for Container "lifecycle-demo-container" in Pod "lifecycle-demo_default(30229739-9651-4e5a-9a32-a8f1688862db)" failed - error: command 'badcommand' exited with 126: , message: "OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: \"badcommand\": executable file not found in $PATH: unknown\r\n"
  Normal   Killing              4s (x2 over 5s)  kubelet            FailedPostStartHook
  Normal   Pulled               4s               kubelet            Successfully pulled image "nginx" in 215.66395ms
  Warning  BackOff              2s (x2 over 3s)  kubelet            Back-off restarting failed container

[정리]

컨테이너는 훅에 접근하기 위해서 훅의 핸들러를 구현해서 등록해야 한다. 훅 핸들러 호출은 컨테이너의 파드 컨텍스트와 동기적으로 작동하기 때문에 컨테이너와는 비동기적으로 동작한다. 만약 훅이 너무 오래 동작하거나 실행되지 않는다면 컨테이너는 running 상태에 도달할 수 없으며 훅이 실패하는 경우에도 컨테이너가 종료된다.

훅 전달은 한 번 이상으로 의도되어 있기 때문에 여러 번 호출될 수 있다. 하지만 일반적으로 훅의 전달은 단 한 번만 이루어지게 된다. kubelet이 재시작된다면 kubelet이 구동된 후에 해당 훅이 재전송되어 훅이 이중으로 전달될 수 있다.

훅 핸들러의 로그는 파드 이벤트로 노출되지 않기 때문에 핸들러가 어떠한 이유라 실패하면, 핸들러는 이벤트를 방송한다.


참고 자료

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

[Pod] 개념  (0) 2022.10.14
[워크로드] 개념  (0) 2022.10.14
[컨테이너] 런타임클래스  (0) 2022.10.14
[컨테이너] 이미지  (0) 2022.10.14
[클러스터 아키텍처] 컨테이너 런타임 인터페이스(CRI)  (0) 2022.10.13