2023. 6. 3. 08:45ㆍ쿠버네티스 (Kubernetes)
Pod이란
동일한 실행 환경에서 구동되는 컨테이너들을 모아놓은 컬렉션입니다.
클러스터에서 배포하는 최소 단위이기 때문에 Pod에 있는 컨테이너들은 언제나 동일한 기기에 위치해 있습니다.
따라서 Pod에 있는 컨테이너들은 많은 것을 공유하는데, IP 주소나 Port, 호스트 이름 뿐 아니라 심지어 IPC도 가능합니다.
반대로 다른 Pod들은 서로 독립적입니다.
그렇다면 Pod이라는 개념은 왜 만들어졌을까요?
위 이미지는 영화 다운로드 서비스를 위한 예시입니다.
하나는 영화 다운로드와 관련해서 사용자들과 상호작용할 수 있는 웹 서비스이고 하나는 영화 파일을 원격 저장소에서 가져오거나 또는 로컬 저장소에서 제거하는 동기화 서비스입니다.
이 두 컨테이너를 하나의 컨테이너로 통합하지 않고 왜 구분되어야 할까요?
먼저 웹 컨테이너는 사용자들의 요청에 빠르게 응답해야 하고 서버 안정성이 높아야 합니다.
하지만 동기화 컨테이너는 그럴 필요까진 없고 요청이 오는 대로 파일을 가져오거나 삭제해주면 됩니다.
만약 파일을 제대로 가져오지 못했거나 디스크 용량이 꽉 차서 동기화 서비스가 종료되었다고 가정해보면
- 구분된 경우, 웹 컨테이너는 여전히 돌고있기에 사용자에게 적절하게 대응할 수 있습니다. (가령 동기화 서비스가 이용 불가한다던가 디스크 용량이 꽉 찼다던가...)
- 통합된 경우, 사용자가 아예 웹 서비스조차 이용하지 못합니다.
또한 웹 서비스가 과도하게 메모리를 많이 잡아먹는다고 가정해보면
- 구분된 경우, 웹 컨테이너에 대한 메모리 사용량을 타협할 수 있습니다. 사실 동기화는 IO가 주된 목적이므로 메모리를 적게 줘도 괜찮지만, 어쨌든 최소 메모리 용량을 확보했으니 돌아가긴 할 것 입니다.
- 통합된 경우, 동기화 서비스를 아예 사용하지 못할 수 있습니다. 또는 어플리케이션 수준에서 설정해야 합니다.
다만 필자의 가치관도 그렇고, 사실 역할이 분명히 구분된다면 Pod 수준에서 분리하는 게 좋습니다.
두 개의 Pod을 만들어 각각 웹 컨테이너와 동기화 컨테이너를 위치한다면, 만일 웹 서비스를 업그레이드하거나 복구할 일이 있을 때 해당 Pod만 재배포를 하면 되니까요.
Pod의 큰 장점은 로드 밸런싱(load balancing)이기도 합니다. 이는 추후 포스팅에서 다루겠습니다.
Pod Manifest
Pod은 Pod Manifest에서 정의됩니다. 쿠버네티스는 선언적 구성(declarative configuration)을 선호하기 때문인데, 이는 처음부터 설정값을 작성해서 원하는 상태를 만들어 놓고 Pod을 생성하는 것입니다.
반대로 일단 생성하고 명령어를 통해 원하는 상태를 만드는 것을 명령형 구성(imperative configuration)이라고 합니다.
명령형과 비교해서, 선언적 구성의 장점은 설정값들이 어떠한 기록물 형태(사실상 파일)로 남는다는 것인데 이를 공유하여 리뷰를 주고 받을 수 있다는 장점이 있습니다.
다시 돌아와서, 설정값을 작성한 파일을 Pod Manifest라고 합니다.
쿠버네티스 API 서버는 이 파일을 받으면 일단 Pod을 생성하는 데, 스케줄러(scheduler)는 API 서버에 방문해 이러한 Pod과 해당 Pod manifest를 찾아 Pod을 구성하고 노드에 위치시킵니다.
Pod 만들어보기
예제 어플리케이션을 실행해봅니다.
kubectl run kuard --image=gcr.io/kuar-demo/kuard-amd64:blue
아래 명령어를 실행해서 Pod의 상태를 확인할 수 있습니다.
kubectl get pods
NAME READY STATUS RESTARTS AGE
kuard 1/1 Running 0 9s
이 어플리케이션은 8080 포트를 사용하고 있는데, 바로 http://localhost:8080에 접속하지 못합니다.
이때는 아래처럼 포트 포워딩을 하면 됩니다.
kubectl port-forward kuard 8080:8080
Pod 삭제하기
다음 단계로 넘어가기 위해 여기서 만들었던 Pod을 제거해줍니다.
kubectl delete pods/kuard
다만 바로 삭제되진 않고, 먼저 보통 30초 정도 Terminating 상태를 가집니다.
주의할 점은 해당 Pod의 컨테이너들에 저장된 데이터까지 모두 삭제된다는 건데, 영속(persist) 데이터를 가지고 싶다면 Persistent Volumns 를 요청하면 됩니다.
Pod Manifest 로 실행해보기
YAML 또는 JSON으로 작성할 수 있습니다. 여기서는 YAML 파일 형식으로 작성하겠습니다.
kuard-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
containers:
- image: gcr.io/kuar-demo/kuard-amd64:blue
name: kuard
ports:
- containerPort: 8080
name: http
protocol: TCP
다 만들었다면 아래 명령어를 통해 Pod을 생성할 수 있습니다.
kubectl apply -f kuard-pod.yaml
이러면 yaml 데이터가 API 서버로 전달되고 시스템은 Pod을 생성해 정상 노드에 구동될 수 있게 스케줄링합니다.
여담으로 - 가 붙은 친구들은 배열을 의미합니다. 가령 - containerPort: 8080 는 [{"containerPort": "8080"}] 처럼 만듭니다.
Pod Detail
특정 Pod의 자세한 정보를 보고 싶다면 아래 명령어를 실행합니다.
kubectl describe pods kuard
Name: kuard
Namespace: default
Priority: 0
Service Account: default
Node: kind-control-plane/172.18.0.2
Start Time: Sat, 03 Jun 2023 08:41:32 +0900
Labels: <none>
Annotations: <none>
Status: Running
IP: 10.244.0.5
IPs:
IP: 10.244.0.5
Containers:
kuard:
Container ID: containerd://4294cf4d753d7f3f159f0719621f6afc0092846d53392173bde434eaea0a8857
Image: gcr.io/kuar-demo/kuard-amd64:blue
Image ID: gcr.io/kuar-demo/kuard-amd64@sha256:1ecc9fb2c871302fdb57a25e0c076311b7b352b0a9246d442940ca8fb4efe229
Port: 8080/TCP
Host Port: 0/TCP
State: Running
Started: Sat, 03 Jun 2023 08:41:36 +0900
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-zk7z2 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-zk7z2:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m53s default-scheduler Successfully assigned default/kuard to kind-control-plane
Normal Pulling 2m53s kubelet Pulling image "gcr.io/kuar-demo/kuard-amd64:blue"
Normal Pulled 2m49s kubelet Successfully pulled image "gcr.io/kuar-demo/kuard-amd64:blue" in 3.444567765s (3.444574966s including waiting)
Normal Created 2m49s kubelet Created container kuard
Normal Started 2m49s kubelet Started container kuard
Health Checks
컨테이너 내 어플리케이션이 정상 동작하는 지 확인하는 것을 Health Checking이라고 합니다.
염두할 점은, 프로세스 수준에서 정상 동작 여부가 아니라 실질적으로 어플리케이션이 응답하는 것을 보는 걸 말합니다.
예를 들어 프로세스에 데드락(deadlock)이 걸려서 사용자 요청을 처리할 수 없다고 했을 때, 프로세스는 여전히 정상 동작하고 기능상으로는 문제가 없지만 어플리케이션이 비정상인 상태일 수 있습니다. 따라서 어플리케이션 수준에서 확인 로직을 가져야 합니다.
이렇게 어플리케이션 수준에서 정상 동작하는 것을 Liveness라고 합니다.
Liveness Probe
일반적으로 어플리케이션에서는 HTTP/HTTPS 통신으로 /healthy나 /hello, /live 등을 만들어 200을 보내 정상 여부를 따집니다.
쿠버네티스에서도 이를 적용하여 비정상이면 조치를 취할 수 있게 만들 수 있는데 아래 예시를 보면서 함께 살펴보겠습니다.
kuard-pod-health.yaml
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
containers:
- image: gcr.io/kuar-demo/kuard-amd64:blue
name: kuard
livenessProbe:
httpGet:
path: /healthy
port: 8080
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 10
failureThreshold: 3
ports:
- containerPort: 8080
name: http
protocol: TCP
본 예시에서는 HTTP GET /healthy 를 이용해 만들었습니다.
일반적으로 쿠버네티스는 응답 상태 코드가 200 이상이고 400 미만일 때 정상이라고 봅니다.
작성한 파라미터들을 설명해보면,
파라미터명 | 설명 |
initaliDelaySeconds | 컨테이너가 생성되고 첫 번째로 요청을 보내기 전 지연시간 |
timeoutSeconds | 최소 응답 지연 시간, 해당 시간 안에 응답이 안 오면 비정상이라고 판단 |
periodSeconds | 주기적으로 요청을 보낼 시간 간격 |
failureThreshold | 비정상을 용인하는 연속 개수, 예를 들어 연속 3번까지는 비정상 상태여도 괜찮지만 이를 넘어가면 어플리케이션에 문제가 있다고 판단 |
아래 명령어를 통해 실행해봅니다. 사이트에 직접 접속해볼 것이니 포트 포워딩도 해줍니다.
kubectl apply -f kuard-pod-health.yaml
kubectl port-forward kuard 8080:8080
localhost:8080에 접속한 뒤 Liveness Probe 탭을 눌러보면 10초마다 상태가 기록되는 것을 볼 수 있습니다.
Fail 버튼을 누르면 500 상태가 기록되는 데, 3번을 넘어가면 컨테이너가 재시작합니다.
재시작 후에는 다시 정상적으로 200이 기록되는 데, 이 때 아래 명령어를 실행해서 Pod의 상태를 보면,
kubectl get pods
NAME READY STATUS RESTARTS AGE
kuard 1/1 Running 1 (17s ago) 6m27s
와 같이 RESTARTS에 카운트가 올라가 있는 것을 볼 수 있습니다.
기본적으로 어플리케이션에 문제가 있으면, Pod은 restartPolicy에 따라서 동작합니다.
Zero Exit Code | Liveness Failure | |
Always (default) | O | O |
OnFailure | X | O |
Never | X | X |
Readiness Probe
Liveness는 어플리케이션 자체의 상태를 확인하는 용도로 사용된다면,
Readiness는 이와 다르게 사용자 요청을 처리할 수 있는 지 확인합니다.
주로 로드 밸런싱(load balancing)에서 쓰이는 데, 예를 들어 Pod에 A, B, C 컨테이너가 있을 때 C의 Readiness가 비정상이라면 밸런서는 트래픽을 A, B에 보내 요청을 처리하도록 합니다.
자세한 내용은 추후 포스팅에서 다루겠습니다.
non-HTTP Health Checks
본인의 어플리케이션이 HTTP를 지원하지 않을 때 여전히 health checking 할 수 있는 방법이 있습니다.
첫 번째로는 tcpSocket을 이용하는 방법입니다.
이는 TCP 통신을 통해 연결이 되면 정상, 연결이 안되는 비정상으로 판단합니다.
두 번째로는 exec으로 스크립트를 실행해보는 방법입니다.
실행 결과가 zero exit code면 정상, 아니면 비정상으로 판단합니다.
Resource Management
시스템 자원을 효율적으로 분배하는 것을 Utilization이라고 합니다.
당연하게도 쿠버네티스는 오케스트레이터(orchestrator)이기 때문에 자원 분배를 쉽고 편리하게 할 수 있도록 해줍니다.
최소 요구 자원 만들기
만일 컨테이너에 현재 컴퓨터의 0.5개의 CPU와 128MB 메모리를 최소 요구 자원으로 주고 싶을 때, 아래와 같이 작성하면 됩니다.
kuard-pod-resreq.yaml
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
containers:
- image: gcr.io/kuar-demo/kuard-amd64:blue
name: kuard
resources:
requests:
cpu: "500m"
memory: "128Mi"
ports:
- containerPort: 8080
name: http
protocol: TCP
최소 자원을 요청한다는 말은, 해당 컨테이너가 구동하기 위한 최소 자원을 말합니다.
따라서 모르고 최대로 놓으면 Pod이 제대로 생성되지 않을 수 있습니다.
요청 자원은 때로는 해당 컨테이너가 가져가는 비율을 나타낼 수도 있습니다.
예를 들어 2 CPU 컴퓨터에서 A라는 컨테이너가 500m의 CPU 요청을 가지고 생성되었을 때,
- A가 2 CPU 자원이 필요하다고 하면 혼자 2 CPU를 사용합니다.
- 동일하게 B가 들어와서 2 CPU가 필요하다고 하면 A, B 모두 1 CPU씩 받습니다.
- 동일하게 C가 들어오고 2 CPU가 필요하다고 하면 3개 모두 0.66 CPU씩 받습니다.
최대 제한 자원 만들기
이전에는 최소 요구이지만, 이번에는 최대 제한을 만드는 것입니다. 간단하게 limits를 추가하면 됩니다.
kuard-pod-reslim.yaml
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
containers:
- image: gcr.io/kuar-demo/kuard-amd64:blue
name: kuard
resources:
requests:
cpu: "500m"
memory: "128Mi"
limits:
cpu: "1000m"
memory: "256Mi"
ports:
- containerPort: 8080
name: http
protocol: TCP
자원 사용량 보기
먼저 노드 리스트를 확인합니다.
kubectl get nodes
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 96m v1.27.1
필자는 kind를 이용해 도커에서 쿠버네티스를 구동하고 있으므로 kind-control-plane 이 노드가 존재합니다.
아래 명령어로 노드 정보를 보면
kubectl describe nodes kind-control-plane
...
ProviderID: kind://docker/kind/kind-control-plane
Non-terminated Pods: (10 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age
--------- ---- ------------ ---------- --------------- ------------- ---
default kuard 500m (4%) 1 (8%) 128Mi (0%) 256Mi (0%) 119s
kube-system coredns-5d78c9869d-7fn4h 100m (0%) 0 (0%) 70Mi (0%) 170Mi (0%) 96m
kube-system coredns-5d78c9869d-smxph 100m (0%) 0 (0%) 70Mi (0%) 170Mi (0%) 96m
kube-system etcd-kind-control-plane 100m (0%) 0 (0%) 100Mi (0%) 0 (0%) 96m
kube-system kindnet-wgzns 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) 96m
kube-system kube-apiserver-kind-control-plane 250m (2%) 0 (0%) 0 (0%) 0 (0%) 96m
kube-system kube-controller-manager-kind-control-plane 200m (1%) 0 (0%) 0 (0%) 0 (0%) 96m
kube-system kube-proxy-gftn8 0 (0%) 0 (0%) 0 (0%) 0 (0%) 96m
kube-system kube-scheduler-kind-control-plane 100m (0%) 0 (0%) 0 (0%) 0 (0%) 96m
local-path-storage local-path-provisioner-6bc4bddd6b-fwjv6 0 (0%) 0 (0%) 0 (0%) 0 (0%) 96m
...
요구 및 제한 자원 및 사용량을 볼 수 있습니다.
이 밖에 파일시스템 및 용량을 관리하는 것이 있긴 한데, 필자는 관심이 크게 없어서 문서로 대체합니다.
다만 volumeMounts.mountPath는 조금 중요한데, 컨테이너에서 생산하는 데이터가 저장되는 경로를 말합니다.
'쿠버네티스 (Kubernetes)' 카테고리의 다른 글
[Kubernetes] 6. 레플리카 (0) | 2023.06.13 |
---|---|
[Kubernetes] 5. 서비스 검색 (0) | 2023.06.12 |
[Kubernetes] 3. 팁 (0) | 2023.06.01 |
[Kubernetes] 2. 클러스터 배포하기 (0) | 2023.06.01 |
[Kubernetes] 1. 컨테이너 생성 및 구동 (0) | 2023.05.31 |