본문 바로가기
Computer Science/Kubernetes

Chapter 3. Pods: running containers in Kubernetes

by zxcvber 2021. 3. 17.

 A container shouldn’t run multiple processes. (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-3/45)

다양한 쿠버네티스 오브젝트 (resources) 를 살펴보는 단원이다. 가장 기본이 되는 Pod 부터 시작한다. 이외의 모든 것들은 pod 를 관리하거나, pod 를 노출하거나, pod 에 의해 사용된다.

3.1 Introducing Pods

Pod 는 컨테이너의 모임이며, 쿠버네티스의 기본적인 building block 이 된다. 하나 기억할 사실은, 하나의 pod 는 하나의 노드 위에 있게 되므로, pod 내부의 모든 컨테이너는 같은 노드에 존재하게 된다.

3.1.1 왜 Pod 를 사용하는가?

그냥 하나의 컨테이너 안에서 모든 것을 실행하면 안 되는 것인가?

하나의 프로세스라면 하나의 컨테이너 안에서 실행해도 괜찮지만, 여러 프로세스가 한꺼번에 동작하는 앱의 경우 한 컨테이너 안에서 돌아가게 되면 관리가 어렵다.

모든 프로세스가 정상적으로 돌아가고 있는지 개발자가 직접 확인해야 하고, 프로세스가 죽으면 직접 다시 시작해줘야 하며, 실제 서비스에서 특정 프로세스가 지나친 병목을 일으키는 경우에는 scale out 을 적용하여 해당 프로세스만 여러 개 실행하여 throughput 을 늘릴 수도 있을 것이다.

그러므로 컨테이너를 여러 개 쓰는 것이 보다 바람직하다.

(여러 개 쓰고 이걸 묶으면 pod 가 되는 것)

3.1.2 Pod 에 대한 이해

컨테이너 하나에서 여러 프로세스를 실행하는 것은 바람직하지 않으므로, 컨테이너 여러 개를 하나로 묶어서 관리해줄 상위 레벨의 개념이 필요하다. 그래서 Pod 가 등장하게 된 것이다.

Pod 내의 partial isolation

Pod 를 사용하면 서로 연관된 프로세스를 실행하는 컨테이너들을 함께 실행할 수 있게 되며, 마치 하나의 머신/컨테이너 안에서 실행되는 것처럼 환경을 제공할 수 있게 된다.

물론 각 컨테이너도 서로 어느 정도 독립적으로 동작하지만, 같은 pod 내의 container 는 같은 namespace 를 가지고 있고, 또 쿠버네티스 volume 을 사용해서 컨테이너들 끼리 파일을 공유하도록 설정할 수도 있다.

Shared IP and port space

같은 pod 내의 컨테이너들은 IP 를 공유하므로, 하나의 포트에는 하나의 서비스만 할당되어야 한다. 또 같은 loopback network interface 를 사용하므로 localhost 를 사용해 컨테이너들 끼리 통신할 수 있다.

Flat inter-pod nework

Pod 마다 IP 가 할당되어 있고, 모든 pod 들은 하나의 flat (network topology 말하는 듯), 공유된 네트워크 주소 공간을 사용한다. 그러므로 pod 들 끼리 서로의 IP 주소를 이용해 통신할 수 있으며, NAT(Network Address Translation) gateway 가 사이에 존재하지 않는다. 이는 pod 가 서로 다른 노드에 배치되더라도 상관없다.

이렇게 동작하기 위해서는 SDN(Software Defined Network) 레이어를 하나 만들어서 사용한다.

요약

Pod 는 논리적인 호스트이며, 컨테이너를 사용하지 않는다면 물리적인 호스트나 VM과 다를바 없다. 한 pod 내의 프로세스들은 같은 머신에서 동작하는 프로세스들이며, 차이점은 각 프로세스가 컨테이너 안에서 돌아간다는 점이다.

3.1.3 Pod 내의 컨테이너 배치

Pod 는 개별적인 머신이지만 한 머신이 하나의 앱을 실행한다고 이해해야한다.

Pod 는 가벼우므로 거의 오버헤드 없이 여러 개를 실행할 수 있으므로, 하나의 pod 에 모든 것을 꾸겨 넣기 보다는 여러 개의 pod 를 사용해서 한 pod 내에는 관련이 깊은 작업만 있또록 관리하는 것이 좋다.

하나의 앱을 여러 컨테이너로 분리

백엔드, 프론트엔드가 있는 서비스를 생각해보면, 각각을 하나의 pod 내에 넣지 말라는 법은 없지만 바람직한 방법은 아니다.

만약 쿠버네티스 클러스터에 여러 개의 노드가 있다면, 차라리 두 개의 pod 로 분리해서 CPU 등 하드웨어 자원을 더 효율적으로 사용할 수 있을 것이다.

더불어 쿠버네티스에서 scaling 의 단위는 pod 이다. 컨테이너 단위로는 복제할 수 없다. (정말? - 확인 필요)

(+ 왜 스케일링의 단위가 컨테이너가 아닌 pod 로 설계했을까? 관련이 깊은 작업들은 묶어서 한꺼번에 스케일링 하기 위해? 각각을 복제하고 묶는 것보다는 나을 것 같긴 하다.)

또한 각 어플리케이션 마다 scaling 을 위한 조건이 다를 수 있다. 프론트/백 모두 한 pod 내에 있었다면 스케일링 했을 경우 프론트/백 모두 2개씩 존재하게 된다. 한편 백엔드의 경우 DB가 있다면 scale out 이 어려울 수 있으며, 경우에 따라서 프론트/백 중 어느 하나만 부하가 심해 하나만 scale out 해야하는 상황이 올 수도 있다.

차라리 이들을 분리하여 각각 하나의 pod 에서 실행되도록 하는 것이 바람직한 방법이다.

어떤 컨테이너들을 한 pod 내에 묶을 것인가

Pod 내에 여러 컨테이너를 넣는다면 그에 합당한 이유가 있어야 한다. 컨테이너가 서로 긴밀하게 연관되어 있어야 한다. Main 컨테이너가 하나 있고 나머지가 해당 컨테이너를 support 한다던가 (sidecar), 혹은 컨테이너들이 하나의 volume 을 공유해야 하는 상황이라던가.

대표적으로 로깅을 담당하는 컨테이너의 경우 (logrotate, 단순 수집, 수집 후 어딘가로 전송) 어플리케이션과 함께 같은 pod 내에 존재하는 것이 나을 것이다.

한 pod 내에 여러 컨테이너를 묶는다면 아래 3가지 질문을 해보면 된다.

  • 같이 실행 되어야 하는가? 아니면 다른 호스트에서 실행되어도 괜찮은가?
  • 여러 컨테이너가 하나처럼 동작하는가? 아니면 독립적인 컴포넌트인가?
  • 함께 스케일링 되어야 하는가? 아니면 따로 스케일링 되어야 하는가?

기본적으로는 pod 를 분리하는 쪽으로 결정하는 것이 바람직하다. 특별한 이유가 없다면!

3.2 YAML/JSON 파일로부터 pod 만들기

보통 pod 를 비롯한 쿠버네티스 리소스를 만들 때는 YAML/JSON 파일을 Kubernetes API 에 POST 해서 만든다.

물론 kubectl run ... 을 사용할 수 있긴 하지만 모든 옵션을 다 지원하지는 않으며, 매번 명령을 입력하기 보다는 파일로 저장하고 관리하여 버전 관리도 할 수 있게 된다.

3.2.1 YAML descriptor

$ kubectl get po <POD_NAME> -o yaml
  • Pod 의 정보를 yaml 형태로 출력하고 싶을 때 사용하는 명령어
  • 뒤에 yaml 대신에 json 을 붙이면 JSON 파일로 출력 결과를 볼 수 있다.

복잡하지만 크게 구성은 다음과 같다.

  • 쿠버네티스 API 버전
  • 쿠버네티스 object type
  • Pod metadata
    • 이름, namespace, 레이블을 비롯한 기타 정보
  • Pod specification/contents
    • Pod 내에 존재하는 내용으로 컨테이너, 볼륨과 같은 정보
  • Pod status
    • 현재 실행중인 pod 의 상세 정보, 내부 IP 등

3.2.2 Pod 을 위한 YAML descriptor 만들기

대략 다음과 같은 내용으로 구성하면 된다.

apiVersion: # 쿠버네티스 API 버전
kind: Pod # Pod 임을 명시
metadata:
  name: # Pod 이름
spec:
  container:
  - image: # 컨테이너를 실행할 이미지
    name: # 컨테이너의 이름
    ports: # 컨테이너가 listen 할 포트
    - containerPort:
      protocol: TCP

3.2.3 kubectl 로 pod 생성하기

$ kubectl create -f <YAML_FILE>

(kubectl apply -f 가 생각나는데 무슨 차이가 있는지 확인해보기)

  • Argument 로 넘겨준 파일의 설정으로부터 pod 를 생성한다.

생성 후 잘 돌아가는지 확인하려면 kubectl get pods 로 확인하면 된다.

3.2.4 로그 확인하기

컨테이너 내의 앱은 파일에 로그를 떨구기보다는 보통은 stdout, stderr 로 로그를 내보낸다. 이렇게 하면 다양한 컨테이너들에서 다양한 어플리케이션을 실행할 때 통일된 방법으로 로그를 확인할 수 있다.

$ docker logs <CONTAINER_ID>
  • 컨테이너 내의 로그 (stdout/stderr)를 파일로 redirection 해준다.
  • 하지만 docker 명령어를 실행하는 것이므로 pod 내에 ssh 로 접근해서 해야한다.

쿠버네티스에서는 더 간편한 방법을 제공해준다.

$ kubectl logs <POD_NAME>
  • 해당 pod 의 로그를 확인할 수 있게 된다.
  • Pod 내의 특정 컨테이너 로그를 확인하려면 -c <CONTAINER_NAME> 을 붙여주면 된다.
  • 컨테이너 로그는 10MB 마다, 매일 rotation 된다.

물론 컨테이너가 삭제되면 로그가 함께 삭제되므로, 클러스터 전체의 로그를 수집하는 부분이 따로 필요하다. (centralized logging) 이는 나중에 17장에서 다룬다.

3.2.5 Pod 에 요청 보내기

2장에서 한 것처럼 kubectl expose 를 사용하는 방법도 있지만 Service object 에 대해서는 나중에 알아보기로 하고, 포트포워딩으로 하는 방법을 소개한다.

$ kubectl port-forward <POD_NAME> <LOCAL_PORT>:<POD_PORT>
  • 로컬의 포트를 pod 의 포트로 포워딩해준다.

3.3 레이블을 이용한 pod 구성

Pod 의 개수가 많아질수록 관리가 힘들어지고, 한 서비스에 pod 가 여러 개 존재하게 되므로 (복제본) pod 를 묶어서 관리할 방법이 필요하다. 쿠버네티스는 pod 뿐만 아니라 다른 object 들도 레이블을 이용해서 관리한다.

3.3.1 레이블

레이블은 key-value pair 로, 쿠버네티스 리소스에 붙일 수 있으며, label selector 를 이용해 리소스를 필터링할 때 사용된다.

한 리소스에 레이블은 여러 개 붙을 수 있으며 key 가 유일하기만 하면 된다.

보통 리소스를 생성할 때 레이블을 붙이지만, 리소스를 다시 생성할 필요 없이 레이블을 붙일 수도 있다.

3.3.2 Pod 생성 시 레이블 붙이기

당연히 yaml 파일에 레이블을 추가해주면 된다. metadata 아래에 추가하면 된다.

metadata:
  name: ...
  labels:
    key_1: value_1
    key_2: value_2
    ...

이렇게 하고 pod 를 실행한 뒤, kubectl get pods 를 해보면 레이블이 보이지 않는다.

--show-labels 옵션을 설정해야 레이블이 같이 출력된다. 대신 모든 레이블이 출력되므로 보기 불편할 수 있다. -L <LABEL_KEYS> 옵션을 주면 해당 key 에 대한 레이블만 보여준다. Key 가 여러 개인 경우 , 로 구분한다.

3.3.3 레이블 수정하기

$ kubectl label po <POD_NAME> <LABEL_KEY>=<LABEL_VALUE>
  • Pod 에 새롭게 레이블을 붙인다.
  • 해당 key 가 존재하지 않아야 한다. 만약 덮어쓰고 싶다면 --overwrite 를 붙여야 한다.

3.4 Label selector 를 사용하여 pod 목록 출력하기

레이블에 따라 검색하는 기능을 제공한다.

  • 특정 key 를 갖거나 갖지 않는지
  • 특정 key-value 를 갖는지
  • 특정 key 를 갖지만 해당 value 가 아닌 값을 갖는지

3.4.1 Listing pods using a label selector

$ kubectl get po -l [CONDITION]
  • -l 옵션을 이용해서 label selector 를 사용할 수 있다.

CONDITION 내에는 다음과 같은 구문을 넣을 수 있다.

  • <KEY>=<VALUE>: 해당 key-value pair 를 갖는 pod 출력
  • <KEY>: 해당 key 를 갖는 pod 출력
  • !<KEY>: 해당 key 를 갖지 않는 pod 출력
    • '!<KEY>' 와 같이 '...' 로 감싸주어야 shell 이 ! 를 계산하지 않는다.
  • <KEY>!=<VALUE>: 해당 key 를 갖지만 value 가 아닌 pod 출력
  • <KEY> in (<VALUE_1>, <VALUE_2>, ...): key 의 값이 value 목록에 포함되는 pod 를 출력
  • <KEY> notin (<VALUE_1>, <VALUE_2>, ...): key 의 값이 value 목록에 포함되지 않는 pod 를 출력

3.4.2 Label selector 에서 여러 조건을 이용하기

조건을 여러 개 적용하고 싶다면 , 로 구분하면 된다.

이 section 에서는 pod 목록을 가져오는 용도로 레이블을 사용했지만, 여러 개의 pod 를 한꺼번에 삭제할 때도 사용할 수 있는 등 특정 명령을 pod 의 부분집합에 적용할 수 있게 된다.

3.5 Pod 스케쥴링 제한

일반적으로는 worker node 들에 pod 들이 적당히 배치되어 돌아간다. 그리고 밖에서 볼 때는 어떤 pod 가 어느 노드에 있는지는 보통 상관 없다. 하지만 pod 가 특정 하드웨어가 필요한 경우에는 pod 가 어느 노드로 스케쥴링 되어야 하는지 제한할 필요가 있다. (GPU가 필요하다던가...)

한편 특정 노드에 이 pod 를 스케쥴링 해주라고 요청하게 되면 쿠버네티스를 사용할 이유가 없어진다. 앱과 인프라를 분리하지 못하게 된다. 그러므로 특정 노드에 스케쥴링 해야한다고 명시하지는 않고, 이 pod 를 실행할 노드가 갖춰야 할 요구사항을 적는다. 이를 위해 노드 레이블과 노드 label selector 를 사용한다.

3.5.1 Categorizing worker nodes

레이블은 다양한 리소스에 붙일 수 있다. 명령어도 거의 비슷하다.

$ kubectl label node <NODE_NAME> <KEY>=<VALUE>

3.5.2 특정 노드들로 pod 스케쥴링

Pod 의 yaml 파일을 수정하면 된다.

노드에 gpu=true 라는 레이블이 있다고 하면, GPU 가 필요한 pod 의 yaml 파일에 다음을 추가한다.

spec:
  nodeSelector:
    gpu: "true"

그러면 이 레이블을 가진 노드에만 pod 가 스케쥴링 되게 된다.

3.5.3 특정 노드 하나로 pod 스케쥴링

가능한데 하지마!

각 노드는 기본적으로 레이블 kubernetes.io/hostname=<ACTUAL_HOSTNAME> 을 가지므로 이를 이용하면 특정 노드에 pod 를 스케쥴링 할 수 있다.

하지만 이 노드가 죽어있으면 pod 가 실행되지 못한다.

개별적인 하나의 노드가 아니라 node label selector 를 이용해 특정 성질을 가진 노드의 집합을 고려하는 것이 쿠버네티스의 사고방식이다.

3.6 Annotating Pods

쿠버네티스 리소스들은 레이블 뿐만 아니라 어노테이션도 가질 수 있는데, 얘도 key-value pair 이다. 레이블과 비슷하지만 selector 가 따로 존재하지는 않아서 이를 이용해 리소스를 분류할 수는 없다.

(다른 툴들이 annotation 을 사용한다고 하는데 어떤 툴들이 쓰는지 알아보기)

3.6.1 Object 의 어노테이션 확인하기

Object 의 yaml 파일을 확인해 보면 metadata 아래에 있다.

3.6.2 어노테이션 추가 및 수정

$ kubectl annotate pod <POD_NAME> <KEY>=<VALUE>

어노테이션 추가시 key 충돌을 막기 위해 unique prefix 를 사용하는 것이 좋다.

kubectl describe pod <POD_NAME> 으로 추가된 어노테이션을 확인할 수 있다.

3.7 Namespace 를 이용한 리소스 그룹화

kubectl get pods 를 했을 때, 매번 레이블을 이용해서 필터링하지 않으면 모든 pod 가 다 목록에 출력된다. 또한 레이블은 한 집합 내에서 부분집합으로 구분하기 위해서 사용했다. 부분집합들은 서로 겹치기도 한다.

만약 아예 다른 집합을 만들어 겹치지 않도록 분리하고 싶은 경우도 있을 것이다. (다른 이유가 더 있지만 - 뭐지?) 쿠버네티스에서는 namespace 를 이용해서 object 를 그룹화 할 수 있다.

linux namespace 와는 다르다!

3.7.1 Namespace 의 필요성

Namespace 를 사용하면 많은 컴포넌트를 가진 복잡한 시스템을 작고 분리된 그룹으로 나누어 관리할 수 있다. 컴퓨팅 리소스를 개발, 배포, QA 환경 용으로 각각 분리할 수 있게 되며, namespace 안에서만 리소스 이름이 유일하면 된다.

만약 여러 사용자가 한 클러스터에 접근하여 작업하는 경우 각 사용자마다 독립적으로 리소스를 관리하고 싶다면 namespace 를 사용하여 사용자별로 분리하면 될 것이다. 의도치 않게 다른 사용자들의 리소스를 건드리거나, 이름이 충돌하는 일이 생기지 않게 된다.

더불어 namespace 에 따라 접근 제한을 할 수 있는데 12~14장에서 더 알아본다.

거의 대부분의 리소스들이 namespace 로 관리할 수 있지만, 몇 개는 그렇지 않으며 대표적으로 노드의 경우 namespace 에 구애받지 않는다.

(아마 다른 cluster 레벨의 리소스도 구애받지 않을 것 같다)

3.7.2 Namespace 확인

$ kubectl get namespace
$ kubectl get ns # short

처음에는 default namespace 에서 작업하게 된다. kubectl get 명령을 할 때에도 namespace 를 지정하지 않아서 default namespace 에 대해 명령을 실행하게 된다.

kubectl get 명령에 -n (--namespace) 옵션을 주어 namespace 를 제한할 수 있다.

3.7.3 Namespace 만들기

Namespace 또한 쿠버네티스 리소스이므로, yaml 파일을 이용해 만들 수 있다.

apiVersion: ...
kind: Namespace
metadata:
  name: ...

파일을 만드는 것은 번거로울 수 있으므로 명령어를 사용하는 방법도 있다.

$ kubectl create namespace <NAMESPACE_NAME>

3.7.4 다른 namespace 의 object 관리

특정 namespace 에 리소스를 만들고 싶다면, yaml 파일에서 metadata 섹션 아래에 namespace: <NAMESPACE_NAME> 엔트리를 넣어주면 된다. 다만 같은 리소스를 여러 namespace 에 만들고 싶을 수 있으므로 다음과 같이 할 수도 있다.

$ kubectl create -f <YAML_FILE> -n <NAMESPACE_NAME>
  • -n 옵션으로 namespace 를 넘겨주면 된다.

-n 옵션을 이용하면 다른 namespace 에 있는 리소스도 관리할 수 있게 되며, -n 옵션이 없으면 현재 설정된 kubectl context 의 namespace 에서 동작하게 된다.

3.7.5 Namespace 가 제공하는 환경 분리 이해하기

Namespace 를 사용하면 object 들을 분리하여 그룹화 한 후, 특정 그룹에 대해서만 작업을 할 수 있지만, object 자체를 격리하는 것은 아니다.

예를 들어 두 pod 가 다른 namespace 에 있다고 해서 반드시 통신이 불가능한 것은 아니다.

3.8 Pod 중지 및 삭제

3.8.1 이름으로 삭제

$ kubectl delete pod <POD_NAME>

3.8.2 Label selector 로 삭제

$ kubectl delete pod -l <KEY>=<VALUE>

(왠지 label selector 에서 제공하는 필터링 기능을 다 사용할 수 있을 것 같다.)

3.8.3 Pod 이 속한 namespace 삭제

$ kubectl delete ns <NAMESPACE_NAME>

3.8.4 Namespace 는 남기고 pod 만 삭제

$ kubectl delete pod --all

3.8.5 거의 모든 리소스 삭제

$ kubectl delete all --all

몇몇 object 들은 사용자가 직접 삭제해줘야 해서 all 로 지워도 지워지지 않는다.

(안 쓰는게 좋을 것 같다.)


Discussion & Additional Topics

kubectl applykubectl create 의 차이점

Kubernetes object 와 Kubernetes resource 의 차이점

Namespace 의 올바른 사용법

JSON vs YAML

Label 과 namespace 의 차이

  • Label selector 를 이용하면 namespace 처럼 분리가 가능
  • 하지만 namespace 는 label 보다 상위 개념
  • Namespace 를 이용하면 resource quota 를 이용해서 하드웨어 리소스 분배가 가능
  • Namespace 를 이용하면 접근 제한 가능

Annotation 의 올바른 사용법

Linux 에 존재하는 namespace 들의 종류

  • UTS namespace 란?

Pod 에 각각 IP 주소가 부여되는 원리

MSA vs Microservice 간 통신으로 인한 오버헤드

  • MSA 를 사용하면
    • 책임이 분배되어 프로젝트 관리가 용이하고
    • 개발하기 편하고 확장이 쉬워짐 (microservice 별로 확장)
  • 다만 통신으로 인한 오버헤드는 증가하는 것이 맞을 것이다.
    • 클라우드 내에서 동작하면 통신으로 인한 오버헤드가 크지는 않겠지만 없다고는 못 할 것 같다.

댓글