성균관대학교 [SKKUDING] 동아리에서 진행한 쿠버네티스 스터디 발표 내용입니다.
💡 https://subicura.com/k8s 의 내용과, 책 ‘시작하세요 도커, 쿠버네티스' 를 참고하여 발표하였습니다.
저희 예제에서는 Minikube를 사용합니다. Minikube란, 개발 용도의 쿠버네티스인데요. Docker Desktop을 설치했던 것과 같이, 개발용도로 사용하는 도구입니다.
Minikube는 로컬에서 가상 머신이나 도커 엔진을 통해 쿠버네티스를 사용할 수 있는 환경을 제공합니다.
하지만 단점은 존재합니다. 바로, ‘개발 용도로 사용’하는 것인데요. 이는 ‘standalone’ 모드를 사용하는 것과 동일합니다.
standalone이란?
standalone은 말 그대로, 독자적으로 존재한다라는 뜻을 담고 있습니다.
제가 standalone 을 처음으로 들어본 것은 Next.js 프레임워크에서 웹어플리케이션을 실행시킬때 standalone모드를 적용시키면, ‘실행’할 때만 필요한 코드만 추출하여 build를 해주는데요. 자세한 것은 아래 링크를 참고하시면 될것 같습니다. 추후에 Next.js를 도입할 때에, 도커 이미지를 최적화하는 데에도 유용하게 사용됩니다.
랠릿 standalone 적용기서론이 길었는데, 쿠버네티스의 standalone모드도 비슷합니다. 본래 쿠버네티스의 목적은 여러 서버의 자원을 클러스터링하여 컨테이너를 배치하는 것이 쿠버네티스의 핵심이지만, 로컬 노드만을 standalone모드로 사용하므로, 쿠버네티스의 기능을 완전히 사용할 수 없는 것이 개발 용도의 쿠버네티스 설치 툴이 가진 단점입니다. 사실 어떻게 보면 당연한 단점이죠.
실제 운영단계에서는 kops, kubespray,kubeadm, EKS,GKE등의 Managed 서비스로 관리를 합니다. 사실 여기선 제가 아는 것은 EKS뿐입니다. EKS로 서비스를 배포하기 위해서는 클라우드 환경에 존재하는 수십개의 서버에 동시에 쿠버네티스를 설치해야만 합니다. (실제로 스꾸딩에서 사용하는 ECS도 각 서버에 ecs-agent라는 container가 존재합니다.)
💡 사실, 쿠버네티스를 진짜로 잘 알고싶다면 EKS와 같이 클라우드 서비스 업체가 관리해주는 방법 말고 그 외의 우리가 들어본적없는 쿠버네티스 설치 툴을 사용하는 것이 좋다고 합니다. 실습도 이렇게하면 더 좋을 것 같네요.
실제로 on-premise환경에서는 kubespray, kubeadm을 이용합니다. ec2에서도 마찬가지입니다.
그럼, 이제 본격적으로 쿠버네티스를 시작해봅시다 !
시작한다고 해놓고 또 시작 전이라고 했는데, 미안합니다.
쿠버네티스의 리소스는 ‘오브젝트’ 형태로 관리합니다.
우리가 이번 스터디에서 배웠던 리소스들, 예컨대 ‘Pods’, ‘Replica Set’, ‘Node’ 등은 하나의 오브젝트입니다.kubcetl api-resources
이 명령어를 치면, 모든 Object를 살펴볼 수 있습니다. 하지만 너무 많아요! 다 몰라도 괜찮대요 ㅎㅎ..
이중 특정 오브젝트의 간단한 설명을 보고싶으면 kubectl explain <object name>
을 사용하시면 됩니다.
YAML파일로 시작해서 YAML파일로 끝난다.
쿠버네티스는 여러개의 YAML파일을 정의해 적용시키는 방식입니다. 컨테이너 자체는 물론이고, 여러 설정값과 secret 값들은 YAML파일로 정의가 됩니다. kubectl명령어로도 쿠버네티스를 사용할 수 있지만, 실상은 YAML파일을 더 많이 사용한다고 하네요.
쿠버네티스는 여러 개의 컴포넌트로 구성되어 있다.
쿠버네티스는 기본적으로 Node위에서 동작합니다.
노드는 마스터노드와 워커노드로 나뉘어져 있습니다.
마스터 노드 :
쿠버네티스가 제대로 동작할 수 있는 관제탑 역할
여기서 마스터 노드에서는 API 서버, 컨트롤러 매니저, 스케쥴러, DNS 서버와 같이 여러 관리를 위한 컴포넌트가 실행됩니다. 실제로 마스터 노드로 ssh로 접속해서 docker ps
를 쳐보면, 해당 컴포넌트의 여러 컨테이너들이 실행되고 있답니다. (이건 안해봄 ㅎㅎ..)
워커 노드 :
마스터 노드의 노예입니다. 애플리케이션이 실행되는 컨테이너에요.
공통점 :
공통적으로 모든 node에는 kubelet이라는 에이전트가 실행됩니다. AWS ECS에서 관리해주는 ecs-agent가 각 EC2에 있는것 처럼요. 이는 쿠버네티스 클러스터 구성을 위해 필수적인데요. kubelet은 컨테이너의 생성, 삭제, 마스터노드와 워커노드의 통신에 사용되는 매우 중요한 agent입니다.
한 마디 요약 : 쿠버네티스는 ‘노드’ 컴포넌트로 구성되어 있으며 이 안에도 수많은 컴포넌트가 존재하고, kubelet이 노드마다 존재하여 쿠버네티스의 중추적인 역할을 한다.
쿠버네티스의 리소스는 ‘오브젝트’ 형태라고 했죠?
그중 가장 기본 단위의 오브젝트인 ‘Pod’ 에 대해 알아봅시다.
컨테이너 애플리케이션의 기본단위를 Pod라고 합니다.
Pod는 한개 이상의 컨테이너로 구성된 컨테이너의 집합입니다. 아까 그림보시면 이해될거에요.
도커에서는 기본 단위가 도커 컨테이너였습니다. 쿠버네티스에서는 Pod입니다.
보통은 One Pod → One Container 가 일반적인 방법이지만, 여러개의 컨테이너가 존재할 수도 있습니다.
예시를 가져와봤습니다.
nginx 웹 서비스를 쿠버네티스에서 생성하기 위한 yaml파일인데요.
이 YAML파일을 쿠버네티스에 적용시켜봅시다. (YAML로 시작해서 YAML로 끝난다 아시죠?)
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: my-nginx-container
image: nginx:latest
ports:
- containerPort: 80
protocol: TCP
💡 쿠버네티스의 YAML파일 형식
apiVersion
쿠버네티스 오브젝트의 API 버전입니다. 당장은 중요하지 않으므로 과감히 넘어갑니다.(라고 쓰여있습니다)kind
리소스의 종류입니다. 우리는 ‘Pod’ 컴포넌트를 만드므로, Pod라고 쓴거죵. 아까kubectl api-resources
커맨드에서 나온 pods가 이친구입니다.metadata
주석과도 같습니다. 메타데이터라고 하면 보통, 리소스의 정보들을 의미하죠? 그래서 해당 항목에서는 name과 같이 해당 오브젝트의 특별한 이름을 지어봅시다. 또한 ReplicaSet이라는 컴포넌트에서 원하는 Pods의 개수를 띄우주기 위한 정보도 포함되어 있습니다.spec
리소스들을 생성하기 위한 자세한 정보입니다. 매우 중요한 정보라고 할 수 있습니다. 사실상 container의 config정보이기 떄문인데요.
위의 예시를 보면 container name을 ‘my-nginx-container’로 지정하였고, nginx 이미지를 받아온후에, 컨테이너 port를 TCP 80번 포트로 설정하였네요.
이러한 정보는 후의 배포단계에서 아주 중요하게 사용됩니다. port 번호로 mapping할수도 있겠지만, container name으로도 mapping이 가능하답니다. 컨테이너가 수없이 많이 늘어나면 일일이 port번호를 수동으로 mapping할 수 없기도 하거든요.
그럼 이제 kubectl apply -f nginx-pod.yaml
로 pod를 생성해봅시다.
pod가 생성되었는지 확인은 각자 알아서 해보시고, 해당 nginx서버로 요청을 보내는 실습을 진행해봅시다.
이를 위해서는 포드 컨테이너 내부 IP로 접근을 해야합니다. 아직 해당 컨테이너가 외부에서 접근할 수 있도록 노출된 상태는 아니거든요. 단지 ‘포트’만 정의했을 뿐입니다.
그러면 kubectl describe
명령어를 사용해 자세한 정보를 알아볼까요?
(실제 터미널 결과를 복붙하였습니다.)
Name: nginx-pod
Namespace: default
Priority: 0
Service Account: default
Node: minikube/192.168.49.2
Start Time: Fri, 10 Nov 2023 01:52:49 +0900
Labels: <none>
Annotations: <none>
Status: Running
IP: 10.244.0.3
IPs:
IP: 10.244.0.3
Containers:
my-nginx-container:
Container ID: docker://03fc89066ecf448c49303b9c59efd4106176d2108b4e77c9b7ee3a3987dfee17
Image: nginx:latest
Image ID: docker-pullable://nginx@sha256:86e53c4c16a6a276b204b0fd3a8143d86547c967dc8258b3d47c3a21bb68d3c6
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Fri, 10 Nov 2023 01:53:19 +0900
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-bphdv (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-bphdv:
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 3m30s default-scheduler Successfully assigned default/nginx-pod to minikube
Normal Pulling 3m30s kubelet Pulling image "nginx:latest"
Normal Pulled 3m kubelet Successfully pulled image "nginx:latest" in 29.928235972s (29.92825368s including waiting)
Normal Created 3m kubelet Created container my-nginx-container
Normal Started 3m kubelet Started container my-nginx-container**
해당 포드의 ip는 10.244.0.3 이네요.
10 과 172 192 는 외부에서 접근할 수 없는 대표적인 사설 ip주소이기도 하고, 해당 pod의 IP는 클러스터 내부에서만 접근이 가능합니다. docker run -p
없이 컨테이너를 실행한 것과 동일합니다.
그래서 해당 nginx 포드로 요청을 보내기 위해서는 ‘서비스 오브젝트’라는 것을 생성해야 한다고 하네요. (나중에 알아봅시다)
일단 지금은 서비스 오브젝트 없이 IP만으로 Nginx Pod에 접근해봅시다. 어케할 수 있을까요?
실제 클러스터 노드 하나에 접속하고, Nginx Pod의 ip로 http 요청을 보내면 됩니다.
kubectl run -i --tty --rm debug --image=alpine:latest --restart=Never ash
저는 해당 커맨드로 alpine 이미지를 통해 pod를 만들고 바로 실행 시켰습니다.
alpine은 ash shell 을 사용하므로, 해당 pod 내에서 -i —tty 옵션으로 실행시켰습니다.
그후 요청을 보낼 curl package를 다운받고 해당 ip로 요청을 보내봤습니다.
중간 오타는 봐주세요 ㅎㅎ.. 성공적으로 요청을 받아옵니다.
그후, nginx pod에 어떤 요청이 왔는지 log로 확인해 볼까요?
맨 밑줄을 보면 curl 요청이 온것이 확인됩니다.
실제 배포 상황에서 어떤 컨테이너가 가끔 죽기도합니다. 우리는 이를 자동으로 살려서 서비스를 운용해야 합니다. 그러기 위해 Replica Set을 사용하는데요.
정해진 수의 동일한 포드가 항상 실행되도록 관리
노드 내부 장애로 인해 포드 RUN이 불가능 하면 다른 노드를 재 생성하여 포드를 RUN
이 두가지 역할을 하는 것이 Replica Set입니다.
예시 YAML을 볼까요
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-replica-pod
spec:
replicas: 3
selector:
matchLabels:
app: nginx-pods-label
template:
metadata:
labels:
app: nginx-pods-label
spec:
containers:
- name: my-nginx-container
image: nginx:latest
ports:
- containerPort: 80
replica 정의 yaml설정에서 matchLabels의 app 값이 곧 template의 labels 의 app값과 동일합니다.
그러면, 해당 label이 붙어있는 pod들은 ‘3’개로 유지시켜줍니다.
즉 replica를 정의하고, template 하위에서 pod를 정의하여서
해당 pod의 label과 동일한 pod를 replica 설정에 맞게 유지시켜 줍니다.
오늘은 여기까지 알아봤는데요. 다음에는 service object를 생성하고 배포하는 방법에 대해 알아보겠습니다.