[Kubernetes in Action] Pods

2021. 9. 25. 21:12DevOps/Docker & Kubernetes

Kubernetes in Action 2nd Edition을 정리한 글입니다.

 

 

 

Overview

쿠버네티스의 Basic Building Block인 Pod의 정의와 동작원리에 대해 설명합니다. 또한 쿠버네티스에서 권장하는 Pod사용방식에 대해 안내하며 JSON, YAML 포맷을 통해 파드를 생성하고 종료하는 방법에 대해 설명합니다. 마지막으로 namespace & label등과 같은 개념에 대해서 설명합니다.

 

 

Introducing Pods

파드(Pod)는 쿠버네티스의 Basic Building Block으로써 컨테이너들을 포함합니다.(일반적으로 1개의 파드에 1개의 컨테이너를 넣는 것을 권장하지만, 경우에 따라서 하나의 파드안에 여러 개의 컨테이너들을 넣을 수 있습니다.) 컨테이너를 하나씩 배포하는 도커와는 다르게 쿠버네티스에서는 기능을 수행하고 배포하는 최소 단위가 바로 이 파드입니다. 파드는 하나의 노드 위에서만 동작하므로, 여러 개의 컨테이너를 포함할 수 있지만, 그러한 경우에도 하나의 파드가 여러개의 노드로 분산될 수는 없습니다. 

 

Why we need pods?

이전 포스팅에서 살펴보았듯, 도커의 컨테이너 시스템은 리눅스 네임스페이스를 사용하여 컨테이너 프로세스를 마치 완전히 새로운 로컬머신에서 동작하는 것처럼 격리시킵니다. 컨테이너 시스템은 원래 "하나의 컨테이너에 하나의 프로세스"를 실행하도록 디자인되었는데, 이는 해당 컨테이너와 관련된 프로세스를 그 외의 관련없는 프로세스들과 격리함으로써 불필요한 프로세스에 대한 restart, health check, logging, monitoring 등을 신경쓰지 않도록 하기 위함입니다.

If you run multiple unrelated processes in a single container, it is your responsibility to keep all those processes running, man- age their logs, and so on.

 

즉, A라는 기능을 수행하는 컨테이너 안에는 A기능 이외의 B, C, D...등에 대한 기능들은 아예 신경쓰지 않고, 오로지 A기능이 제대로 동작하는지만 신경쓴다는 의미입니다. 이렇게 함으로써, 관심있는 특정 프로세스만 재시작하거나, 해당 프로세스에 대한 로그를 분류하는등 container-specific한 job들을 효율적으로 수행할 수 있습니다.

 

 

도커 컨테이너 시스템이 하나의 컨테이너에서 하나의 프로세스를 실행하는 것을 권장함으로써 프로세스 단위로 기능을 수행할 수 있도록 한다면 파드는 이러한 컨테이너를 한번 더 감싸서 쿠버네티스에서 사용하는데에 필요한 여러 추가적인 정보들을 담도록 되어 있습니다. 즉 컨테이너는 도커와 같은 가상화 시스템(쿠버네티스는 도커 이외에도 다른 가상화 서비스들을 지원합니다.)이 사용하는 단위이고, 파드는 쿠버네티스에서 사용하는 단위인 것입니다. 파드를 사용함으로써, 쿠버네티스는 컨테이너의 운영의 책임을 도커와 같은 가상화 서비스에 넘기고, 쿠버네티스에서 관리하는데 필요한 내부 IP등의 정보들을 추가적으로 넣어줌으로써 "컨테이너 오케스트레이션"에만 집중할 수 있게 됩니다. 

 

 

Introducing The Flat Inter-Pod Network

각각의 파드는 쿠버네티스 시스템 안에서 서로 소통할 수 있는 고유한 internal IP를 가집니다. 파드들이 서로 다른 노드에 속해있더라도 이 internal IP를 통해 별도의 네트워크 게이트웨이 없이 소통할 수 있습니다.

 

 

 

 

Organizaing Containers across pods properly

컨테이너 시스템을 사용하는 파드는 VM과는 다르게 lightweight 서비스이기 때문에 추가하는데 드는 overhead가 거의 없습니다. 따라서 파드는 원하는 만큼 생성해도 되며, 사용자는 애플리케이션을 단일 파드에 속한 여러 컨테이너 구조로 구성하는 것이 아닌 프론트, 백등의 서비스들로 분할하여 여러개의 파드로 구성하는 것을 권장합니다. 이유는 다음과 같습니다.

 

 

  1. Scaling에 대한 이점을 가져갈 수 있다
    하나의 애플리케이션을 하나의 파드에 여러 컨테이너로 구성된 구조로 조직하게 되면, horizontal scaling을 할 때에 불필요한 리소스가 낭비될 수 있습니다. 이를 테면 프론트엔드 서비스만 scaling하면 되는데 단일 파드로 조직되어 있으면 어쩔수 없이 백엔드 서비스까지 Scaling되는 것입니다. 쿠버네티스에서 배포의 기본 단위는 파드이므로 쿠버네티스 명령어를 사용해서 특정 컨테이너만 따로 배포할 수는 없습니다.

  2. Kubernetes Utilization을 최대한 활용할 수 있다.
    사용할 수 있는 Worker node가 2개일 때 하나의 파드로 구성된 애플리케이션을 배포하려고 한다면 어쩔 수 없이 하나의 노드에 해당 파드를 배포할 수 밖에 없습니다. (하나의 파드가 여러개의 노드에 배포될 수는 없습니다.) 이는 CPU사용 관점에서 분명 비효율적입니다. 하지만 서비스 단위로 여러개의 파드로 쪼개 배포한다면 쿠버네티스 시스템이 하나의 노드에는 프론트, 하나의 노드에는 백엔드 파드를 배포하는 Utilization을 수행해 줍니다. 

  3. Overhead가 없다.
    여러개의 파드를 사용하더라도 하이퍼바이저등을 사용하는 것이 아닌 그냥 컨테이너를 namespace를 사용해서 분리하는 것이기 때문에 오버헤드가 거의 없습니다. 따라서 큰 부담없이 1, 2의 장점을 얻을 수 있습니다.

 

 

 

Creating Pods from YAML or JSON descriptors

파드와 같은 쿠버네티스 리소스들은 JSON, YAML descriptor를 쿠버네티스 API Endpoint에 Post 함으로써 생성됩니다. 물론 이전 포스팅에서 살펴본 바와 같이 kubernetes run 커맨드를 사용하는 방식도 있지만, 하나의 커맨드에 모든 정보를 담을 수가 없고, 또 버전 관리가 어렵기 때문에 큰 규모의 프로젝트를 관리하는 경우 descriptor를 사용하는 것이 좋습니다.

 

우선 이전 포스팅에서와 같이 Minikube를 사용해서 세팅하도록 하겠습니다. 참고로 Minikube를 사용할 경우 로컬 docker image가 pull되지 않아 ErrImagePull, ImagePullError등의 에러가 발생할 수 있는데 이는 minikube를 사용할 경우 자체 minikube docker registry를 사용해야 하기 때문입니다. 이 글을 참고하셔서 수정하시면 됩니다.

 

자세한 Kubernetes Object Spec은 공식 문서를 참고해주세요

https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/

 

Understanding Kubernetes Objects

This page explains how Kubernetes objects are represented in the Kubernetes API, and how you can express them in .yaml format. Understanding Kubernetes objects Kubernetes objects are persistent entities in the Kubernetes system. Kubernetes uses these entit

kubernetes.io

 

 

이전 포스팅에서 생성했던 파드를 kubectl delete pod/yeoul-container 커맨드를 통해 지우고, YAML 파일을 생성해서 해당 파일로부터 파드를 생성해보겠습니다.

 

yeoul-manifest.yaml

apiVersion: v1
kind: Pod
metadata:
  name: yeoul-pod
spec:
  containers:
  - image: yeoul-image
    imagePullPolicy: IfNotPresent
    name: yeoul-test-container
    ports:
    - containerPort: 8080
      protocol: TCP

 

아래의 커맨드를 입력하면 성공적으로 파드가 생성된 것을 확인할 수 있습니다.

kubectl apply -f yeoul-manifest.yaml

 

Viewing Application Logs

컨테이너화 된 애플리케이션(여기서는 yeoul-image 컨테이너)은 일반적으로 파일 대신 standard output (stdout) stream에 로그를 남깁니다. 따라서 kubectl logs ${resource} 커맨드를 사용해서 컨테이너 stdout에 남겨진 로그를 확인할 수 있습니다. 

 

kubectl logs yeoul-pod

 

이 예시에서는 파드안에 yeoul-image 컨테이너 하나만 실행되고 있기 때문에 문제가 없지만, 만약에 파드 안에 여러 개의 컨테이너가 실행되고 있을 경우에는 -c 옵션을 사용해서 컨테이너 이름을 명시해 주어야 합니다. 

 

kubectl logs yeoul-pod -c yeoul-test-container

 

 

Port Forwarding A Local Network Port to a Port in the Pod

이전 포스팅에서 파드는 생성될때마다 새로운 로컬 IP를 받게 되며, 컨테이너가 죽을 경우에 파드도 죽게 됩니다. 따라서 실제 배포환경에서는 서비스가 파드에 연결되는 것이 아닌 LoadBalancer 서비스 등을 통해 연결이 됩니다. 하지만 경우에 따라서 실제 로컬 머신에서 Pod에 직접 연결이 필요한 경우가 있는데, 이때 Port Forwarding을 사용할 수 있습니다. (Individual Pod에 대해 커넥션 테스트를 하는 경우 등)

 

kubectl port-forward yeoul-pod 8888:8080

 

 

Port-Forward 기능을 사용하면 다음과 같이 로컬 머신에서 쿠버네티스의 포트포워딩 프로세스가 동작하게 되며 이 프로세스가 파드와의 연결을 처리하게 됩니다.

 

 

Organizing Pods with Labels 

위의 예시에서는 1~2개의 파드를 가지고 테스트를 했지만 실제로 마이크로 서비스 아키텍쳐에서는 시스템을 구성하는 파드가 수십개를 넘어가는 경우가 대부분입니다. 따라서 이 파드들을 적절하게 조직하고 관리하는 방법이 필요한데 이를 위해서 쿠버네티스에서는 Label과 Namespace를 제공합니다. 

 

 

레이블은 임의의 key-value 쌍으로 구성되며, labelSelector를 통해 쿠버네티스에서 해당 레이블을 갖는 리소스들을 골라낼 수 있습니다. 레이블은 다음과 같이 YAML파일에서 "labels"를 명시함으로써 추가할 수 있습니다.

 

apiVersion: v1
kind: Pod
metadata:
  name: yeoul-pod-2
  labels:
    creation_method: manual
    env: prod
spec:
  containers:
  - image: yeoul-image
    imagePullPolicy: IfNotPresent
    name: yeoul-container
    ports:
    - containerPort: 8080
      protocol: TCP
kubectl get po --show-labels

 

기존에 존재하고 있는 파드에 레이블을 추가하려면 kubectl label 커맨드를 실행하면 됩니다.

kubectl label po yeoul-pod creation_method=manual

 

 

실제로 파드를 선택할 때 특정 레이블을 포함하는 파드를 선택하도록 labelSelector를 사용할 수 있습니다. (자세한 YAML예시는 이후 포스팅에서 Deployment를 설명할때 자세히 다룹니다)

kubectl get po -l creation_method=manual

 

 

Using Namespaces to Group Resources

쿠버네티스에서는 레이블을 통해 노드들을 그룹화하는 기능을 제공합니다. 하지만 수많은 서비스들을 배포하고, 수많은 파드들을 생성하고 지울 때마다 매번 label을 생성하고 관리하는 것은 번거로운 일입니다. 따라서 쿠버네티스에서는 "네임스페이스" 를 통해 서비스들을 분리하는 방법을 제공합니다.

 

 

쿠버네티스의 네임스페이스는 이전 포스팅에서 설명했던 리눅스 컨테이너와는 조금 다른 개념입니다. 프로세스를 "격리"하는 느낌과는 조금 다르게 쿠버네티스의 네임스페이스는 그냥 리소스를 "구분"하는 기능만을 제공합니다. 즉, Object의 이름에 스코프를 제공하는 것입니다. 따라서 같은 리소스 이름을 갖더라도 네임스페이스가 다르면 쿠버네티스는 다른 리소스로 구분하게 됩니다. 

 

 

apiVersion: v1
kind: Namespace
metadata:
  name: yeoul-namespace

다음과 같은 YAML파일을 만들고 kubectl apply -f를 통해 네임스페이스를 생성합니다. 혹은 다음과 같이 command를 통해 생성할 수도 있습니다.

 

kubectl create namespace yeoul-namespace

 

 

이 yeoul-namespace안에 속하는 yeoul-pod(이미 default namespace에 동일한 이름의 파드가 있습니다.)를 생성하여 쿠버네티스가 네임스페이스가 다르면 같은 이름의 리소스들도 다르게 취급하는지를 확인해보겠습니다. 

 

kubectl apply -f yeoul-manual.yaml -n yeoul-namespace
kubectl get po -n yeoul-namespace

 

해당 네임스페이스에 동일한 이름의 파드가 잘 생성되는 것을 확인할 수 있습니다.

반응형