언젠가는 부딪혀야할 docker 와 AWS.. 죽여줘
뭐든지 처음이 어렵다고 도커를 이용한 배포는 처음이라 죽을맛이였따
환경
프록시 서버로 nginx를 이용하면 대략 다음과 같은 구조가 나온다.
- Dockerfile 작성
이 방법에선 프론트는 빌드한 코드를 직접 nginx 컨테이너에 마운트 해줄거기 때문에 도커이미지를 생성할 필요가 없다.
일단 도커이미지를 생성하기 위해 서버 루트경로에 Dockerfile을 생성한다.
FROM node:16-buster
LABEL authors="daechanjo"
WORKDIR /app
COPY package*.json ./
COPY . .
RUN npm install
EXPOSE 5001
CMD ["yarn", "start"]
npm install
을 실행하게 된다.요약하면 node.js 환경을 설정하고 응용 프로그램의 종속성을 설치하며 해당 컨에티너 내에서 포트5001을 노출하고 컨테이너가 실행될 때 응용 프로그램을 시작하는 명령어라고 볼 수 있다.
이제 해당 파일을 이미지로 빌드하기만 하면 되는데 여기에서 첫 번째 문제가 발생했다.
- 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에서 다시 받아오면 이미지 준비과정은 끝이난다.
docker push 계정/이미지이름:태그
docker pull 계정/이미지이름:태그
환경변수 및 nginx.conf
docker-compose 를 설정하기 전 서버에서 사용할 환경변수와 nginx.conf 를 설정해주자. 서버와 nginx는 컨테이너로 띄울거기 때문에 외부 작업 디렉토리 경로에서 작성후 후에 마운트해주면 된다
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
이제 거의 다왔다...
- 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
최상단의 버전도 꼭 기입해줘야 한다. 없으면 에러
중복항목은 생략하면서 설명하면 다음과 같다.
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으로 옮길까
vm 작업 디렉터로에 빌드파일을 저장할 경로를 생성해준다
권환설정을 꼭 해줘야한다. 안그러면 외부에서 파일을 전송해줄 때 거절당한다
sudo mkdir -p /front/build && sudo chown $USER:$USER /front/build
이제 로컬에서 빌드한 프론트파일을 해당 경로로 전송해주면 된다
scp -r ./build/* 계정@IP:/front/build