이번 장에서는 docker를 통해 서비스를 구현해봅시다.
Docker compose를 사용하여 컨테이너화 시킬겁니다. docker compose란, 여러 개의 컨테이너로부터 이루어진 서비스를 구축, 실행하는 순서를 자동으로 하여, 관리를 간단히하는 기능이다.
컨테이너에는 nestjs와 postgre를 사용하겠습니다. 시작해봅시다.
https://engineer-mole.tistory.com/221
자 순서대로 해봅시다. nestjs 도커이미지를 만들어야합니다.
npm install -g @nestjs/cli
nest new nestjs-dockerized
cli로 명령을 실행하기 위해 npm install로 cli를 설치해주고 nestjs-dockerized 앱을 만들어줍시다.(npm으로 설치하면 됩니다.!)
설치를 마치고 나면 아래 사진처럼 파일들을 확인할 수 있습니다.
src에는 파일들이 돌아갈 로직들이 담겨있습니다. package.json을 보면 실행방법이나, 디버그 방법들이 정리 되어있습니다. nestjs파일 구조는 이전 글을 보면 알수 있습니다.
이렇게 설치된 경로로 이동해 아래 명령을 입력하면 서버를 실행시킬수 있습니다.
npm run start:dev
크롬으로 이렇게 3000번 포트에 접속하면 접속이 된다.
이제 Dockerfile을 만들고 nestjs가 동작하는데 필요한 툴들을 자동화해보자.
설치한 nestjs-dockerized폴덩안에 Dockerfile을 만들었다.
주석 처리해두었다!
FROM node:12.13-alpine As development
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=development
COPY . .
RUN npm run build
FROM node:12.13-alpine as production
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY . .
COPY --from=development /usr/src/app/dist ./dist
EXPOSE 8080
CMD ["node", "dist/main"]
먼저 공개 저장소 에서 사용할 수 있는 공식 Node.js 이미지를 사용하도록 Docker에 지시합니다 .
Node의 12.13 버전을 지정하고 Alpine 이미지를 선택합니다.
알파인이 가볍고 속도가 빨라 주로 컨테이너화 할때 많이 쓰이는데 원하는 os가 있다면 다른거 사용해도 됩니다. (명령어도 다르기때문에 사용자가 많은 Alpine권장!)
다단계 빌드 기능을 사용하고 있으므로 AS 문을 사용하여 development 라는 이미지 이름도 지정합니다
도커가 alpine os 를 하고나면 그 후 명령들이 실행될 경로입니다.
설정 후 WORKDIRDocker가 실행하는 각 명령( RUN문에 정의됨)은 지정된 컨텍스트에서 실행됩니다.
COPY package*.json ./
RUN npm install --only=development
COPY . .
마지막으로 앱이 폴더에 빌드되었는지 확인합니다. 우리 애플리케이션은 TypeScript 및 기타 빌드 타임 종속성을 사용하므로 개발이미지내부의 /dist folder에서 이 명령을 실행해야 합니다.
package.json을 먼저 복사하는 이유는 npm install 후에 앱에 필요한 종속성을 다운받아 주는데 이 때 이렇게 다운 받을때 먼저 pacakage.json을 보고 그곳에 명시된 종속성들을 다운받아서 설치해줍니다. 하지만 package.json 컨테이너 안에 없기에 찾을 수 없다는 에러가 발생하게 됩니다.
그리고 development를 설치해줍니다. development는 라이브러리 같은겁니다.
하나의 이미지를 더 만듭니다.
이 명령문을 다시 사용하여 FROMDocker에 이전 이미지에 대한 연결 없이 새로운 이미지를 생성해야 한다고 지시합니다.
이번에는 이름을 지정합니다 production.
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
ARG 명령문은 docker build 커맨드로 이미지를 빌드 시, --build-arg 옵션을 통해 넘길 수 있는 인자를 정의하기 위해 사용합니다.
ENV와 달리 ARG로 설정한 값은 이미지가 빌드되는 동안에만 유효하오니 주의 바랍니다.
ENV로 이미지 빌드 이외에도 설정해두기 위해 정의합니다.
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY . .
위에 했던거랑 비슷하지만, 이번에는 production을 설치했습니다.
다른 패키지는 설치하지 않았습니다.
여기 에서 이미지에서 빌드된 폴더를 복사합니다. 이렇게 하면 최종 이미지에 설치 되지 않은 디렉토리 만 가져옵니다.
production만 설치했기때문에 distdevelopment의 폴더만 복사합니다.(/distdevelopment/distdevDependencies)
도커 컨테이너가 사용할 포트를 정합니다.
여기에서 이미지가 실행될 때 실행할 기본 명령을 정의합니다.
다단계 빌드 기능 덕분에 이미지 development의 불필요한 부분을 제외하고 development와 production을 설치했습니다.
자이제 도커 이미지를 만들어 봅시다. docker 파일이 있는 경로에서
아래 명령어를 실행하세요!
docker build -t app-name .
app-name은 임의로 하고싶은대로 하시면 됩니다.
그후 실행합니다. 저는 8090포트로 도커 포트를 열어줬고, 컨테이너 내부에서 3000번으로 연결해주었습니다.
docker run -p 8090:3000 app-name
Docker-compose란? 방금까지 nestjs로된 서버를 컨테이너화 진행하였습니다. 하지만, nestjs만 실행한다고 하나의 서비스가 구현되지는 않습니다. 우리는 nestjs를 확장시켜줄 postgre와 Redis를 실행해 봅시다.
아래 코드를 살펴보자.
version: '3.7'
services:
main:
container_name: main
build:
context: .
target: development
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules
ports:
- ${SERVER_PORT}:${SERVER_PORT}
- 9229:9229
command: npm run start:dev
env_file:
- .env
networks:
- webnet
depends_on:
- redis
- postgres
redis:
container_name: redis
image: redis:5
networks:
- webnet
postgres:
container_name: postgres
image: postgres:12
networks:
- webnet
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
PG_DATA: /var/lib/postgresql/data
ports:
- 5432:5432
volumes:
- pgdata:/var/lib/postgresql/data
networks:
webnet:
volumes:
pgdata:
docker-compose 버전 3.7을 사용하도록 지정합니다. 다단계 빌드를 지원하기 때문에 이 버전을 사용합니다.
그런 다음 세 가지 서비스를 정의합니다. main, redis, postgres.
제가 아직 완전히 자리잡지 않은개념이라 일단 글을 작성하고 추후 가다듬는식으로 설명하겠습니다. 아래 설명은 아직 최종본이아닙니다. 틀린정보가 있을수 있으니 가볍게 읽어주시기 바랍니다!
main블락부터 만듭니다. main은 우리가 기본적으로 사용할 서버입니다.
그후 해당 컨테이너를 이름으로 편하게 쓰기위해 container_namemaindocker-compose 는 다양한 docker-compose 명령으로 이름을 지어줍니다.
build:
context: .
target: development
main블락을 빌드할때 필요한 설정입니다.
Docker데몬에서 빌드를 구성할 파일을 보내기 위한 경로입니다. 우리는 전체 애플리케이션으로 하기 때문에 .을 보냅니다.
이렇게 하면 yaml파일과 같은 경로에 있는 Dockerfile을 불러와 빌드하고 이미지를 만듭니다.
target또한 속성 을 정의 하고 로 설정합니다 development. 이 속성 덕분에 Docker는 이제 Dockerfile의 첫 번째 부분만 빌드하고 빌드의 프로덕션 부분을 완전히 무시합니다(두 번째 FROM명령문 전에 중지됨).
Dockerfile에서 명령으로 .CMD ["node", "dist/main"] 가 있지만, 이것은 개발환경에서 실행하는게 아닙니다.
yaml파일의 설정이 모두 완료되고 실행하기위해 npm run start:dev으로 설정이 완료된 실행을 진행합니다.
command: npm run start:dev의 문제는 Docker가 작동하는 방식으로 인해 호스트 시스템(컴퓨터)에서 파일을 변경해도 컨테이너에 반영되지 않는다는 것입니다. 그래서 우리는 Dockerfile에서 COPY를 통해 파일이 변경되면 내용을 복사해서 넣어주었습니다.
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules
볼륨 디렉토리를 마운트하는 방법이며 두 가지를 정의합니다.
첫 번째는, WORKDIR /usr/src/app로 지정된 현재디렉토리에 마운트하는 방법입니다. 이렇게 하면 호스트 시스템에서 파일을 변경할 때 컨테이너에서도 파일이 변경됩니다. 이제 프로세스는 컨테이너 내부에서 계속 실행되는 동안 파일이 변경될 때마다 애플리케이션을 계속 다시 시작합니다.
두 번째 볼륨은 해킹입니다. 컨테이너에 첫 번째 볼륨을 탑재하면 실수로 node_modules 디렉터리를 로컬에 있는 디렉터리로 덮어쓸 수도 있습니다. 개발자는 일반적으로 Visual Studio Code가 의존하는 개발 도구(예: eslint 또는 @types와 같은 패키지)로 인해 호스트 컴퓨터에 node_modules가 있습니다.
이를 염두에 두고 node_modules컨테이너에 있는 기존 항목이 재정의되는 것을 방지하는 익명 볼륨을 사용할 수 있습니다.
(두번쨰 방법 잘 이해못햇음. )
다음은 중요한 port다.
도커 컨테이너는 자신의 네트워크를 가지고 있다, 그래서 포트를 사용한다, 우리는 호스트시스템에서 정의해주어야한다. HOST_PORT:CONTAINER_PORT 형식이다.
The ${SERVER_PORT} syntax means that the value will be retrieved from the environment variables.
ports:
- ${SERVER_PORT}:${SERVER_PORT}
- 9229:9229
Node.js 애플리케이션으로 작업할 때 일반적으로 파일을 사용하여 환경 변수를 한 곳에 보관합니다.
위의 port에서 하는 것처럼 구성에서 환경 변수를 사용하고 있기 때문에 변수가 정의된 경우에만 파일에서 변수를 로드합니다.
각 서비스에는 자체 내부 네트워크가 있기 때문에(컨테이너가 다르기 때문에) 우리는 또한 그들이 통신할 수 있도록 자체 네트워크를 만듭니다.
네트워크는 파일 맨 아래에 정의되어 있습니다. 여기서는 docker-compose에게 이 특정 서비스에서 사용하도록 지시합니다.
우리의 다른 두 서비스는 redis및 postgres입니다. 애플리케이션이 시작되면 Postgres 데이터베이스와 Redis 스토리지를 모두 사용할 준비가 된 것으로 예상합니다. 그렇지 않으면 애플리케이션이 충돌할 수 있습니다.
아래 명령을 통해 실행해볼수 있습니다.
docker-compose up
참고사이트 :
https://nodejs.org/ko/docs/guides/nodejs-docker-webapp/
NestJS 및 Docker를 사용한 컨테이너식 개발
NestJS Dockerized | Simple NestJS Starter dockerized 유투브