[Kubernetes 시작하기] 02. 컨테이너 생성과 실행

2023. 11. 20. 02:01Infrastructure/Kubernetes

반응형
  1. 컨테이너 이미지
  2. Docker를 활용한 애플리케이션 이미지 빌드
    1. Dockerfile
    2. 이미지 크기 최적화
    3. 이미지 보안
  3. 다단계 이미지 빌드
  4. 원격 레지스트리에 이미지 저장
  5. 컨테이너 런타임 인터페이스
    1. docker로 컨테이너 실행하기
    2. 리소스 사용량 제한
  6. 정리

k8s는 분산 애플리케이션을 생성/배포/관리하기 위한 플랫폼입니다.

k8s 기반으로 시스템을 구축하기 위해서는 우리들이 실행하고자하는 프로그램이 포함되어있는 애플리케이션 컨테이너 이미지를 빌드하는 것과 분산 시스템을 구성하는 방법을 알아야합니다

애플리케이션 프로그램은 (애플리케이션을 구성하고 있는) 프로그래밍 언어의 런타임, 라이브러리, 소스코드로 구성되어 있습니다.

대부분의 애플리케이션은 외부 라이브러리에 의존성을 가지고 있는 경우가 많습니다.
외부 라이브러리는 일반적으로 특정 머신에 설치한 OS의 공유 컴포넌트 형태로 제공됩니다.

공유 라이브러리에 의존적일 경우 다른 OS로 애플리케이션을 이관했을 때, 애플리케이션 내부에 포함된 공유라이브러리가 OS에 맞지않아 동작되지 않는 등의 문제가 발생할 수 잇습니다.

k8s가 등장하기 이전의 전통적인 운영방식에서는 모든 프로그램이 시스템상에서 동일한 버전의 공유 라이브러리를 공유하는 것으로 이 문제를 해결하였습니다.
다만, 이 경우에는 공유 라이브러리에 대한 의존도에 의해 불필요한 결합도가 높아지고, 복잡성이 증가된다는 단점이 있습니다.

애플리케이션을 개발할 때, 다른 사람과 개발환경을 쉽게 공유할 수 있도록 구성하는 것은 매우 중요합니다.
Docker를 이용하면 실행파일을 패키징 하여, 다른 팀원들이 손쉽게 다운로드하고 이를 registry(= 컨테이너 이미지 저장소)에 푸시할 수 있습니다.

컨테이너 레지스트리는 대부분의 public cloud에서 사용가능하고, 클라우드 환경에서 이미지를 빌드하는 서비스도 사용 가능합니다.

레지스트리를 이용하여 애플리케이션을 private 이미지에 올리고, 이를 이용하여 관리 및 배포를 하며, 이미지 builder 서비스를 이용하여 CI/CD를 편리하게 행합니다.


1. 컨테이너 이미지

컨테이너 이미지는 OS 컨테이너 내부에서 프로그램을 실행하는데에 필요한 모든 파일들을 캡슐화한 바이너리 패키지로, 파일 시스템 계층으로 구성되어있습니다.

컨테이너 이미지를 실행하여 OS 컨테이너 내부에서 실행되는 애플리케이션을 생성할 수 있습니다.

Docker는 가장 유명하고 널리 사용되고 있는 컨테이너 이미지 포맷으로, 사실상 거의 표준처럼 이용되고 있습니다.
우리는 docker 명령어를 이용하여 컨테이너를 패키징/배포/실행 할 수 있습니다.

도커 이미지는 단일 파일이 아닌 다른 파일을 가리키는 Manifest 파일의 명세입니다.
간접적으로 참조하고 있는 특성을 통해 효율적으로 저장하고 전송하는 것이 가능합니다.

컨테이너 이미지는 컨테이너 환경 설정 및 애플리케이션 구성파일 등을 포함합니다.

컨테이너는 크게 2가지 범주로 구분합니다.

  1. 시스템 컨테이너
  2. 애플리케이션 컨테이너

시스템 컨테이너는 가상머신처럼 동작하고, 전체 부팅 프로세스를 실행합니다.
ssh, cron과 같이 VM에서 일반적으로 자주 쓰이는 커멘드들을 포함하고 있습니다.
초창기에는 시스템 컨테이너 유형이 일반적으로 쓰였으나, 최근에는 애플리케이션 컨테이너가 실용성이 높아 더 각광받고 있습니다.

애플리케이션 컨테이너는 일반적으로 단일 프로그램을 실행합니다.
하나의 컨테이너가 하나의 프로그램을 실행한다는 것은 확장성면에서 큰 장점입니다.

이런 특성으로 인하여, 일반적으로 k8s같은 컨테이너 오케스트레이션은 애플리케이션 컨테이너로 구성된 분산 시스템을 구축/배포합니다.


2. Docker를 활용한 애플리케이션 이미지 빌드

2.1. Dockerfile

Dockerfile을 이용하여 애플리케이션을 도커 이미지로 패키징 할 수 있고, 이를 이용하여
도커 컨테이너 이미지 생성을 자동화할 수 있습니다.

리액트 기본 애플리케이션을 이용하여 도커 이미지를 패키징하는 예제 코드를 실습해봅시다.

※ 리액트 애플리케이션을 다운받기 위해서는 선행적으로 node가 설치되어있어야 합니다.


2.1.1. 애플리케이션 생성 및 Dockerfile 생성

먼저 디렉토리를 생성 한 후,

mkdir docker-react-app
cd docker-react-app

npx를 이용하여 create-react-app을 현재 디렉토리에 설치합니다.

npx create-react-app .

이미지 복사시, 제외시킬 파일들을 .dockerignore 에 작성합니다.

node_modules

도커 컨테이너 이미지를 패키징하는 명세에 해당하는 Dockerfile을 생성합니다.

FROM node:alpine

WORKDIR /usr/src/app

COPY package.json .

RUN npm install

COPY . .

CMD [ "npm", "run", "start" ]

도커 이미지 생성

docker build -t jiniworld/docker-react-app .
[+] Building 105.9s (10/10) FINISHED                                                                                                         docker:desktop-linux
 => [internal] load build definition from Dockerfile                                                                                                         0.0s
 => => transferring dockerfile: 158B                                                                                                                         0.0s
 => [internal] load .dockerignore                                                                                                                            0.0s
 => => transferring context: 2B                                                                                                                              0.0s
 => [internal] load metadata for docker.io/library/node:alpine                                                                                               2.9s
 => [1/5] FROM docker.io/library/node:alpine@sha256:4a512d1538b1a8281b58cab0b366a5c62436566bb63e7dcd4a6769c98edb3b5f                                         5.3s
 => => resolve docker.io/library/node:alpine@sha256:4a512d1538b1a8281b58cab0b366a5c62436566bb63e7dcd4a6769c98edb3b5f                                         0.0s
 => => sha256:7ab0a8aeffef38c226acdcd921d3877f2a0887e66254c96704e8faa54729ffd6 2.37MB / 2.37MB                                                               0.9s
 => => sha256:2d5d499b0ef9279f09adbe89093cb1188a646d5bcae72c23c82b7cba981be8fa 448B / 448B                                                                   0.3s
 => => sha256:4a512d1538b1a8281b58cab0b366a5c62436566bb63e7dcd4a6769c98edb3b5f 1.43kB / 1.43kB                                                               0.0s
 => => sha256:fe8982961121d058349f1d6f22115582a665a54fa2b8e585551070723bf66aa7 1.16kB / 1.16kB                                                               0.0s
 => => sha256:ece0d10eb54d6fd5d3ef2f329f3f264fa500bd8e7b725e8b327bea18d02adaa8 7.15kB / 7.15kB                                                               0.0s
 => => sha256:915db32f2887698a56b738a38cbdbc8ffcdd6bf738a1e5e762de36950b514347 43.51MB / 43.51MB                                                             4.2s
 => => extracting sha256:915db32f2887698a56b738a38cbdbc8ffcdd6bf738a1e5e762de36950b514347                                                                    1.0s
 => => extracting sha256:7ab0a8aeffef38c226acdcd921d3877f2a0887e66254c96704e8faa54729ffd6                                                                    0.0s
 => => extracting sha256:2d5d499b0ef9279f09adbe89093cb1188a646d5bcae72c23c82b7cba981be8fa                                                                    0.0s
 => [internal] load build context                                                                                                                            0.1s
 => => transferring context: 1.55MB                                                                                                                          0.0s
 => [2/5] WORKDIR /usr/src/app                                                                                                                               0.1s
 => [3/5] COPY package.json .                                                                                                                                0.0s
 => [4/5] RUN npm install                                                                                                                                   95.1s
 => [5/5] COPY . .                                                                                                                                           0.0s
 => exporting to image                                                                                                                                       2.2s
 => => exporting layers                                                                                                                                      2.2s
 => => writing image sha256:31901e7478d86557e45b0563553b302ad2fa9604bd581705dacc628430da0b5b                                                                 0.0s
 => => naming to docker.io/jiniworld/docker-react-app                                                                                                        0.0s

What's Next?
  View a summary of image vulnerabilities and recommendations → docker scout quickview

도커 이미지 실행

docker run -d --name docker-react-app -p 8080:8080 jiniworld/docker-react-app

2.2. 이미지 크기 최적화

도커 컨테이너 이미지는 계층화된 파일 시스템으로 구성되어있습니다.
때문에, 도커파일을 구성할 때 이미지 크기를 최적화하기 위해 알맞게 계층 구조 설정을 해야합니다.

아래와 같이 2가지 구성이 있다고 할 때
두 구성 중, 2번째 구성이 이미지 크기를 최적화한 구성이라고 할 수 있습니다.

구성 1

  • A계층: 기본 OS 컨테이너
    • B계층: 소스코드 server.js 추가
      • C계층: node 패키지 설치

구성 2

  • A계층: 기본 OS 컨테이너
    • B계층: node 패키지 설치
      • C계층: 소스코드 server.js 추가

1번째 구성의 경우, node계층이 server.js에 종속되어있기 떄문에
server.js 파일이 변경되는 경우 그 하위 계층의 다운로드 및 업로드가 일어나게 됩니다.

이미지 크기를 최적화하기 위해서는 변경 가능성이 적은것을 위에 배치하고, 변경이 빈번한 것을 아래에 구성하는 것이 중요합니다.

아래는 노드기반의 도커 컨테이너 이미지의 Dockerfile 예시입니다.
먼저, package*.json 을 먼저 복사한 후, npm install 로 앱 실행에 필요한 패키지를 설치한 후,
나머지 소스코드를 복사한 후, 실행합니다.

일반적으로 소스코드가 의존성 패키지보다 빈번하게 수정되기 때문에 이와 같은 순서로 배치하였습니다.

FROM node:16

WORKDIR /usr/src/app

COPY package*.json .

RUN npm install

COPY . .

CMD [ "npm", "start" ]

2.3. 이미지 보안

보안이 유지되어야하는 정보와 이미지가 혼재되어서는 안됩니다.

이미지에 설정되어있는 라이브러리가 많을 수록, 그 애플리케이션에 대한 취약점이 늘어날 수 있습니다.
따라서 컨테이너 이미지 내의 파일은 최소화하여 되도록 작은 크기의 이미지를 구성하는 것을 추천합니다.


3. 다단계 이미지 빌드

이미지 크기를 크게 만드는 대표적인 실수 중 하나는
애플리케이션 컨테이너 이미지 구성 중 프로그램의 컴파일을 포함시키는 것 입니다.

도커 파일 내에 컴파일을 포함시키는 것은 컨테이너 이미지 빌드를 매우 손쉽게 할 수 있게 합니다.

하지만, 컴파일 수행이 도커파일 내에 쓰여져 있으면 운영환경에는 필요하지 않은 개발도구들이 컨테이너 이미지 내에 포함되게 됩니다.
불필요한 파일들이 많아지면 용량이 커지게 되고, 이는 컨테이너 배포 속도에도 영향을 주게됩니다.

코드 컴파일을 이미지 내에 포함시키는 것이 아닌 다단계 빌드(multistage build) 방식으로 구성하는 것을 권장합니다.

다단계 빌드를 통해 도커 이미지를 단일 이미지가 아닌 여러 이미지로 생성합니다.
각 이미지는 stage로 간주하며, 아티팩트는 이전 단계에서 현재 단계로 복사할 수 있습니다.

예를 들어, build stage와 deploy stage로 구성된 이미지가 있다고 할 때,
build stage에서는 컴파일러와 프로그램을 실행하기 위해 필요한 툴, 그리고 소스코드만 포함되어있으며 build stage 결과물이 deploy stage로 넘어갑니다.
deploy stage에서는 그 파일들을 배포하는 이미지입니다

다단계 빌드 방식은 컴파일결과물이 이미지 내에 포함되어있지 않기 때문에 컨테이너 이미지 크기를 매우 작게 구성할 수 있고,
이에 의해 배포 시간을 단축할 수 있습니다.


4. 원격 레지스트리에 이미지 저장

컨테이너 이미지를 여러 서버에서 일일히 내려받고 업로드하는 것은 인적 자원도 많이 들 뿐만 아니라 예기치 못한 실수를 범할 수 있습니다.

이런 이유로 도커 이미지를 원격 registry를 저장하여 다운받는 형태로 많이 사용합니다.

registry는 GitHub과 같이 public과 private을 제공하는데,
외부에 공개하지 않고 특정 팀원들끼리만 공유하고자한다면 private registry를 사용하는 것이 좋고.
만일 공개되어도 무방하다면 public registry를 사용해도 됩니다.

registry는 일종의 저장소입니다.
이 곳에 이미지를 올리기 위해서는 먼저 docker login 명령어로 로그인을 선행해야합니다.


5. 컨테이너 런타임 인터페이스

docker run 커멘드로 컨테이너 이미지를 실행시킬 수 있습니다.

5.1. docker로 컨테이너 실행하기

5.1.1. kuard

gcr.io/kuar-demo/kuard-arm64:green 이미지를 사용하여 kuard 라는 이름의 컨테이너를 배포해봅니다.

docker run -d --name kuard -p 8080:8080 gcr.io/kuar-demo/kuard-arm64:green
  • -d, --detach
    • 도커 이미지 컨테이너를 백그라운드(daemon)로 실행시키며 컨테이너 ID를 출력합니다.
  • --name 컨테이너명
    • 컨테이너명을 지정합니다.
    • 만일 이름을 지정하지 않을 경우 gracious_banzai와 같은 형태로 랜덤으로 생성됩니다
  • -p, --publish 호스트포트:컨테이너포트
    • host와 컨테이너의 port를 forward 연결합니다.



5.1.2. 직접 생성한 도커 이미지 실행

아래는 직접 생성한 jiniworld/docker-react-app 이미지를 실행시킨 예제코드입니다.

docker run -d --name docker-react-app -p 8080:8080 jiniworld/docker-react-app
27e517ed82e390b0fbdaa899715e0a89b0d8f741595a3a4f0e2ea745c62900fa

docker ps 명령어를 사용하면 도커 컨테이너 목록을 조회할 수 있습니다.

docker ps
CONTAINER ID   IMAGE                        COMMAND                  CREATED         STATUS         PORTS                    NAMES
27e517ed82e3   jiniworld/docker-react-app   "docker-entrypoint.s…"   4 seconds ago   Up 3 seconds   0.0.0.0:8080->3000/tcp   docker-react-app

5.2. 리소스 사용량 제한

도커는 리눅스 커널이 기본적으로 제공하는 cgroup 기술을 이용하여 애플리케이션이 좀 더 적은 리소스를 사용하도록 설정할 수 있습니다.
k8s에서도 이것을 활용하여 각 파드에서 사용하는 리소스 양을 제한합니다.

docker run -d --name kuard -p 8080:8080 \
  -m 200m --memory-swap 1G \
  -c 1024 \
  gcr.io/kuar-demo/kuard-arm64:green
  • -m, --memory 바이트
    • 메모리 제한을 설정
  • --memory-swap 바이트
    • swap 메모리 제한을 설정
  • -c, --cpu-shares 정수
    • CPU 작업시 자원을 프로세스에 얼마나 할당할지에 대한 리소스 제한
    • 1024가 기본 할당 비율이며, 기본보다 CPU를 두배로 사용하고 싶다면 2048로 설정하면 됩니다. (0.5배로 설정하고 싶다면 512)

6. 정리

이미지 빌드를 마친 후, 필요없는 이미지는 docker rmi 이미지명(또는 이미지 ID) 제거합니다.
이미지를 제거하지 않으면 시스템 상에 영구적으로 존재하니 이부분을 유의해야 합니다.

기존에 빌드했던 이미지를 제거하지 않고 동일한 이름으로 이미지를 빌드할 경우 이전에 생성했던 이미지는 그대로 있는채로 새 이미지 빌드됩니다.


현재 머신에 존재하는 이미지 목록을 조회하고 싶으면 docker images 명령을 이용하면 됩니다.

docker images
REPOSITORY                     TAG       IMAGE ID       CREATED          SIZE
jiniworld/docker-react-app     latest    31901e7478d8   48 minutes ago   549MB
gcr.io/kuar-demo/kuard-arm64   green     ca8c55a31e7a   4 years ago      22.1MB

docker system prune 를 이용하면 빌드 프로세스에 의해 캐시된 미사용 이미지 계층과 중지된 상태의 컨테이너들을 한번에 제거할 수 있습니다.
이 명령어를 cron으로 설정해두면 일정 주기로 불필요한 이미지를 제거할 수 있습니다.

docker system prune
WARNING! This will remove:
  - all stopped containers
  - all networks not used by at least one container
  - all dangling images
  - all dangling build cache

Are you sure you want to continue? [y/N]

++

  • Kubernetes Up & Running Third Edition
728x90
반응형