docker로 프로젝트의 이미지를 만들어보자! - (1)

HanSH·2024년 3월 5일

NestJS

목록 보기
18/29
post-thumbnail

docker로 이미지 생성부터 로드밸런싱을 거쳐 무중단 배포까지 진행되는 시리즈물입니다!
여러 기술들을 시도해보았지만 실패하였던 것도 작성합니다!
추후에 이런 작업을 할 때 참고해주세요!

최종 디렉터리 구조는 아래와 같습니다.

docker
├── db
│   ├── db_config		# file
│   └── db_data			# dir
├── env
│   ├── database.env	# file
│   └── server.env		# file
├── nginx
│   └── nginx.conf		# file
├── rg-back
│   ├── src				# dir
│   │   ├── ...
│   │   ...
│   │   └── ...
│   ├── test			# dir
│   │   ├── ...
│   │   ...
│   │   └── ...
│   ├── .eslintrc.js
│   ├── package.lock.json
│   ├── package.json
│   ├── bun.lockb
│   ...
│   ├── .dockerignore
│   ├── Dockerfile
│   └── README.md
└── docker-compose.yml

docker image

간단하게 말하면 OS + 실행파일을 가진 가장 작은 단위라고 할 수 있습니다. 원래 정의와는 다르지만 그렇다고 이해하는 것이 좋아요. (실제로 nodejs만 봐도 리눅스를 담고있습니다. 커널을 담지 않았을 뿐..)
"""docker의 컨테이너를 만들기 위한 모든 설정 파일이 들어있는 최소한의 단위""" 라고 볼 수도 있겠네요.

아무튼 왜 이미지를 만드냐? 하면 실행에 필요한 모든 파일이 있기 때문에 환경변수 작업만 조금 해주면 그대로 배포할 수 있기 때문이죠! 이 환경변수도 docker-compose.yml을 작성하면 일일히 타이핑 칠 필요도 없고요.
또한 특정 버전에서 오류가 발생했을 때에도 대처할 수 있는 방법이 달라요.

  • 이미지를 사용하지 않는다면 버전 관리 시스템에서 이전 버전을 clone 하여 가져오고 build까지 다시 해야함.
  • 이미지를 사용하면 이전에 build한 내용이 남아있기 때문에 image만 다운받고 컨테이너를 실행하면 됨.

물론 이미지를 다운받는 시간이 좀 더 걸리긴 하지만 관리해야하는 컨테이너 수가 많아지면 이미지를 다운받는 시간보다 clone 후 build 하는 시간이 더 길어요. 그렇기때문에 이미지를 활용하는 것이 더 좋죠.

이미지 생성

현재 host os가 윈도우라 wsl2를 쓰고 있는데 wsl에 도커를 또 설치하는 것은 좀 이상하다 생각해요... 어차피 docker image는 어느 os에서나 사용할 수 있으니 윈도우에서 진행할게요!

1. 프로젝트 파일 clone

진행하였던 nestjs 프로젝트를 clone했어요.
여기에는 build, module을 제외한 모든 파일이 있죠!


실제 실행에는 필요 없는 eslint, prettier, readme, gitignore은 넣을 필요가 없어요. 따라서 dockerignore에 작성해서 복사하지 않게 할거에요.

2. Dockerfile 작성

이 파일들을 이미지로 만들기 위해서는 Dockerfile 이란거를 작성해야해요.
저는 간단하게 아래와 같이 작성해주었어요.

FROM node:20.10.0-slim

WORKDIR /app

COPY . .

RUN npm i -g bun
RUN bun i

EXPOSE 3000

RUN bun run build

CMD ["bun", "start"]

각 명령을 자세히 알아봅시다! 더 자세한 내용은 이쪽을 참고해주세요!

  • FROM: 이미지 파일을 가져옵니다. FROM <image> AS builder 를 통해 뒤의 다른 단계에서 이 단계의 파일을 가져올 수 있습니다.
  • WORKDIR: 리눅스의 cd 명령어와 동일하다고 생각하면 됩니다. 해당 디렉토리가 없다면 새로 생성을 하고 이동합니다.
  • COPY: 파일을 복사합니다. 정확한 명령은 COPY <origin_file> <dest_file>, 만약 file에 .을 입력한다면 dockerignore에 적힌 요소를 제외한 origin의 모든 파일 및 폴더를 복사합니다.
    --from=&lt;stage&gt; 구문을 통해 이전 스테이지의 결과를 가져올 수 있습니다!
    ※ 이 경우 상대경로를 사용할 경우 WORKDIR의 경로를 잘 생각해주시기 바랍니다.
  • RUN: 빌드 중 실행할 명령을 입력합니다. 멀티 스테이지를 사용할 경우, 여기서 패키지만 설치하고 module들을 가져오는 식으로 실제 빌드 타임을 줄일 수도 있습니다!
  • EXPOSE: 외부로 노출할 포트를 지정합니다.
  • CMD: 이미지가 컨테이너화된 후 실행할 명령어를 지정합니다. CMD 명령어는 Dockerfile 내 단 한번만 사용된다고 생각하면 편합니다.

3. dockerignore 작성

// .dockerignore
node_module
dist
logs
.git
.env
.eslint.js
.prettierc
.gitignore

프로젝트 설정에 맞게 작성해주시면 됩니다. 저는 이곳에서 사용한 이미지대로 ignore를 작성해주었어요.

4. 이미지 생성

docker build <target_dir_name> -t <tagname>으로 빌드 후 이미지를 생성해줄거에요. 태그 이름을 적지 않아도 되지만, 이후 none이라는 이름으로 생성이 되니 태그를 적어주는게 좋아요.
이때 태그는 img:<version>로 버전을 적어줄 수도 있어요.

태그를 넣어 빌드하면 이렇게 버전 관리도 가능해요!

5. 실행

docker run <image> <command>를 하면 실행할 수 있어요.

생성한 이미지를 docker hub에 업로드

도커를 사용한다면 회원가입이 되어있을거라 생각됩니다. 이를 가정하고 진행하겠습니다.
먼저 docker hub에 개인 리포지토리를 하나 만들어줍시다.

public으로 하든 private로 하든 상관 없지만, 원치않는 env가 노출될 수 있으니 private로 만들어줍시다.

이후 docker push <repository>/<img_name>:<version>으로 이미지를 업로드 해줍시다!


여기까지 했으면 1탄 - 이미지 생성은 끝났어요! 다음엔 docker-compose를 이용하여 컨테이너 실행하는 법을 알아볼거에요.


삽질 리스트

도커에서 새로운 작업을 시도하는 동안 겪었던 시행착오들을 정리하였습니다!

멀티 스테이지 빌드

패키지 설치, 빌드, 실행 세 단계로 나눠 최종 이미지 용량을 줄이고 빌드 시간까지 줄이는 매우 획기적인 방법이다! 이를 이 프로젝트의 이미지 생성에 사용해보려 하였으나 npm i -g로 설치하면 이 패키지는 복사가 불가능...

시도하였던 방법을 적어보자면

global으로 설치한 패키지도 복사

이 경우는 복사를 하여도 시스템이 인식하지 못하였다.

bun을 npm i로 설치

이 경우에도 bun을 인식하지 못하였다.


결국 멀티 스테이지 빌드를 사용하지 못하였다.

멀티 스테이지 빌드 대신 alpine 버전 사용

용량을 더 줄이기 위해 해당 버전을 사용하려 하였다.
결론을 말하자면 일반, slim 버전과는 달리 alpine은 다른 아키텍처를 사용하기 때문에 패키지 설치에서부터 오류를 뿜어댔다.

profile
저는 말하는 싹 난 감자입니다

0개의 댓글