2021. 9. 23. 21:30ㆍDevOps/Docker & Kubernetes
Kubernetes in Action 2nd Edition을 정리한 글입니다.
Overview
도커와 쿠버네티스를 사용하여 간단한 애플리케이션을 Deploy 해보는 Chapter입니다. 간단한 애플리케이션을 컨테이너화 하고 쿠버네티스를 통해 배포해보면서 쿠버네티스의 동작 원리를 이해합니다. MacOS환경에서 진행되었으며 Docker, Kubernetes 등의 설치와 관련된 내용들에 대해서는 다루지 않습니다.
Busybox Example
우선 도커 플랫폼이 어떻게 동작하는지 알아보기 위해 이미 만들어져있는 Busybox이미지를 사용해서 실행시켜 보도록 하겠습니다.
# docker run <image>:<tag>
# tag를 명시하지 않으면 latest tag의 이미지를 찾습니다.
docker run busybox:latest
Busybox는 하나의 실행 파일 안에 리눅스에서 자주 사용되는 명령어들을 모아놓은 압축파일로, 대부분의 Dockerfile의 base image가 됩니다. 여기에서는 그냥 가벼운 이미지 예제를 테스트하기 위해서 사용하였습니다. 위의 명령어를 실행하면 다음과 같은 결과가 콘솔에 찍히며 종료됩니다. (busybox 이미지 안에는 서버를 켜는 등의 별다른 추가적인 실행 명령어가 없기 때문에 그냥 run 하자마자 종료됩니다)
docker run 커맨드를 실행하면 로컬 머신에 해당 이미지가 존재하지 않아도 애플리케이션이 잘 실행되는 것을 알 수 있습니다. 이는 도커가 로컬 머신의 해당 이미지의 존재 여부를 확인하고, 있으면 해당 이미지를 사용해서 애플리케이션을 실행하고, 없으면 도커 이미지를 모아놓은 Remote Repository(Docker Hub)를 찾아서 다운받기 때문입니다. (pulling from library/busybox) 만약 Remote Repository에도 해당 이미지가 없거나 권한이 없는 상태에서 private repository의 image를 pull 하려고 시도하는 경우에는 docker run이 실패하게 됩니다.
이러한 도커의 기능 덕분에 관리자는 docker run 커맨드 하나로 애플리케이션을 손쉽게 실행할 수 있습니다.
Creating a trivial Node.js App
이후 있을 Kubernetes배포를 위해 실행되자마자 바로 종료되는 애플리케이션이 아닌 간단한 서버 애플리케이션을 생성해서 실행시켜보겠습니다. 실제로 Docker가 컨테이너 프로세스를 격리된 환경에서 실행하도록 보장하는지, 실제로 해당 프로세스가 Host OS위에서 제대로 동작하고 있는지 등을 살펴보려 합니다. (아래의 app.js와 Dockerfile은 같은 디렉토리에 있어야 합니다.)
// app.js
const http = require('http');
const os = require('os');
console.log("yeoul test server running...");
const handler = function(request, response) {
console.log("Received request from " + request.connection.remoteAddress);
response.writeHead(200);
response.end("You've hit " + os.hostname() + "\n");
};
const www = http.createServer(handler);
www.listen(8080);
// Dockerfile
FROM node:14
ADD app.js /app.js
CMD ["node", "app.js"]
// Dockerfile이 존재하는 root directory에서 실행해야 합니다.
// docker build -t <Image>:<Tag> <directory>
docker build -t yeoul-test:tag1 .
위와 같이 app.js 서버 코드와 Dockerfile을 생성하고 build를 하면 아래와 같이 docker image가 생성되게 됩니다.
Understanding How An Image is Built
이미지 생성과정을 자세히 살펴보면 다음과 같습니다.
- docker build 명령어를 실행합니다.
- 도커 클라이언트가 해당 명령어를 받아서 Dockerfile이 들어있는 디렉토리 정보를 도커 데몬에 넘깁니다.
- 도커 데몬은 Dockerfile을 읽어서 이미지를 만드는데 필요한 레이어(이미지)들을 로컬 머신에서 찾고, 없는 경우에는 Docker Hub에서 pull 받아옵니다.
- 받아온 여러 이미지 레이어를 차례로 쌓아올린 다음(해당 이미지 레이어들은 Read-Only), 새로운 이미지 레이어 (위 예시에서는 ADD, CMD로 기술된 레이어 2개)를 쌓아 올려 새로운 이미지를 만듭니다.
즉, 기존에 Docker Hub에 있던 read-only image를 pull받은 이후에 그 위에 이미지 레이어를 추가하여 새로운 이미지를 만드는 과정인 것입니다.
Understanding That Processes in a Container Run in the Host OS
위에서 만든 이미지를 실행시킨 다음, 실제로 해당 컨테이너가 Host OS위의 격리된 프로세스 위에서 돌고 있는지 확인해보겠습니다.
docker run --name yeoul -p 8080:8080 -d yeoul-test
docker ps 명령어를 통해 현재 Running 상태인 컨테이너를 살펴본 결과 방금 빌드한 "yeoul-test"이미지가 yeoul이라는 컨테이너 이름으로 잘 빌드되어 실행되고 있는 것을 확인할 수 있습니다.
실제로 localhost:8080 포트를 통해 요청을 보내보면 제대로 요청을 받고 있음을 확인할 수 있습니다.
그렇다면 이번에는 지난 포스팅에서 살펴봤듯, 해당 도커 컨테이너가 Host OS위에서 다른 네임스페이스로 동작하고 있는지를 확인해보도록 하겠습니다.
해당 컨테이너로 직접 접속해서 컨테이너 안에서 돌고 있는 프로세스의 목록을 확인해 보면 다음과 같이 app.js가 1번 프로세스로 돌고 있는 것을 확인할 수 있습니다. 즉 완벽하게 "격리"된 상태에서 컨테이너가 돌고 있는 것입니다.
해당 컨테이너에서 나와서 로컬머신(호스트)에서 다시 한번 ps aux를 통해 실행되고 있는 전체 프로세스들의 목록 중 app.js가 돌고 있는지를 한번 확인해보면 1번이 아닌 41570번 프로세스로 돌고 있는 것을 확인할 수 있습니다. 즉 실제로는 Host OS에서 돌고 있는 프로세스이지만 linux namespace를 통해 격리된 환경에서 실행되고 있는 것입니다.
Deploying your App in Kubernetes
다음과 같은 명령어로 쿠버네티스에서 위에 생성해두었던 로컬 이미지를 사용하여 컨테이너를 실행할 수 있습니다. (minikube에서는 아래와 같이 imagepullpolicy를 해주어야 컨테이너 실행 시에 imagepullerror를 방지할 수 있습니다.)
kubectl run yeoul --image=yeoul-test --port=8080 --image-pull-policy=IfNotPresent
위의 명령어를 실행했을때 실제로 쿠버네티스에서는 어떤 일이 일어나는지 확인해보겠습니다. 도커 이미지를 생성하는 과정은 위에서 설명하였으므로 생략하도록 하겠습니다.
- kubctl 명령어를 실행시키면 로컬머신이 이 명령어를 받아서 Kubernetes 마스터 노드에 있는 REST API Server에 Replication Controller를 생성하도록 요청합니다.
- 이 컨트롤러는 스케쥴러에 의해 워커노드에 하나의 Pod를 생성하게 됩니다. (쿠버네티스에서 스케쥴링이란 파드를 노드에 할당하는 것을 의미합니다.)
- Pod가 생성된 노드의 Kubelet은 해당 Pod가 스케쥴되었고, 도커 이미지가 필요하다는 것을 판단하여 Docker Hub로부터 필요한 image를 Pull 받습니다.
- 이미지를 Pull 받은 이후, 도커가 해당 이미지를 통해 컨테이너를 실행합니다.
파드는 자체적으로 IP를 가지고 있지만, 이는 internal ip이기 때문에 kubernetes cluster외부에서는 해당 Pod에 접근할 수가 없습니다. 따라서 LoadBalancer Service를 사용해서 Pod를 외부에서 접근할 수 있도록 설정해주어야 합니다. 이를 "expose"라고 합니다.
minikube를 사용하는 경우 minikube는 자체적으로 LoadBalancer 서비스를 제공하지 않기 때문에 다음과 같이 minikube service를 이용하여 Pod에 접근할 수 있습니다.
쿠버네티스의 Basic Building Block은 컨테이너가 아닌 Pod입니다. Pod는 쿠버네티스의 워커 노드에 있는 Kubelet에 의해 컨트롤되며, 사용자는 kubectl 명령어를 통해 단일 엔트리 포인트인 REST API Server와만 통신함으로써 이 기능들을 사용할 수 있습니다.
실제로 수많은 마이크로서비스들을 수많은 Pod와 함께 사용하다 보면 Pod를 일일이 관리하는 것이 사실상 불가능해집니다. Kubernetes는 따라서 Pod의 Ip를 노출하기보다는 Active 한 Pod를 능동적으로 연결해주는 "Serivce"의 Ip를 노출함으로써 사용자가 뒷단에서 일어나는 Pod의 교체에 영향을 받지 않고 서비스를 안정적으로 사용할 수 있도록 도와줍니다.
Reference
https://baeji77.github.io/dev/infra/kubernetes/use-local-docker-image-in-kubernetes/
'DevOps > Docker & Kubernetes' 카테고리의 다른 글
[Kubernetes in Action] Volumes (0) | 2021.10.09 |
---|---|
[Kubernetes in Action] Services (0) | 2021.10.02 |
[Kubernetes in Action] Replication and Other Controllers (0) | 2021.09.26 |
[Kubernetes in Action] Pods (0) | 2021.09.25 |
[Kubernetes in Action] Introducing Kubernetes (0) | 2021.09.21 |