Docker

Docker Compose로 NodeJS, Nginx 설치 및 실행하기 # YAML

Developer88 2024. 9. 13. 14:02
반응형

Docker Compose 는 여러 컨테이너로 구성된 애플리케이션을 정의하고 관리하게 해주는데요.

오늘은 Docker Compose를 이용해서,

NodeJS, NGINX를 설치하고 서버를 실행하는 방법을 정리해 보도록 하겠습니다.

 

이 글에서는 NGINX나 Docker의 기초에 대해서는 다루지 않고 있는데요.

Docker에 관한 글은 아래 글을 참조해 주시구요.

>> Docker와 DockerHub를 이용한 배포 와 실행 # NodeJS Dockerfile

 

NGINX에 관한 글은 아래 글을 참조해 주세요.

>> NGINX에 대한 정리 #Upstream #Reverse Proxy #Proxy_pass

 

1. Docker Compose

하나의 프로덕트를 서버에서 실행하려면,

여러개의 Docker 컨테이너가 필요합니다.

가장 간단한 구조를 생각해도 아래3가지 정도는 필요하지요.

  • 웹서버(Nginx)
  • 웹앱(NodeJS 등)
  • DB(SQLite, MySql 등)

이렇게 여러개의 서비스를 띄우기 위해, 여러개의 컨테이너를 사용해야 하는데요.

터미널에서 run명령어로 docker 이미지들을 실행해서,

컨테이너들을 띄우고,

포트등 네트워크에 관한 옵션도 넣어줘야 합니다.

 

이것들을 정해진 순서에 맞추어,

한번에 진행할 수 있도록 해주는 툴이 바로 Docker Compose 입니다.

여러개의 Docker Container를 실행시켜주는 툴인 것이지요.

 

2. docker-compose 의 사용 순서

Docker에서는 'Dockerfile > 이미지 > 컨테이너' 순서로,

컨테이너를 생성하여 사용하였습니다.

Docker Compose는,

컨테이너를 빌드하기 위한,

Dockerfile들이 필요합니다.

 

Docker Compose를 사용하는 순서를 정리해 보면 다음과 같습니다.

  1. 각 앱들의 Dockerfile 작성
  2. docker-compose.yml 파일을 프로젝트의 루트 디렉토리에 생성
  3. 각 서비스들에 대해 docker-compose 파일에 설정 시작
    • Node.js 애플리케이션을 위한 Dockerfile 경로
    • 포트 매핑
    • 환경 변수
    • 볼륨 등
  4. 네트워크 설정: 필요할 시, 서비스 간의 네트워크를 정의해 컨테이너간에 통신이 되도록 함
  5. Volumes 정의: 데이터베이스 파일, 로그 파일 등 컨테이너 삭제후에도 유지되야 할 데이터 volumes 정의
  6. 환경변수 파일 정의: .env 파일이나 environment 섹션을 사용하여 서비스에 필요한 환경 변수를 관리
  7. 빌드 및 실행: docker-compose up 명령어를 실행하여 서비스를 빌드하고 실행

 

위에서 정리한 것처럼, 가장 먼저 할것은, docker-compose.yml 파일을 정의하는 것 인데요.

확장자 이름이 yml인 이 파일은 yaml이라는 형식의 파일입니다.

우선, 이것에 대해서 알아보도록 하겠습니다.

 

3. yaml 파일

YAML은 "YAML Ain't Markup Language"의 약자로, 

마크업 언어가 아님을 의미합니다. 

공식 페이지에 따르면, 

YAML은 인간 친화적인  data serialization표준으로 설명되어 있습니다.

JSON과 같은 다른 직렬화 형식과 비교해 보면,

YAML의 이해와 사용이 훨씬 쉽다는 것을 알 수 있습니다.

 

YAML 파일은 "---"으로 시작하는 것이 하나의 특징이며,

아래 이미지의 오른쪽에서 볼 수 있는 것처럼, 주석 사용도 가능합니다.

array는 "-" 으로 표현해주고, null 값은 아무 것도 적지 않음으로써 표현됩니다.

이러한 특성들로 인해 YAML은 JSON에 비해 가독성이 높으며, 

비교적 인간 친화적인 형식이라고 할 수 있습니다.

 

 

 

웹에서 'json to yaml'을 검색하시면,

변환해주는 많은 사이트를 볼 수 있는데요.

이를 활용하면 이해하는데 많은 도움이 됩니다.

 

4. Docker Compose 설치

Mac이나 Windows버전의 Docker 앱을 설치하셨다면,

Docker Compose는 이미 설치가 되어 있어서 걱정을 할 필요가 없습니다.

다만, Linux의 경우는 아래와 같이, 파일을 다운받아서, standalone 버전을 설치 해 주어야 합니다.

 

curl -SL https://github.com/docker/compose/releases/download/v2.24.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose

 

참고로 위 명령어 중, 각각은 다음의 의미를 가지고 있습니다.

  • curl(client for url): url 에서 데이터를 다운로드 받음
  • -SL: curl 옵션으로, 서버와 연결이 실패하거나 리다이렉션되는 경우, 계속 다운로드를 진행하도록 하려 할때 사용
    • -S: Silent의 약자로, 에러메시지를 표시하지 않음
    • -L: Location의 약자로, 다운로드하는 파일이 리다이렉트되면 해당 위치에서 다운로드를 합니다.
  • -o: 다운로드한 파일을 원하는 이름으로 지정하는 옵션
    • 위에서는 /usr/local/bin/ 경로의 docker-compose 파일로 저장하라는 의미

 

이렇게 다운로드한, docker-compose파일에 실행 권한을 주어야 합니다.

일반적으로 다운로드한 파일에는, 실행권한이 없기 때문입니다.

다운로드한 파일에 실행권한이 디폴트로 들어가 있으면, 악의적인 파일이 보안이슈를 일으킬 수 있기 때문에 미리 막아놓는 것 이지요.

 

먼저 정상적으로 다운로드 되었는지 아래 명령어로 확인해 봅니다.

 

ls -l /usr/local/bin/

 

확인해 보면, 권한에 아래와 같이 되어있는 것을 발견할 수 있습니다.

첫번째, 다운로드를 한 파일소유자의 권한이 r(읽고), w(쓰기)만 되어있는 것을 볼 수 있습니다.

 

파일이 존재하는 것을 확인되었다면,

아래 명령어로 실행권한을 추가해 줍니다.

 

sudo chmod +x /usr/local/bin/docker-compose

 

 

위의 chmod 명령어는 파일의 권한을 변경해주는 명령어이고요.
'+x'는 실행권한을 추가하는 옵션입니다.

 

다시 위에서 보았던, ls명령어로 바뀐 권한을 확인해 줍니다.

아래와 같이 바뀐 것을 확인해 볼 수 있습니다.

소유자, 그룹, 기타유저들 모두에게 실행을 의미하는 'x'가 부여된 것을 볼 수 있습니다.

 

만약, 일반사용자에게는 실행권한을 주지 않으려면 아래와 같이 해 주면 됩니다.

기타 사용자를 나타내는(o)에게 실행 권한(x)을 제거하는 명령어 입니다.

 

sudo chmod o-x docker-compose

 

참고로, chmod 에서는 아래와 같이, 소유자, 그룹, 일반사용자를 구분합니다.

  • u: 소유자(User)
  • g: 그룹(Group)
  • o: 기타 사용자(Others)

다 되었으면, 아래 명령어로 정상적으로 설치된 것을 확인해 줍니다.

 

docker-compose version

 

아래와 같이 설치된 것을 확인할 수 있습니다.

이제 글2에 정리하였던 것처럼,

Dockerfile들을 만들고 

 

 

5. NGINX Docker파일 작성

각 서비스들의 Docker file을 작성하고,

docker-compose파일을 작성해 실행할 텐데요.

nginx의 docker파일부터 작성하도록 하겠습니다.

 

nginx의 경우 네트워크 환경설정파일인 nginx.conf 파일이 필요하므로,

이것부터 간단히 작성해 보겠습니다.

 

5-1. nginx.conf 파일 생성

nginx.conf는 아래와 같이 간단하게 proxy 서버를 생성해 주었습니다.

이 때, 다음 사항을 지켜주어야 합니다.

  • upstream의 server docker-compose에 webapp(nginx등)의 컨테이너 이름이 들어가야 함

Docker Compose에서 컨테이너는 Service Name(서비스 이름)으로 참조할 수 있는데요.

예를 들어, upstream블록에 nginx의 컨테이너의 이름을 넣어주면,

나중에 container이름을 docker-compose파일에서 설정할 때,

이 부분도 맞추어서 수정해 주어야 합니다.

 

http {
  upstream backend {
    server nodejs: 8080;  # docker에서 사용할 service name
  }

  server {
    listen 80;
    server_name localhost;
    location {
      proxy_pass http://backend;
      proxy_http_version 1.1;
    } 
  }
}

 

참고로, proxy_pass는 클라이언트로부터의 요청을 다른 서버로 중계해주는 것을 말하구요.

upstream은 이런 요청을 중계할 서버들의 그룹들을 지정해 줍니다.

 

5-2. NGINX Dockerfile 작성

NGINX의 DockerFile을 작성해 보겠습니다.

nginx의 stable버전을 이미지로 가져와 사용하구요.

환경설정파일을 COPY 명령어로 '/etc/nginx' 디렉토리에 복사해 줍니다.

 

중간에 COPY 명령어로 SSL인증서를 복사해 주었구요.

 

마지막 줄에서 CMD 명령어로 Nginx를 실행해 주었습니다.

여기서 '-g daemon off'라고 옵션을 넣어 주었는데요.

원래 daemon은 백그라운드에서 실행되는데, off옵션을 붙여서,

터미널에서 실행되도록 해서, 

컨테이너가 실행 중일 때,

Nginx의 로그 및 상태를 쉽게 모니터링하고 추적할 수 있도록 도와 줍니다.

 

FROM nginx:1.25.4

COPY conf/nginx.conf /etc/nginx/conf.d/default.conf

COPY nginx.conf /etc/nginx/nginx.conf

# SSL인증서 복사하기
COPY /etc/letsencrypt/test.io/fullchain.pem /etc/nginx/certs/fullchain.pem
COPY /etc/letsencrypt/test.io/privkey.pem /etc/nginx/certs/privkey.pem

 

 

참고로, Nginx를 실행하는 명령어를 Dockerfile에서 명시하지 않아도 됩니다.

왜냐하면, 최신 공식 Nginx Docker 이미지는 이미 아래와 같은 명령어를 포함하고 있어서,

컨테이너가 시작될 때 자동으로 Nginx를 실행시키기 때문입니다.

  • CMD ["nginx", "-g", "daemon off;"]

이는 Nginx가 포그라운드에서 실행되도록 해주는데요.

Docker 컨테이너가 계속 실행 상태를 유지하게 합니다.

 

확인하고 싶다면, 공식이미지로 들어가서 Dockerfile을 보면 아래와 같이,

CMD명령어가 존재하는 것을 볼 수 있습니다.

 

 

 

6.  NodeJS Dockerfile 생성

이번에는, NodeJS의 Dockerfile을 생성해 주겠습니다.

 

FROM node:current-slim

WORKDIR /usr/src/app

COPY package*.json ./

# 설치시 인터랙션 방지
ENV DEBIAN_FRONTEND=noninteractive

ENV NODE_ENV production

RUN npm install -g pm2

# 개발자나 테스트 도구 등 필요하지 않은 모듈 설치 제한
RUN npm install --only=production

COPY . .

CMD ["pm2-runtime", "start", "npm", "--", "start"]

 

7. Docker-compose 작성

각 서비스들의 Dockerfile이 작성되었다면,

이제 docker-compose.yml 파일을 작성할 차례입니다.

 

여기서 사용하는 yaml 은 위에서 정리해 보았던 yaml과는 다르게,

첫줄에 "---"를 붙여주지는 않습니다.

 

먼저 전체 docker-compose.yaml을 보고 하나씩 정리해 보도록 하겠습니다.

 

version: '3.8'
services:
  nginx:
    restart: unless-stopped
    build:
      context: ./nginx    
      dockerfile: Dockerfile.dev
    volumes:
      - ./nginx/logs:/var/log/nginx
      - ./nginx/conf/test.conf:/etc/nginx/conf.d/test.conf
    ports:
      - '80:80'

  nodejs:
    build:
      context: ./nodejs
      dockerfile: Dockerfile.dev
    environment: 
      - NODE_ENV=development      
    env_file:
      - .env
    volumes:
      - ./nodejs:/usr/src/app     
      - /usr/src/app/node_modules  
      - ./db:/usr/src/app/db
    command: ["npm", "run", "dev"]
    ports:
      - '3000:3000'

 

 

7-1. version

파일에서 볼 수 있듯이, 첫줄에서 버전을 적어 주도록 하였는데요.

이것은 커스텀하게 임으로 적는 문서 버전이 아니라,

docker-compose의 버전을 가르킵니다.

현재는 Version 3.8이 가장 최신 버전입니다.

 

version: '3.8'

 

버전과 관련해서는 아래 공식문서를 참조할 수 있습니다.

>> https://docs.docker.com/compose/compose-file/compose-versioning/

 

7-2. services

services는 컨테이너들을 가르킵니다.

실행하고자 하는 컨테이너들을 적어 주는데요.

여기서는 nodejs와 nginx라고 정의한 컨테이너를 두개 사용하였습니다.

 

version: '3.8'
services:
  nginx:
    ...

  nodejs:
    ...

 

7-3. ports

docker run 명령어를 사용할 때 "-p" 옵션을 사용하여서,

inbound port와 컨테이너의 port를 매칭시켜주었었는데요.

여기서 그것을 해주면 됩니다.

다만 특이한 점은, yaml의 array형태로 value를 넣어주기 때문에,

앞에 "-"을 붙여준다는 사실입니다.

 

nginx:
    ...
    ports:
      - "80:80"

 

7-4. volumes

A. 호스트 컴퓨터 디렉토리와 매칭

volumes는 컨테이너가 종료되어도 지속되어야 하는 데이터를 관리할 때 필요한데요.

컨테이너 내부의 파일 시스템과,

도커를 띄운 호스팅 컴퓨터의 파일 시스템 간에,

데이터를 영구적으로 저장하고 공유할 수 있게 해주는 방법입니다.

 

주로 아래의 파일들에 대해서 사용하게 됩니다.

  • 데이터베이스 파일
  • 환경 구성 파일
  • 로그 파일 

컨테이너 내부의 데이터를 보존하고 공유하는 데 유용하게 사용됩니다.

 

아래는 호스트 컴퓨터의 현재 디렉토리(.) 내의 nodejs 폴더와,

도커 컨테이너 내의 '/usr/src/app' 디렉토리가 공유되는 것을 의미합니다. 

이렇게 되면, 호스트 컴퓨터의 nodejs 폴더 내의 파일이 수정되면,

컨테이너의 /usr/src/app 디렉토리에도 즉시 반영됩니다.

 

다시 정리해보면, 앞 부분이 동기화될 로컬 컴퓨터의 디렉토리이고,

':' 뒷 부분이 컨테이너 내부의 디렉토리입니다.

(그래서 컨테이너를 띄우고, 로컬에서 '/usr/src/app'을 조회해도 디렉토리는 존재하지 않는다고 나옵니다.)

 

version: '3.8'
services:
  ...
  nodejs:
	...
    volumes:
      - ./nodejs:/usr/src/app
    ...

 

 

그런데 왜 nodejs의 소스파일과 컨테이너내의 소스파일을 매칭시키는 것 일까요?

이것은 개발 과정을 편리하고 효율적으로 만들기 위해서입니다.

이 방식을 사용하면,

호스트 컴퓨터에서 소스 파일을 수정하면 자동으로 컨테이너 내부의 파일도 업데이트되어,

실시간으로 변경사항을 반영할 수 있게 되기 때문입니다.

위의 예에서, nodejs폴더의 파일을 변경하면,

컨테이너 내부의 '/usr/src/app'도 바로 변경되는데요.

개발자가 코드를 변경할 때마다 컨테이너를 재빌드하거나 재시작할 필요 없이,

변경 사항을 바로 확인할 수 있게 됩니다.

 

B. 호스트 컴퓨터 디렉토리 매칭없이 Volumes 마운트

이번에는 매칭된 디렉토리가 없는 경우를 보겠습니다.

 

매칭이 안되서 호스팅 컴퓨터의 특정 디렉토리와 공유가 안될뿐이구요.

volumes로 마운트된 데이터나 파일들이 컨테이너가 삭제되더라도,

유지가 되는 것은 동일합니다.

 

version: '3.8'
services:
  app:
	...
    volumes:
      - /usr/src/app/node_modules

 

 

7-5. build

Docker Compose의 build 설정은,

컨테이너 이미지를 구성하고 실행하기 위한 설정을 하는 곳 입니다.

많이 사용하는 2가지 설정옵션은 다음과 같습니다.

 

설정옵션 내용
context Docker 이미지를 빌드할 때 참조할 파일들이 있는 디렉토리 경로를 지정

예) context: ./app 은 'app' 폴더를 빌드 컨텍스트로 사용
dockerfile 커스텀하게 사용할 Dockerfile 의 이름을 지정

원래 기본값이 'Dockerfile'이지만,
아래와 같이 다른 이름으로 지정가능.

예) dockerfile: Dockerfile.dev

 

 

예제를 보면서 이해해 보겠습니다.

아래의 build 섹션은, 2가지의 설정옵션으로 아래와 같이 구성되어 있습니다.

  • Context: './nginx'
    • 현재 디렉토리의 'nginx' 하위 디렉토리를 빌드 컨텍스트로 사용
  • Dockerfile: 'Dockerfile.prod'
    • 'Dockerfile.prod' 파일을 사용하여 이미지를 빌드

 

version: '3.8'
services:
  nginx:
    ...
    build:
      context: ./nginx
      dockerfile: Dockerfile.prod
    ...

 

 

Dockerfile작성시에,

배포용과는 다른 개발용으로 폴더를 만들어서 Dockerfile.dev와 같이 파일을 만들어 사용하는데요.

dev용을 사용해서 build할 , context나 dockerfile 옵션을 활용하면 되겠지요.

 

7-6. restart

restart는 Docker 컨테이너를 어떻게 재시작할지 정의하는 옵션인데요.

아래의 4가지 옵션이 존재합니다.

  • no
  • on-failure: 컨테이너가 비정상적으로 종료된 경우에만 재시작
  • always: Docker 데몬이 실행 중인 한, 컨테이너가 종료되면 항상 다시 시작
    사용자에 의해 중지되어도 재시작 정책을 변경하지 않는이상 다시 시작
  • unless-stopped: 사용자에 의해 컨테이너가 중지되기 전에는 항상 다시 시작됩니다.
    사용자가 중지시키면 Docker 데몬이 다시 시작되어도 중지 됨.
    on-failure와 always의 중간안으로 프로덕트 운영에 적절하다고 생각합니다.

 

웹서비스는 중지되지 않고 재시작되어, 요청과 응답을 연결해줄수 있어야 하겠지요.

 

7-7. env_file

env_file은 컨테이너에서 사용할 환경 변수들을 정의한 파일의 경로를 지정합니다.

여기에 데이터베이스 연결 정보, API 키, 비밀번호 등과 같은 설정을 저장해 주면 되겠지요.

아래에서는 docker-compose.yml이 위치한 곳에 .env도 위치하게 하였습니다.

 

version: '3.8'
services:
  nodejs:
	...
    env_file:
      - .env
    ...

 

 

 

'.env'파일에는 아래와 같이 'KEY=VALUE' 형태로 값을 저장해 주면 됩니다.

 

 

이 값은 , Docker 컨테이너 내부에서 환경 변수로 접근할 수 있게 됩니다.

컨테이너 내부로 접근해서, 'printenv'로 현재 환경변수들을 확인할 수도 있구요.

NodeJS 코드에서는 'process.env' 객체를 사용하여 아래와 같이 환경 변수에 접근할 수 있습니다

 

console.log(process.env.DB_USER);

 

7-8. environment

아래와 같이 데이터베이스의 아이디와 비밀번호값을 직접 전달할 수도 있습니다.

다만, 이렇게 되면, docker-compose 를 버전관리하기 어려워 지므로, 좋은 방법은 아니라고 생각합니다.

git을 이용해 버전관리를 해도 문제가 없는 수준의 환경설정 값만 environment 값을 이용하고,

아이디, 비밀번호, API키 등은 env_file을 이용하는 것이 좋습니다.

 

version: '3'
services:
  my_service:
    build: .
    environment:
      - DB_USER=myuser
      - DB_PASSWORD=mypassword

 

6-8. 의존성 설정(depends_on)

depends_on 옵션을 사용하면,

특정 서비스에 의존성을 부여해서,

해당 서비스가 시작하고 나서야 시작하도록 해 줄 수 있습니다.

즉, 서비스 간의 시작 순서를 관리하기 위해 사용됩니다.

하나의 서비스가 다른 서비스에 의존할 때 해당 서비스가 먼저 시작되어야 함을 지정합니다.

 

특히 데이터베이스가 해당되는 경우가 많을 텐데요.

데이터베이스서비스가 시작되고 나서,

애플리케이션 서비스가 시작되야,

제대로 동작할 수 있는 경우가 많기 때문입니다.

 

한가지 주의할 점은,

이것은 서비스 시작 순서만을 정할 뿐이고,

서비스간의 통신이나 응답상태를 확인해 주지는 않습니다.

 

version: '3'
services:
  db:
    image: mysql:latest
    environment:
      MYSQL_ROOT_PASSWORD: example

  web:
    build: .
    depends_on:
      - db
    ports:
      - "8080:8080"

 

Nginx와 NodeJS도 마찬가지 입니다.

일반적으로 웹앱으로 사용되는 NodejS가 먼저 시작이 되서,

요청을 처리할 준비가 되어 있어야,

Nginx가 성공적으로 요청을 전달할 수 있겠지요.

Node.js가 준비되지 않은 상태에서 Nginx가 요청을 전달하면,

'502 Bad Gateway' 오류가 발생할 수 있습니다.

 

아래와 같이 해주면, NodeJS가 시작된 후에,

Nginx를 시작하도록 할 수 있습니다.

 

version: '3.8'
services:
  nginx:
	...
    depends_on:
      - nodejs

  nodejs:
    ...

 

7. Docker-compose에서 서비스간의 연결

예전에는 서비스간 연결을 위해서, links를 정의했었는데요.

Docker Compose 파일의 버전 3부터는,

모든 서비스가 동일한 네트워크에 속하게 되며,

서비스 이름을 사용하여 서로 통신(참조)할 수 있습니다.

아래에서는 간단하게 환경변수를 서로 참조하는 것을 보도록 하겠습니다.

 

version: '3.7'

services:
  nodeapp:
    container_name: nodeapp
    image: node:14
    volumes:
      - ./nodejs:/usr/src/app
    working_dir: /usr/src/app
    environment:
      - DB_URL=mongodb://mongo:27017/mydatabase
    ports:
      - "3000:3000"
    depends_on:
      - mongo
    command: npm start

  mongo:
    container_name: mongo
    image: mongo:4.4
    environment:
      - MONGO_INITDB_DATABASE=mydatabase
    volumes:
      - mongodb_data:/data/db
    ports:
      - "27017:27017"

 

위의 설정을 이용해서 띄운 Nodejs에서의 코드는 다음과 같습니다.

컨테이너내부의 환경변수값(process.env.DB_URL)을 가져와서,

DB_URL에 접근해서 코드를 실행하는 것을 볼 수 있습니다.

 

const mongoose = require('mongoose');

const dbUrl = process.env.DB_URL;

mongoose.connect(dbUrl, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
  console.log('Connected to MongoDB');
});

 

8. Docker-compose 명령어

이제 Docker-compose의 명령어를 통해 실행을 할 수 있는데요.

먼저 이미지를 빌드하고 실행하는 명령어부터 알아보겠습니다.

 

8-1. up

up명령어로 docker-compose.yaml 에 정의된대로 docker-compose를 실행시킬 수 있는데요.

docker-compose.yml 파일이 위치한 디렉토리에서 아래와 같이 실행하면 됩니다.

 

docker-compose up

 

"-d" 옵션을 넣어주면, docker run -d로 실행했던 것처럼,  서비스를 백그라운드에서 실행시킬 수 있는데요.

백그라운드로 실행하면, up명령어로 실행시킬 때의 로그들을 보지 않을 수 있습니다.

 

docker-compose up -d

 

모든 것이 정상적으로 설정되었다면,

"curl localhost -I"로 response를 정상적으로 볼 수 있겠습니다.

참고로 이 명령어는 cURL을 사용하여 HTTP 헤더만 요청하는 것입니다.

 

 

 

Dockerfile을 수정하였다면,

아래 명령어로 새로운 이미지를 빌드해 주어야 합니다. 

아무리 수정해도 이미지가 새로 생성되지 않으면,

그것이 반영된 새로운 컨테이너가 없기 때문입니다.

 

docker-compose up --build

 

주의할 점은, 이렇게 해도 이미지 생성시에 캐쉬가 된 부분 때문에,

환경설정값등은 반영되지 않을 수 있습니다.

이럴 경우, 8-2의 캐쉬없이 빌드하는 명령어를 별도로 실행시켜 주고 나서,

'docker-compose up' 명령어를 실행해 주면 됩니다.

 

만약, docker-compose.yml을 수정한 경우라면,

컨테이너를 새로 다시 만들어주는 아래 명령어를 실행해 주어야 합니다.

다른 yml파일은 다른 환경이 설정되어있기 때문입니다.

 

docker-compose up --force-recreate

 

8-2. 캐쉬 제거하기 (캐쉬없이 빌드하기)

Docker는 각 빌드 단계에서 생성된 이미지를 캐시하는데요.

이렇게 해서, 동일한 Dockerfile을 사용해 반복적으로 이미지를 빌드할 때 드는 시간을 줄여줍니다.

 

그런데 때로는 이전에 생성된 캐시를 사용하지 않고,

이미지를 새로 빌드해야 하는 경우가 있습니다.

환경 설정이 변경되었을 때와 같은 경우가 그러한데요.

이럴 때는 이전에 성된 캐시를 사용하지 않고 새로운 이미지를 생성해야 합니다. 

 

이 때 아래 명령어를 이용해 주면 됩니다.

이미지를 빌드할 때 --no-cache 옵션을 사용해 캐시를 무시하고 빌드를 해주면 됩니다.

 

docker-compose build --no-cache

 

8-3. start & stop

이미 컨테이너가 생성되었을 때 사용하면 되는데요.

멈출때는 stop을 해 주면 되고요.

 

docker-compose stop

 

시작할 때는 start를 해 주면 됩니다.

 

docker-compose start

 

8-4. 재시작하기

docker-compose를 재시작하기 위해서는 아래 명령어를 이용해 주면 되는데요.

실행하면 Docker Compose 파일에서 정의된 모든 서비스(컨테이너)가 중지되고 다시 시작됩니다.

 

docker-compose restart

 

만약 특정한 서비스만 재시작하려고 한다면 아래와 같이 해주면 됩니다.

 

docker-compose restart <서비스명>

 

만약 환경설정 값등이 변경되어서,

기존에 캐쉬된 이미지를 다시 빌드 시켜야 한다면,

아래와 같은 순서로 명령을 내려주면 됩니다.

 

docker-compose build --no-cache <서비스명>
docker-compose restart <서비스명>

 

8-5. down

컨테이너의 서비스를 멈추지만,

그냥 멈추는 것이 아니라, 컨테이너와 network등을 같이 제거합니다.

 

docker-compose down

 

'-v' 또는 '--volume' 을 추가해서 down 명령어를 실행하면,

volume을 정의해 디렉토리에 데이터를 따로 mount하였을 경우,

볼륨까지 삭제하도록 할 수 있습니다.

 

실제 프로덕션에서 docker-compose down을 할 때는 매우 주의해야하구요.

'-v'옵션은 DB의 데이터가 사라져버릴 수 있으니,

절대로 함부로 실행해서는 안됩니다.

 

프로덕션레벨의 컨테이너에 대해서는,

볼륨에 대한 접근에 대해 엄격해야 합니다.

 

만약, 프로덕션레벨의 컨테이너를 종료하려면,

아래 명령어로 종료해 주고요.

docker-compose stop

 

재시작해야한다면 위에서 본 'docker restart' 명령어를 사용해 주어야 합니다.

 

8-6. prune

docker-compose를 down한 경우에도,

마운트된 volume이 남아있게 됩니다.

원래 그런 용도로 volume을 마운트 하는 것 이구요.

그런데 가끔 잘못 설정되어 있는 데이터가 남아서 문제를 일으키는 경우가 있어서,

이를 삭제해야 하는 경우도 생기게 됩니다.

 

이럴 때는 아래 명령어를 이용해서 매칭되어 연결되지 않은 volume을 삭제할 수 있습니다.

 

docker volume prune

 

'-a' 옵션을 사용하면, 모든 사용되지 않는 volume을 제거해 줄 수 있습니다.

 

 

특정한 volume을 삭제하려고 할 때는 아래 명령어를 사용하면 됩니다.

 

docker volume rm [volume_name]

 

8-7. logs

docker-compose에 'log'명령어를 사용하면 지금까지의 로그를 볼 수 있습니다.

뒤에 인자로 서비스명을 추가하면, 해당 서비스에 대한 로그를 볼 수 있습니다.

 

docker-compose logs

 

특정 서비스의 로그를 보려면 다음과 같이 해주면 됩니다.

 

docker-compose logs nginx

 

'-f'옵션'을 이용하면 로그를 실시간으로 스트리밍해 볼 수 있습니다.

여기서 f는 'follow'의 약자입니다.

 

docker-compose logs -f

 

특정한 서비스의 로그만 실시간으로 볼수도 있는데요.

예를 들어, 'nodejs'컨테이너의 로그를 실시간으로 보려면 아래와 같이 해주면 됩니다.

 

docker-compose logs -f nodejs

 

8-8. ps 명령어

현재 실행중인 서비스들의 상태를 볼 수 있도록 도와주는데요.

서비스의 이름, 상태, 포트등을 확인할 수 있습니다.

 

docker-compose ps

 

만약 중지된 컨테이너까지 모두 보고자 할 경우에는,

'-a' 옵션을 넣어주면 됩니다.

 

docker-compose ps -a

 

8-9. 컨테이너 삭제

컨테이너를 삭제하고자 한다면 다음의 명령어로 삭제할 수 있습니다.

 

docker-compose rm <컨테이너명>

 

만약 모든 컨테이너를 삭제하고자 한다면, '컨테이너명'없이,

rm만 넣어주면 됩니다.

 

8-9. exec

'exec'명령어를 사용하면,

실행 중인 서비스의 컨테이너 내에서 명령을 실행할 수 있습니다.

 

아래는 docker-compose exec 명령어를 사용하여,

nodejs 서비스의 컨테이너 내에서 Bash 셸을 실행하는 예제입니다:

 

docker-compose exec nodejs /bin/bash

 

컨테이너 내부의 OS버전명도 확인할 수 있는데요.

대부분의 linux 배포판에서 아래 명령어를 통해서 os버전과 정보를 얻을 수 있습니다.

 

cat /etc/*release

 

실행해보면 아래와 같은 모습을 볼 수 있습니다.

 

 

만약, 특정 volume 내부에 접근해서 'ls -la'명령어를 실행시키기 위해서는 아래 명령어를 실행하면 됩니다.

물론 위에서와 같이 bash쉘에 접근해서 거기서 'ls -la'명령어를 실행시켜도 됩니다.

 

docker-compose exec nodejs ls -la </path/to/volume>

 

9. nginx 컨테이너 재시작하기

앞단의 웹서버로서 동작하는 nginx의 환경변수를 바꾸고, 재시작을 해야하는 경우가 있습니다.

docker-compose를 이용해서 nginx 컨테이너를 재시작하는 방법은 2가지가 있습니다.

  1. docker-compose명령어로 nginx컨테이너만 재시작하기
  2. nginx 컨테이너 내부에 접근해서 nginx 재시작 명령어 실행하기

2가지 경우를 알아 보도록 하겠습니다.

 

9-1. 컨테이너 재시작하기

2번째 방법은 심플하게, container를 다시 띄우는 것입니다.

nginx컨테이너만 중단했다가 다시 띄우는 것 입니다.

다만, 컨테이너가 다시 올라가는 동안, 서비스 중단이 발생하게 됩니다.

 

docker-compose build --no-cache nginx
docker-compose restart nginx

 

 

9-2. 컨테이너 내부에 접근해서 nginx 재시작 명령어 실행하기

컨테이너 내부에 접근해서 nginx 재시작 명령어를 실행시키는 방법입니다.

nginx는 서비스가 무중단되면서 변경사항이 반영되도록 하는 재시작 명령어를 가지고 있기 때문입니다.

 

docker-compose exec nginx nginx -s reload

 

다만 이 방법에는 제약이 있는데요.

docker-compose.yml파일에서,

nginx의 환경설정 파일인 conf파일이 volume에 마운트 되어있어야 합니다.

예를 들면 아래와 같습니다.

...
volumes:
  - ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf

 

이렇 게 해야하는 이유는,

외부의 파일과 컨테이너 내부의 환경설정파일이 동기화 되어 있어야,

그 변경사항을 바탕으로 컨테이너 내부에서 nginx 명령으로 재시작할 수 있기 때문입니다.

 

컨테이너 내부에 들어가서 위 명령어를 사용할 수도 있구요.

컨테이너 외부에서 아래 명령어로 실행시킬 수도 있습니다.

 

docker exec <컨테이너 이름 또는 id> chmod 600 <파일명과 패스>

 

다만, 이 방식을 사용할 때 알아두어야 할 점이 있는데요.

바로바로 변경사항이 반영된다는 점은 장점이지만,

설정 파일을 변경하는 즉시 변경 사항이 nginx에 적용되므로 적용 시점을 조절하기 어려워질 수 있습니다.

 

nginx는 웹 서버 및 리버스 프록시로 사용되므로, 

설정 변경이 즉시 적용되면 예기치 않은 동작이 발생할 수 있습니다. 

특히 실제 서비스 중인 프로덕션 환경에서는 설정 변경으로 인한 서비스 중단이나 오동작이 발생할 수 있고요.

 

nginx 설정 파일을 볼륨에 마운트하는 방식은 개발 환경에서 사용하는 것이 좋습니다.

이런 환경에서는 설정 변경을 빠르게 적용하고 테스트할 수 있는 것이 도움이 되기 때문입니다.

 

9-3. 환경설정 변경하기 전 오류 확인하기

nginx의 환경설정파일에 문법적 오류가 없는지 확인해주는 명령어가 있는데요.

변경사항을 적용하기 전에 아래 명령어를 실행해서 미리 체크하는 것이 좋습니다.

docker-compose exec nginx nginx -t

 

 

10. NPM모듈 설치시 Docker-container에 반영하기

새로운 이미지를 빌드하기 위해서, 아래 명령어로 빌드해 줍니다.

 

docker-compose build --no-cache

 

up명령으로 기존 컨테이너를 새로운 컨테이너로 대체해 줍니다.

이 과정에서 자동으로 기존 컨테이너를 stop하고, 대체시켜주므로, 
서비스가 멈추는 시간을 최소화 시켜줍니다.

즉, 사용자가 수동으로 컨테이너를 멈추고(stop)다시 시작할 필요가 없다는 뜻 입니다.

 

docker-compose up

 

 

11. Docker-compose 사용관련 팁

11-1. Github와 Docker hub 연동 방법

GitHub와 Docker Hub를 연동하는 과정은 다음과 같습니다:

  1. GitHub 리포지토리 준비: GitHub에서 코드를 관리합니다.
  2. Docker Hub 계정 설정: Docker Hub에서 무료 계정을 생성하고, GitHub 계정과 연동 설정을 합니다.
    • 이를 통해 GitHub에서 Docker Hub로 이미지를 자동으로 전송할 수 있습니
  3. Dockerfile 작성: Dockerfile을 작성하여, 어떻게 이미지를 빌드할지 정의합니다.
  4. 자동 빌드 설정: Docker Hub에서 GitHub 리포지토리를 연동하고, 자동 빌드 옵션을 설정합니다.
    • 이 과정에서 Docker Hub는 GitHub 리포지토리의 변경 사항을 감지하고,
      변경이 발생할 때마다 자동으로 Docker 이미지를 빌드합니다.
  5. 코드 변경 및 푸시: GitHub에 코드 변경 사항을 푸시하면, 
    Docker Hub에서 자동으로 새 Docker 이미지를 빌드하고 저장소에 저장합니다.

 

이렇게 GitHub와 Docker Hub를 연동하면, 코드 변경을 GitHub에서 관리하면서도 Docker 이미지를 자동으로 빌드하고 관리할 수 있습니다. 이는 개발자가 소프트웨어를 보다 효율적으로 관리하고 배포할 수 있는 강력한 도구입니다.

 

11-2. '.dockerignore'파일의 위치

docker-compose를 이용할 경우, 

Dockerfile은 각 서비스 폴더아래에 위치하게 됩니다.

아래에서는 nginx와 nodejs를 사용하고 있는데요.

사용하는 Dockerfile의 위치는 각각의 폴더 밑에 존재합니다.

이 때, '.dockerignore'의 위치도 Dockerfile과 동일한 위치에 존재해야 합니다.

 

예를 들어, nodejs서비스의 경우, Dockerfile이 있는 곳에,

'.dockerignore'파일이 위치해서 'node_modules'폴더를 제외시켜 주지 않는다면,

로컬에 맞게 컴파일되고 설치된 모듈 파일들이,

다른 OS를 가진 컨테이너 내부에 복사되어 버리는 일이 발생할 수 있습니다.

 

version: '3.8'
services:
  nginx:
    restart: unless-stopped
    build:
      context: ./nginx
      dockerfile: Dockerfile
    ...

  nodejs:
    build:
      context: ./nodejs
      dockerfile: Dockerfile
    ...

 

11-3. 컨테이너 내의 특정파일 복사하기

docker 컨테이너 내부의 파일을 복사하는 것에 대해서는 아래 글에서 다루고 있으니,

이름 참조해 주세요.

>> Docker 컨테이너 내부의 파일 복사하기: cp 명령어 사용법

 

11-4. 로깅파일 폴더 Volume 마운트

winston같은 모듈을 이용해서 NodeJS의 로깅을 할 경우,

Volume마운트를 해 주어야 합니다.

이 때, Nginx와 같이 사용하고 있다면,

시스템 레벨의 액세스 에러로그는 Nginx가,

앱의 디테일한 로그는 winston같은 모듈이 담당해주면 되겠지요.

 

docker-compose의 volumes설정도 아래와 같이 해주면 됩니다.

 

version: '3.8'
services:
  nginx:
    ...
    volumes:
      - ./nginx/logs:/var/log/nginx

  nodejs:
    ...
    volumes:
      - ./nodejs:/usr/src/app      # 소스 코드 마운트
      - /usr/src/app/node_modules  # 컨테이너의 node_modules 익명 볼륨
      - ./logs:/usr/src/app/logs   # winston 로그 파일 마운트
    command: ["npm", "run", "dev"]

 

11-5. 개발용 Dockerfile

프로젝트 root폴더에 존재하는 Dockerfile이 배포를 위한 도커파일이라고 할 수 있는데요.

개발용으로 따로 폴더를 하나 만들어서,

개발용 Dockerfile을 만들어서 운영할수도 있습니다.

  • Dockerfile.dev

 

11-6. yaml 파일 작성 레퍼런스

docker-compose의 yaml파일 작성시 공식 레퍼런스 링크는 아래와 같습니다.

>> https://docs.docker.com/compose/compose-file/

 

docker-compose의 기본적인 부분들을 이용하여,

NodeJS와 NGINX의 두개의 컨테이너를 생성하게 하도록 해 보았는데요.

추후 더 좋은 내용들에 대해서는 이 글을 통해 업데이트 하도록 하겠습니다.

 

11-7. nginx Docker 이미지 내부의 환경설정 파일 내용 보기

Nginx 도커 이미지에 기본적으로 들어가 있는 환경설정 파일은 아래와 같이 볼 수 있습니다.

 

docker run --rm nginx:1.25.4 cat /etc/nginx/nginx.conf

 

위 명령어는 nginx:1.25.4 이미지를 사용하여 임시 컨테이너를 생성하고,

'/etc/nginx/nginx.conf' 파일의 내용을 출력한 다음,

컨테이너를 자동으로 제거합니다(--rm 옵션).

 

이런 방식으로 간단히 파일을 리스팅하는 것도 가능합니다.

 

docker run --rm nginx:1.25.4 ls -la /etc/nginx/conf.d

 

11-8. 생성된 Docker image확인하고 삭제하기

docker-compose를 통해서 이미지들이 생성되고, 마운트되는데요.

이 이미지들이 남아서 하드에 생각보다 많은 용량을 차지할 수 있습니다.

 

먼저 아래 명령어로 남아있는 이미지들을 확인해 주고요.

 

docker images

 

아래 명령어로 이미지를 삭제해 주면 됩니다.

'rmi'는 해당 이미지들을 삭제해주는 명령어 입니다.

 

docker rmi -f <이미지 ID>

 

남아있는 도커 이미지들을 모두 삭제하려면 아래 명령어를 사용해주면 됩니다.

여기서 사용된 'docker images -q'는 모든 이미지의 ID를 쿼리해 주는 명령어 입니다.

 

docker rmi $(docker images -q)

 

 

아래 명령어는 아주 강력한 명령어로,

사용하지 않는 컨테이너, 네트워크, 이미지 등을 모두 정리해 줍니다.

 

docker system prune -a

 

실행하면 아래와 같은 경고가 나오는 것을 볼 수 있습니다.

 

 

 

이제 Docker-compose를 이용해서, NodeJS와 Nginx를 설치하고,

도커 컨테이너를 띄워서 서비스를 실행하는 방법까지 정리해 보았습니다.

 

이상으로 Docker-compose를 이용해서 NodeJS와 Nginx를 설치하는 방법에 대해서 정리해 보았습니다.

728x90