[BooTakHae] 배포 실습

Kim Hyen Su·2024년 8월 14일

BooTakHae

목록 보기
20/22
post-thumbnail

배포 실습


개요

이전까지는 Docker Container와 Image 관련 명령어들을 사용하여 컨테이너를 실행해봤습니다. 이번 포스팅에서는 실제 Docker build를 통해 이미지 파일을 생성하고 Dockerhub에 올리는 작업을 해보겠습니다.

Docker 이미지 배포 관련 포스팅

Docker 이미지 만들기

Docker는 이미지를 만들기 위해서 컨테이너의 상태를 이미지로 저장하는 방법을 사용합니다.

예를 들어, 어떤 애플리케이션을 이미지로 만든다면 리눅스만 설치된 컨테이너에 애플리케이션을 설치하고 그 상태를 그대로 이미지로 저장합니다.

이러한 과정은 콘솔에서 명령어를 직접 입력하는 것과 별차이가 없기 때문에 shell 스크립트를 잘알아야 하지만 좋은 샘플이 많이 있기 때문에 이를 활용하여 이미지 생성이 가능합니다. 또한, 컨테이너의 가벼운 특성과 레이어 개념을 이용하여 생성과 테스트를 빠르게 수행할 수 있습니다.

Sinatra 웹 애플리케이션 샘플

Ruby 기반의 Sinatra 웹 애플리케이션 샘플을 사용하여 도커라이징(Dockerizing, 도커 이미지를 만드는 행위)을 해보겠습니다.

Sinatra 를 사용하기 앞서 관련 폴더를 생성하고 Gemfileapp.rb를 생성해줍니다.

Gemfile

source 'https://rubygems.org;'
gem 'sinatra'
gem 'rackup'

app.rb

require 'sinatra'
require 'socket'

get '/' do
	Socket.gethostname
end

Gemfile은 패키지를 관리하기 위한 파일이고, app.rb는 호스트명을 출력하는 API를 생성하기 위한 파일입니다.

이제 패키지를 설치하고 서버를 실행해보겠습니다.

$ docker run --rm -p 4567:4567 -v ${PWD}:/usr/src/app -w /usr/src/app ruby bash -c "bundle install && bundle exec ruby app.rb -o 0.0.0.0"

현재 호스트 OS에 ruby가 설치되어있지 않으므로, 컨테이너를 사용하여 ruby를 실행해줍니다.

호스트의 디렉토리를 루비가 설치된 컨테이너의 디렉토리에 마운트 한 뒤 명령어를 실행하게 되면 로컬에 개발 환경을 구축하지 않더라도 도커 컨테이너를 개발환경으로 사용할 수 있게 됩니다.

서버가 정상적으로 실행되었다면 웹 브라우저에 가서 테스트 해봅니다.

Sinatra 서버

[사진]

위와 같이 정상적으로 페이지에 도커 컨테이너의 호스트명이 보이는 것을 확인할 수 있습니다. 이제 이미지를 만들 준비가 끝났습니다.

Ruby Application Dockerfile

도커는 이미지를 만들기 위해 Dockerfile 이라는 이미지 빌드용 DSL(Domain Specific Language) 파일을 사용합니다. 단순한 텍스트 파일로 일반적인 소스와 함께 관리됩니다.

일단 리눅스 서버에서 테스트로 설치해보고 지속적으로 Dockerfile을 최적화하는 과정을 통해서 작성해줘야 합니다. 하지만, Dockerfile을 처음 작성해보기 때문에 웹 애플리케이션을 ubuntu에 배포하는 과정을 먼저 살펴보겠습니다.

작업은 다음과 같이 수행됩니다.
ubuntu 설치 → ruby 설치 → 소스 복사 → Gem 패키지 설치 → Sinatra 서버 실행

위 과정을 그대로 shell 스크립트로 옮겨보겠습니다.

# 1. ubuntu 설치 (패키지 업데이트)
apt-get update

# 2. ruby 설치
apt-get install ruby
gem install bundler

# 3. 소스 복사
mkdir -p /usr/src/app
scp Gemfile app.rb root@ubuntu:/usr/src/app # From host

# 4. Gem 패키지 설치
bundle install

# 5. Sinatra 서버 실행
bundle exec euby app.rb

unbuntu 컨테이너를 실행하고 위 명령어를 그대로 실행하면 웹 서버를 실행할 수 있습니다. 리눅스에서 테스트가 끝났으니 이 과정을 Dockerfile로 만들면 됩니다.

아직 자세한 명령어를 배우진 않았지만 일단 만들어 보겠습니다. 핵심 명령어는 파일을 복사하는 COPY와 명령어를 실행하는 RUN 입니다.

# 최신 Ubuntu 이미지를 사용하거나 Ruby 공식 이미지를 사용
FROM ubuntu:22.04
RUN apt-get -y update

# 시스템 패키지 업데이트 및 필요한 패키지 설치
RUN apt-get -y install ruby

# Bundler 설치
RUN gem install bundler

# 애플리케이션 소스 복사
COPY . /usr/src/app

# 작업 디렉토리 설정
WORKDIR /usr/src/app

# 번들 설치
RUN bundle install

# 포트 노출
EXPOSE 4567

# 애플리케이션 실행
CMD ["bundle", "exec", "ruby", "app.rb", "-o", "0.0.0.0"]

shell 스크립트 내용을 그대로 Dockerfile로 옮겼습니다. 차이점은 도커 빌드 중에는 키보드를 입력할 수 없기 때문에, y/n 을 물어보는 것을 방지하기 위해 -y 옵션을 추가한다는 정도입니다.

Docker build

이미지를 빌드하는 명령어는 다음과 같습니다.

$ docker build [OPTIONS] PATH | URL | -

생성할 이미지 이름을 지정하기 위한 -t(tag) 옵션만 알면 빌드가 가능합니다.

Dockerfile을 만든 디렉토리로 이동하여 다음 명령어를 입력합니다.

$ docker build -t app .

output :

빌드 명령어 실행시 Dockerfile에 정의한 명령어들이 한 줄씩 실행됩니다. 최종적으로 Building ... FINISHED를 통해서 정상적으로 이미지가 생성된 것을 알 수 있습니다.

생성한 도커 이미지로 3개의 컨테이너를 실행해보겠습니다.

docker run -d -p 8080:4567 app
docker run -d -p 8081:4567 app
docker run -d -p 8082:4567 app

output :

하나의 이미지에서 생성된 3개의 컨테이너가 정상적으로 실행하는 것을 확인했습니다.

Dockerfile 기본 명령어

도커 이미지를 생성하기 위한 Dockerfile의 기본적인 명령어를 살펴보겠습니다.

FROM

FROM <image>:<tag>

FROM ubuntu:16.04

베이스 이미지를 지정합니다. 반드시 지정해야 하며 어떤 이미지도 베이스 이미지가 될 수 있습니다. tag는 될 수 있으면 latest 보다 구체적인 버전을 지정하는 것이 좋습니다. 이미 만들어진 다양한 베이스 이미지는 DockerHub에서 확인할 수 있습니다.

MAINTAINER

기존에 Dockerfile을 관리하는 사람의 이름 또는 이메일 정보를 적는 옵션입니다. 빌드에 딱히 영향을 주지 않아서 위에서 빌드 시 추가하지는 않았지만, 현재에는 deprecated 되어있는 상태입니다.

MAINTAINER 대신에 사용하는 것이 LABEL 입니다.

LABEL

LABEL은 MAINTAINER와 같이 이미지에 메타데이터를 추가해주기 위한 명령어로, Key-Value로 구성됩니다.

이는 다음과 같이 사용 가능합니다.

LABEL email="exampl@docker.com
LABEL name="kim"
LABEL version="1.0"
LABEL description="My Ruby Application"

이와 같이 LABEL을 통해 Key-Value 형태로 표현이 가능하며, 이는 MAINTAINER를 대체 가능하므로, 필요한 경우 LABEL을 사용하여 메타데이터를 추가해주면 됩니다.

COPY

COPY <src>... <dest>

COPY . /usr/src/app

파일이나 디렉토리를 이미지로 복사해줍니다. 일반적으로 소스를 복사하기 위해 사용합니다. target 디렉토리가 없다면 자동으로 생성해줍니다.

ADD

ADD <src>... <dest>
ADD . /usr/src/app

COPY 명령어와 매우 유사하나 몇가지 추가 기능이 있습니다. src에 파일 대신 URL을 대신 입력할 수 있고 src에 압축 파일을 입력하는 경우 자동으로 압축을 해제하면서 복사됩니다.

RUN

RUN <command>
RUN ["executable", "param1", "param2"]
RUN bundle install

가장 많이 사용하는 구문입니다. 명령어를 그대로 실행합니다. 내부적으로 /bin/sh -c 뒤에 명령어를 실행하는 방식입니다.

CMD

CMD ["executable", "param1", "param2"]
CMD command param1 param2
CMD bundle exec ruby app.rb

도커 컨테이너가 실행되었을 때 실행되는 명령어를 정의합니다. 빌드할 때는 실행되닌 않으면 여러 개의 CMD가 존재할 경우에 가장 마지막의 CMD만 실행됩니다. 한꺼번에 여러 개의 프로그램을 실행하고 싶은 경우에는 run.sh 파일을 작성하여 데몬으로 실행하거나 supervisor나 forego와 같은 여러 개의 프로그램을 실행하는 프로그램을 사용합니다.

WORKDIR

WORKDIR /path/to/workdir

RUN, CMD, ADD, COPY 등이 이루어질 기본 디렉토리를 설정합니다. 각 명령어의 현재 디렉토리는 한 줄 한 줄마다 초기화되기 때문에 RUN cd/path를 하더라도 다음 명령어에선 다시 위치가 초기화됩니다. 같은 디렉토리에서 계속 작업하기 위해서 WORKDIR을 사용합니다.

EXPOSE

EXPOSE <port> [<port>...]
EXPOSE 4567

도커 컨테이너가 실행되었을 때 요청을 기다리고 있는 포트를 지정합니다. 여러개의 포트를 지정할 수 있습니다.

VOLUME

VOLUME ["/data"]

컨테이너 외부에 파일시스템을 마운트 할 때 사용합니다. 반드시 지정하지 않아도 마운트 할 수 있지만, 기본적으로 저장하는 것이 좋습니다.

ENV

ENV <key> <value>
ENV <key>=<value> ....
ENV DB_URL mysql

컨테이너에서 사용할 환경변수를 지정합니다. 컨테이너를 실행할 때 -e 옵션을 사용하면 기존 값을 오버라이딩 하게됩니다.

추가로, 명령어에 대해서 조금 더 궁금한 분들은 공식문서를 참고하세요.

Docker Image Registry

도커는 빌드한 이미지를 서버에 배포하기 위해 직접 파일을 복사하는 대신에 도커 레지스트리 라는 이미지 저장소를 사용합니다. 도커 명령어를 이용하여 이미지를 레지스트리에 push하고 다른 서버에서 pull받아서 사용하는 구조입니다.

일반적으로 Docker에서 서비스 중인 DockerHub를 많이 사용합니다.

DockerHub

도커 허브는 도커에서 제공하는 기본 이미지 저장소로 여러 베이스 이미지 및 공식 이미지들이 저장되어 있습니다. 일반 사용자들이 만든 이미지도 많은 갯수가 저장되어 있습니다.

회원가입만 하게되면 대용량의 이미지를 무료로 저장할 수 있고 다운로드 또한 무료입니다. 단, 기본적으로 모든 이미지는 공개되어 누구나 접근이 가능하며, 비공개로 사용하려면 유료 서비스를 이용해야합니다.(하나는 무료입니다)

DockerHub에 기존에 만들었던 ruby 웹 애플리케이션 이미지를 업로드 하겠습니다.

로그인

우선, 도커 허브 계정을 사용하기 위해서는 로그인을 해줘야합니다.

$ docker login

output :

PS C:\project\sinatra> docker login
Log in with your Docker ID or email address to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com/ to create one.
You can log in with your password or a Personal Access Token (PAT). Using a limited-scope PAT grants better security and is required for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/

Username: crazymin180@gmail.com
Password:

Login Succeeded

ID와 패스워드를 입력하면 로그인이 되고 ~/.docker/config.json에 인증정보가 저장되어 로그아웃 전까지 로그인 정보가 저장됩니다.

이미지 태그

도커 이미지 이름은 다음과 같은 형태로 구성됩니다.

[Registry URL]/[사용자 ID]/[이미지명]:[tag]

Registry URL은 기본적으로 도커 허브를 바라보고 있고 사용자 ID를 지정하지 않으면 기본값을 사용합니다. 따라서 ubuntu = library/ubuntu = docker.io/library/ubuntu는 모두 동일한 표현입니다.

도커의 tag명령어를 이용하여 기존에 만든 이미지에 추가로 이름을 지어줄 수 있습니다.

$ docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]

앞에서 만들었던 app 이미지에 계정정보와 버전 정보를 추가해보겠습니다.

$ docker tag app kimhyensu/sinatra-app:1

output :

이제 push 명령을 이용해 DockerHub에 이미지를 전송해보겠습니다.

$ docker push kimhensu/sinatra-app:1.0

output :

PS C:\project\sinatra> docker push kimhyensu/sinatra-app:1.0
The push refers to repository [docker.io/kimhyensu/sinatra-app]
08c7095ad166: Pushed
5f70bf18a086: Mounted from library/redis
e5b469c562fb: Pushed
b99366fd9667: Mounted from library/ruby
c5605d67905e: Mounted from library/ruby
895c582a7270: Mounted from library/ruby
28e03088bc15: Mounted from library/ruby
0d80db6a0977: Mounted from library/ruby
916d866d5b0d: Mounted from library/ruby
8f4ceb8cc1a2: Mounted from library/ruby
1.0: digest: sha256:9c38464f6d51a7cd31abf1c7316eaa74298de35d38d3021310b3d81e57374e9a size: 2416

성공적으로 이미지를 도커허브에 푸쉬했습니다. 이제 어디서든 push 해놓은 이미지를 사용할 수 있습니다.

Deployment

컨테이너 배포 방식

컨테이너를 배포하는 방식은 기존에 애플리케이션을 배포하는 방식과 큰 차이점이 존재합니다.

기존에 애플리케이션을 배포하는 방식은 사용하는 언어, 프레임워크, 웹서버, 리눅스 배포판, 개발자의 취향에 따라 각각 다른 방식을 사용했습니다.

ftp, rsync, ant, gradle ... 등 다양한 배포툴이 저마다의 장점을 가지고 등장하였고 배포하는 방식을 하나로 정의한다는건 거의 불가능했습니다.

하지만, 컨테이너를 사용하게 되면 어떤 환이든 상관없이 배포 방식이 동일해지고 과정도 단순해집니다. 단지, 이미지를 다운로드 받고 컨테이너를 실행하면 끝입니다.

컨테이너 업데이트

도커를 사용하게 되면 기존의 애플리케이션을 업데이트하는 방식도 배포와 큰 차이가 없습니다.

최신 이미지를 기반으로 새 컨테이너를 만들고 이전 컨테이너를 중지하면 됩니다. 최신 소스를 어떻게 복사할지 서버 프로세스는 어떻게 재시작할지 고민할 필요가 없습니다.

단, 컨테이너를 중지하지 않고 새로운 컨테이너로 업데이트 하는 방식은 존재하지 않습니다. 컨테이너의 무중단을 고려하기 위해서는 nginx와 같은 Load Balancer 및 2대 이상의 컨테이너를 사용해야 가능합니다.

도커를 이용한 배포, 그 자체는 매우 단순한 작업이지만, 여러 대의 서버를 관리하고 문제없이 업데이트 하는 건 완전히 새로운 이야기 입니다.

여러 대의 서버를 관리하기 위해서는 CPU나 메모리 같은 자원 분배에 대한 고민과 Service Discovery, Orchestration 등의 개념에 대해 학습해야 합니다. 이는 추후 먼날에 제가 학습할 거라고 믿습니다..!

마무리

분산 시스템의 발전으로 클라우드의 사용 빈도가 커지고, 장소를 가리지 않고 여러 서버를 클릭한번으로 생성이 가능해졌습니다. 이는 서버를 임시로 대여하는 개념이 되었고, 오토 스케일링이라는 환상적인 기능은 부하에 따라 자동으로 서버 갯수를 늘리고 줄여줍니다.

아직 모르는 상위 개념들에 대해서 꾸준히 학습해가면서, 조금 더 개발 또는 인프라적인 지식을 확장해나가야겠습니다. 이번 포스팅은 여기서 마무리하겠습니다.

profile
백엔드 서버 엔지니어

0개의 댓글