docker-compose 사용기

DaeChan Jo·2023년 10월 21일
0

데일리삽질

목록 보기
4/8

언젠가는 부딪혀야할 docker 와 AWS.. 죽여줘
뭐든지 처음이 어렵다고 도커를 이용한 배포는 처음이라 죽을맛이였따

환경

  • 개발 환경 : Apple M1 Pro (macOS Sonoma 14.1)
  • Server : express.ts + MySql (prisma)
  • VM : Ubuntu 20.04.6 LTS

프록시 서버로 nginx를 이용하면 대략 다음과 같은 구조가 나온다.

  1. Dockerfile 작성

이 방법에선 프론트는 빌드한 코드를 직접 nginx 컨테이너에 마운트 해줄거기 때문에 도커이미지를 생성할 필요가 없다.

일단 도커이미지를 생성하기 위해 서버 루트경로에 Dockerfile을 생성한다.

FROM node:16-buster

LABEL authors="daechanjo"

WORKDIR /app

COPY package*.json ./
COPY . .

RUN npm install

EXPOSE 5001

CMD ["yarn", "start"]
  • FROM : docker 이미지를 생성할 때 기본 이미지(바탕)을 지정한다. 여기에선 debian linux 배포의 buster 버전을 기반으로 한 16버전을 사용했다.
  • LABEL : 메타데이터
  • WORKDIR : 컨테이너 내에서 작업 디렉토리를 '/app' 으로 설정한다. 이후의 모든 명령은 해당 디렉토리에서 실행된다
  • COPY : 호스트에서 해당 파일을 WORKDIR 로 지정해준 경로에 복사한다. 이 때 docker의 레이어 캐싱 메커니즘이 활용되는데, 패키지 파일이 복사될 때 해당 단계를 캐시하고 패키지 파일이 변경되지 않는다면 이미지를 빌드할 때마다 Node.js 종속성을 다시 설치할 필요가 없다.
  • RUN : 이 단계에서 컨테이너 내에서 npm install 을 실행하게 된다.
  • EXPOSE : 컨테이너 내에서 어떤 포트를 노출시킬건지 정하는 명령어이다. 실제로 포트를 호스트 시스템에 공개하진 않는다.
  • CMD : 해당 이미지를 기반으로 컨테이너가 시작될 때 실행할 명령어를 지정한다. 후에 docker-compose.yml 에서 추가적으로 설정할거라 생략해도 되지만 충돌이 일어난다거나 그런건 아니라서 나중을 위해(개별로 테스트한다던지..) 미리 작성해두자.

요약하면 node.js 환경을 설정하고 응용 프로그램의 종속성을 설치하며 해당 컨에티너 내에서 포트5001을 노출하고 컨테이너가 실행될 때 응용 프로그램을 시작하는 명령어라고 볼 수 있다.

이제 해당 파일을 이미지로 빌드하기만 하면 되는데 여기에서 첫 번째 문제가 발생했다.

  1. docker build (M1 너란 녀석...)

빌드를 마치고 해당 이미지를 도커허브에 올린 뒤, VM에서 이미지를 불러와 실행시켰는데, 컨테이너가 정상적으로 실행되었다가 바로 종료되어버렸는데 이유는 m1(arm64) 에서 생성한 도커이미지가 vm(amd64)에 호환이 안되는 이슈였다.

해결법을 찾아보니 docker 이미지를 빌드할 때 멀티플랫폼을 지원해주기에 해당 기능을 이용하기로 했다. (해당 빌드과정에서 멀티플랫폼 또한 지원해주지 않는 이상한 에러에 막혔는데 어차피 amd64로만 빌드되면 장땡?이기에 멀티플랫폼을 빠르게 손절했다..)
명령어 마지막에 . 을 놓치지 말자

docker buildx build --platform=linux/amd64 --load --tag 계정/이미지이름:태그 .

 => [internal] load .dockerignore                                                                                                                                                                                    0.0s
 => => transferring context: 148B                                                                                                                                                                                    0.0s
 => [internal] load build definition from Dockerfile                                                                                                                                                                 0.0s
 => => transferring dockerfile: 184B                                                                                                                                                                                 0.0s
 => [internal] load metadata for docker.io/library/node:16-buster                                                                                                                                                    1.8s
 => [auth] library/node:pull token for registry-1.docker.io                                                                                                                                                          0.0s
 => [1/5] FROM docker.io/library/node:16-buster@sha256:f77a1aef2da8d83e45ec990f45df50f1a286c5fe8bbfb8c6e4246c6389705c0b                                                                                              0.0s
 => [internal] load build context                                                                                                                                                                                    0.0s
 => => transferring context: 19.20kB                                                                                                                                                                                 0.0s
 => CACHED [2/5] WORKDIR /app                                                                                                                                                                                        0.0s
 => CACHED [3/5] COPY package*.json ./                                                                                                                                                                               0.0s
 => [4/5] COPY . .                                                                                                                                                                                                   0.2s
 => [5/5] RUN npm install                                                                                                                                                                                           36.5s
 => exporting to image                                                                                                                                                                                               2.7s
 => => exporting layers                                                                                                                                                                                              2.7s
 => => writing image sha256:39414574afead4a824ab5bc7dd963d4561a4a3005d0a11f86a11802564e4945c                                                                                                                         0.0s 
 => => naming to docker.io/daechanjo/wordy:0.1.0 

그러면 Dockerfile에 작성한 명령어 시퀀스를 관람할 수 있다.
앞서 말한 레이어 캐싱 메커니즘도 확인해볼 수 있다.

이제 생성한 이미지를 docker 허브에 올려주고, vm에서 다시 받아오면 이미지 준비과정은 끝이난다.

  • local : docker push 계정/이미지이름:태그
  • vm : docker pull 계정/이미지이름:태그

환경변수 및 nginx.conf

docker-compose 를 설정하기 전 서버에서 사용할 환경변수와 nginx.conf 를 설정해주자. 서버와 nginx는 컨테이너로 띄울거기 때문에 외부 작업 디렉토리 경로에서 작성후 후에 마운트해주면 된다

nginx.conf

worker_processes auto;

events {
    worker_connections 1024;
}

http {
    server {
        listen 80;
        server_name localhost;

        location / {
            root /usr/share/nginx/html;
            try_files $uri $uri/ /index.html;
        }

        location /api/ {
            proxy_pass http://서버컨테이너이름:포트;    <- 알맞게수정
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
         }
    }
}

이 설정은 nginx를 리버스 프록시 서버로 설정하여 HTTP 요청을 처리하고 요청 경로에 따라 정적 html파일로 갈지, 서버로 요청을 보낼지 결정해준다. 하나하나 뜯고 맛보고 즐겨보자

  • worker_processes auto
    이 라인은 nginx가 들어오는 요청을 처리하는 데 사용할 워커 프로세스의 수를 지정한다. "auto" 옵션은 nginx에게 CPU 코어 수를 기반으로 워커 프로세스의 수를 자동으로 결정하도록 지시한다.

  • events
    이벤트 처리와 관련된 설정을 정의한다. 각 워커 프로세스가 동시에 처리할 수 있는 최대 연결 수를 1024로 설정

  • http
    서버 구성

  • server
    이 블록은 nginx가 포트 80(HTTP)에서 대기하는 가상 서버 구성을 정의한다. "localhost"라는 서버 이름과 함께 HTTP 요청을 처리하는 기본 서버 블록이 된다.

  • location
    nginx가 루트 경로 "/"와 "/api" 에 일치하는 요청을 처리하는 방법을 정의한다. 즉, 각 요청의 경로에 따라 프론트로 보낼지, 백엔드로 보낼지 결정한다고 볼 수 있다.


그리고 서버에서 사용할 환경변수를 .env을 생성해 작성해주면 된다. 이 때 주의할 점은 리눅스환경에서 따옴표와 쌍따옴표를 잘못사용하면 하나의 값으로 처리되기때문에 생략해주어야 한다. 간혹 이퀄이 포함된 특수문자는 불가피하게 따옴표를 써줘야하는데 그럴 땐 `` 로 감싸주면 된다.

ex)
SERVER_URL=http://00.00.000.000:8080

이제 거의 다왔다...



  1. docker-compose.yml
version: '3'
services:
  db:
    image: mysql
    container_name: db-container
    env_file: .env
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
    ports:
      - "5000:3306"
  nginx:
    image: nginx
    container_name: nginx-container
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./front/build:/usr/share/nginx/html
    ports:
      - "80:80"
    depends_on:
      - server
  server:
    image: daechanjo/wordy:0.1.0
    container_name: server-container
    ports:
      - "5001:5001"
    env_file: .env
    environment:
      DATABASE_URL: ${DATABASE_URL}
      SERVER_URL: ${SERVER_URL}
      SERVER_PORT : ${SERVER_PORT}
      JWT_SECRET_KEY: ${JWT_SECRET_KEY}
      JWT_TOKEN_EXPIRES: ${JWT_TOKEN_EXPIRES}
      SESSION_SECRET_KEY: ${SESSION_SECRET_KEY}

      GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
      GOOGLE_SECRET_KEY: ${GOOGLE_SECRET_KEY}
    command : ["sh", "-c", "npx prisma migrate deploy && yarn start"]
     
     depends_on :
       db :
         condition : service_healthy

최상단의 버전도 꼭 기입해줘야 한다. 없으면 에러
중복항목은 생략하면서 설명하면 다음과 같다.

  • db 서비스
    image: pull 받아온 mysql 이미지
    container_name: 해당 서비스를 띄울 컨테이너 이름
    env_file: .env 파일을 사용하여 환경 변수를 로드하며, 해당 환경 변수로 MySQL 루트 비밀번호 및 데이터베이스 이름을 설정
    ports: 호스트의 포트 5000을 컨테이너 내부의 MySQL의 포트 3306과 연결
  • nginx 서비스
    volumes: 호스트의 nginx.conf 파일을 컨테이너 내부의 /etc/nginx/nginx.conf로 볼륨 마운트 한다.
    호스트의 front/build 디렉터리를 컨테이너 내부의 /usr/share/nginx/html 디렉터리로 볼륨 마운트하여 정적 웹 페이지를 제공한다.
    depends_on : nginx 서비스는 server 서비스에 의존하며, server 서비스가 실행되고 난 후 nginx 서비스를 실행시키도록 한다.

  • server 서비스:
    command: npx prisma migrate deploy && yarn start 명령을 실행하여 Prisma 마이그레이션을 배포하고 애플리케이션을 시작한다.
    server 서비스는 db 서비스에 의존하며, db 서비스가 정상 상태일 때 서버 컨테이너를 실행시킨다.


이렇게 길고 험난했던 설정이 끝이나면 하...
명령어 한줄로 지정한 서비스들의 컨테이너를 한 번에 띄우고 관리할 수 있게 된다. 여기서 -d 옵션은 백그라운드 옵션!

docker-compose up -d
Creating network "elice_default" with the default driver
Creating db-container ... done
Creating server-container ... done
Creating nginx-container  ... done

컨테이너가 정상적으로 생성되었다고 에러없이 실행중일거란 보장은 없다. docker ps로 실행중인 컨테이너의 상태를 확인하자..


컨테이너를 한번에 띄웠으니, 한번에 내리는것도 가능하다 짱편해

docker-compose down
Stopping nginx-container  ... done
Stopping server-container ... done
Stopping db-container     ... done
Removing nginx-container  ... done
Removing server-container ... done
Removing db-container     ... done
Removing network ..._default

다시 배포를 하고 브라우저로 접속되면 성공이다.
단 nginx를 프록시서버로 사용하고 있기 때문에 모든 통신은 nginx를 거친다는 점을 기억하자.




추가

빌드한 프론트 파일을 어떻게 vm으로 옮길까

  1. vm 작업 디렉터로에 빌드파일을 저장할 경로를 생성해준다
    권환설정을 꼭 해줘야한다. 안그러면 외부에서 파일을 전송해줄 때 거절당한다
    sudo mkdir -p /front/build && sudo chown $USER:$USER /front/build

  2. 이제 로컬에서 빌드한 프론트파일을 해당 경로로 전송해주면 된다
    scp -r ./build/* 계정@IP:/front/build

profile
BackEnd Developer

0개의 댓글