Review

개인적으로 도커의 핵심은 '컨테이너'라 생각한다. 왜냐하면 도커와 관련된 중요한 대부분의 기술은 컨테이너를 잘 활용하는 쪽으로 초첨을 맞추고 있다고 판단하기 때문이다. 그런데 대부분의 교재나 기사에서 도커 이미지와 관련된 내용부터 설명하는 것은 컨테이너에 필요한 파일과 설정값을 저장하는 기본적인 단위로 사용되며 해당 이미지의 작성이 컨테이너 활용의 초석이기 때문이다.

'Docker - 이미지와 컨테이너, 10분 정리'라는 글에서 파이썬 웹 프레임워크인 Django를 사용한 이미지 작성 방법을 소개했는데, 이번에는 Node.jsExpress.js를 사용한 웹 서비스를 사용해서 도커 이미지와 관련된 내용을 복습해보자.

도커 이미지를 작성하는 순서는 대략적으로 아래와 같다.

  1. Epxress.js를 사용한 웹 애플리케이션 작성
  2. Dockerfile 작성
  3. Dockerfile을 기반으로한 도커 이미지 작성
  4. Dockerfile 에러 발생시 수정
  5. 도커 이미지를 컨테이너로 실행
  6. 해당 컨테이너 실행 확인

1. Epxress.js를 사용한 웹 애플리케이션 작성

macOS의 경우 brew를 사용하여 node.js를 설치하고, 다른 운영체제의 경우 node.js 홈페이지를 확인하여 설치한다. python과 마찬가지로 node.js의 패키지 매니저인 npm을 사용해서 웹 애플리케이션을 위한 환경을 설정하고 준비해보자.

$ brew install nodejs
$ mkdir part-02 && cd part-02 && npm init -y
npm init -y
Wrote to /Users/sd/Works/HandsOn-Docker/part-02/package.json:

{
  "name": "part-02",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
$ npm install --save express

npm을 사용해서 express.js까지 설치가 완료되면 index.js 파일을 추가하여 아주 간단한 형태의 웹 애플리케이션을 작성한다.

// index.js 작성
const express = require('express')
const app = express()

app.get('/', (req, res) => {
  res.send('Hello Docker!')
})

app.listen(8080, () => {
  console.log('Listening on port 8080')
})

package.json 파일의 'scripts' 부분을 수정해서 'serve'를 추가한다.

// package.json 수정
...
"scripts": {
    "serve": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
},
...

커맨드라인에서 npm run serve 로 웹 서비스가 제대로 작동하는지 확인하자!

2. Dockerfile 작성

Dockerfile은 아래와 같이 작성한다. alpine 리눅스를 베이스 이미지로 사용하고, 해당 이미지에 npm install 명령어를 사용해서 필요한 패키지를 설치한 후 npm run serve 명령어로 웹 서비스를 실행하는 구조로 되어있다.

아래를 참고해서 Dockerfile을 작성하자.

// Dockerfile
FROM alpine
RUN npm install
CMD ["npm", "run", "serve"]

3. Dockerfile을 기반으로한 도커 이미지 작성

앞서 만든 Dockerfile을 사용해서 도커 이미지를 작성해보자.

$ docker build .
Sending build context to Docker daemon  1.974MB
Step 1/3 : FROM alpine
latest: Pulling from library/alpine
8e402f1a9c57: Pull complete
Digest: sha256:644fcb1a676b5165371437feaa922943aaf7afcfa8bfee4472f6860aad1ef2a0
Status: Downloaded newer image for alpine:latest
 ---> 5cb3aa00f899
Step 2/3 : RUN npm install
 ---> Running in e493c187d4d1
/bin/sh: npm: not found
The command '/bin/sh -c npm install' returned a non-zero code: 127

이미지 생성 과정에서 /bin/sh: npm: not found 에러가 발생한다. 'npm'이 없어서 에러가 발생하는 것으로 alpine에 node.js가 설치되어 있지 않아서 이런 문제가 발생한 것이다. 특정 베이스 이미지에 필요한 패키지가 없어서 발생하는 문제는 필자와 같은 초보 사용자에게 흔히 발생하는 문제라 할 수 있다.

4. Dockerfile 에러 발생시 수정

이런 문제를 해결하기 위한 방법으로 1) node.js를 alpine 이미지에 설치하는 것과 2) node.js를 기본적으로 제공하는 베이스 이미지를 사용하는 방법이 있다. 이번 실습에서는 node.js를 제공하는 이미지를 사용하도록 변경해보겠다.

// Dockerfile 수정
FROM node
RUN npm install
CMD ["npm", "run", "serve"]

Dockerfile 수정 후 도커 이미지를 새롭게 빌드하면 아래와 같이 뭔가 애매하게 빌드되는 것을 확인할 수 있다.

$ docker build .
docker build .
Sending build context to Docker daemon  1.974MB
Step 1/3 : FROM node
latest: Pulling from library/node
22dbe790f715: Pull complete
0250231711a0: Pull complete
6fba9447437b: Pulling fs layer
6fba9447437b: Pull complete
c2b4d327b352: Pull complete
270e1baa5299: Pull complete
08ba2f9dd763: Pull complete
edf54285ab13: Pull complete
4d751c169397: Pull complete
Digest: sha256:7991efd8c404b230c7dc40f114fb89e148fd79b0e6819027747f8e616782e1fa
Status: Downloaded newer image for node:latest
 ---> 9ff38e3a6d9d
Step 2/3 : RUN npm install
 ---> Running in 24de758ac11f
npm WARN saveError ENOENT: no such file or directory, open '/package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '/package.json'
npm WARN !invalid#2 No description
npm WARN !invalid#2 No repository field.
npm WARN !invalid#2 No README data
npm WARN !invalid#2 No license field.

up to date in 1.012s
found 0 vulnerabilities

Removing intermediate container 24de758ac11f
 ---> b50a632cd5ff
Step 3/3 : CMD ["npm", "serve"]
 ---> Running in ebb895472268
Removing intermediate container ebb895472268
 ---> 422678182ff2
Successfully built 422678182ff2

제대로 빌드되었는지 확인하기 위해서 docker iamges명령어로 사용해서 확인해보면 이미지가 빌드되어 있다. 하지만 이런 간단한 애플리케이션을 생성하는데 904MB가 필요하다는 점도 놀랍고, 중간에 npm WARN saveError ENOENT: no such file or directory, open '/package.json'에러가 발생했기 때문에 해당 도커 이미지가 제대로 작성된 것으로 판단된지 않는다.

$ docker images
docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
<none>              <none>              422678182ff2        About a minute ago   904MB
node                latest              9ff38e3a6d9d        2 days ago           904MB

일단 용량문제부터 해결해보자. 베이스 패키지의 용량이 작은 것을 선택하기 위해서 alpine OS에 node를 설치해서 용량을 줄인 node:alpine 으로 베이스 이미지를 변경(해당 내용은 https://hub.docker.com/_/node/를 참고하자.)한다.

// Dockerfile 수정
FROM node:apline
RUN npm install
CMD ["npm", "run", "serve"]

그리고 'npm' 사용시 에러가 발생하는 부분은 package.json이 베이스 이미지에 포함되어 있지 않기 때문이다. 따라서 ADD 명령과 WORKDIR을 사용해서 파일을 복사하고, 해당 명령어를 실행하는 디렉토리를 지정한다.

// Dockerfile 수정
FROM node:apline
ADD . /app
WORKDIR /app
RUN npm install
CMD ["npm", "serve"]

이제 새롭게 이미지를 빌드해보자.

$ docker build .
Sending build context to Docker daemon  1.974MB
Step 1/5 : FROM node:alpine
alpine: Pulling from library/node
8e402f1a9c57: Pull complete
5e2195587d10: Pull complete
6f595b2fc66d: Pull complete
Digest: sha256:6da4878fc63b98ef5fde771b1f05fec9c796e49d249816fe8d544f336ae89d80
Status: Downloaded newer image for node:alpine
 ---> a13f3a3ed57f
Step 2/5 : ADD . /app
 ---> 913fd7d01680
Step 3/5 : WORKDIR /app
 ---> Running in 2dca1a650b0d
Removing intermediate container 2dca1a650b0d
 ---> 054645992ae1
Step 4/5 : RUN npm install
 ---> Running in 17e20fa94d8c
npm WARN part-02@1.0.0 No description
npm WARN part-02@1.0.0 No repository field.

audited 121 packages in 0.631s
found 0 vulnerabilities

Removing intermediate container 17e20fa94d8c
 ---> 5fbf735dffda
Step 5/5 : CMD ["npm", "serve"]
 ---> Running in cacf59a8312e
Removing intermediate container cacf59a8312e
 ---> 39c9b83c44d7
Successfully built 39c9b83c44d7

생성된 도커 이미지를 확인해보면 77MB로 용량이 많이 축소되었음을 확인할 수 있다. 이미지 생성이 올바로 되었지만 Tag가 존재하지 않아서 불편하다. 해당 이미지를 삭제(docker rmi)하고 별도의 태그를 붙여서(`docker build -t TagName) 다시 이미지를 생성하자.

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
<none>              <none>              39c9b83c44d7        About a minute ago   77MB
...

// 도커 이미지 삭제
$ docker rmi 39c9b83c44d7
Deleted: sha256:39c9b83c44d75396b02dd369a982fa6e3c8073e06261e8ac0cbbf80d91491c98
Deleted: sha256:5fbf735dffda1a6a6444b5a7205a8e0da8cfe6cae410d3533fa385fe26eaf865
Deleted: sha256:c52cebf34af3d8a246af1e1ab81c57e737cf98cded3c960502b3a081e159aa3c
Deleted: sha256:054645992ae1bd77f4df6f53b65c35b560f4bad5f14ffea07d6b1ca1f6f61010
Deleted: sha256:913fd7d01680edbc9860d9aa84c30eb0cf25d8847d74fcd0d8f923afa032f576
Deleted: sha256:f603e27156194e3adcaa1474f07aebe0b5bdfa9e2bfd8910f0edf1b9f629ff19

$ docker build -t sigmadream/nodesimpleweb .

5. 도커 이미지를 컨테이너로 실행

도커 이미지를 컨테이너로 실행하는 방법은 매운 간단하다. 중요한 점은 웹 서비스를 도커 외부에서 접속할 수 있도록 포트를 매핑하는 명령어인 -p를 빼먹지 말아야 한다.

$ docker run -it -p 8080:8080 --rm sigmadream/nodesimpleweb

> part-02@1.0.0 serve /app
> node index.js

Listening on port 8080

6. 해당 컨테이너 실행 확인

브라우져나 curl 등을 사용해서 컨테이너의 실행을 확인하자.

$ curl localhost:8080
Hello Docker!

Docker Compose

대부분의 서비스는 하나의 애플리케이션으로 구성되지 않는다. 웹 애플리케이션의 경우 java, python, node.js를 사용해서 웹 애플리케이션을 작성한다고 해도 데이터베이스는 별개의 제품을 사용하는 경우가 대부분이다.

도커 컨테이너는 앞서 설명했듯이 독립적으로 실행되기 때문에 여러 애플리케이션과 연동해서 사용하기 위해선 별도의 방법이 필수적이다. 앞선 예에서 컨테이너와 외부 서비스를 연결하는데 사용한 -p 옵션이 대표적인 방법이다.

하지만 도커 허브에서 제공하는 다양한 도커 이미지를 사용한 컨터이너와 연계해서 시스템을 구성하고자 할 때, 어떻게 해야 할까? 포트 연결만으로 서비스를 구성할 수 없을 때 우리가 선택할 수 있는 방법이 뭐가 있을까?

가장 단순하고 직관적인 방법으로 컨테이너 두 개를 연결하는 것으로 도커에서 --link 옵션을 제공한다.

간단한 예제를 통해서 --link 사용법을 알아보자. 먼저 psql을 도커를 사용해서 백그라운드에서 실행하자.

$ docker run -d --name db postgres
Unable to find image 'postgres:latest' locally
latest: Pulling from library/postgres
f7e2b70d04ae: Already exists
027ad848ac9c: Pull complete
7c040ef66643: Pull complete
b891079ad2eb: Pull complete
cb64a97e42d9: Pull complete
1b88625f7d89: Pull complete
a6ac0b663e77: Pull complete
594497f0a694: Pull complete
ca7201b6a21f: Pull complete
48cdfad3f2fd: Pull complete
912fb62e7390: Pull complete
1e6365c64609: Pull complete
eda829b73ec7: Pull complete
1dafb86732d6: Pull complete
Digest: sha256:0babc396eccb4e05c3ccf499a5eed9475486874b6bf4e3bb65c8c0bea0980e9f
Status: Downloaded newer image for postgres:latest
390a3f775c76bbcbe96a17cf44c20ba8af9f95a7d88c45fbfde5c078fb99079d

백그라운드에서 실행하고 있는 psql 서버에 접속할 수 있도록 --link 옵션을 적용해서 psql에 연결하자.

$ docker run -it --name os --link db:pg ubuntu /bin/bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
6cf436f81810: Pull complete
987088a85b96: Pull complete
b4624b3efe06: Pull complete
d42beb8ded59: Pull complete
Digest: sha256:7a47ccc3bbe8a451b500d2b53104868b46d60ee8f5b35a24b41a86077c650210
Status: Downloaded newer image for ubuntu:latest
root@4c8437e6f23b:/#

두 컨테이너가 연결되어 있는지 확인해보기 위해서 psql의 환경 설정을 출력해보자.

root@4c8437e6f23b:/# set | grep PG
PG_ENV_GOSU_VERSION=1.11
PG_ENV_LANG=en_US.utf8
PG_ENV_PGDATA=/var/lib/postgresql/data
PG_ENV_PG_MAJOR=11
PG_ENV_PG_VERSION=11.2-1.pgdg90+1
PG_NAME=/os/pg
PG_PORT=tcp://172.17.0.2:5432
PG_PORT_5432_TCP=tcp://172.17.0.2:5432
PG_PORT_5432_TCP_ADDR=172.17.0.2
PG_PORT_5432_TCP_PORT=5432
PG_PORT_5432_TCP_PROTO=tcp

도커에서 제공하는 Ubuntu 베이스 이미지의 경우 ping이 포함되어 있지 않기 때문에 iputils-ping을 설치하자.

$ root@4c8437e6f23b:/# apt install iputils-ping
Reading package lists... Done
Building dependency tree
Reading state information... Done

이제 psql의 연결 주소(PG_PORT_5432_TCP_ADDR=172.17.0.2)로 ping을 보내면 응답하는 것을 확인할 수 있다.

root@4c8437e6f23b:/# ping $PG_PORT_5432_TCP_ADDR
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.140 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.064 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.053 ms
64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.079 ms
^C
--- 172.17.0.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3128ms
rtt min/avg/max/mdev = 0.053/0.084/0.140/0.033 ms

결론적으로 말해서 두 컨테이너는 --link라는 옵션을 사용해서 연결할 수 있다. 하지만 이런 형태로 도커 컨테이너를 연결해서 시스템을 구성하는 것은 많은 피로를 유발한다.

도커에서 제공하는 컨테이너를 손쉽게 연결해주는 방법이 필수적이다. 하나의 서비스를 구성하기 위해서 필요한 여러개의 컨테이너를 각각의 서비스로 정의하고 컨테이너를 하나의 묶음 단위로 관리할 수 있는 방법을 제공해야 한다.

도커에서 기본적으로 제공하는 것은 도커 컴포즈(Docker Compose)이다. 도커 컴포즈는 여러 컨테이너의 실행 환경과 설정 내용을 도커 컴포즈 설정 파일(docker-compose.yml)에 기록해서, 이 파일을 순차적으로 적용하여 컨테이너를 생성하는 방식이다.

Docker Compose 실습

만약 node.js로 만든 웹 애플리케이션에 redis를 사용해서 접속자를 출력하는 웹 애플리케이션을 작성하자. 앞서 만든 복습 예제를 활용해도 되지만, 간단한 웹 애플리케이션이기 때문에 프로젝트를 다시 만들어보자. Express.js와 redis 접속을 위한 node.js 클라이언트를 설치하자.

$ mkdir part-03 && part-03 && npm init -y
Wrote to /Users/sd/Works/HandsOn-Docker/part-03/package.json:

{
  "name": "part-03",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

$ npm install --save express redis

npm을 사용해서 실행할 수 있도록 package.json 파일을 수정하자.

...
"scripts": {
  "serve": "node index.js",
  "test": "echo \"Error: no test specified\" && exit 1"
},
...

index.js 파일을 작성하자.

// index.js
const express = require('express')
const redis = require('redis')

const app = express()
const client = redis.createClient();
client.set('visits', 0)

app.get('/', (req, res) => {
  client.get('visits', (err, visits) => {
    res.send('현재 접속자 수는 ' + visits + ' 입니다')
    client.set('visits', parseInt(visits) + 1)
  })
})

app.listen(8080, () => {
  console.log('Listening on port 8080')
})

이제 실행해보자. 실행과 동시에 redis에 연결할 수 없다는 에러가 발생한다.

$ npm run serve

> part-03@1.0.0 serve /Users/sd/Works/HandsOn-Docker/part-03
> node index.js

Listening on port 8080
events.js:173
      throw er; // Unhandled 'error' event
      ^

Error: Redis connection to 127.0.0.1:6379 failed - connect ECONNREFUSED 127.0.0.1:6379
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1083:14)
Emitted 'error' event at:
    at RedisClient.on_error (/Users/sd/Works/HandsOn-Docker/part-03/node_modules/redis/index.js:406:14)
    at Socket.<anonymous> (/Users/sd/Works/HandsOn-Docker/part-03/node_modules/redis/index.js:279:14)
    at Socket.emit (events.js:197:13)
    at emitErrorNT (internal/streams/destroy.js:82:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:50:3)
    at processTicksAndRejections (internal/process/next_tick.js:76:17)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! part-03@1.0.0 serve: `node index.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the part-03@1.0.0 serve script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/sd/.npm/_logs/2019-03-10T17_57_51_391Z-debug.log

redis가 설치되어 있지 않아서 에러(Error: Redis connection to 127.0.0.1:6379 failed)가 발생한 것이기 때문에 brew를 사용해서 redis를 설치하고, redis를 서비스(데몬)로 실행하자(다른 운영체제의 경우 redis 홈페이지를 참고해서 설치하고 실행하자).

$ brew install redis
$ brew services start redis

다시 실행해보면 잘 작동하는 것을 확인할 수 있다.

$ npm run serve

> part-03@1.0.0 serve /Users/sd/Works/HandsOn-Docker/part-03
> node index.js

Listening on port 8080

$ curl localhost:8080
현재 접속자 수는 0 입니다

$ curl localhost:8080
현재 접속자 수는 1 입니다

$ curl localhost:8080
현재 접속자 수는 2 입니다

$ curl localhost:8080
현재 접속자 수는 3 입니다

너무 잘되기 때문에 Dockerfile을 작성해서 도커 이미지로 만들자.

//Dockerfile
FROM node:alpine

ADD . /app
WORKDIR /app

RUN npm install

CMD ["npm", "run", "serve"]

해당 이미지를 node-app이란 태그로 이미지를 작성하고, 해당 이미지를 컨테이너로 실행한다.

$ docker build -t node-app .

-p를 사용해서 컨테이너 내부와 외부를 연결하고 실행하자.

$ docker run -it -p 8080:8080 --rm node-app

> part-03@1.0.0 serve /app
> node index.js

Listening on port 8080
events.js:173
      throw er; // Unhandled 'error' event
      ^

Error: Redis connection to 127.0.0.1:6379 failed - connect ECONNREFUSED 127.0.0.1:6379
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1083:14)
Emitted 'error' event at:
    at RedisClient.on_error (/app/node_modules/redis/index.js:406:14)
    at Socket.<anonymous> (/app/node_modules/redis/index.js:279:14)
    at Socket.emit (events.js:197:13)
    at emitErrorNT (internal/streams/destroy.js:82:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:50:3)
    at processTicksAndRejections (internal/process/next_tick.js:76:17)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! part-03@1.0.0 serve: `node index.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the part-03@1.0.0 serve script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2019-03-10T17_13_51_390Z-debug.log

redis가 서비스로 작동하지만 redis 연결 설정에서 오류가 발생한다. 다시 말하지만 도커 컨테이너는 독립적으로 실행하기 때문에 외부 서비스와 연동에 세심한 주의를 기울여야 한다.

이제 도커 컴포즈를 설정 파일을 사용해서 두 컨테이너를 연결해보자.

//docker-compose.yml
version: '3'
services:
  redis-server:
    image: 'redis'
  node-app:
    build: .
    ports:
      - "8080:8080"

services 항목에 redis-servernode-app을 설정하였다. index.js에서 redis 접속 주소(redis-server)와 포트를 설정한다.

// const client = redis.createClient();
const client = redis.createClient({
  host: 'redis-server',
  port: 6379
});

docker-compose up 명령어를 사용해서 도커 컴포즈를 실행해보자. 장황한 로그가 지나고 나면 해당 서비스가 제대로 작동함을 확인할 수 있다.

$ docker-compose up
Creating network "part-03_default" with the default driver
Pulling redis-server (redis:)...
latest: Pulling from library/redis
f7e2b70d04ae: Pull complete
421427137c28: Pull complete
4af7ef63ef0f: Pull complete
b858087b3517: Pull complete
2aaf1944f5eb: Pull complete
8270b5c7b90d: Pull complete
Building node-app
Step 1/5 : FROM node:alpine
 ---> a13f3a3ed57f
Step 2/5 : ADD . /app
 ---> 7f95079926fb
Step 3/5 : WORKDIR /app
 ---> Running in 40be62c351c1
Removing intermediate container 40be62c351c1
 ---> 4ade1e525971
Step 4/5 : RUN npm install
 ---> Running in ec5a35492a60
npm WARN part-03@1.0.0 No description
npm WARN part-03@1.0.0 No repository field.

audited 125 packages in 1.128s
found 0 vulnerabilities

Removing intermediate container ec5a35492a60
 ---> a1bbd95e7c6c
Step 5/5 : CMD ["npm", "run", "serve"]
 ---> Running in de6f7dd2c258
Removing intermediate container de6f7dd2c258
 ---> e9c36765fe92
Successfully built e9c36765fe92
Successfully tagged part-03_node-app:latest
WARNING: Image for service node-app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating part-03_redis-server_1 ... done
Creating part-03_node-app_1     ... done
Attaching to part-03_node-app_1, part-03_redis-server_1
redis-server_1  | 1:C 10 Mar 2019 17:20:56.818 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis-server_1  | 1:C 10 Mar 2019 17:20:56.818 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=1, just started
redis-server_1  | 1:C 10 Mar 2019 17:20:56.818 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis-server_1  | 1:M 10 Mar 2019 17:20:56.819 * Running mode=standalone, port=6379.
redis-server_1  | 1:M 10 Mar 2019 17:20:56.819 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
redis-server_1  | 1:M 10 Mar 2019 17:20:56.819 # Server initialized
redis-server_1  | 1:M 10 Mar 2019 17:20:56.819 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
redis-server_1  | 1:M 10 Mar 2019 17:20:56.819 * Ready to accept connections
node-app_1      |
node-app_1      | > part-03@1.0.0 serve /app
node-app_1      | > node index.js
node-app_1      |
node-app_1      | Listening on port 8080

docker-compose를 백그라운드에서 실행하는 방법은 -d 옵션을 사용하는 것이다.

$ docker-compose up -d
Creating part-03_redis-server_1 ... done
Creating part-03_node-app_1     ... done

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
27c7d580f0a9        redis               "docker-entrypoint.s…"   4 seconds ago       Up 3 seconds        6379/tcp                 part-03_redis-server_1
85dda2f0095d        part-03_node-app    "npm run serve"          4 seconds ago       Up 3 seconds        0.0.0.0:8080->8080/tcp   part-03_node-app_1

그리고 중지하는 방법은 docker-compose down이다.

$ docker-compose down
Stopping part-03_redis-server_1 ... done
Stopping part-03_node-app_1     ... done
Removing part-03_redis-server_1 ... done
Removing part-03_node-app_1     ... done
Removing network part-03_default

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

너무 간단하게 docker-compose에 대해서 알아보았는데, 이 튜토리얼의 세부적인 사항은 다음편(과연?!)에 알아보도록 하겠다.