[쿠버네티스] Chapter 5. Running applications in Pods


Three basic objects

basic_object

Pod

컨테이너를 개별적으로 배포하는 대신 컨테이너 그룹을 단일 단위(팟)로 배포하고 관리한다. 포드는 여러 개의 컨테이너를 포함할 수 있지만, 주로 하나만을 갖고 있다. 포드에 여러 개의 컨테이너가 있는 경우 모든 컨테이너가 동일한 worker 노드에서 실행된다. 단일 포드 인스턴스는 여러 노드에 걸쳐 있지 않다.

pod

팟이 필요한 이유는 뭘까..?
하나의 컨테이너에 여러 프로세스를 포함하면 안 되는 이유는 뭘까..?

동일한 컴퓨터에서 실행되어야 하는 IPC(프로세스 간 통신) 또는 공유 파일을 통해 서로 통신하는 여러 프로세스로 구성된 응용 프로그램을 생각해보자. 애플리케이션을 구성하는 모든 프로세스를 하나의 컨테이너에서 실행할 수 있지만, 이로 인해 컨테이너를 관리하기가 매우 어려워진다.

컨테이너는 단일 프로세스만 실행하도록 설계되었다. 예를 들어, 컨테이너에서 실행되는 프로세스는 표준 출력에 로그를 기록해야 한다고 하자. 로그를 표시하는 데 사용하는 Docker 및 Kubernetes 명령은 이 출력에서 캡처된 내용만 표시합니다. 단일 프로세스가 컨테이너에서 실행 중인 경우 이 프로세스는 유일한 기록 장치이지만 컨테이너에서 여러 프로세스를 실행하면 모두 동일한 출력에 쓰게 된다. 따라서 로그는 서로 얽혀 있으며 기록된 각 라인이 어느 프로세스에 속하는지 구별하기가 어려워진다.

컨테이너가 단일 프로세스만 실행해야 한다는 또 다른 이유는 컨테이너의 루트 프로세스가 종료될 때에만 컨테이너 런타임이 컨테이너를 재시작한다는 것이다. 하위 프로세스를 생성하는 경우 이러한 모든 프로세스를 계속 실행할 책임이 있게 된다. 결과적으로 컨테이너 런타임에서 제공되는 기능을 최대한 활용하려면 각 컨테이너에서 하나의 프로세스만 실행하는 것이 제일 좋다.

팟은 어떻게 여러 컨테이너를 결합할까?
단일 컨테이너에서 여러 프로세스를 실행해서는 안 되므로 여러 컨테이너로 분할된 경우에도 관련 프로세스를 함께 실행할 수 있는 또 다른 상위 수준의 구성이 필요해진다. 이러한 프로세스들은 일반 컴퓨터의 프로세스처럼 서로 통신할 수 있어야 하는데 이는 바로 팟이 도입된 이유이다.

포드를 사용하면 밀접하게 관련된 프로세스를 함께 실행할 수 있으므로 마치 모든 프로세스가 단일 컨테이너에서 실행되는 것과 동일한 환경을 제공할 수 있다. 이러한 프로세스는 일부 리소스를 공유하므로 완전히 고립되어 있지는 않다. 포드는 이러한 상호 연결된 컨테이너를 하나의 단위로 관리할 수 있도록 한다.

컨테이너가 고유한 리눅스 네임스페이스 집합을 사용하지만 일부는 다른 컨테이너와 공유할 수도 있는데 이러한 네임스페이스 공유는 정확히 쿠버네티스와 컨테이너 런타임에서 컨테이너를 포드로 결합하는 방법이다.

pod

포드의 모든 컨테이너는 동일한 네트워크 네임스페이스를 공유하므로 포드에 속하는 네트워크 인터페이스, IP 주소 및 포트 공간을 공유하게 된다. 공유 포트 공간 때문에 동일한 포드의 컨테이너에서 실행되는 프로세스는 동일한 포트 번호에 바인딩될 수 없는 반면, 다른 포드의 프로세스는 자체 네트워크 인터페이스와 포트 공간을 가지고 있어 서로 다른 포드 간의 포트 충돌을 제거한다.

scaling_app

위와 같이 설계해야 필요한 컨테이너만 scaling할 수 있고 리소스 낭비를 막을 수 있다. 이렇게 대부분이 하나의 팟에 하나의 컨테이너가 있게 설계하는데 그렇지 않은 경우는 어떤 경우일까?

Sidecar Container

여러 컨테이너를 단일 포드에 배치하는 것은 애플리케이션이 1차 프로세스와 1차 프로세스의 작동을 보완하는 하나 이상의 프로세스로 구성된 경우에만 적합하다. 보완공정이 진행되는 컨테이너는 오토바이 사이드카와 유사해 사이드카 컨테이너라고 불리는데, 이는 오토바이를 보다 안정적으로 만들고 추가 승객을 태울 수 있는 가능성을 제공한다. 그러나 오토바이와 달리 포드에는 둘 이상의 사이드카가 있을 수 있다!!

sidecar_container

예를 들면, Node.js 애플리케이션을 실행하는 하나의 컨테이너와 함께 포드를 배포했다고 하자. Node.js 응용 프로그램은 HTTP 프로토콜만 지원하는 상황이다. HTTPS를 지원하기 위해 우리는 자바스크립트 코드를 조금 더 추가할 수 있지만, 기존 애플리케이션을 전혀 변경하지 않고도 할 수 있다. HTTPS 트래픽을 HTTP로 변환하고 Node.js 컨테이너로 전달하는 reverse proxy 컨테이너를 부가적으로 추가하면 된다. 따라서 Node.js 컨테이너는 기본 컨테이너이고 프록시를 실행하는 컨테이너는 사이드카 컨테이너이다.

애플리케이션의 기존 코드를 변경하는 것과 달리 사이드카를 추가하면 팟에서 추가 프로세스가 실행되어야 하기 때문에 팟의 리소스 요구 사항이 증가한다. 그러나 레거시 애플리케이션에 코드를 추가하는 것은 더 어려울 수 있다. 코드를 수정하기 어렵거나 빌드 환경을 설정하기 어렵거나 소스 코드 자체를 더 이상 사용하지 못할 수 있기 때문이다. 추가 프로세스를 추가하여 애플리케이션을 확장하는 것이 더 저렴하고 빠른 옵션일 수 있다.

Creating the Pod object from the YAML file

팟 및 기타 Kubernetes 객체는 일반적으로 JSON 또는 YAML 매니페스트 파일을 생성하여 Kubernetes API에 게시함으로써 생성된다.

Interacting with Pods

  • Kubernetes 네트워크 모델
    • 각 팟은 다른 모든 팟에서 액세스할 수 있으며, 각 노드는 클러스터의 모든 노드에 연결할 수 있다.
  • $minikubessh 명령을 실행하여 노드에 로그인하면 애플리케이션에 액세스할 수 있다.

kubectl_proxy 개발 중에 포드에서 실행 중인 응용 프로그램과 통신하는 가장 쉬운 방법은 로컬 컴퓨터의 네트워크 포트에 바인딩된 프록시를 통해 특정 포드와 통신할 수 있는 kubectl port-forward 명령을 사용하는 것이다.

Running multiple containers in a pod

HTTPS를 통해 클라이언트에 서비스를 제공할 수 있도록 TLS 지원을 추가하려면 app.js를 바꿀 수도 있지만 더 쉬운 옵션이 있다. 앞선 사이드카 컨테이너에서 Node.js 응용 프로그램과 함께 reverse proxy를 실행하고 응용 프로그램을 대신하여 HTTPS 요청을 처리하도록 할 수 있다.

Ambassador container는 포드 외부 서비스 액세스를 간소화하는 특수 유형의 사이드카 컨테이너이다. Envoy proxy를 사용하여 프록시를 사용하여 HTTP&HTTPS 요청을 처리할 수 있다. envoy_proxy

Init containers

init_containers Init container들은 차례로 실행되며 포드의 주요 컨테이너가 시작되기 전에 모두 성공적으로 완료되어야 하고 모두 완료되면 일반 컨테이너들이 병렬적으로 실행된다. Init 컨테이너는 팟의 일반 컨테이너와 동일하지만 병렬로 실행되지 않으며 한 번에 하나의 init 컨테이너만 실행됩니다.

Init 컨테이너는 일반적으로 다음을 달성하기 위해 포드에 추가된다.

  • 팟의 기본 컨테이너에서 사용하는 볼륨의 파일을 초기화(보안 인증서 저장소에서 기본 컨테이너가 사용하는 인증서 및 개인 키 검색, 구성 파일 생성, 데이터 다운로드 등)
  • 팟의 네트워킹 시스템을 초기화: 포드의 모든 컨테이너가 동일한 네트워크 네임스페이스를 공유하기 때문에 네트워크 인터페이스와 구성이 변경되면 init 컨테이너가 기본 컨테이너에 영향을 미칩니다.
  • 전제 조건이 충족될 때까지 팟의 주요 컨테이너의 시작을 지연: 기본 컨테이너가 컨테이너가 시작되기 전에 사용 가능한 다른 서비스에 의존하는 경우
  • 외부 서비스에 팟 실행을 시작하려고 함을 알림: 애플리케이션의 새 인스턴스가 시작될 때 외부 시스템에 알림을 보내야 하는 특별한 경우 init 컨테이너를 사용하여 이 알림을 전달

기본 컨테이너에서도 위의 작업들을 수행할 수 있지만 init을 사용하는 것이 왜 더 좋을까?
초기화 작업을 수행하기 위해 init 컨테이너를 사용하는 것은 기본 컨테이너 이미지를 재구성할 필요가 없으며 단일 init 컨테이너 이미지를 다양한 응용 프로그램에서 재사용할 수 있다. 이 기능은 모든 포드에 동일한 인프라별 초기화 코드를 삽입하려는 경우에 특히 유용하다.

또 다른 중요한 이유는 보안이다. 공격자가 클러스터를 손상시키는 데 사용할 수 있는 도구나 데이터를 기본 컨테이너에서 init 컨테이너로 이동함으로써 팟의 공격 표면을 줄일 수 있다. 예를 들어, 팟이 외부 시스템에 등록되어야 한다고 가정해보자. 팟에는 이 시스템에 대해 인증하기 위한 일종의 비밀 토큰이 필요합니다. 등록 절차가 기본 컨테이너에서 수행되는 경우, 이 비밀 토큰이 파일 시스템에 있어야 한다. 기본 컨테이너에서 실행 중인 응용 프로그램에 취약성이 있으며 이를 통해 공격자가 파일 시스템의 임의 파일을 읽을 수 있는 경우 공격자가 이 토큰을 얻을 수 있게 된다. init 컨테이너에서 등록을 수행함으로써 토큰은 공격자가 쉽게 손상시킬 수 없는 init 컨테이너의 파일 시스템에서만 사용할 수 있어야 한다.