[Kubernetes] 1. 컨테이너 생성 및 구동

2023. 5. 31. 09:06쿠버네티스 (Kubernetes)


시작하면서

 

쿠버네티스는 어플리케이션을 생성 - 배포 - 관리하는 플랫폼입니다.

 

먼저 컨셉을 이해하기 전에 어떻게 이게 가능할 것인지 스스로에게 질문을 하면 좋은데,

만약 우리가 신나게 프로그래밍하고 실행했다고 하면, 컴파일 후에 만들어진 바이너리(binary) 덩어리가 시스템 메모리 상에 올려져서 우리가 원했던 동작을 수행할 것입니다.

 

그렇다면 해당 바이너리 덩어리를 캡쳐해서 보관하고 있다가 필요할 때 꺼내서 시스템 메모리에 올려버리면 그게 1개든 10개든 동작할 것입니다. 그리고 일단 올리고 보면 그게 잘 동작하는 지 또는 얼마나 시스템 자원을 먹는 지 등 차차 궁금해집니다. 마찬가지로 뭔가 잘못되면 복구할 수도 있어야겠죠.

 

사실 모든 질문에 대한 답을 만들어가면 쿠버네티스의 컨셉을 이해하게 됩니다.

 

 

 

 


컨테이너 이미지

 

컨테이너란 개발 코드들과 디펜던시들을 패키징해서 만든 소프트웨어 1개 단위를 말합니다. 

 

What is a Container? | Docker

A container is a unit of software that packages code and its dependencies so the application runs quickly and reliably across computing environments.

www.docker.com

 

이름 자체만 보면 만들고 보면 또 무언가를 담아야 완성될 것 같은데, 그렇지 않습니다.

우리는 컨테이너 속이 어떻게 생겼는지 탐방할 필요 없이 그 자체로만 이해하면 됩니다.

 

Comparing Containers and Virtual Machines

 

VM은 단순히 그냥 컴퓨터 안에 또 컴퓨터를 만든다고 보면 됩니다. 어떤 어플리케이션을 여러 개 구동하기 위해서는 OS를 늘려야 하겠죠.

반면에 컨테이너 쪽은 호스트 OS 1개를 공유합니다. 대신 Docker라는 플랫폼 위에서 어플리케이션이 여러 개 구동되죠.

따라서 어플리케이션 수를 늘리고 싶을 때, OS를 설치하는 것부터 시작하지 않아도 됩니다.

 

당연하게도 플랫폼에 무언가를 하고 싶으면 플랫폼이 요구하는 정의에 맞춰 제공해줘야 합니다.

따라서 우리는 어플리케이션을 컨테이너 이미지로 만들어 제공해야 합니다.

 

컨테이너 이미지란 (일전의 컨테이너 정의와 함께) 개발 코드들과 디펜던시들을 묶은 바이너리 패키지입니다. 따라서 어플리케이션을 구동하기 위해 필요한 모든 정보를 담고 있어야 합니다.

쉽게 생각하면 본인이 IDE에서 코드를 실행할 때를 생각하면 됩니다. SDK나 JDK를 설정하고, pip, pom, gradle 등으로 라이브러리를 설치하고, 개발해서 실행 버튼을 누르면 알아서 컴파일 후에 프로세스가 실행될텐데, 단순히 장소를 IDE에서 도커로 변경해주면 되죠.

 

컨테이너 이미지는 점차 도커 이미지로 표준화되었습니다. 우리가 컨테이너 이미지라고 안 부르고 도커 이미지라고 불러도 위화감이 없는 건 메신저를 카톡으로 부르는 것과 마찬가지일테죠.

While the OCI standard achieved a 1.0 release milestone in mid-2017, adoption of these standards is proceeding slowly. The Docker image format continues to be the de facto standard, and is made up of a series of filesystem layers. Each layer adds, removes, or modifies files from the preceding layer in the filesystem. This is an example of an overlay filesystem. The overlay system is used both when packaging up the image and when the image is actually being used.
- Kubernetes: Up and Running, 15p

 

 

시스템 컨테이너

시스템 컨테이너(system container)는 작은 단위의 VM과 같습니다. ssh, cron, syslog와 같은 시스템 서비스를 포함해서 OS 수준의 인프라를 가지고 싶을 때 이용하죠. 당연하게도 이 또한 컨테이너 이미지이기 때문에 Docker에서 운용됩니다.

 

 

어플리케이션 컨테이너

어플리케이션 컨테이너(application container)는 우리가 익히 알던 컨테이너로 주로 한 개 프로그램을 실행하기 위해 있습니다. 해당 컨테이너를 자유롭게 확장해서 구성할 수도 있고, 실패했을 때 복구도 쉽게 할 수 있죠. 나중에 소개해드리겠지만, Pod이라는 개념을 사용합니다. 

 

 

 

 

 


도커 이미지 빌드하기

 

일전에도 소개했지만 쿠버네티스와 같은 오케스트레이션(orchestration) 시스템은 어플리케이션 컨테이너들을 빌드하고 배포해서 분산 시스템(distributed system)을 만드는 것을 중점으로 하고 있습니다.

따라서 어떻게 도커 이미지를 빌드하는 지부터 알아야 합니다.

 

Dockerfile

Dockerfile은 도커 컨테이너 이미지를 자동으로 만들어주는 스크립트입니다.

먼저 간단하게 Node.js 프로그램에 대해 어플리케이션 이미지를 만들어보도록 하죠.

 

package.json

{
	"name": "simple-node",
	"version": "1.0.0",
	"description": "A sample simple application",
	"main": "server.js",
	"scripts": {
		"start": "node server.js"
	},
	"author": ""
}

 

server.js

var express = require('express');
var app = express();
app.get('/', function (req, res) {
	res.send('Hello World!');
});
app.listen(3000, function () {
	console.log('Listening on port 3000!');
	console.log(' http://localhost:3000');
});

 

.dockerignore

node_modules

 

Dockerfile

# Start from a Node.js 10 (LTS) image
FROM node:10

# Specify the directory inside the image in which all commands will run
WORKDIR /usr/src/app

# Copy package files and install dependencies
COPY package*.json ./
RUN npm install

# Copy all of the app files into the image
COPY . .

# The default command to run when starting the container
CMD [ "npm", "start" ]

 

여기서 .dockerignore 파일은 이미지에 복사해서 넣을 때 무시할 파일 또는 디렉터리를 지정합니다.

 

여기까지 작성했다면 docker 명령어를 통해 간단하게 도커 이미지를 만들 수 있습니다.

docker build -t simple-node .

만든 이미지를 도커에서 실행해보고 싶다면 아래 명령어를 실행하면 됩니다.

docker run --rm -p 3000:3000 simple-node​

 

참고로 이렇게 만든 도커 이미지는 로컬(local docker registry)에서만 존재합니다.

 

 

 

 

 


이미지 크기 최적화하기

 

그냥 무턱대고 Dockerfile을 만들다보면 이미지 크기가 눈에 띄게 커지는 경험을 할 수 있습니다.

개인적으로는 한 번 일단 겪어보고(?) 읽으면 체감이 되서 좋을 듯 한데, 빌드 시 매번 많은 시간을 기다려야 하는 불편함이 있습니다.

 

첫 번째로는 파일을 넣고 제거할 때, 실제 이미지에서 삭제되는 게 아니라는 점입니다.

.
 - 레이어 A : 큰 파일을 포함합니다.
   - 레이어 B : 큰 파일을 제거합니다.
     - 레이어 C : B에 대해서 바이너리를 추가해 빌드합니다.

 

레이어 B에서 큰 파일을 제거했지만 레이어 A에서 여전히 큰 파일을 포함하고 있기 때문에 실제 이미지를 push 또는 pull 했을 때는 여전히 큰 파일을 통신하게 됩니다.

따라서 어차피 사용 안 할 건 처음부터 넣지 않는 게 좋습니다.

 

두 번째로는 레이어의 순서입니다.

.
 - 레이어 A : OS를 포함합니다.
   - 레이어 B : 소스 코드를 추가합니다.
     - 레이어 C : node 패키지를 설치합니다.
.
 - 레이어 A : OS를 포함합니다.
   - 레이어 B : node 패키지를 설치합니다.
     - 레이어 C : 소스 코드를 추가합니다.

 

대부분의 경우, 디펜던시보다는 소스 코드를 업데이트 할 일이 많습니다.

레이어 C가 레이어 B에 종속 관계를 가지고 있으므로, 레이어 B에 업데이트가 일어났을 때 레이어 C에도 업데이트를 해줘야 하는데, 후자와 달리 전자에 대해서는 소스 코드를 변경할 때마다 매번 패키지를 재설치해줘야 합니다.

따라서 변경 빈도 수로 정렬해서 만들어 주는 게 좋습니다.

 

 

 

 


멀티 스테이지

 

큰 이미지를 빌드할 때, 하나의 이미지로만 하는 것보단 여러 개의 이미지로 나눠서 빌드하고 배포하는 게 시간이 크게 절약됩니다.

이미지 1개당 1개의 스테이지라고 생각하면 되는데, 앞선 스테이지의 결과를 재사용하도록 만들면 됩니다.

 

먼저 싱글 스테이지의 예시를 보면,

FROM golang:1.11-alpine

# Install Node and NPM
RUN apk update && apk upgrade && apk add --no-cache git nodejs bash npm

# Get dependencies for Go part of build
RUN go get -u github.com/jteeuwen/go-bindata/...
RUN go get github.com/tools/godep
WORKDIR /go/src/github.com/kubernetes-up-and-running/kuard

# Copy all sources in
COPY . .

# This is a set of variables that the build script expects
ENV VERBOSE=0
ENV PKG=github.com/kubernetes-up-and-running/kuard
ENV ARCH=amd64
ENV VERSION=test

# Do the build. This script is part of incoming sources.
RUN build/build.sh

CMD [ "/go/bin/kuard" ]

 

Go 개발 도구라던지, node, npm 등 결과가 모여있는 이미지 크기는 500MB를 넘습니다.

 

다음으로 멀티 스테이지의 예시를 보면,

# STAGE 1: Build
FROM golang:1.11-alpine AS build

# Install Node and NPM
RUN apk update && apk upgrade && apk add --no-cache git nodejs bash npm

# Get dependencies for Go part of build
RUN go get -u github.com/jteeuwen/go-bindata/...
RUN go get github.com/tools/godep
WORKDIR /go/src/github.com/kubernetes-up-and-running/kuard

# Copy all sources in
COPY . .

# This is a set of variables that the build script expects
ENV VERBOSE=0
ENV PKG=github.com/kubernetes-up-and-running/kuard
ENV ARCH=amd64
ENV VERSION=test

# Do the build. Script is part of incoming sources.
RUN build/build.sh


# STAGE 2: Deployment
FROM alpine

USER nobody:nobody
COPY --from=build /go/bin/kuard /kuard

CMD [ "/kuard" ]

 

해당 Dockerfile을 실행하면 두 개의 이미지가 생성됩니다. 하나는 Go 개발 도구 및 node, npm와 소스 코드를 포함한 이미지이고 다른 하나는 이전에 만든 이미지를 활용해 바이너리 파일을 배포하는 이미지이죠.

마지막 이미지는 20MB 남짓이기 때문에 배포 시간을 크게 절약할 수 있는 이점이 있습니다.

 

결과를 실행하고 싶으면 아래 명령어를 실행하면 됩니다.

docker build -t kuard .
docker run --rm -p 8080:8080 kuard

 

 

 

 


도커 문서

 

쿠버네티스를 시작하기 전에, docker cli를 사용해보는 것도 좋습니다.

하지만 0. 쿠버네티스와 도커에서 설명했듯 본 카테고리에서는 도커에 대해 자세히 알고 싶지 않다는 가정에서 출발했기 때문에 아래 문서로 대신하겠습니다.

 

Docker Docs: How to build, share, and run applications

 

docs.docker.com

 

 

참고로 지금까지 만든 이미지를 제거하고 싶다면

docker rmi <tag-name>​

또는

docker rmi <image-id>

를 실행하면 됩니다.

당연한 말이긴 한데, 기존 이미지를 직접적으로 제거하지 않으면 동일한 이름으로 새로운 이미지를 빌드해도 여전히 남아있습니다.

 

tag 이름이나 image 아이디가 궁금하다면 아래 명령어를 실행하면 됩니다.

docker images

 

 

'쿠버네티스 (Kubernetes)' 카테고리의 다른 글

[Kubernetes] 5. 서비스 검색  (0) 2023.06.12
[Kubernetes] 4. POD  (0) 2023.06.03
[Kubernetes] 3. 팁  (0) 2023.06.01
[Kubernetes] 2. 클러스터 배포하기  (0) 2023.06.01
[Kubernetes] 0. 쿠버네티스와 도커  (0) 2023.05.31