ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Infra] Dockerfile & Docker-Compose 작성
    프로젝트/뉴스타 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 옵션을 통해 컨테이너가 정상적으로 운영되고 있는 지 확인할 수 있을 것이다.
Designed by Tistory.