프로젝트/뉴스타
[Infra] Dockerfile & Docker-Compose 작성
cks._.hong
2024. 6. 11. 08:56
Dockerfile & Docker-Compose 작성 왜 궁금했을까❓
뉴스타 서비스의 경우 Docker를 이용하여 어플리케이션을 띄우기로 했다. 그래서 Dockerfile과 Docker-compose 작성법에 대해서 알아보고 뉴스타 서비스에 적용하는 과정까지 살펴보도록 하겠다.
1. Dockerfile 문법
Dockerfile reference
Find all the available commands you can use in a Dockerfile and learn how to use them, including COPY, ARG, ENTRYPOINT, and more.
docs.docker.com
명령 | 설명 |
FROM | 베이스 이미지 설정 |
LABEL | 이미지의 Metadata 설정 |
CMD | Docker Container를 생성할 때, 실행하는 쉘 명령 (docker run) |
RUN | 이미지를 생성할 때, 실행하는 쉘 명령 |
ENTRYPOINT | Docker Container가 시작할 때, 실행하는 쉘 명령 (docker start) |
EXPOSE | Docker Container 외부에 오픈할 Port 설정 |
ENV | Docker Container 내부에서 사용할 환경 변수 설정 |
WORKDIR | Docker Container 내부에서 작업 디렉토리 설정 |
COPY | 파일, 디렉토리를 Docker Container에 복사 |
ADD | 파일, 디렉토리, URL를 Docker Container에 복사 및 압축 해제 |
SHELL | Docker Container 기본 Shell 설정 |
ARG | Dockerfile내에서 변수 설정 |
USER | Docker Container 내부에서 작업을 하는 사용자 ID 설정 |
ONBUILD | 생성한 이미지를 기반으로 새로운 이미지를 생성할 때 명령어를 설정 |
VOLUME | 이미지를 위한 볼륨 생성 |
MAINTAINER | 이미지를 생성한 개발자 정보 설정 |
STOPSIGNAL | Docker Container를 STOP 할 때 Signal을 설정 |
HEALTHCHECK | Docker Container의 프로세스 상태를 체크 |
2. Docker Compose 주요 문법
명령 | 설명 |
version | Docker Compose 파일의 버전 설정 |
services | Docker Container의 그룹을 설정 |
image | 사용할 Docker Image의 이름 설정 |
build | 이미지를 빌드할 Dockerfile의 경로를 설정 |
ports | 호스트와 Container 간의 네트워크 포트 매핑을 설정 |
env_file | Docker Container에서 사용할 환경 변수를 포함하는 파일의 경로를 설정 |
environment | Docker Container에서 사용할 환경 변수를 설정 |
depends_on | 해당 서비스가 시작하기 전에 먼저 시작해야 하는 서비스를 설정 |
command | Docker Container가 시작할 때, 실행할 명령을 설정 |
links | 해당 서비스와 연결할 다른 서비스를 설정 |
networks | 서비스 간의 네트워킹을 설정 |
volumes | Docker Container의 데이터를 유지하기 위해 설정 |
3. Dockerfile / Docker compose 실습
3.1. Spring Boot
# 베이스 이미지 설정
FROM openjdk:17 AS builder
# 워킹 디렉토리 설정
WORKDIR /usr/src/app
# 빌드 파일 복사
COPY build.gradle gradlew settings.gradle gradle src .
# 실행 권한 부여
RUN chmod +x gradlew
# 프로젝트 빌드
RUN ./gradlew clean bootJar
# 빌드 파일 변수 설정
ARG JAR_FILE=build/libs/*.jar
# 빌드 파일 복사
COPY ${JAR_FILE} app.jar
# 타임존 설정
RUN apk add tzdata && ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
RUN echo Asia/Seoul > /etc/timezone
# jar 파일 실행
ENTRYPOINT ["java","-jar", "-Dspring.profiles.active=prod", "app.jar"]
- gradle을 따로 설치하지 않고 빌드 파일들을 복사했다. 또한, Docker Container의 시간을 맞추기 위해 타임존을 설정했다.
docker build -t newstar_back .
- newstar_back 이름을 가진 이미지로 빌드 수행
3.2. FastAPI
# 베이스 이미지 설정
FROM python:3.9.13
# 워킹 디렉토리 설정
WORKDIR /app/
# 코드 및 의존성 등 프로젝트 파일 복사
COPY . .
# 패키지 업데이트
RUN pip install --upgrade pip
# 패키지 설치
RUN pip install -r requirements.txt
# 타임존 설정
RUN ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
RUN echo Asia/Seoul > /etc/timezone
# 환경 변수 production 모드 설정
ENV APP_ENV=prod
# unicorn을 통해 프로젝트 실행
CMD uvicorn --host=0.0.0.0 --port 8000 app.main:app
- ENV 명령어를 이용하여 APP_ENV에 prod 값을 부여하고 있다. 뉴스타 프로젝트는 dev, prod, create 등으로 환경을 분리해서 관리하고 있기 때문이다.
docker build -t fastapi_back .
- fastapi_back 이름을 가진 이미지로 빌드 수행
3.3. React
# 베이스 이미지 설정
FROM node:alpine AS builder
# 워킹 디렉토리 설정
WORKDIR /usr/src/app
# 의존성 목록 복사
COPY package.json .
# 의존성 설치
RUN npm install --force
# 프로젝트 파일 복사
COPY . .
# 프로젝트 빌드
RUN npm run build
# 베이스 이미지 다시 설정
FROM nginx:latest
# nginx 설정 파일 복사
COPY ./default.conf /etc/nginx/conf.d/default.conf
# React Build 파일 nginx 정적 파일 반환 경로에 복사
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
# Nginx 실행
CMD [ "nginx", "-g", "daemon off;"]
# default.conf
server {
listen 3000;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
- React의 경우 Node 이미지에서 프로젝트 파일을 빌드하고 있다.
- Nginx 베이스 이미지를 가져와서 커스텀 설정을 적용하고 React에서 빌드해서 나온 결과물을 가져와서 / 경로의 반환 파일 경로에 복사했다.
- 뉴스타 서비스는 Nginx를 Reverse Proxy로도 사용하고 WebServer로도 활용을 했다. 그러면 가장 외부와 가까운 Nginx에서 정적 파일들을 반환하면 될 것인데 왜 이렇게 역할을 나눈 것인지 궁금할 수도 있다.
- 가장 앞단에 존재하는 Nginx가 forwarding 업무를 수행하고 정적 파일도 반환하고 추후에는 로드 밸러싱까지 담당할 것이라 부하가 많아질 것이라 판단해서 2개로 나눠서 운영을 하기로 결정했다.
docker build -t newstar_front .
- newstar_front 이름을 가진 이미지로 빌드 수행
3.4. Jenkins
FROM jenkins/jenkins:lts
USER root
RUN apt-get update && \
apt-get -y install apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common && \
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey && \
add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
$(lsb_release -cs) \
stable" && \
apt-get update && \
apt-get -y install docker-ce
RUN groupadd -f docker
RUN usermod -aG docker jenkins
- Jenkins의 경우 Docker out of Docker를 사용하기 때문에 외부 도커 서버와의 연결을 위해 docker-ce를 설치한다.
docker build -t jenkins/custom .
- jenkins/custom 이름을 가진 이미지로 빌드 수행
3.5. Docker compose
docker network create app-net
- 추후, Docker compose 간의 통신이 요구되는 무중단 서비스를 계획 중에 있어서 네트워크를 만들었다.
version: '3'
services:
spring:
container_name: spring
image: newstar_back
restart: always
expose:
- 8080
env_file:
- /env/.env
environment:
TZ: Asia/Seoul
react:
container_name: react
expose:
- 3000
image: newstar_front
fastapi:
container_name: fastapi
image: fastapi_back
restart: always
expose:
- 8000
env_file:
- /env/.env
environment:
TZ: Asia/Seoul
nginx:
container_name: nginx
image: nginx:latest
restart: always
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./nginx/service-url.inc:/etc/nginx/conf.d/service-url.inc
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
ports:
- 80:80
- 443:443
command: '/bin/sh -c ''while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'''
mysql:
container_name: mysql
image: mysql:8.0
restart: always
expose:
- 3306
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
TZ: Asia/Seoul
LC_ALL: C.UTF-8
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
volumes:
- /mysql/:/var/lib/mysql
- ./config/my.cnf:/etc/mysql/conf.d/my.cnf
redis:
container_name: redis
image: redis:latest
restart: always
expose:
- 6397
environment:
TZ: Asia/Seoul
labels:
- "name=redis"
- "mode=standalone"
jenkins:
container_name: jenkins
image: jenkins/custom
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /jenkins:/var/jenkins_home
- /env/.env:/env/.env
ports:
- 9999:8080
elastic:
container_name: elastic
image: docker.elastic.co/elasticsearch/elasticsearch:7.10.0
restart: always
ports:
- 9200:9200
environment:
- discovery.type=single-node
kibana:
container_name: kibana
image: docker.elastic.co/kibana/kibana:7.10.1
restart: always
ports:
- 5601:5601
environment:
ELASTICSEARCH_URL: http://elastic:9200
ELASTICSEARCH_HOSTS: http://elastic:9200
privileged: true
certbot:
container_name: certbot
image: certbot/certbot
restart: unless-stopped
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
networks:
default:
name: app-net
external: true
- 각 이미지에 맞는 설정을 입력하고 네트워크는 커스텀 네트워크로 설정을 해주었다.
docker compose -f docker-compose.yaml up -d
docker ps -a
- 위 명령어를 통해 Docker compose 파일이 실행되고 ps -a 옵션을 통해 컨테이너가 정상적으로 운영되고 있는 지 확인할 수 있을 것이다.