Week 1 도커 복습과 Hello, Kubernetes

이태성·2023년 5월 6일
0

1.1 도커 복습

도커는 컨테이너를 실행하기 위한 실행 환경(컨테이너 런타임) 및 툴킷입니다. 쿠버네티스는 도커 이외의 컨테이너 런타임도 지원하도록 개발되어 있기에 쿠버네티스를 사용할 경우 도커에 대한 학습이 많이 필요하지는 않습니다. 따라서 이 책에서는 쿠버네티스로 컨테이너화된 애플리케이션을 실행하는 데 필요한 도커의 기본적인 내용만 설명합니다.

주로 필요한 내용은 도커 이미지에 대한 지식입니다. 최근에는 도커 이미지가 아닌 OCI 표준의 컨테이너 이미지를 사용하는 방법이나 다른 도구에서 도커 이미지를 빌드하는 방법 등도 존재합니다. 그러나 아직 도커 이미지를 많이 사용하고 있으며, 도커 이미지 작성 메뉴얼인 도커 파일을 사용해 이미지를 생성하는 경우가 대부분입니다. 그렇기 때문에 도커 이미지를 제대로 만들어 도커 이미지 보관/배포 서버인 도커 레지스트리로 푸시(업로드)하는 방법을 알아 두어야 합니다. 쿠버네티스를 사용할 때 도커에 대해 반드시 알아두면 좋은 지식을 좀 더 자세히 설명하면 다음과 같습니다.

  • 도커 컨테이너 설계
  • 도커 파일 작성법
  • 도커 이미지 빌드
  • 도커 레지스트리로 이미지 푸시

1.1.1 도커 컨테이너란?

도커 컨테이너는 도커 이미지를 기반으로 실행되는 프로세스입니다. 도커 이미지만 있다면 환경의 영향을 받지 않고 다양한 환경에서 컨테이너를 기동시킬 수 있어 이식성이 높습니다.
자바 언어에 WORA(Write Once, Run Anyware)라는 컨셉이 있는데, 도커 컨테이너에도 한번 빌드한 도커 이미지는 어느 환경에서나 동일한 동작을 보장한다는 BORA(Build Once, Run Anyware)컨셉이 있습니다.

또한, 도커 컨테이너는 가상 머신에 비해 ‘가볍다’, ‘시작과 중지가 빠르다’ 등의 장점이 있습니다.

가상 머신은 하이퍼바이저를 이용하여 게스트 OS를 동작시킵니다.하지만 도커 컨테이너는 호스트 머신의 커널을 이용하면서 네임스페이스 분리와 cgroups를 이용한 제어를 통해 독립적인 OS와 같은 환경을 만듭니다. 따라서 게스트 OS 기동을 기다릴 필요가 없으므로 프로세스를 빠르게 시작하고 중지할 수 있습니다.

1.1.2 도커 컨테이너 설계

도커 컨테이너를 생성할 때 주로 주의해야 할 점은 다음 네가지 입니다.

  • 1 컨테이너당 1 프로세스
  • 변경 불가능한 인프라 이미지로 생성한다.
  • 경량의 도커 이미지로 생성한다.
  • 실행 계정은 root 이외의 사용자로 한다.

가장 중요한 점은 ‘1 컨테이너당 1 프로세스’만 기동하도록 생성하는 것입니다. 기존 가상 머신 환경처럼 하나의 이미지 안에 여러 프로세스를 올리는 방법은 추천하지 않습니다.

도커 컨테이너는 애플리케이션과 해당 애플리케이션을 실행하기 위한 실행 환경을 패키징함으로써 애플리케이션을 쉽게 실행하기 위한 도구로, 애플리케이션에서 중요한 역할을 합니다. 주변의 에코시스템도 이 사상을 바탕으로 만들어진 것들이 많으므로, 이를 무시하고 도커 컨테이너에 여러 프로세스를 기동하도록 만들면 주변 에코시스템과 맞지 않거나 관리가 힘들어집니다.

두 번째로 중요한 점은 변경 불가능한 인프라를 구현하는 이미지로 생성하는 것입니다. 변경 불가능한 인프라는 ‘환경을 변경할 때 오래된 환경은 없애고 새로운 환경을 만든다’, ‘한번 만들어진 환경은 절대 변경되지 않게 한다’ 라는 개념입니다. 전자는 쿠버네티스를 사용하는 경우 컨테이너가 자동으로 환경을 다시 만들지만, 후자는 컨테이너 이미지 관리자가 고려해야 합니다. 예를 들어, 컨테이너 기동 후에 외부에서 실행 바이너리를 가져오거나 패키지를 설치하면 외부 영향에 따라 컨테이너 이미지 실행 결과가 달라집니다. 도커 컨테이너는 버전을 관리할 수 있으므로 컨테이너 이미지 안에 애플리케이션 실행 바이너리나 관련 리소스를 가능한 포함시켜 컨테이너 이미지를 변경 불가능한 상태로 만들어야 합니다.

외부 요인이 컨테이너에 미치는 영향

세 번째로 유의할 점은 도커 이미지를 최대한 경량으로 만드는 것입니다. 컨테이너를 실행할 때 노드상에서 사용할 도커 이미지가 없다면 외부에서 이미지를 풀(다운로드)하여 가지고 와야 합니다. 그러므로 도커 이미지는 가급적 경량인 상태로 만들어야 합니다. 바로 실행 가능한 방법으로 dnf/yum이나 apt로 패키지를 설치한 후 저장소 패키지 목록 등의 캐시 파일을 삭제하는 것을 생각해 볼 수 있습니다. 나중에 설명할 멀티 스테이지 빌드를 활용하면 이미지에 필요한 파일만 추가할 수 있습니다. 또 기본 이미지가 경량인 배포판 이미지를 사용하는 것도 하나의 방법입니다. 이러한 경량 배포판으로는 알파인 리눅스나 Distroless가 있습니다. 여기서 자세한 설명은 생량하지만, 도커 파일 최적화에 따라 레이어를 줄이거나 도커 이미지를 생성할 때 스쿼시를 사용하여 이미지를 경량화할 수도 있습니다.

마지막 포인트는 컨테이너 내부에서 프로세스를 기동하는 실행 계정 권한을 최소화하는 것 입니다. 특히 root 사용자를 사용하면 큰 보안사고로 이어질 수 있으므로 최대한 사용하지 않도록 합니다.

1.1.3 도커 파일 작성법

도커 이미지는 도커 파일을 기반으로 이미지가 빌드됩니다. 도커 파일은 코드 1-1 과 같은 형태로 작성하는 컨테이너 이미지 작성 매뉴얼 같은 것입니다. 여기서는 HTTP 요청이 오면 ‘Hello, Kubernetes’라고 응답하는 Go언어 애플리케이션(main.go)을 실행하는 컨테이너 이미지의 생성방법을 예로 들어 설명합니다.

# Alpine 3.11 버전 golang 1.14.1 이미지를 base 이미지로 사용
FROM golang:1.14.1-alpine3.11

# 빌드할 머신에 있는 main.go 파일을 컨테이너에 복사
COPY ./main.go ./

# 빌드 시 컨테이너 내부에서 명령어 실행
RUN go build -o ./go-app ./main.go

# 실행 계정을 nobody로 지정
USER nobody

# 컨테이너가 기동할 때 실행할 명령어 정의
ENTRYPOINT ["./go-app"]

FROM에서는 기반이 되는 도커 이미지를 지정합니다. 도커 파일의 기본적인 흐름은 FROM으로 지정한 base 이미지에 RUN이나 COPY 등의 명령을 사용해 패키지 설치나 파일 복사 등의 여러 가지 처리를 하여 이미지를 생성하는 것 입니다.

base 이미지로 사용할 수 있는 대표적인 이미지는 다음과 같습니다.
초경량의 대표적인 이미지로 scratch이미지가 있지만 셸도 설치되어 있지 않아 디버그가 어렵다는 단점이 있습니다.

이미지명이미지 사이즈
scratch최소
alpine작다
distroless작다
ubuntu크다
centos크다
Universal Base Image크다

그다음에는 COPY 명령으로 빌드할 머신에 있는 파일을 컨테이너에 복사합니다. 가장 많이 사용하는 RUN 명령은 빌드 시에 컨테이너에서 명령어를 실행하는 명령입니다. dnf/yum 명령어나 apt 명령어로 패키지 설치 등 빌드에 필요한 명령어를 싱행하는 것이 일반적인 사용법입니다.

ENTRYPOINT와 CMD는 컨테이너 기동할 때 실행하는 명령어를 지정할 때 사용됩니다. 예를 들어 ENTRYPOINT=bin/sleep, CMD=3600으로 되어 있으면 컨테이너가 기동할 때 /bin/sleep 3600이 실행됩니다.

ENTRYPOINTCMD실행되는 명령어
“/bin/ls”“-a”/bin/ls -a
“/bin/ls”지정안함/bin/ls
지정안함/bin/ls/bin/ls
“/bin/sh”, “-c”“ls -a”/bin/ls -c “ls -a”

ENTRYPOINT와 CMD는 컨테이너 기동 시에 별도 옵션을 지정하면 실행할 때마다 덮어 쓰기를 할 수 있습니다. 실제로는 ENTRYPOINT에 기본적으로 바꿀 필요가 없는 부분을 정의해 두고, CMD에 기본값 인수 등을 정의하는 것이 일반적입니다. 인수를 지정하고 명령어를 실행하려는 경우에는 CMD 부분만 변경하여 실행할 수 있게 합니다. 예를 들어 ENTRYPOINT에 /bin/sleep을 지정해 두고 CMD에는 나중에 sleep하는 시간(초)을 지정하여 사용할 수 있습니다.

그리고 이번 예제에서는 USER 명령을 사용하여 실행 계정을 nobody로 지정했는데, 서비스 환경에서는 구별하기 쉽도록 별도의 전용 계정을 만들어 사용하는 것을 추천합니다.

마지막으로 도커 파일을 작성하는 주요 명령 목록을 다음 표에 정리했습니다.

명령요약
FROMbase 이미지를 지정
MAINTAINER컨테이너 이미지 관리자 정보 기입
(현재는 비추천, LABEL 명령을 대신 사용)
LABEL컨테이너 이미지의 메타데이터를 key:value 형식으로 지정
(예: LABEL maintainer=”Park SangUK polo1497@example.com”)
USER명령어를 실행할 계정 지정
WORKDIR명령어를 실행할 작업 디렉토리 지정 (디렉토리가 없는경우 생성함)
EXPOSE컨테이너 실행 시 Listen할 포트 지정
COPY로컬에 있는 파일을 컨테이너로 복사
ADD로컬에 있는 tar.gz 파일의 압축을 풀고 파일을 컨테이너로 복사
RUN컨테이너 안에서 빌드에 필요한 명령어 실행
ENTRYPOINT컨테이너 기동 시에 실행할 명령어
CMD컨테이너 기동 시에 실행할 명령어 인수

1.1.4 도커 이미지 빌드

실제로 위의 도커 파일을 가지고 docker image build 명령어를 사용하여 이미지를 빌드해보겠습니다. -t 옵션으로 이미지의 이름과 태그를 지정 할 수 있습니다. 태그에는 일반적으로 도커 이미지의 버전을 지정합니다.

도커 이미지는 docker image ls 명령어로 확인 할수 있습니다. base 이미지로 사용한 golang:1.14.1-alpine3.11 가 빌드 시에 다운로드되어 로컬에 있는 것을 확인 할 수 있습니다.

  • 빌드시 사용 된 base 이미지 golang:1.14.1-alpine3.11 의 용량 364MB
  • 제가 생성한 이미지 sample-image:0.1 의 용량 371MB
  • 비교해보면 7MB정도 용량이 증가한것을 확인할 수 있습니다. (base이미지를 기반으로 새로운 이미지를 빌드했으므로 용량이 증가)

멀티 스테이지 빌드

방금 설명한 도커 파일에서는 base 이미지로 golang:1.14.1-alpine3.11 을 사용했습니다. 이는 alpine이미지(5.37MB)를 기반으로 만들어진 아주 작은 이미지 입니다. 그러나 golang의 도커 이미지에는 Go 컴파일 도구 등 (약 359MB)도 포함되어 있고, 다음과 같이 생성된 sample-image 이미지에도 해당 컴파일 도구가 포함되어 이미지 사이즈가 매우 커지는 문제가 있습니다.

이 문제는 도커의 멀티 스테이지 빌드를 사용하면 해결할 수 있습니다. 도커 멀티 스테이지 빌드는 여러 컨테이너 이미지를 사용하여 처리하고 결과물만 실행용 컨테이너 이미지에 복사하는 구조입니다. 위 도커 파일 예제에서 Golang 이미지는 애플리케이션 컴파일에 필요한 도구로만 사용되고 결과물을 실행하는 데는 필요하지 않습니다. 그래서 도커 파일이 멀티 스테이지 빌드를 사용하도록 수정하면 코드 1-2와 같습니다.

# Stage 1 컨테이너(애플리케이션 컴파일)
FROM golan:1.14.1-alpine3.11 as builder
COPY ./main.go ./
RUN go build -o /go-app ./main.go

# Stage 2 컨테이너(컴파일한 바이너리를 포함한 실행용 컨테이너 생성)
FROM alpine:3.11
# Stage 1에서 컴파일한 결과물을 복사
COPY --from=builder /go-app .
ENTRYPOINT ["./go-app"]

위 도커 파일의 멀티 스테이지 빌드에서는 1단계에서 golang의 도커 이미지를 사용하여 Go 언어로 작성된 애플리케이션을 컴파일 합니다. 그리고 2단계에서 다른 도커 이미지를 사용하여 컴파일하고 컴파일한 애플리케이션 바이너리를 1단계 컨테이너에서 복사해 옵니다.

예제는 2단계 구성이였지만 3단계 이상의 구성도 가능합니다. 이처럼 멀티 스테이지 빌드를 사용하여 소스 코드 컴파일에 필요한 도구를 실제 애플리케이션을 기동시키는 컨테이너에 포함하지 않아도 됩니다. 기동용 컨테이너에 불필요한 소프트웨어를 설치하지 않는 것은 보안 측면에서도 좋습니다. 또한, 최근에는 빌드킷 등을 사용해 빌드 단계의 의존 관계를 자동으로 파악하고 빌드 단계를 병렬로 처리하는 것도 가능합니다. 그래서 멀티 스테이지 빌드를 사용해 도커 이미지의 빌드 처리를 전체적으로 빠르게 할 수도 있습니다.

실제로 멀티 스테이지 빌드를 사용한 도커 파일로 이미지 빌드를 해보겠습니다, 기본값으로 Dockerfile이라는 이름의 파일을 사용해 빌드하지만 -f 옵션을 추가하여 사용하는 파일을 변경할 수 있습니다.

docker image build -t sample-image:0.2 -f Dockerfile-MultiStage .

생성한 도커 이미지를 확인해보면 sample-image:0.2가 멀티 스테이지 빌드를 사용한 이미지입니다. base 이미지가 alpine:3.11로 바뀌었기 때문에 이미지 사이즈가 12.5MB로 대폭 줄어든 것을 확인할 수 있습니다.

1.1.5 이미지 레이어 통합과 이미지 축소화

그 외에도 도커 이미지를 경량화하는 방법이 있습니다.

다이브(Dive)는 도커 이미지(OCI 표준 이미지)를 조사하는 도구 입니다. 각각의 레이어에서 어느 파일에 변경이 있어 어느정도의 용량이 소비되는지를 CUI에서 조사 할 수 있습니다.

레이어마다 같은 파일의 변경이 많은 경우 docker image build —-squash옵션을 사용하면 최종 파일 상태를 가진 한개의 레이어에 하나로 합쳐지므로 컨테이너 이미지를 축소 할 수 있습니다.

1.1.6 도커 레지스트리로 이미지 업로드

도커 레지스트리는 도커 이미지를 보관하는 저장소 서버입니다. 유명한 저장소로는 도커허브, Google Container Registry(GCR), Amazon Elastic Container Registry(Amazon ECR) 등이 있습니다. 도커 허브에는 누구나 사용할 수 있도록 공개된 도커 이미지가 많이 등록되어 있습니다. ‘공식 이미지’와 ‘사용자가 만든 비공식 이미지’가 있으므로 컨테이너 이미지를 사용할 떄는 먼저 신뢰할 수 있는 이미지인지 확인하고 사용해야 합니다. 기본적으로 Docker Official Images라고 명시 되어있는 공식이미지를 사용할 것을 추천 합니다.

한편 공식 이미지 중에서도 보안 업데이트가 되지 않은 취약한 이미지가 있기에 Trivy나 Clair 등과 같은 컨테이너 보안 스캔 도구의 사용도 검토해보길 바랍니다.

그럼 생성한 이미지를 도커 레지스트리에 푸시(업로드)해보겠습니다. 지금까지 이미지 이름을 ‘sample-image:0.1’로 생성했지만 도커 레지스트리에 푸시하려면 이미지 이름을 다음과 같은 형식으로 해야합니다.

Docker 레지스트리 호스트명/네임스페이스/저장소:태그

도커 허브를 사용할 경우 Docker 레지스트리 호스트명은 생략가능하며 네이스페이스는 도커 허브 사용자 이름을 지정합니다.

docker.io/DOCKERHUB_USER/sample-image:0.1
또는
DOCKERHUB_USER/sample-image:0.1

이미지를 푸시할 수 있도록 docker login 명령어를 사용해 도커 레지스트리에 로그인 합니다. 이렇게 하면 빌드한 도커 이미지를 도커 허브에 푸시할 수 있습니다. 이미지를 푸시하려면 푸시할 이미지를 지정하고 docker image push 명령어를 실행합니다.

# 도커 허브 로그인
$ docker login
Username : DOCKERHUB_USER
Password : ********
Login Succeeded

# 이미지 태그 변경
$ docker image tag sample-image:0.1 DOCKERHUB_USER/sample-image:0.1

# 도커 허브에 이미지 업로드
$ docker image push DOCKERHUB_USER/sample-image:0.1

1.1.7 컨테이너 기동

도커 컨테이너를 기동하려면 다음과 같은 명령어를 사용합니다. 그러나 쿠버네티스를 사용할 경우에는 쿠버네티스가 컨테이너를 기동하기 때문에 다음 명령어를 사용하지 않습니다.

# 도커 컨테이너 기동(localhost:12345 포트를 8080/TCP 포트로 전송)
$ docker container run -dp 12345:8080 sample-image:0.1

# 애플리케이션 동작 확인
$ curl http://localhost:12345
Hello, Kubernetes

1.2 쿠버네티스의 세계로

도커의 기본적인 사항에 대한 복습을 마쳤습니다. 다음장 부터는 실제 쿠버네티스 세계로 들어가보겠습니다.

profile
가능한거 부터 차근차근 하자

0개의 댓글