NodeJS의 Docker 구축~ 개발 환경 모델과 CI 구축까지~

2년 동안 키워온 Docker file이 좋아진 느낌에 경험과 기술을 공유했기 때문이다.샘플 코드는 NestJS, Express 등 다른 프레임워크를 사용해도 참고할 수 있다(NestJS는 이전에 Express를 사용했기 때문이다).그리고 prisma는 처음 사용하기 때문에 쓰는 방법이 다를 수 있습니다.그때 지적해 주세요.
샘플 코드
https://github.com/RNm-dove/nestjs_docker_sample
이 보도가 덮인 것은
  • Docker for Desktop을 로컬에서 사용하여 개발합니다.
  • Docker를 사용하여 CI 서버(GiitHub Action)에서 테스트합니다.
    네.
  • Docker를 사용한 적이 있는 사람을 대상으로 한 기사입니다.Docker 및 Docker for Desktop은 설명되지 않습니다.

    Docker for Desktop을 사용하여 개발 환경 제작 모드


    Docker를 사용하여 환경을 개발할 때 NodeJS에는 몇 가지 모드가 있습니다.
    귀속 마운트와 볼륨 마운트의 차이를 미리 이해해 주십시오.
    https://amateur-engineer-blog.com/docer-compose-volumes/

    node_modules를 포함한 모든 파일을 호스트와 용기를 통해 귀속 설치합니다.


    docker-compose
    version: '3.7'
    
    services:
      # 開発用
      app:
        build:
          context: .
          dockerfile: ./Dockerfile
        ports:
          - 3000:3000
        init: true
        volumes:
          # バインドマウント
          - .:/home/node/nestjs_docker_sample
    

    node_modules 이외의 모든 파일을 호스트와 용기로 연결하여 설치합니다.node_modules 마운트 음량입니다.


    docker-compose
    version: '3.7'
    
    services:
      # 開発用
      app:
        build:
          context: .
          dockerfile: ./Dockerfile
        ports:
          - '3000:3000'
        init: true
        volumes:
          # バインドマウント node_modules以外のすべてのファイルを指定
          - ./src:/home/node/nestjs_docker_sample/src
          - ./test:/home/node/nestjs_docker_sample/test
          # ...省略
          # ボリュームマウント
          - node_modules:/home/node/nestjs_docker_sample/node_modules
          
    volumes:
      node_modules:
    

    node_modules를 포함한 모든 파일을 호스트와 용기를 통해 귀속 설치합니다.용기 측면의 nodemodules는 호스트의 node입니다modules에 덮어쓰지 않기 위해 디렉터리 구조에 볼륨을 설치합니다.


    컨테이너 측면의 디렉토리 구조
    | - /home/node/nestjs_docker_sample
        | - package.json
        | - package-lock.json
        | - node_modules
        | - app
            | - src
                | - index.js
                | - ...
    
    인산염 측면의 목록 구조
    | - nestjs_docker_sample
        | - Dockerfile
        | - package.json
        | - package-lock.json
        | - node_modules
        | - src
            | - index.js
            | - ...
    
    docker-compose
    version: '3.7'
    
    services:
      # 開発用
      app:
        build:
          context: .
          dockerfile: ./Dockerfile
        ports:
          - '3000:3000'
        init: true
        volumes:
          # バンドマウント
          - ./:/home/node/nestjs_docker_sample/app/
          # ボリュームマウント
          # the volume above prevents our host system's node_modules to be mounted
          - node_modules:/home/node/nestjs_docker_sample/app/node_modules/
        command: bash -c "rm -rf /usr/local/app/node_modules/* && nodemon index.js"
          
    volumes:
      node_modules:
    

    node_modules는 암호화 볼륨에만 마운트됩니다.VScode를 이용한 원격 컨테이너를 개발해 컨테이너 안에서 개발한다.


    docker-compose
    version: '3.7'
    
    services:
      # 開発用
      app:
        build:
          context: .
          dockerfile: ./Dockerfile
        ports:
          - '3000:3000'
        init: true
        volumes:
          # ボリュームマウント
          - 'node_modules:/home/node/nestjs_docker_sample/node_modules
          
    volumes:
      node_modules:
    
    두 번째와 세 번째는 거의 같다.
    왜 이런 모델이 있었을까? 개발의 용례가 다르기 때문이다.

    개발 환경 용례


    컨테이너만 개발하고 싶을 때


    먼저, nodemodules는 암호화 볼륨에만 마운트됩니다.이어 VSCODE 원격 컨테이너를 사용하거나 컨테이너 내부에서vim 개발을 진행한다.
    VScode 원격 컨테이너는 획기적이지만 편집기의 확장 기능과 약간의 라그기의 단점을 다시 준비할 필요가 있다.
    명령과 npm install는 모두 용기 내부에서 실행된다.VScode 원격 컨테이너를 사용하면git 사용자와 어떻게 할까요?

    컨테이너는 환경이 있지만 호스트 편집기에서 린다와 포맷기를 사용하고 싶습니다.


    node_modules 파일 그룹이 인산염 옆에 있지 않으면, 때때로 린다와 포맷기가 정상적으로 작동하지 못할 수도 있습니다.호스트 편집기에서 개발하는 데 어려움을 겪을 수 있는 형식 오류를 표시합니다.따라서 인산에도 노드가 있다modules 파일을 저장합니다.
    이런 상황에서는 두 가지 패턴이 있다.
    문제는 귀속이 설치되어 있으면 용기 옆의 파일이 호스트 편지 옆의 파일에 의해 강제로 덮어씌워진다는 것이다.이것은 처음 마운트할 때 매우 곤란할 것이다.
  • Docker file에 npm ci라고 쓰면 컨테이너 생성 시 nodemodules에 라이브러리가 포함된 상태에서 생성됩니다.
  • node_modules를 귀속 설치 대상으로 하고 docker-compose를 시작합니다.
  • 처음 마운트할 때 호스트 편지 옆에 있는 nodemodules는 비어 있습니다.
  • 인산염 측면의 nodemodules에서 컨테이너 옆에 있는 nodemodules가 덮어쓰여집니다.
  • 용기 옆의 nodemodules가 비어 있습니다.
  • 이런 흐름.이것은 귀속 마운트 후 수동으로 컨테이너에 들어가면npm ci 해결할 수 있습니다.인산 측면의 node프로그램 라이브러리가modules에 들어갔기 때문에, 나중에 귀속을 설치할 때 덮어써도 괜찮습니다.다만 Docker filenpm ci로 어렵게 한 번 더 바인딩하고 설치한 후npm ci는 두 번 번거롭다.
    이것에 관해서는 두 가지 해결 방법이 있다.Docker file의 다중 레벨 구축을 이용하여 개발할 때 이미지에 포함되지 않는 방법npm ci 및 디렉터리 구조에 공을 들인다이것은 모듈스 폴더가 인산염 측과 용기 측이 충돌하지 않도록 하는 방법이다.
    인산선소 방면과 node모듈을 연결하면 늦는다는 보고도 있습니다.이 일대에는 이 보도에 대해 상세한 기록이 있다.Docker와 Mac의 상성에 익숙한 사람이 있다면 저에게 알려주세요.
    https://burnedikt.com/dockerized-node-development-and-mounting-node-volumes/

    모드 1 인산염으로 강제 덮어쓰기


    상당히 간단한 구조.node_modules를 포함하는 모든 파일만 연결할 수 있습니다.npm install 시스템이 느려지지만 이것보다 쉽고 간단해서 내가 좋아하는 거야.Docker file 프로세스 중 설치된 nodemodules 덮어쓰기에 대한 질문은 Docker file을 사용하여 피할 수 있습니다.npm install 시스템과 명령은 용기 내에서 실행되고git는 용기 밖에서 실행됩니다.

    모드2 인산선소의 강제 커버를 받아들일 수 없습니다


    상당히 복잡한 구조.호스트 측과 용기 측의 디렉터리 구조를 바꾸어 귀속을 설치할 때 nodemodules 충돌을 피할 방법을 강구하다.그리고 각각 집행npm ci.그 밖에 용기 옆의 node음량은 입출력 성능이 악화되지 않도록 modules를 로드합니다.상당히 복잡해졌기 때문에 추천하지 않습니다.방법은 이쪽 기사에 쓰여 있다.
    https://burnedikt.com/dockerized-node-development-and-mounting-node-volumes/
    이 방법으로 인산염 측도 npm ci할 수 있기 때문에 인산도 명령을 집행할 수 있다.

    컨테이너는 환경이 있지만 호스트 편집기에서 린다나 포맷기를 사용하지 않아도 된다.


    node_modules를 제외한 모든 파일을 연결합니다. node-모듈에만 음량을 마운트합니다.
    https://gotohayato.com/content/544/
    그러나 호스트 편집기에서는 린다, 포맷기, 확장 기능이 효력을 잃을 수 있습니다.

    도대체 뭐가 좋을까


    제 추천은 컨테이너에서만 VSCODE의 원격 컨테이너 기능을 사용하는 것입니까, 아니면 node를 사용하는 것입니까?modules를 포함하는 모든 파일을 연결하여 설치하는 방법입니다.
    이 글에서는'컨테이너는 환경이 있지만 호스트 편집기에서 개발하고 싶다. 린다와 포맷기를 사용하고 싶을 때'의 패턴 1 방법을 설명한다.

    Docker file 및 docker compose 만들기


    다중 레벨 구문을 사용합니다.BuildKit 사용법이 명확하지 않습니다.BuildKit가 잘 쓰는 Docker file의 사용법을 아는 사람이 있다면 기사를 기다리겠습니다.프로그램 이름은nestjsdocker_나 Sample.
    주안점
  • node 기반 Docker 이미지에 node라는 홈 디렉터리와 사용자가 존재하기 때문에 이 이미지를 사용합니다.
  • 글로벌 설치를 위해서는 환경 변수를 설정해야 합니다.
  • 개발 단계에서 전 세계에 cli시스템을 설치하면 용기를 사용하지 않고 명령을 이용할 수 있다.
  • 개발 환경에서 Docker filenpm ci에서 수행되지 않습니다.새 개발자는 이미지 구축docker-compose up 후 다시 진행npm ci.
  • CI에서 실행되는 개별 테스트 환경을 만듭니다.
  • 전용 테스트 환경에서 Docker filenpm ci에서 docker compose로 볼륨에 불러옵니다.
  • Docker file입니다.프리스마는 이번에 샘플 제작을 처음 접한 만큼 프로드 무대는 움직이지 않을 것으로 보인다.
    infra/node/Dockerfile
    ###############
    #    base     #
    ###############
    # 本番のベース。ここではosの必須ライブラリ以外は何もいれない。
    FROM node:14.19-alpine3.15 as base
    
    ENV LANG=ja_JP.UTF-8
    ENV HOME=/home/node
    ENV APP_HOME="$HOME/nestjs_docker_sample"
    
    WORKDIR $APP_HOME
    
    # port 番号 いまDockerfileにEXPOSE使っても効果ないって聞いた。ただinformativeなだけ。
    # https://shinkufencer.hateblo.jp/entry/2019/01/31/233000
    EXPOSE 3000
    
    # global install curlはローカルで簡単にAPIチェックできるように入れた。
    # gitはjestのwatchモードに必要
    # postgresql-clientはDBでpostgres使ってるなら必要
    RUN apk upgrade --no-cache && \
        apk add --update --no-cache \
        postgresql-client curl git
    
    # https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#global-npm-dependencies
    # npmのグローバル設定
    ENV NPM_CONFIG_PREFIX=/home/node/.npm-global
    ENV PATH=$PATH:/home/node/.npm-global/bin
    
    # package系のコピー
    COPY package*.json ./
    # .npmrcがあるならここでコピー
    # COPY .npmrc ./
    
    # すべてのファイルをnodeユーザーのものに
    RUN chown -R node:node .
    
    USER node
    
    RUN echo "WORKDIR is $WORKDIR . HOME is $HOME . LANG is $LANG ." && npm config list
    
    ###############
    #     dev     #
    ###############
    # docker-composeでコードを共有する前提
    # このステージでnpm ciしても、結局マウント時にホスト側のnode_modulesで塗りつぶされてしまうのでやらない。
    FROM base as dev
    ENV NODE_ENV=development
    
    # グローバルインストールしたい系のやつはここでインストール
    RUN npm i -g @nestjs/cli
    RUN npm i -g prisma
    
    ###############
    #     test    #
    ###############
    # docker-composeでnode_modules以外のコードを共有する前提。
    # docker-composeのprebuild-testに対応。
    FROM dev as test
    ENV NODE_ENV=test
    
    RUN npm ci
    
    ###############
    #    build    #
    ###############
    # ソースコードをビルドする。
    # ビルド時にテストファイルを除外しているので、テストはビルド前に行う。
    FROM test as build
    
    COPY --chown=node . .
    
    RUN npm run build
    
    ###############
    #    prod     #
    ###############
    # ターゲットを指定しなければデフォルトで実行される
    # dependenciesのみインストールされている
    FROM base as prod
    ENV NODE_ENV=production
    
    # 設定ファイル系。実行に必要なやつをコピペする。
    # もしかしたらここにprisma関連のソースコード必要かも。本番は検証していないです。
    COPY --from=build /$APP_HOME/dist  /$APP_HOME/.dockerignore ./
    
    RUN npm ci --only=production \
        && npm cache clean --force
    
    # アプリ実行コマンド
    CMD ["node", "src/main.js"]
    
    docker-compose입니다.
    docker-compose
    version: '3.7'
    
    services:
      # 開発用
      app:
        build:
          context: .
          dockerfile: ./infra/node/Dockerfile
          # マルチステージビルドのターゲットを指定
          target: dev
        ports:
          - '3000:3000'
          # デバッガ用
          - '9229:9229'
        # PID1問題に対応
        init: true
        volumes:
          # 開発環境ではnode_modulesをバインドマウントさせる。イメージビルド時はnpm ciは実行していない。開発初期にコンテナ上でnpm ciを行う。
          - '.:/home/node/nestjs_docker_sample'
        env_file:
          - .env.local
        command: npm run start:dev
    
      # CIテストで利用前提。
      prebuild-test:
        build:
          context: .
          dockerfile: ./infra/node/Dockerfile
          target: test
        ports:
          - '3000:3000'
        init: true
        volumes:
          - ./:/home/node/nestjs_docker_sample
          - ./coverage:/home/node/nestjs_docker_sample/coverage
          # https://stackoverflow.com/questions/30043'872/docker-compose-node-modules-not-present-in-a-volume-after-npm-install-succeeds
          # テスト環境ではnode_modulesをバインドマウントさせない。ホスト側のnode_modulesをバインドマウントしてしまうと、イメージビルド時でインストールしたnpm ciがまっさらになってしまうのでCI上でもう一度インストールする必要がある。ボリュームマウントを利用。
          - node_modules:/home/node/nestjs_docker_sample/node_modules
        env_file:
          - .env.local
        command: npm run ci:test
    
      postgres:
        build: ./infra/postgres
        volumes:
          - pg-data:/var/lib/postgresql/data
          - ./infra/postgres/initdb:/docker-entrypoint-initdb.d
        ports:
          - '5432:5432'
        environment:
          - POSTGRES_HOST_AUTH_METHOD=trust
          
    volumes:
      pg-data:
        driver: 'local'
      node_modules:
    

    GiitHub Action 구축


    .github/workflows/docker-image.yml
    name: Docker Image CI
    
    on: push
    
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          - name: echo docker and compose version
            run: docker -v && docker-compose -v
          - name: build stateful server and migrate
            run: docker-compose up -d --build postgres
          - name: create coverage dir
            run: mkdir -p coverage && chmod 777 coverage
          - name: run migrate & test
            run: docker-compose run prebuild-test
          # Save coverage report in Coveralls
          #- name: Upload coverage to Codecov
          #  uses: codecov/codecov-action@v2
          #  env:
          #    CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
    

    makefile로 docker-compose 명령 정리하기


    Makefile
    .PHONY: init
    init:
    	make clean
    	docker-compose build
    	docker-compose run --rm app npm ci
    	docker-compose run --rm app prisma migrate deploy
    
    .PHONY: clean
    clearn:
    	docker-compose down --volumes
    
    .PHONY: dev
    dev:
    	docker-compose down app
    	docker-compose up app
    
    .PHONY: unit
    unit:
    	docker-compose run --rm app npm run test
    
    .PHONY: e2e
    e2e:
    	docker-compose run --rm app npm run test:e2e
    
    .PHONY: infra
    infra:
    	docker-compose down postgres 
    	docker-compose up postgres
    
    .PHONY: bash
    bash:
    	docker-compose up --no-start app 
    	docker-compose start app
    	docker-compose exec app sh
    

    개발 방법


    초기 설정


    $ make init
    

    개발 중


    $ make dev
    
    종료 후 Cntl-c kill 사용

    e2e 테스트


    $ make infra
    
    추가 태그
    $ make e2e
    

    단원 테스트


    $ make unit
    

    명령을 집행하다


    npm install, prisma-cli, nest-cli 명령을 실행하려는 경우
    $ make bash
    
    에서 용기에 들어가서 실행합니다.끝나고 할게요.

    좋은 웹페이지 즐겨찾기