본문 바로가기
DevOps/Kubernetes

[6] 쿠버네티스 파드

by 비어원 2022. 1. 5.
728x90

쿠버네티스를 사용하는 가장 큰 이유는 컨테이너 애플리케이션을 쉽게 배포하고 관리하기 위해서이다. 쿠버네티스에서는 컨테이너 애플리케이션을 파드라는 오브젝트로 추상화하여 관리한다. 그러면 어떻게 쿠버네티스에서 컨테이너 애플리케이션을 관리하는지 알아보자.

파드

파드는 쿠버네티스에서 생성하고 관리하는 배포 가능한 가장 작은 컴퓨팅 단위이다. 보통 쿠버네티스의 컨테이너 애플리케이션을 파드라고도 부르기도 하지만 엄밀히 말하면 컨테이너 애플리케이션을 포함한 여러가지를 내포하고 있다.

쿠버네티스에서는 하나 이상의 컨테이너 애플리케이션과 네트워크 리소스, 그리고 필요하다면 스토리지까지를 한꺼번에 모아 파드 라는 오브젝트로 추상화하고 있다. 여기서 네트워크 리소스는 각 파드마다 고유한 IP를 가지며, 파드 내에 있는 컨테이너끼리는 localhost를 사용한다.

 

파드는 대표적으로 (1). 하나의 컨테이너 애플리케이션만 단독으로 띄우거나, (2) 하나의 메인 애플리케이션과 사이드카를 같은 파드에 띄우거나, (3) 하나 이상의 컨테이너와 컨테이너끼리 공유하는 볼륨까지 같이 띄운다. 그림으로 보면 다음과 같다.

파드 띄워보기

쿠버네티스를 설치했다면, kubectl 명령어로 간단히 파드를 띄워볼 수 있다. 아래 명령어로 nginx를 한번 띄워보자.

$ kubectl run nginx --image=nginx:1.23 --port=80

파드를 띄웠다면 kubectl로 파드가 정상적으로 배포가 되었는지 확인해보자.

$ kubectl get po
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          51s

kubectl get po -o w
ide
NAME    READY   STATUS    RESTARTS   AGE    IP           NODE       NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          55s   172.17.0.2   minikube   <none>           <none>
  • -o wide 옵션으로 파드가 어떤 노드에 스케줄링 되었는지 등 더 자세한 정보를 볼 수 있다.

 

추가적으로 describe 명령어로 파드의 상세 스펙을 확인해볼 수 있다.

$ kubectl describe po nginx

describe 명령어로는 크게 3가지 부분을 보면 되는데, Containers 부분과 Condition 부분, Events 부분을 주로 본다.

 

먼저 Containers 부분에서 어떤 이미지를 사용하여 컨테이너를 띄웠고, 해당 컨테이너가 어떤 포트를 사용하는지 등을 알 수 있다. 그리고 Condition 부분에서는 파드가 (특정 노드로) 스케줄링 되었는지, 컨테이너와 파드가 Ready 상태에 있는지 등을 알 수 있다. 그리고 Events 에서는 파드가 구동되는 중에 어떤 과정이 있었는지 확인할 수 있다. Events는 특히 디버깅 용도로 자주 확인하게 될 것이다.

 

파드에 요청 보내보기

일단 파드가 잘 띄워졌으면 실제로 요청을 받는지 확인이 팔요하다. 하지만 파드의 IP는 쿠버네티스 클러스터에서 사용하는 내부 가상 IP이기 때문에 로컬 환경에서 통신이 불가능하다. 대신, 쿠버네티스 워커노드 안에서는 파드 IP로 접근이 가능하다.

그러면 쿠버네티스 노드 바깥에서 파드에 직접 접근이 불가능할까? 아니다! 쿠버네티스에서는 포트포워딩을 사용하여 디버깅 목적으로 로컬 PC에서 파드로 접근할 수 있는 방법을 제공해준다. 간단히 kubectl 명령어로 포트포워딩이 가능하다. 터미널에서 다음 명령어를 입력하자.

$ kubectl port-forward pods/nginx 8080:80

 

그리고 다른 터미널이나 웹 브라우저를 사용하여 localhost:8080 으로 접속해보자. 통신이 가능할 것이다.

포트포워딩 방식은 파드를 쿠버네티스 외부로 노출시키는 방법 중 하나이다. 하지만 이 방식은 디버깅 목적으로 사용하도록 권장하고, 실제로 파드를 쿠버네티스 외부로 노출시키는 방식은 따로 있다.

 

Clean up

생성한 파드를 제거하는 방법은 다음과 같다.

$ kubectl delete po nginx

 

매니페스트 파일로 파드 배포하기

지금까지 kubectl run 명령어를 통해 파드를 간단히 배포해 보았다. 이런 방법 대신 쿠버네티스 매니페스트 파일을 통해 파드를 배포할 수도 있다. 매니페스트 파일이란, 파드를 포함한 쿠버네티스 오브젝트의 스펙을 정의한 파일이라고 보면 된다. 보통, 파드를 포함한 모든 쿠버네티스 오브젝트들은 매니페스트 파일로 스펙을 명세하고 관리한다. 매니페스트 파일로 파드를 명세하는 방법은 다음과 같다.

 

1. 아래 내용으로 파일을 만든다. (pod.yaml)

apiVersion: v1 
kind: Pod 
metadata: 
  name: nginx 
spec: 
  containers: 
    - name: nginx # 컨테이너 이름 
      image: nginx:1.23 # 컨테이너 이미지 이름 
      ports: 
        - containerPort: 80 # 컨테이너가 사용하는 포트번호

 

2. 다음 명령어로 파드를 생성한다.

$ kubectl apply -f pod.yaml

 

이렇게 생성한 파드는 이전에 kubectl run 명령어를 통해 생성한 파드와 똑같은 스펙을 가진다.

파드 리소스 할당하기

파드는 쿠버네티스에서 관리하는 배포 가능한 가장 작은 컴퓨팅 단위이다. 파드가 컴퓨팅 단위이기 때문에, VM처럼 특정 파드에서 사용할 수 있는 리소스 양을 제공해줄 수 있다. 파드에게 할당할 수 있는 리소스는 보통 CPU, Memory이고, GPU도 할당해줄 수 있다. 매니페스트 파일을 사용하여 파드에게 CPU, Memory를 할당할 수 있다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx:1.23
    name: nginx
    ports:
    - containerPort: 80
    resources:
      requests:
        cpu: "0.3"
        memory: 128Mi
      limits:
        cpu: "0.5"
        memory: 256Mi

여기서 requests와 limits가 있는데, requests는 파드가 처음 생성될 때 할당이 되는 리소스 양을 의미하며, 파드가 구동 중일 때 할당된 CPU나 Memory를 초과하여 사용되어야 하는 경우, limits에서 설정된 양까지 동적으로 할당받을 수 있다. 언뜻보면 limits가 동적으로 할당받는 것 때문에 좋은 기능일 수도 있으나, 무분별하게 사용되는 경우 노드 장애까지 초래할 수 있다는 단점이 있다. 이에 대한 자세한 내용은 QoS에 대한 내용을 포스팅하게 된다면 정리해보려고 한다.

명령형 API와 선언형 API

이번 글에서 두 가지 방식으로 파드를 생성하는 방법에 대하여 배웠다. 하나는 kubectl run 명령어를 사용하여 파드를 생성하는 방법과, 다른 하나는 매니페스트 파일을 생성하여 그 파일을 통해 kubectl apply 명령어를 사용하여 파드를 생성하는 방법이 있다. 여기서 kubectl run과 같은 방식을 명령형 API 방식이라고 하고, kubectl apply와 같은 방식을 선언형 API 방식이라고 한다.

 

명령형 API 방식은 CRUD(생성, 조회, 수정, 삭제) 방식으로, 간단히 명령어를 통해 쿠버네티스에게 어떤 오브젝트를 생성, 조회, 수정, 삭제 해달라고 알려준다. 지금까지 사용했던 명령형 API들을 예시로 들자면 다음과 같다.

  • kubectl run nginx --image=nginx:1.23 --port=80 (생성, nginx:1.23 이미지를 사용하여 80포트를 노출시키는 컨테이너를 nginx라는 이름의 파드로 배포)
  • kubectl get po (조회, default 네임스페이스에 있는 파드를 조회)
  • kubectl describe po nginx (조회, default 네임스페이스에 있는 nginx 파드의 상세 정보 조회)
  • kubectl delete po nginx (삭제, nginx라는 파드를 삭제)

그리고 추가로 수정에 대해서는 두 가지 명령형 API가 있다.

  • kubectl edit po nginx (수정, nginx 파드를 터미널에서 대화형으로 수정)
  • kubectl patch po nginx --patch='{"spec":{"containers":[{"name": "nginx", "image": "nginx:1.24"}]}}' (수정, nginx 파드의 nginx 이름의 컨테이너에 해당하는 image를 nginx:1.24로 변경)

그리고 선언형 API 방식은 생성하려고 하는 쿠버네티스 오브젝트의 스펙을 매니페스트 파일로 선언한 후 kubectl apply -f 명령어를 통해 쿠버네티스에 배포하는 방식이다. 매니페스트 파일을 선언하여 쿠버네티스에 전달하기 때문에 선언형이라고 불린다. 그리고 선언형의 경우에는 생성, 수정만 가능하다. (kubectl apply의 경우엔 그렇다.) 수정의 경우, 매니페스트 파일의 spec 부분을 수정한 후 kubectl apply -f를 하면 쿠버네티스 오브젝트가 수정된다.

 

그러면 어떤 방식의 API가 선호될까? 일단 두 방식의 장단점이 있어서 적재적소에 활용하는 것이 좋다. 일단 명령형 API는 사용하기 아주 간단해서 테스팅 목적에서 자주 사용된다. 하지만 명령형 API를 사용하여 쿠버네티스를 관리한다면 변경사항 추적이 어렵고, 쿠버네티스 오브젝트를 수정할 때 어떤 내용을 수정해야 할지 계속 확인해야 한다는 단점이 있다. 그리고 백업이 어렵다는 단점이 있다.

 

그래서 명령형 API의 단점 때문에 보통 쿠버네티스를 관리할 때는 선언형 API가 선호된다. 선언형 API는 파일로 쿠버네티스 오브젝트를 관리하기 때문에 Git과 같은 형상관리 툴을 사용하여 파일을 관리한다면 변경 추적이 쉽고, 매니페스트 파일이 쿠버네티스 오브젝트와 같기 때문에 백업이 쉽다는 장점이 있다. (파일만 백업하면 되기 때문) 하지만, 매니페스트 파일을 최초로 선언하여 생성하는게 명령형 API에 비해 비교적 오래 걸린다는 단점이 있다. 하지만 딱히 뚜렷한 단점이 없기 떄문에 보통 선언형 API를 사용한다.

 

파드 라이프사이클

파드는 수명이 있으며, 이 파드는 정해진 라이프 사이클을 따른다. 파드는 정해진 단계(Phase) 가 있으며, 파드 내의 컨테이너는 어떠한 상태(Status)를 가진다. 파드가 실행되는 동안 쿠버네티스는 여러 컨테이너의 상태를 추적하고 정상적이지 않은 파드를 다시 정상 상태로 만들기 위한 조치를 한다.

 

파드의 단계 (Phase)

처음에 파드가 생성되면 Pending 단계에서 시작하는데, 이 때는 파드가 노드로 스케줄링 되지 않았거나 컨테이너를 생성하지 못한 단계이다. 만약 파드에 어떤 노드에 스케줄링되고 컨테이너가 하나 이상 생성되었다면 이 파드는 Running 단계로 된다. 그런 다음 파드의 컨테이너가 성공적으로 종료되었으면 Succeeded 상태가 되고 어떠한 이유로 컨테이너가 실패로 종료되었다면 Failed 상태가 된다.

이러한 파드의 라이프사이클은 status.phase 에서 확인할 수 있다.

$ kubectl get po nginx-deployment-574b87c764-798d8 -o yaml
apiVersion: v1
  kind: Pod
  metadata:
    # ...
  spec:
    containers:
    - image: nginx:1.14.2
      imagePullPolicy: IfNotPresent
      name: nginx
      # ...
  status:
    conditions:
      # ...
    hostIP: 192.168.64.2
    phase: Running
    podIP: 172.17.0.6
    podIPs:
    - ip: 172.17.0.6
    qosClass: BestEffort
    startTime: "2022-01-02T05:26:53Z"

 

컨테이너 상태 (Status)

파드 전체의 phase 뿐만 아니라 쿠버네티스는 파드 내부의 각 컨테이너의 상태도 추적한다. 컨테이너 라이프사이클 훅을 사용하여 컨테이너 라이프사이클의 특정 지점에서 실행할 이벤트를 트리거할 수도 있다.

스케줄러가 노드에 파드를 할당하면 kubelet은 컨테이너 런타임을 사용하여 해당 파드에 대한 컨테이너를 생성하기 시작한다. 여기서 컨테이너가 가질 수 있는 상태는 Waiting, Running, Terminated 이다.

  • Waiting: 컨테이너를 시작하기 위해 필요한 작업을 실행하는 중인 상태이다. (이미지 Pull 등) Waiting 상태일 때는 kubectl을 통해 해당 상태에 있는 이유를 Reason 필드를 통해 확인할 수 있다.
  • Running: 컨테이너가 문제 없이 실행되고 있는 상태이다.
  • Terminated: 컨테이너 실행이 정상적으로 완료되었거나 어떤 이유로 실패하여 종료된 상태이다. Terminated 상태일 대는 kubectl을 통해 종료된 이유와 종료 코드, 해당 컨테이너 실행기간 (시작, 종료시간) 을 확인할 수 있다.

 

컨테이너 프로브 (probe)

Probe는 컨테이너에서 kubelet 에 의해 주기적으로 수행하는 진단이다. 진단을 수행할 때 kubelet은 컨테이너에 의해 구현된 핸들러를 호출한다. 핸들러는 아래와 같이 세 가지 타입이 있다.

  • ExecAction: 컨테이너 내에서 지정된 명령어를 실행한다. 명령어 상태 코드가 0이면 성공
  • TCPSocketAction: 지정된 포트에서 컨테이너 IP 주소에 대한 TCP 검사를 수행한다. 포트가 활성화 되어 있으면 성공
  • HTTPGetAction: 지정된 포트 및 경로에서 컨테이너 IP 주소에 대한 HTTP GET 요청을 수행한다. 상태 코드가 200 이상 400 미만이면 성공

Probe는 세 가지 결과 중 하나를 가진다.

  • Success: 진단 결과 성공
  • Failure: 진단 결과 실패
  • Unknown: 진단 자체가 실패

그리고 kubelet은 선택적으로 세 가지 종류의 프로브를 수행하고 그에 반응할 수 있다.

  • livenessProbe: 컨테이너가 동작 중인지 여부를 확인한다. 해당 프로브가 실패한다면 kubelet은 컨테이너를 죽이고 컨테이너 재시작 정책에 따라 다시 살릴지 결정한다.
  • readinessProbe: 컨테이너가 요청을 처리할 준비가 되있는지 여부를 확인한다. 해당 프로브가 실패한다면 엔드포인트 컨트롤러는 파드에 관련된 모든 서비스의 엔드포인트에서 해당 파드의 IP주소를 제거한다.
  • startupProbe: 컨테이너 내의 애플리케이션이 시작되었는지를 나타낸다. 해당 프로브가 주어진 경우, 성공할 때 까지 다른 나머지 프로브는 활성화되지 않는다. 해당 프로브가 실패할 경우 kubelet은 컨테이너를 죽이고 컨테이너 재시작 정책에 따라 다시 살릴지 결정한다.

 

마무리

실제로 쿠버네티스에서는 파드만을 정의하여 직접 관리하지 않는다. 파드는 레플리카의 특징을 가지고 있어서 스케일링에 자유롭다. 그래서 파드를 쿠버네티스 관리자가 직접 관리하지 않고, 쿠버네티스에서는 디플로이먼트 오브젝트를 통해 파드의 레플리카 개수를 유지하도록 자동으로 관리하며, 파드의 상태가 좋지 않으면 자동으로 파드를 죽이고 재배포하며, 파드의 레플리카 수를 직 · 간접 적으로 조절하여 스케일링을 할 수 있다.

 

다음 장에서는 쿠버네티스 디플로이먼트에 대하여 알아보자.

2022.01.23 - [DevOps/Kubernetes] - [7] 쿠버네티스 디플로이먼트

 

[7] 쿠버네티스 디플로이먼트

쿠버네티스는 파드를 생성하여 컨테이너 애플리케이션을 구동한다. 그런데 실제로 쿠버네티스 환경으로 운영할 때는 파드 자체를 생성하지 않고 디플로이먼트와 같은 워크로드 리소스를 생성

beer1.tistory.com

 

 

728x90

댓글