안녕하세요

이번 실습에서는 microservice architecture 로 web application을 만들어 보겠습니다.

docker container를 활용할 것이고 docker환경에서 deploy, debugging, testing을 어떻게 하는지 알아 보겠습니다.

본 과정을 진행하기 위해선 Docker, Microservice 개념에 대해서 기본적인 이해가 필요합니다.

전체 application은 아래와 같은 구조를 갖습니다.

회원 가입과 로그인 이 있고, location에 따른 날씨 정보를 보여주는 application 입니다. DB 는 postgres를 사용하고 날씨 정보는 ibm cloud weather company data를 이용 합니다.

 

본 실습은 아래와 같은 구성으로 진행 됩니다.

Contents

1. Project Setup
2. Docker Config
3. Postgres Setup
4. Users Service Setup
5. Locations Service Setup
6. Web Services Setup
7. Testing
8. Workflow
9. Test Setup
10. general docker command

목표

1. Microservice 를 위한 Docker 와 Docker componse 구성
2. container에 volume 붙이기
3. Docker container 내에서 unit test , integration test수행하기.
4. functional test 수행
5. running Docker container Debug 하기
6. container 간의 통신 ( AJAX)

실습을 진행하면서 source 내용 복사/붙여넣기 하실때, indent가 맞지 않아 에러가 발생할 수 있습니다.
주의 하여 작성 하시고, 잘 안될시 완성본 소스를 글의 하단에 첨부 해놓았으니 참고 하세요.

1. Project Setup

$ git clone https://github.ibm.com/yjh/node-docker-api-dev.git
$ cd node-docker-api-dev

다운로드 받은 파일을 자세히 보면 아래와 같은 구조를 갖고 있습니다.

├── services
│   ├── locations
│   │   ├── gulpfile.js
│   │   ├── knexfile.js
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── app.js
│   │   │   ├── db
│   │   │   │   ├── connection.js
│   │   │   │   ├── create.sql
│   │   │   │   ├── migrations
│   │   │   │   │   └── 20170405114746_locations.js
│   │   │   │   ├── queries.js
│   │   │   │   └── seeds
│   │   │   │       └── locations.js
│   │   │   ├── routes
│   │   │   │   ├── _helpers.js
│   │   │   │   └── locations.js
│   │   │   └── server.js
│   │   └── tests
│   │       └── integration
│   │           ├── routes.index.test.js
│   │           └── routes.locations.test.js
│   └── users
│       ├── gulpfile.js
│       ├── knexfile.js
│       ├── npm-debug.log
│       ├── package.json
│       ├── src
│       │   ├── app.js
│       │   ├── auth
│       │   │   ├── _helpers.js
│       │   │   └── local.js
│       │   ├── db
│       │   │   ├── connection.js
│       │   │   ├── create.sql
│       │   │   ├── migrations
│       │   │   │   └── 20170403223908_users.js
│       │   │   └── seeds
│       │   │       └── users.js
│       │   ├── routes
│       │   │   └── users.js
│       │   └── server.js
│       └── tests
│           ├── integration
│           │   ├── routes.index.test.js
│           │   └── routes.users.test.js
│           └── unit
│               ├── auth.helpers.test.js
│               └── auth.local.test.js
├── tests
│   ├── main.test.js
│   └── package.json
└── web
├── gulpfile.js
├── package.json
└── src
├── app.js
├── public
│   ├── main.css
│   └── main.js
├── routes
│   ├── _helpers.js
│   └── index.js
├── server.js
└── views
├── _base.html
├── error.html
├── login.html
├── main.html
├── nav.html
├── register.html
└── user.html

폴더 구조를 살펴 보면 Service 와 web으로 나뉘어 있고 Service에 location 과 user 가 있습니다.
web ,location, user 3개의 application이 있습니다. location 과 user는 각각 DB를 갖고 있습니다.

 

각 application을 docker container로 build 하여 실행하기 전에 제대로 동작하는지 local npm 환경에서 실행을 해보겠습니다.

Users:

1. cd services/users
2. npm install
3. node src/server.js
4. 브라우저에서 http://localhost:3000/users/ping 로 접속
5. pong 이란 결과가 보이면 정상
6. ctrl + c 로 server 중지

Locations:

1. cd services/locations
2. npm install
3. node src/server.js
4. 브라우저에서 http://localhost:3001/locations/ping 로 접속
5. pong 이란 결과가 보이면 정상
6. ctrl + c 로 server 중지

2. Docker Config

local npm 환경에서 잘 동작하는 것을 확인 했습니다. 동일한 application을 docker container 형태로 구동시켜 보겠습니다.

project root에 아래 파일을 추가 합니다.

/docker-compose.yml
/services/locations/.dockerignore
/services/locations/src/db/.dockerignore
/services/users/.dockerignore
/services/users/src/db/.dockerignore
/tests/.dockerignore
/web/.dockerignore

docker-compose.yml 파일에 아래 내용을 추가 합니다.

version: ‘2.1’

각 .dockerignore 파일에 아래 내용을 추가 합니다.

.git
.gitignore
README.md
docker-compose.yml
node_modules

3. Postgres Setup

Dockerfile을 추가 합니다.

/services/locations/src/db/Dockerfile
/services/users/src/db/Dockerfile

Dockerfile에 아래 내용을 추가 합니다.

FROM postgres

# run create.sql on init
ADD create.sql /docker-entrypoint-initdb.d

docker-compose.yml 파일을 아래와 같이 수정 합니다.

  version: '2.1'

  services:
      users-db:
        container_name: users-db
        build: ./services/users/src/db
        ports:
          - '5433:5432'
        environment:
          - POSTGRES_USER=admin
          - POSTGRES_PASSWORD=admin
        healthcheck:
          test: exit 0

      locations-db:
        container_name: locations-db
        build: ./services/locations/src/db
        ports:
          - '5434:5432'
        environment:
          - POSTGRES_USER=admin
          - POSTGRES_PASSWORD=admin
        healthcheck:
          test: exit 0

docker compose는 여러 container를 한번에 build , deploy 하는 것입니다.
docker-compose.yml는 그에 대한 내용을 정의 하는 파일 입니다.
user-db와 location-db라는 container를 정의 했습니다.
build 파라미터에 정의한 location에서 Dockerfile을 찾을 것이고 Dockerfile에 정의된 내용으로 build를 할 것입니다.

두 service를 build 해보겠습니다.

$ docker-compose up –build -d

build가 완료 되고 container가 running 될 것입니다.

$docker ps

CONTAINERID        IMAGE                        COMMAND                  CREATED             STATUS                            PORTS                    NAMES
477a4c536c78     nodedockerapi_users-db    "docker-entrypoint..."   9 seconds ago   Up 6 seconds (health: starting)   0.0.0.0:5433->5432/tcp   users-db
6997e2b6e707    nodedockerapi_locations-db   "docker-entrypoint..."   9 seconds ago     Up 5 seconds (health: starting)   0.0.0.0:5434->5432/tcp   locations-db

container 상태를 확인하기 위하여 각 container에 shell로 접속 해보겠습니다.
이 단계에서는 container에 접속만 해보겠습니다.
(container는 stateless 입니다. shell로 접속은 되지만 변경 작업을 한다고 하여도 저장되지 않습니다. )

$ docker-compose run users-db bash
# exit
$ docker-compose run locations-db bash
# exit

4. Users Service Setup

Dockerfile을 추가 합니다.
/services/users/Dockerfile

Dockerfile에 아래 내용을 추가 합니다.

FROM node:latest

# set working directory
RUN mkdir /src
WORKDIR /src

# install app dependencies
ENV PATH /src/node_modules/.bin:$PATH
ADD package.json /src/package.json
RUN npm install

# start app
CMD [“npm”, “start”]

docker-compose.yml 파일에 아래 내용을 추가 합니다. (service section에 포함되게 추가 해주세요.)

users-service:
  container_name: users-service
  build: ./services/users/
  volumes:
    – ‘./services/users:/src/app’
    – ‘./services/users/package.json:/src/package.json’
  ports:
    – ‘3000:3000’
  environment:
    – DATABASE_URL=postgres://admin:admin@users-db:5432/node_docker_api_users_dev
    – DATABASE_TEST_URL=postgres://admin:admin@users-db:5432/node_docker_api_users_test
    – NODE_ENV=${NODE_ENV}
    – TOKEN_SECRET=changeme
  depends_on:
    users-db:
      condition: service_healthy
  links:
    – users-db

container_name: users-service 라는 container를 정의 했습니다.
build: build 위치를 지정했습니다.
volumes : volumes을 지정했습니다. 실제 code가 있는 경로를 volume으로 지정함으로써 code 변경을 바로 바로 container에 적용 할 수 있습니다. volume 이 없다면 source code가 바뀔 때 마다 새로운 이미지 build를 하고 container를 다시 올려야 합니다. 이런 번거로움을 없애고자 container 에 source경로를 volume으로 붙였습니다.
일반적으로 development 단계에서는 code변화를 빠르게 적용하고 feedback을 보고자 이런 방식을 취합니다.
depends_on: user-service는 user-db 이 올라온 뒤에 running 될 것입니다.
links: user-service에서 user-db에 5423 port로 접근 가능. user-db:5423 형태.

 

NODE_ENV setup

$ export NODE_ENV=development

위와 같이 환경변수로 NODE_ENV를 설정 하고 code 내에서 process.env.NODE_ENV 형태로 접근 할 수 있습니다.
development 일 경우 production 일 경우 test 일 경우 등으로 환경설정을 나누어 하기 위한 목적입니다.

새로 추가한 service만 build 해보겠습니다.

$ docker-compose up –build -d users-service

Knex migrate를 위해 project root 경로에 migrate.sh 파일을 만들고 아래 내용을 추가 합니다.
(Knex.js는 Node.js SQL 빌더 및 query기능을 제공하는 라이브러리입니다. MySQL, MariaDB, PostgreSQL, SQLLite, Oracle, MSSQL과 같이 대부분의 RDBMS를 지원합니다.)

#!/bin/sh

docker-compose run users-service knex migrate:latest –env development –knexfile app/knexfile.js
docker-compose run users-service knex seed:run
–env development –knexfile app/knexfile.js

Docker Compose에서 구동한 컨테이너에서 새로운 커맨드를 실행하고자 할 때에는 docker-compose run 커맨드를 사용합니다. 위 커맨드는 user-service 컨테이너의 knex 명령을 수행 합니다. db 정보를 code와 sync하는 migrate command와 기초data를 넣는 seed 커맨드 입니다.
knex에 대한 이해가 필요한 커맨드 이므로 간단하게만 설명하고 넘어가겠습니다.
knex 는 full ORM은 아니지만 비슷한 역할을 합니다. (Sequelize 와 같은 ORM 방식을 생각하면 쉽습니다)
migrate라는 command를 통해, code로 실제 db의 table 또는 column 속성등을 update 할 수 있습니다. SQL 이 아니라 programming language로 db를 control합니다.
seed는 기초data를 넣는 명령입니다. 일반적으로 test를 위해 사용하거나, initial data를 db에 넣기위해 사용됩니다.

본 과정은 docker 에 대해 알아보는 것이므로 구체적인 programming 관련 사항에 대해 서는 참고만 하세요.

migrate.sh을 수행 해보겠습니다.

$ sh migrate.sh

warning이 나올 수 있는데 무시하고 넘어가겠습니다.

Test를 위한 API는 아래와 같이 구성되어 있습니다.

Endpoint

HTTP Method

CRUD Method

Result

/users/ping

GET

READ

pong

/users/register

POST

CREATE

add a user

/users/login

POST

CREATE

log in a user

/users/user

GET

READ

get user info

httpie를 이용하여 post action을 테스트 해보겠습니다.(curl 을 사용해도 되고, http request client program이 있다면 그것을 이용해도 됩니다.)

$ http POST http://localhost:3000/users/register username=michael password=herman
$ http POST http://localhost:3000/users/login username=michael password=herman

아래와 같이 success 결과를 보실 수 있습니다.

$ http POST http://localhost:3000/users/register username=michael password=herman

HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: GET, PUT, POST, DELETE
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 170
Content-Type: application/json; charset=utf-8
Date: Thu, 11 Oct 2018 08:55:35 GMT
ETag: W/"aa-U8XkufKkOlzHVt3cVQJUAw"
X-Powered-By: Express

{
"status": "success",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDA0NTc3MzUsImlhdCI6MTUzOTI0ODEzNSwic3ViIjoyfQ.mDziK5xtUpVNa7RwiWVdD_ti1e798ZyLknQS6BulyOc"
}
$ http POST http://localhost:3000/users/login username=michael password=herman

HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: GET, PUT, POST, DELETE
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 170
Content-Type: application/json; charset=utf-8
Date: Thu, 11 Oct 2018 08:55:54 GMT
ETag: W/"aa-1+JRC+koMwYxgq9slz+91w"
X-Powered-By: Express

{
"status": "success",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDA0NTc3NTQsImlhdCI6MTUzOTI0ODE1NCwic3ViIjoyfQ.aPWpmmT4SONNwUlgT051MNDhpA5-EU3eNxsqqABQYE8"
}

5. Locations Service Setup

Dockerfile을 추가 합니다.
/services/locations/Dockerfile

Dockerfile에 아래 내용을 추가 합니다.

FROM node:latest

# set working directory
RUN mkdir /src
WORKDIR /src

# install app dependencies
ENV PATH /src/node_modules/.bin:$PATH
ADD package.json /src/package.json
RUN npm install

# start app
CMD [“npm”, “start”]

docker-compose.yml 파일에 아래 내용을 추가 합니다. (service section에 포함되게 추가 해주세요.)

locations-service:
  container_name: locations-service
  build: ./services/locations/
  volumes:
    – ‘./services/locations:/src/app’
    – ‘./services/locations/package.json:/src/package.json’
  ports:
    – ‘3001:3001’
  environment:
   
DATABASE_URL=postgres://admin:admin@locations-db:5432/node_docker_api_locations_dev
   
DATABASE_TEST_URL=postgres://admin:admin@locations-db:5432/node_docker_api_locations_test
    – NODE_ENV=${NODE_ENV}
    – TOKEN_SECRET=changeme
    – WEATHER_HOST=${WEATHER_HOST}
  depends_on:
    locations-db:
      condition: service_healthy
    users-service:
      condition: service_started
  links:
    – locations-db
    – users-service

 

날씨 data를 얻기 위해 날씨 api service를 등록 하겠습니다.
IBM CLOUD의 weather company data service 를 사용 해보겠습니다.

1. https://bluemix.net 에 가입 합니다.
2. Catalog -> Weather Company Data 서비스 생성
3. service credential 에서 url 확인, (service credential 이 없다면 파란색 New credential 버튼을 클릭 하여 credential을 생성 합니다.) -> 아래 이미지를 참고 하세요.
4. url을 env 로 등록 합니다.

$ export WEATHER_HOST= [ibm cloud weather compay 서비스 url 정보 기입]

   

location service를 build 합니다.

$ docker-compose up –build -d locations-service

migrate.sh 파일에 아래 코드를 추가 합니다. (코드 내용은 이전에 설명한 것과 동일 합니다.)

docker-compose run locations-service
knex migrate:latest
–env development –knexfile app/knexfile.js
docker-compose run locations-service knex seed:run
–env development –knexfile app/knexfile.js

migrate를 수행 합니다.

$ sh migrate.sh

Test를 위한 API는 아래와 같이 구성되어 있습니다.

Endpoint

HTTP Method

CRUD Method

Result

/locations/ping

GET

READ

pong

/locations

GET

READ

get all locations

/locations/user

GET

READ

get all locations by user

/locations/:id

GET

READ

get a single location

/locations

POST

CREATE

add a single location

/locations/:id

PUT

UPDATE

update a single location

/locations/:id

DELETE

DELETE

delete a single location

curl 또는 httpie를 이용하여 test 할 수 있습니다.

$ http GET http://localhost:3001/locations/ping

아래와 같은 결과가 나올 것입니다.

$ http GET http://localhost:3001/locations/ping

HTTP/1.1200 OK
Access-Control-Allow-Headers:Content-Type
Access-Control-Allow-Methods:GET, PUT, POST, DELETE
Access-Control-Allow-Origin:*
Connection:keep-alive
Content-Length:4
Content-Type:text/html; charset=utf-8
Date:Thu, 11 Oct 2018 11:03:34 GMT
ETag:W/"4-b9sIeqP7+8uCh6WToJGeYQ"
X-Powered-By:Express

pong

6. Web Services Setup
Dockerfile을 추가 합니다.

/web/Dockerfile

Dockerfile에 아래 내용을 추가 합니다.

FROM node:latest

# set working directory
RUN mkdir /src
WORKDIR /src

# install app dependencies
ENV PATH /src/node_modules/.bin:$PATH
ADD package.json /src/package.json
RUN npm install

# start app
CMD [“npm”, “start”]

docker-compose.yml 파일에 아래 내용을 추가 합니다. (service section에 포함되게 추가 해주세요.)

web:
  container_name: web
  build: ./web/
  volumes:
    – ‘./web:/src/app’
    – ‘./web/package.json:/src/package.json’
  ports:
    – ‘3003:3003’
  environment:
    – NODE_ENV=${NODE_ENV}
    – SECRET_KEY=changeme
  depends_on:
    users-service:
      condition: service_started
    locations-service:
      condition: service_started
  links:
    – users-service
    – locations-service

web service를 build하겠습니다.

$ docker-compose up –build -d web

브라우저에서 http://localhost:3003/login 에 접속 합니다.
login 하라는 메뉴가 나오면 register 후에 login 합니다.
아래와 같은 페이지가 나올 것입니다.

본 웹 페이지는 location-service 로 부터 (weather api 를 등록한 service) 정보를 받아서 출력하는 역할을 합니다.
web service의 코드를 좀 살펴보겠습니다.
/web/src/routes/index.js 에서 weather api 받아오는 값을 처리 합니다. ajax 방식으로 처리하는데 GET request의 uri 가
아래와 같습니다.

uri: http://locations-service:3001/locations/

모든 서비스가 local에서 수행되는데,  request uri가 locations-service:3001 입니다. container base로 서비스가 동작 하기 때문입니다. web service에서 location-service:3001 형태로 접근 할 수 있는 이유는 docker-compose.yml 의 web service 정의 부분에서 links에 locations-service를 지정 해줬기 때문입니다.

7. Testing

docker 환경에서 unit testing 하는 방법을 알아보겠습니다.
test coding은 이미 되어 있습니다. code를 짜는 법이 아니라 docker 환경에서 test 를 구동하는 방법에 대해 알아보겠습니다.

환경변수를 test로 수정 합니다.

$ export NODE_ENV=test

container를 update 합니다.
새로 지정된 환경변수를 적용한다는 의미 입니다.

$ docker-compose up -d

test를 수행 합니다.

$ docker-compose run users-service npm test
$ docker-compose run locations-service npm test

test 결과가 console에 나타날 것입니다.

8. Workflow

container base환경에서 code가 어떻게 동작 하는지 간단히 살펴 보겠습니다.
1. container에 volume 형태로 source code를 mount 합니다.
2. local 환경에서 source code를 수정합니다.
3. container의 node daemon이 이 변화를 감지하고 app을 restart하여 변경된 code를 적용합니다.
4. local 환경에서 debugging을 위해 사용하는 console.log 의 결과를 docker-compose logs -f 명령을 통해 확인 합니다.

 

9. Test Setup

functional test service를 만들어 보겠습니다.

Dockerfile을 추가 합니다.
/tests/Dockerfile

Dockerfile에 아래 내용을 추가 합니다.

FROM node:latest

# set working directory
RUN mkdir /src
WORKDIR /src

# install app dependencies
ENV PATH /src/node_modules/.bin:$PATH
ADD package.json /src/package.json
RUN npm install

docker-compose.yml 파일에 아래 내용을 추가 합니다. (service section에 포함되게 추가 해주세요.)

tests:
  container_name: tests
  build: ./tests/
  volumes:
    – ‘./tests:/src/app’
    -‘./tests/package.json:/src/package.json’
  depends_on:
    users-service:
      condition: service_started
    locations-service:
      condition: service_started
  links:
    – users-service
    – locations-service
    – web

tests service를 build 합니다.

$ docker-compose up –build -d tests

아래 명령을 통해 test를 수행 합니다.

$ export NODE_ENV=test
$ docker-composeup -d
$ docker-composerun tests npm test

 

10. docker 환경에서 필요한 command

• postgres psql 사용법
container-id는 docker ps 명령으로 알 수 있습니다.

$ docker exec -ti <container-id> psql -U ostgres

• stop container

$ docker-compose stop

• bring down container

$ docker-compose down

• force build

$ docker-compose build –no-cache

• remove image

$ docker rmi $(docker images -q)

• unit test

$ export NODE_ENV=test
$ docker-compose up -d
$ docker-compose run users-service npm
test
$ docker-compose run locations-service npm
test

• functional test

$ export NODE_ENV=test
$ docker-compose up -d
$ docker-compose run tests npm
test

 

완성된 코드 : https://github.ibm.com/yjh/node-docker-api-completed.git

 

본 글은 아래 글을 참고하여 작성 하였습니다. (일부 수정됨)
원문 : https://mherman.org/blog/developing-and-testing-microservices-with-docker/

토론 참가

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.