Docker를 이용한 서비스 컨테이너화 작업

최민길(Gale)·2023년 5월 6일
1

Docker & Container

목록 보기
1/4

안녕하세요 오랜만에 인사드립니다. 최근 정보처리기사 준비와 몸 상태가 좋지 않아 몸 상태를 회복하느라 게시글을 많이 남기지 못했네요ㅠㅠ 앞으로는 자주 게시글을 남길 수 있도록 해보겠습니다.

기존 서버의 문제점 중 하나는 하나의 EC2 내에 테스트 서버, 본 서버, 알림 서버 등 여러 구성 요소들이 존재하기 때문에 각 파트들과의 독립성이 떨어서 서비스 안정성이 떨어진다는 것이 있었습니다. 이를 개선하기 위해 컨테이너를 이용하여 각 파트들을 격리시켜 독립적으로 실행하여 서비스 안정성을 향상시키는 작업을 진행했습니다.

컨테이너란 클라우드 가상화 기술로 각 컨테이너들은 하나의 OS를 공유하며 하이퍼바이저 없이 격리되어 동작합니다. 따라서 컨테이너 별로 독립적으로 동작하여 안정적으로 서비스를 배포할 수 있으며 격리된 프로세스로 동작하기 때문에 오버헤드가 낮다는 장점이 있습니다.

본 서버에 배포하기 전 로컬 환경에서 컨테이너가 정상적으로 작동하는지를 테스트해보았습니다. EC2와 같은 조건으로 컨테이너가 생성되어야 하기 때문에 현재 본 서버 구성에 맞는 Dockerfile을 생성하여 이미지를 생성 후 해당 이미지로 컨테이너를 생성하는 방식으로 진행했습니다.

Docker 이미지의 경우 베이스 이미지부터 시작해서 새로운 이미지를 중첩해서 레이어를 쌓아가는 방식으로 생성됩니다. 따라서 Dockerfile의 명령어 수에 따라 레이어가 생성되기 때문에 명령어를 최소화하는 것이 중요합니다. 우선 베이스 이미지를 지정해주는 FROM 명령어의 경우 여러 개를 실행하는 각각의 FROM 이전에 실행된 명령은 초기화됩니다. 이번 Dockerfile의 경우 내부에 nginx와 php를 직접 설치하였기 때문에 ubuntu를 베이스 이미지로 사용하였고, 만약 nginx 이미지로 Docker 이미지를 생성하고 싶다면 FROM nginx:latest 등의 명령어를 이용하여 nginx가 설치된 상태로 진행하실 수 있습니다.

# 이미지 세팅
# FROM을 여러 개 실행하면 각각의 FROM 이전에 실행된 명령은 초기화
FROM ubuntu:20.04
MAINTAINER Gale <cmg4739@gmail.com>

컨테이너 실행 시 github에서 클론 작업까지 자동적으로 진행하는 Dockerfile을 만들기 위해 github의 계정 비밀번호 및 어떤 브랜치를 이용할 것인지 등 외부에서 입력받을 변수들이 필요합니다. 이를 ARG 명령어를 이용하여 이미지 빌드 시 --build-arg 옵션을 이용하여 받아오고 Dockerfile 내부에서 사용할 수 있도록 ENV 명령어를 이용하여 환경 변수 설정을 진행하였습니다.

# 환경 변수 세팅
# docker build -t toda:test --build-arg password=password .
ARG password branch
ENV PASSWORD=$password BRANCH=$branch
Dockerfile 빌드 예시
docker build -t container:v1 --build-arg password=password --build-arg branch=master .

이어서 ubuntu 내에 필요한 프로그램들을 설치해줍니다. 이 때 RUN 명령어를 최소화하여 레이어 개수를 줄이기 위해 최대한 한 번에 명령을 실행할 수 있도록 진행합니다.

#3. nginx 설치
apt-get install nginx -y && \

#4. PHP 설치
apt-get install 	php8.0-common \
			php8.0-cli \
			php8.0-fpm \
			php8.0-cgi \
			php8.0-curl \
			php8.0-mysql \
			php8.0-opcache \
			php8.0-redis -y

nginx 및 php의 설정을 커스텀하기 위해선 각각 nginx.conf 또는 default 파일, 그리고 php.ini 파일을 커스텀해야 합니다. 이를 Dockerfile에서 구현할 수 있는 방법은 크게 두 가지입니다. 우선 첫 번째는 수정된 파일을 Dockerfile이 존재하는 폴더 내에 위치한 후 이를 COPY 명령어를 이용하여 컨테이너 내로 이동시켜 설정 파일을 대체하는 작업입니다. 두 번째는 현재 컨테이너 내의 설정 파일의 문구를 sed -i 명령어를 이용하여 원하는 문자열로 대체합니다. 저는 Dockerfile 자체에서 어떤 부분이 수정되었는지 명확하게 확인하기 위해 후자의 방법을 선택했습니다.

# sed -i 's(구문자)(바꿀 단어)(구분자)(대체단어)(구분자)g' 파일명
WORKDIR /etc/nginx/sites-available
RUN	sed -i 's%root /var/www/html;%root /var/www/'$BRANCH';%g' default && \
	sed -i 's%server_name _;%server_name localhost;%g' default && \
	sed -i 's/index index.html/index index.php index.html/g' default && \
	sed -i 's%try_files $uri $uri/ =404;%try_files $uri $uri/ /index.php?$query_string;%g' default && \
	sed -i 's%#location ~ \\%location ~ \\%g' default && \
	sed -i 's%#	include snippets/fastcgi-php.conf;%	include snippets/fastcgi-php.conf;%g' default && \
	sed -i 's%#	fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;%	fastcgi_pass unix:/run/php/php8.0-fpm.sock;%g' default && \
	sed -i 's/#	fastcgi_pass 127.0.0.1:9000;/}/g' default

#7. php 설정 파일 변경
WORKDIR /etc/php/8.0/fpm
RUN	sed -i 's%;date.timezone =%date.timezone = Asia/Seoul%g' php.ini && \
	sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g' php.ini && \
	sed -i 's/session.cookie_httponly =/session.cookie_httponly = 1/g' php.ini && \
	sed -i 's/;session.cookie_secure =/session.cookie_secure = 1/g' php.ini && \
	sed -i 's/memory_limit = 128M/memory_limit = 256M/g' php.ini && \
	sed -i 's/post_max_size = 8M/post_max_size = 56M/g' php.ini && \
	sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 1024M/g' php.ini && \
	sed -i 's/max_file_uploads = 20/max_file_uploads = 50/g' php.ini && \
	sed -i 's/;opcache.memory_consumption=128/opcache.memory_consumption=128/g' php.ini && \
	sed -i 's/;opcache.interned_strings_buffer=8/opcache.interned_strings_buffer=8/g' php.ini && \
	sed -i 's/;opcache.max_accelerated_files=10000/opcache.max_accelerated_files=50000/g' php.ini && \
	sed -i 's/;opcache.revalidate_freq=2/opcache.revalidate_freq=60/g' php.ini && \
	sed -i 's/;opcache.enable_cli=0/opcache.enable_cli=1/g' php.ini && \
	sed -i 's/;opcache.enable=1/opcache.enable=1 opcache.jit=tracing opcache.jit_buffer_size=100M/g' php.ini

제가 가장 힘들었던 부분은 내부에서 설치한 프로그램을 실행시키는 부분이었습니다. 이는 CMD 또는 ENTRYPOINT 명령어를 이용하여 진행할 수 있습니다. 단 주의해야 할 점은 두 명령어는 Dockerfile 내에서 한 번만 실행되기 때문에 두 개 이상을 진행하게 된다면 이전 명령은 무시됩니다. 따라서 실행하고자 하는 명령어들을 && 를 이용하여 한번에 실행시켜주거나, 별도의 쉘 스크립트 파일을 만든 후 쉘 스크립트 파일 내에 여러 명령을 진행하고 CMD 또는 ENTRYPOINT를 이용해서 쉘 스크립트 파일을 실행하는 방식으로 진행해야 합니다. 이 때 후자의 경우 쉘 스크립트 파일을 COPY 후 chmod 명령을 통해 실행 권한을 부여해주어야 합니다.

또한 nginx의 경우 nginx 서버를 foreground로 돌리지 않으면 컨테이너를 background로 실행해도 컨테이너 안의 서버가 실행이 안된 상태이기 때문에 정상적으로 작동되지 않습니다. 따라서 daemon off로 foreground로 계속 실행 중인 상황으로 만들어야 정상적으로 동작합니다.

CMD와 ENTRYPOINT는 약간의 차이가 있습니다. ENTRYPOINT의 경우 지정된 명령을 수행하는 반면 CMD의 경우 컨테이너를 실행 시 넘겨주는 값이 존재할 경우 해당 값으로 대체하여 실행됩니다. 따라서 컨테이너가 수행될 때 변경되지 않을 실행 명령은 CMD 보다는 ENTRYPOINT 로 정의하는게 좋으며, 변경 가능성이 있는 default 값들은 CMD로 정의해주는게 좋습니다. 현재 Dockerfile에서는 nginx와 php 실행 시 사용되기 때문에 변동사항이 없어 ENTRYPOINT로 진행했습니다.
출처 : https://bluese05.tistory.com/77

# 내부에 설치한 모듈은 설정 파일을 직접 실행시켜야 정상적으로 동작
# CMD, ENTRYPOINT의 경우 Dockerfile 내에서 단 한번만 실행
# nginx 서버를 foreground로 돌리지 않으면 컨테이너를 background로 실행해도 컨테이너 안의 서버가 실행이 안된 상태이기 때문에 daemon off로 foreground로 계속 실행 중인 상황으로 만들기
ENTRYPOINT service php8.0-fpm start && nginx -g "daemon off;"

다음은 전체 Dockerfile입니다.

# prod 서버 배포
# 도커의 경우 명령어마다 레이어가 만들어지기 때문에 명령어 수를 줄이는게 중요

# 이미지 세팅
# FROM을 여러 개 실행하면 각각의 FROM 이전에 실행된 명령은 초기화
FROM ubuntu:20.04
MAINTAINER Gale <cmg4739@gmail.com>

# 환경 변수 세팅
# docker build -t toda:test --build-arg password=password .
ARG password branch
ENV PASSWORD=$password BRANCH=$branch

#1. 기본 세팅
RUN apt-get update && \

#2. PPA 등록
# add-apt-repository를 하려면 아래 설치 필요
apt install software-properties-common -y && \
add-apt-repository ppa:ondrej/php -y && \

#3. nginx 설치
apt-get install nginx -y && \

#4. PHP 설치
apt-get install 	php8.0-common \
			php8.0-cli \
			php8.0-fpm \
			php8.0-cgi \
			php8.0-curl \
			php8.0-mysql \
			php8.0-opcache \
			php8.0-redis -y

#5. git clone
WORKDIR /var/www
RUN apt-get install git -y && \
git clone -b $BRANCH --single-branch https://Choimingil:$PASSWORD@github.com/Choimingil/TODA_db.git $BRANCH

# sed -i 's(구문자)(바꿀 단어)(구분자)(대체단어)(구분자)g' 파일명
WORKDIR /etc/nginx/sites-available
RUN	sed -i 's%root /var/www/html;%root /var/www/'$BRANCH';%g' default && \
	sed -i 's%server_name _;%server_name localhost;%g' default && \
	sed -i 's/index index.html/index index.php index.html/g' default && \
	sed -i 's%try_files $uri $uri/ =404;%try_files $uri $uri/ /index.php?$query_string;%g' default && \
	sed -i 's%#location ~ \\%location ~ \\%g' default && \
	sed -i 's%#	include snippets/fastcgi-php.conf;%	include snippets/fastcgi-php.conf;%g' default && \
	sed -i 's%#	fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;%	fastcgi_pass unix:/run/php/php8.0-fpm.sock;%g' default && \
	sed -i 's/#	fastcgi_pass 127.0.0.1:9000;/}/g' default

#7. php 설정 파일 변경
WORKDIR /etc/php/8.0/fpm
RUN	sed -i 's%;date.timezone =%date.timezone = Asia/Seoul%g' php.ini && \
	sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g' php.ini && \
	sed -i 's/session.cookie_httponly =/session.cookie_httponly = 1/g' php.ini && \
	sed -i 's/;session.cookie_secure =/session.cookie_secure = 1/g' php.ini && \
	sed -i 's/memory_limit = 128M/memory_limit = 256M/g' php.ini && \
	sed -i 's/post_max_size = 8M/post_max_size = 56M/g' php.ini && \
	sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 1024M/g' php.ini && \
	sed -i 's/max_file_uploads = 20/max_file_uploads = 50/g' php.ini && \
	sed -i 's/;opcache.memory_consumption=128/opcache.memory_consumption=128/g' php.ini && \
	sed -i 's/;opcache.interned_strings_buffer=8/opcache.interned_strings_buffer=8/g' php.ini && \
	sed -i 's/;opcache.max_accelerated_files=10000/opcache.max_accelerated_files=50000/g' php.ini && \
	sed -i 's/;opcache.revalidate_freq=2/opcache.revalidate_freq=60/g' php.ini && \
	sed -i 's/;opcache.enable_cli=0/opcache.enable_cli=1/g' php.ini && \
	sed -i 's/;opcache.enable=1/opcache.enable=1 opcache.jit=tracing opcache.jit_buffer_size=100M/g' php.ini
    
# 내부에 설치한 모듈은 설정 파일을 직접 실행시켜야 정상적으로 동작
# CMD, ENTRYPOINT의 경우 Dockerfile 내에서 단 한번만 실행
# nginx 서버를 foreground로 돌리지 않으면 컨테이너를 background로 실행해도 컨테이너 안의 서버가 실행이 안된 상태이기 때문에 daemon off로 foreground로 계속 실행 중인 상황으로 만들기
ENTRYPOINT service php8.0-fpm start && nginx -g "daemon off;"

아래 사진은 Dockerfile을 빌드한 후 성공적으로 이미지가 생성된 것을 보여줍니다.

생성된 이미지를 컨테이너로 실행시키고 그 결과를 캡쳐한 이미지입니다.

profile
저는 상황에 맞는 최적의 솔루션을 깊고 정확한 개념의 이해를 통한 다양한 방식으로 해결해오면서 지난 3년 동안 신규 서비스를 20만 회원 서비스로 성장시킨 Software Developer 최민길입니다.

0개의 댓글