프로그래밍/Docker

Docker 사용방법 실습 - Node.js / MySQL / Nginx 사용 서비스 만들기

Lou Park 2022. 1. 6. 00:46

Docker series

[ - ] Docker 개념정리 포스팅 [https://jizard.tistory.com/322]

[ x ] Docker 실습 포스팅

 

Project File Tree

├── app
│   ├── Dockerfile
│   ├── index.js
│   ├── node_modules
│   ├── package-lock.json
│   └── package.json
├── docker-compose.yml
├── mysql
│   └── Dockerfile
└── nginx
    ├── Dockerfile
    └── default.conf

*Github에서 전체 프로젝트 소스보기 [https://github.com/lx5475/Docker-]

 

Docker를 사용하여 독립된 환경에서 mysql와 nginx를 구동하는 Node.js 앱 환경을 구성해보도록 하자. Node.js 앱의 내용은 mysql 연결이 잘 되는지 확인하는 코드다. app/index.js에서 다음과 같은 정보로 Mysql 커넥션 풀을 생성하고, 커넥션을 얻을 수 있다면 "Connection success"를 출력하고, 그렇지않으면 에러 메세지를 출력한다.

app.get('/db_check', (req, res) => {
  if (!pool) {
    var pool = mysql.createPool({
      host: "docker-mysql",
      user: "root",
      password: "nodejs",
      database: "db_sample",
      port: 3306
    });
  }
  pool.getConnection((err, connection) => {
      if (err) {
        console.log(err);
        res.send(err.message);
      } else {
          connection.release();
          res.send("Connection success")
      }
  });
})

위 파일 트리에 보면 app, mysql, nginx 폴더마다 Dockerfile들이 보인다. Dockerfile은 일종의 매크로를 적어둔 것과 같고, 이를 이용해 Docker 이미지를 생성할 수 있다.

 

 

Dockerfile 명령문 알아보기

Dockerfile 예시를 보면서 간단한 Docker DSL을 알아보자. 아래는 app/Dockerfile의 내용이다.

# Base 이미지를 nodeJS alpine 버전으로 사용
FROM node:alpine

# 작업 디렉토리 전환
WORKDIR /usr/src/app

# local 컴터에있는  package.json 파일을 현재 워킹 디렉토리에 복사 
COPY package*.json ./

# local machine 에서 npm install 실행 
RUN npm install

COPY . .

EXPOSE 5000

ENTRYPOINT ["node"]
CMD ["index.js"]

 

FROM

이미지는 마치 웨하스처럼 레이어를 쌓아가면서 중첩하여 사용할 수 있는데, 가장 기본이 되는 베이스 이미지를 지정해주기 위해 FROM 명령문을 사용한다. 아래와 같은 용법으로 사용할 수 있다.

FROM <이미지>:<버전>
FROM <이미지> # 생략시 latest이다.

 

WORKDIR

작업 디렉토리를 전환하는 것이다. WORKDIR로 디렉토리를 전환하면 이후 명령어는 모두 해당 디렉토리를 기준으로 수행된다.

 

COPY

호스트 컴퓨터에있는 파일을 Docker 이미지 내의 파일시스템으로 복사한다.

COPY <host src> <dest>

EXPOSE

컨테이너가 리스닝할 포트를 설정한다. 나중에 docker compose YAML 설정을 통해서 포트를 설정해도 되므로 생략해도 된다.

 

CMD

컨테이너를 띄울때 실행할 커맨드 또는 ENTRYPOINT 명령문의 커맨드에 인자를 덧붙일때 사용할 수 있다. RUN 명령문도 비슷하게 명령어를 실행할때 사용되는데, 둘의 차이를 기억하자.

- RUN: 이미지 빌드시 항상 실행됨

- CMD: 컨테이너 생성시에만 실행됨

CMD ["node", "index.js"] # 다음 문장과 동일한표현 = node index.js

 

ENTRYPOINT

사실 예제에서 CMD \["node", "index.js"\]처럼 해주어도 되지만 굳이 ENTRYPOINT를 설정해준 이유가 있다. ENTRYPOINT 명령문은 컨테이너가 실행될때 같이 실행되고, 컨테이너가 종료될때 같이 종료되기 때문이다. Node.js 앱을 위한 컨테이너이기 때문에 컨테이너와 애플리케이션의 생명주기를 같이 맞춰주어야 할 필요성이있다.

Dockerfile 주요 명령문을 알아보았으니 나머지 Dockerfile 내용들도 대충 어떤일을 하는 것인지 알 수 있을 것이다.

========= mysql/Dockerfile ========= 
FROM mysql
CMD ["mysqld"]
========= nginx/Dockerfile ========= 
FROM nginx
COPY default.conf /etc/nginx/conf.d/default.conf

 

Dockerfile로 이미지 만들기

우리는 Docker compose를 이용할 것이지만 Docker runbuild도 알아둘 필요성이 있기에 짚고넘어간다.

docker build 명령어로 louloulou라는 Tag 이름으로 현재 디렉토리에 위치한 Dockerfile을 토대로 이미지를 생성하는 명령어다. Dockerfile 내용은 위에서 본 app/Dockerfile과 같다.

docker build -t louloulou .

 

이미지가 생성된 후 아래처럼 만들어진 이미지를 볼 수 있다. (왤케 용량커?!ㅋㅋㅋㅋㅋ)

$ docker images
REPOSITORY TAG       IMAGE ID       SIZE
louloulou  latest    000050dcd617   1.01GB

 

이제 이 이미지를 실행시켜 컨테이너가 동작하도록 해보자. localhost:8080에 들어가면 정상적으로 Express 작동되는 중...

$ docker run -dp 8080:5000 louloulou
2933930471f63c81dcc3040d472748efbe5ebeb1ae6e03647468073135cb9c4f

언제까지 헬로월드

이제 진행중인 container 목록에 방금 만든 컨테이너가 뜬다.

$ docker ps
CONTAINER ID   IMAGE     STATUS       PORTS                  
2933930471f6   louloulou Up 2 minutes 0.0.0.0:8080->5000/tcp

얼마든지 멈추고, 얼마든지 삭제할 수 있다.

$ docker stop 2933930471f6
2933930471f6 # 스톱~

$ docker rm 2933930471f6
2933930471f6 # 삭제~

 

Nginx 세팅

크게 특별한 세팅은 없다. 하지만 proxy\_pass를 nodeserver라는 이름의 host와 5000번 port를 사용했다는 것을 눈여겨보면된다.

server {
    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_pass http://nodeserver:5000;
    }
}

어벤져스 어셈블, Docker Compose!

매번 변화가있을때마다 각각의 Dockerfile을 통해 이미지를 만들필요없이 docker-compose.yml 설정파일 하나를 만들어두면 일괄적으로 생성할 수 있다. 세부 설정들도 가능한데, 같이 알아보자.

version: "3.8"
services:
    mysql:
        container_name: "nodeserver-mysql"
        restart: unless-stopped
        hostname: "docker-mysql"
        build:
            context: ./mysql
        ports:
            - "6603:3306"
        environment:
            - MYSQL_ROOT_PASSWORD=nodejs
    nodeserver:
        container_name: "nodeserver-app"
        restart: unless-stopped
        depends_on:
            - mysql
        build:
            context: ./app
        ports:
            - "5000:5000"
    nginx:
        container_name: "nodeserver-nginx"
        restart: always
        build:
            context: ./nginx
        ports:
            - "80:80"

가장 위쪽에 version에는 스키마 버전을 정의해야한다. 여기에서 현재 스키마 버전과 호환성 매트릭스를 확인 할 수 있는데 대부분의 경우 가장 최신버전을 사용하는 것이 좋다.

 

services에는 서비스 항목과 컨테이너 이미지를 정의한다.

 

- container_name: 만들어질 컨테이너 이름을 정의

- hostname: 컨테이너 내의 hostname이다. 컨테이너간의 통신에서 DNS 처럼 쓰인다. 위에서 index.js에서 mysql 연결시 "docker-mysql"이라는 hostname을 사용하는 것을 볼 수 있다.

- restart: Docker가 종료되었을때 해당 컨테이너가 실행될지 옵션이다. 많이 쓰이는 것은 no / unless-stopped / always. nginx는 대개 일부러 멈춰놓지는 않기때문에 always 옵션을 주었다.

  ㄴ **no**: 실행하지 않음

  ㄴ **unless-stopped:** 컨테이너를 명시적으로 멈추기전까지 항상 재시작

  ㄴ **always**: 항상 재시작

- build - context: docker build 명령을 실행할 디렉토리 경로다.

- ports: 앞쪽 포트는 호스트 컴퓨터의 포트, 뒷쪽 포트는 Docker 내부에서 사용하는 포트다. 앞쪽 포트를 6603으로 세팅해두면 호스트 컴퓨터에서 mysql -h127.0.0.1 --port 6603 명령어를 통해 접근 가능하다!

- environment: 환경변수를 지정해 줄 수 있다.

- depends_on: 서비스간의 종속성을 추가하여 순차적인 실행을 할 수 있도록 해준다. 여기에서는 mysql을 먼저 실행한 뒤에 nodeserver가 실행될 수 있도록 했다.

 

 

docker-compose.yml을 모두 작성했다면 이제 실행해볼 차례다. 여러 컨테이너를 생성해서 Docker 고래등에 적재(up)하고 하역(down)하는 것을 상상하면 이 명령어들이 좀 귀여워보일지도?!

# docker-compose.yml에 정의된 컨테이너를 한번에 생성하고 실행한다 (*올린다).
# -d 옵션은 백그라운드에서 띄우기 위해 넣는다.
# --build 옵션을 넣을 수도 있는데, 이 옵션이 있으면 캐시된 이미지를 체크하지않고 무지성으로 
# 새로 빌드를 하기때문에 소스가 수정되었을때 넣어주면 좋다.
$ docker-compose up -d 

# docker-compose.yml에 정의된 컨테이너를 한번에 정지시키고 삭제한다 (*내린다).
$ docker-compose down

# docker-compose.yml에 정의된 컨테이너 중 내려가있는 컨테이너를 올리기 위해서 사용한다.
$ docker-compose start <container_name>

# docker-compose.yml에 정의된 컨테이너 중 올라가있는 컨테이너를 내리기 위해서 사용한다.
$ docker-compose stop <container_name>

docker compose up --build -d 를 통해 성공적으로 컨테이너들을 올리고나서 docker ps를 때려보면 이렇게 성공적으로 컨테이너들이 올라간것이 보인다.

$ docker ps
CONTAINER ID   IMAGE                        PORTS                            
39a83afb462b   docker_practice_nodeserver   0.0.0.0:5000->5000/tcp           
cd258df8c429   docker_practice_mysql        33060/tcp, 0.0.0.0:6603->3306/tcp
0686be3171d4   docker_practice_nginx        0.0.0.0:80->80/tcp

 

걸어서 컨테이너 속으로

localhost/db_check으로 가서 mysql이 제대로 연결되었나 확인해보면 이렇게 오류 메세지가 뜬다. 말 그대로 db_sample이라는 데이터베이스가 없다는 것인데, 보통은 초기 설정 sql 을 같이 넣어서 이미지를 생성하지만 우리는 그러지 않았기 때문에 저렇게 오류가난다. Docker 컨테이너 내부로 들어가서 database를 직접 만들어보자.

$ docker exec -it <mysql 컨테이너의 ID> bash

 

명령어를 치면 컨테이너의 파일 시스템 속으로 들어갈 수 있다.

root@docker-mysql:/# mysql -u root -p
Enter password: nodejs

mysql> create database db_sample;

 

데이터베이스를 만든 직후 localhost/db_check을 새로고침하면 다음과 같이 제대로 연결되었다는 메세지가 뜬다!


참고자료

https://www.daleseo.com/dockerfile/

https://docs.microsoft.com/ko-kr/visualstudio/docker/tutorials/use-docker-compose

https://www.daleseo.com/docker-compose/