들어가며
‘좌충우돌 kubernetes 익히기’ 시리즈의 세 번째 이야기를 해 보려고 합니다. 지난 글에서는 두차례로 나눠 k8s objects, controllers, control plane에 관해 설명했습니다. 3탄에서는 k8s에서 가장 기본적인 object인 pod에 대해 구체적으로 살펴보도록 하겠습니다.
POD
pod란 영어 단어를 네이버 영어사전에서 검색해 보면 ‘(물개, 고래 등의) 작은 떼’라고 나옵니다. Kubernetes 이름의 어원처럼, pod 또한 배, 바다, 항해와 관련 있는 용어가 의도적으로 쓰인 것처럼 보입니다. 이를 k8s에 맞춰 재해석한다면 docker container의 작은 떼, 작은 무리라고 보면 될 것 같습니다. 동물의 왕국 같은 다큐멘터리 영상을 보면 물개나 고래는 주로 무리를 지어 서식하고 이동합니다. k8s에서도 한 개 이상의 docker container 무리를 pod라는 최소 단위로 묶어서 서비스를 운영하게 됩니다.
한 번 더 강조하지만 k8s의 최소 운영 단위는 container가 아니라 pod입니다. 하나의 pod 안에는 1개 이상의 container가 존재할 수 있지만, 일반적으로는 1개의 container가 들어가게 됩니다.
그러면 하나의 k8s cluster가 이미 준비되어 있다는 가정하에 간단한 pod를 해당 cluster에 배포해 보도록 하겠습니다.
우선, 배포할 pod를 정의해야 합니다. 원래 kubectl create 명령으로 간단한 pod 하나는 배포할 수 있습니다만, 이보다는 pod template yaml 파일을 작성하여 배포하는 게 좋습니다. 사람의 기억력은 한계가 있어서 이렇게 template를 작성해 두고 소스 코드 관리하듯 관리해 두면 언제든지 재활용할 수 있어 좋습니다.
간단한 샘플 pod template yaml를 작성하면 다음과 같습니다. (출처: https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/)
apiVersion: v1 kind: Pod metadata: name: myapp-pod labels: app: myapp spec: containers: - name: myapp-container image: busybox command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
yaml 에서는 indentation(들어쓰기)이 중요하니 작성 시 유의해야 합니다. 가끔 *.yaml 이라는 확장자를 사용하지 않고 *.yml 확장자를 사용하기도 하는데 가급적이면 공식사이트는 *.yaml의 사용을 권장하고 있습니다.
위와 같이 모든 k8s resource들은 template yaml 파일로 정의할 수 있습니다. 즉, 여러분이 목적으로 하는 서비스를 여러 개의 k8s resource template yaml 파일로 나눠서 표현할 수 있는 것이죠. 심지어 하나의 전체 yaml 파일 안에 여러 개의 resource template을 정의해 넣어도 됩니다.
전통적으로 절차 명령적인(imperative) 방법에서는 terminal 열어 ‘A 만들고, B 만들고, A와 B 연결하고’ 하는 식의 작업을 한 줄 한 줄 명령을 입력해서 서비스를 구성하였습니다. 그러나, k8s에서는 선언적인(declarative) 방법으로 이렇게 tempate yaml 파일로 미리 정의해 놓고 순서에 상관없이 한꺼번에 적용하면 k8s가 알아서 서비스를 배포 운영해 줍니다.
그럼, 다시 위의 yaml 파일 내용을 좀 더 자세히 살펴보도록 하겠습니다.
apiVersion: v1
이것은 사용할 k8s API의 버전 명세입니다. 단순히 v1 이런 것도 있지만 apps/v1 이런 식으로 API 그룹명(여기서는 apps)을 명세해야 하는 경우도 있습니다. k8s에서는 k8s 리소스를 생성할 때 연관된 k8s API를 기본적으로 호출해 사용하게 됩니다.
kind: Pod
생성할 k8s 리소스 타입을 지정합니다. 우리는 지금 Pod를 정의하고 있습니다.
metadata: name: myapp-pod labels: app: myapp
od의 metadata로 name과 label을 정의합니다. 생성할 pod의 이름은 myapp-pod이며, label은 app: myapp으로 세팅했습니다. 모든 k8s object나 controller들은 기본적으로 name을 갖고 일반적으로 label도 갖게 됩니다.
name은 동일한 namespace 상에서는 유일한 값을 이름으로 가져야 합니다. label은 특정 k8s object만을 나열한다거나 검색할 때 유용하게 쓰일 수 있는 key-value 쌍입니다.
NAMESPACE
namespace라는 새로운 용어가 나왔는데 하나의 k8s cluster는 여러 개의 namespace로 나눠서 각 namespace 별로 독립적인 서비스 환경을 갖출 수 있습니다. 전형적인 예로 dev, qa, stage, prod 같은 환경을 하나의 k8s cluster에 각각의 namespace로 구분하여 만들 수 있습니다.
k8s를 사용하다 보면 RBAC 같은 리소스에서 cluster scope 인가 namespace scope 인가를 구분해야 하는 경우가 있습니다. cluster scope 경우에는 전체 cluster에 영향을 미치는 scope이고 namespace scope 경우는 해당 namespace 안에서만 영향을 주는 scope라 보면 됩니다.
다시 pod 정의 부분으로 돌아와서 나머지를 살펴보면 다음과 같습니다.
spec: containers: - name: myapp-container image: busybox command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
생성할 pod의 구체적 내용을 정의하는 spec 부분을 확인할 수 있습니다. spec 하위로 여러 가지 항목들을 나열할 수 있는데 여기서는 containers라는 container 리스트 한가지 항목만 보입니다.
이 글 서두에서 얘기했듯이 하나의 pod에는 1개 이상의 container를 포함할 수 있습니다. 바로 이 containers에 원하는 만큼의 container를 정의해 넣으면 됩니다. 여기서는 하나의 container만 정의했습니다.
container name은 myapp-container이고, 동일한 pod 내에서 유일한 이름을 가져야 합니다.
docker image는 busybox로 지정되어 있습니다. docker image는 docker registry에서 pull을 받아 오게 되는데, docker registry가 명시되어 있지 않다면 docker 공식 public registry인 docker hub(https://hub.docker.com)에서 해당 image를 가져오게 됩니다. 만약 private docker registry를 사용한다면 docker image 이름 앞에 해당 url을 명시해 줘야 하며 k8s에서는 remote docker registry와 통신은 http가 아닌 https로만 하게 되어 있어서 private docker registry에 반드시 TLS 인증서를 설치해둬야 합니다.
command 항목은 container가 생성된 초기에 실행되는 명령이나 스크립트를 수록하게 됩니다. dockerfile에서 ENTRYPOINT, CMD의 역할을 한다고 보면 됩니다. 관련된 비교 설명은 이 문서에 자세히 나와 있습니다.
여기서 container 생성 초기에 실행하는 내용은 단순히 ‘Hello Kubernetes!’ 문자열을 화면(stdout)에 출력하고 1시간 동안 sleep 하라는 명령입니다. 실행 이후를 예상해보면 화면에 문자열 출력한 뒤 1시간 동안 container 실행 상태를 유지하다가 container를 종료하게 됩니다. 만약 이 container가 실행 종료되면 해당 pod도 실행이 종료되어 아마도 상태(status)가 Completed로 변경될 것입니다.
Let’s Deploy
위 내용을 다 작성했다면 sample-pod.yaml이라는 파일명으로 저장한 뒤, 해당 pod를 다음 명령으로 미리 준비된 k8s cluster에 배포할 수 있습니다.
kubectl apply -f sample-pod.yaml
kubectl은 k8s에서 사용하는 기본 cli 툴입니다. 그 기본적인 사용법은 이 문서를 참조하면 됩니다.
여기서 잠깐, kubectl과 관련된 여담 한가지 곁들이겠습니다. 유명한 k8s 관련 podcast 중의 하나인 Kubernetes Podcast from Google 을 청취하다가 알게 된 사실입니다. (여러분도 들어보세요. 영어 공부는 물론, 나중에 시간 좀 지나면 해당 트랜스크립트 텍스트도 사이트에 공개됩니다.) 이 podcast에서 어느 날 k8s 주요 창시자(혹은 maintainer) 중 한 명이랑 인터뷰하는 얘기를 들을 수 있었습니다. podcast 운영자가 ‘kubectl을 어떻게 발음합니까? 큐브씨티엘이라고 부르시나요? 큐브컨트롤이라고 부르시나요?’라고 물어봤습니다. 그 창시자는 상당히 단호한 어조로 ‘큐브컨트롤입니다!’라고 대답했습니다. 그러므로, 이제부터 kubectl을 ‘큐브컨트롤’이라고 불러보면 어떨까 싶습니다.
다시 본론으로 돌아가 kubectl apply 명령을 살펴보겠습니다. kubectl로 다양한 명령을 내릴 수 있는데, 크게 세 가지 유형으로 명령을 내릴 수 있습니다.
첫 번째는 절차 명령적(imperative) 유형, 두 번째는 절차 명령적 오브젝트 구성(imperative object configuration) 유형, 세 번째는 선언적 오브젝트 구성(declarative object configuration) 유형입니다.
뭔가 내용이 복잡할 것 같은데 자세한 것이 궁금하면 이 문서를 참조해 보기 바랍니다. 그래도 대강 요점만 설명해보면…
첫 번째 절차 명령적 유형은 단순히 k8s object 하나씩 한 땀 한 땀 명령을 내리는 것이고, 두 번째 절차 명령적 오브젝트 구성 유형은 첫 번째 것과 유사하지만 구성(configuration) yaml 파일을 만들어 한꺼번에 여러 개의 k8s object 들과 관련한 명령을 내릴 수 있습니다. 세 번째 선언적 오브젝트 구성 유형 경우는 본 글의 예제와 같이 ‘kubectl apply -f yaml 파일’ 명령을 내리는 경우를 가리키는데 선언적(declarative)으로 명령을 내려서 ‘나는 최종적으로 이런 상태를 원하니까 k8s 네가 알아서 조정해서 실행해줘’라는 식의 유형이라고 보면 됩니다.
위 세 가지 kubectl 명령 유형을 따로 구분해 신경쓰며 사용할 필요는 없지만, 크게 절차 명령적(imperative) 방식과 선언적(declarative) 방식 두 가지의 장단점을 알고 사용하면 보다 능숙하게 k8s를 다룰 수가 있게 될 것입니다.
POD 생성 배포 결과 확인
이제 kubectl get pods 명령으로 현재 namespace에 배포된 pod 목록을 살펴보겠습니다.
$ kubectl get pods NAME READY STATUS RESTARTS AGE myapp-pod 1/1 Running 0 15s
위의 결과 화면처럼 myapp-pod가 15초 전에 잘 생성되어 Running 하는 것을 확인할 수 있습니다.
해당 pod의 stdout 내용도 kubectl logs pod/{pod name} 명령으로 살펴볼 수 있습니다.
$ kubectl logs pod/myapp-pod Hello Kubernetes!
의도한대로 Hello Kubernetes! 가 보입니다.
pod의 해당 container 안으로 ssh 접속해 들어가 보고 싶으면 docker exec 명령과 유사한 kubectl exec -it 명령을 사용하면 됩니다.
$ kubectl exec -it myapp-pod -- /bin/sh / # echo $HOSTNAME myapp-pod
위 kubectl exec 명령을 내릴 때 주의해야 할 점은 만약 해당 pod 안에 여러 개의 container가 존재한다면 pod 명 뒤에 -c 옵션으로 container 명도 지정해 줘야 합니다. 본 예제에서는 다행히 pod에 하나의 container만 존재하기 때문에 굳이 -c 옵션을 줄 필요가 없습니다. 여기서 한가지 발견한 것은 환경변수 $HOSTNAME이 기본적으로 pod 이름으로 설정된다는 점입니다.
마무리하며
POD는 k8s에서 다루는 가장 기본이 되는 단위 리소스 object입니다. 이번 글에서 아주 간단한 pod를 생성해서 k8s cluster에 배포해 보고 확인해 봤습니다. 꼭 기억해 둬야 할 내용은 하나의 pod에 여러 개의 container가 들어갈 수도 있다는 것입니다.
원래 계획은 pod 뿐만 아니라 service, deployment에 대해서도 설명하려고 했는데 글을 적다 보니 namespace, kubectl 등에 대해서도 설명하다가 분량이 많아져서 다음 편에 계속 연재하도록 하겠습니다.
김영한, DevOps Engineer, CGEX