지금까지 배운 것을 토대로 웹 애플리케이션 제작 실습을 해본다.
마스터-슬레이브 구조로 구성. insert, update, delete 쿼리는 master db에서 처리하고, select 쿼리는 slave db가 처리한다. 마스터 컨테이너는 레플리카를 한 개만 둔다.
흔히 볼 수 있는 평범한 구성. 모두 레플리카 수를 우선 3개로 한다.
3개의 stack이 서로 소통해야 하므로, 모두 하나의 네트워크를 공유하도록 한다.
mysql의 설정파일인 mysqld.conf를 수정해 마스터-슬레이브 db를 구축한다. 스택 내의 각 서비스들이 중복이 없도록 이 파일안에 server-id를 설정해야 한다.
add-server-id.sh
를 통해 이 작업을 수행한다.
#!/bin/bash -e
OCTETS=(`hostname -i | tr -s '.' ' '`)
MYSQL_SERVER_ID=`expr ${OCTETS[2]} \* 256 + ${OCTETS[3]}`
echo "server-id=$MYSQL_SERVER_ID" >> /etc/mysql/mysql.conf.d/mysqld.conf
그 다음 master인지 slave인지의 여부를 환경변수로 받아 경우에 맞게 db 설정을 해주고 서버를 시작하는 prepare.sh파일을 작성한다. (내용 생략)
FROM mysql:5.7
# (1) 패키지 업데이트 및 wget 설치
RUN apt-get update
RUN apt-get install -y wget
# (2) entrykit 설치
RUN wget https://github.com/progrium/entrykit/releases/download/v0.4.0/entrykit_0.4.0_linux_x86_64.tgz
RUN tar -xvzf entrykit_0.4.0_linux_x86_64.tgz
RUN rm entrykit_0.4.0_linux_x86_64.tgz
RUN mv entrykit /usr/local/bin/
RUN entrykit --symlink
# (3) 스크립트 및 각종 설정 파일 복사
COPY add-server-id.sh /usr/local/bin/
COPY etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/mysql.conf.d/
COPY etc/mysql/conf.d/mysql.cnf /etc/mysql/conf.d/
COPY prepare.sh /docker-entrypoint-initdb.d
COPY init-data.sh /usr/local/bin/
COPY sql /sql
# (4) 스크립트, mysqld 실행
ENTRYPOINT [ \
"prehook", \
"add-server-id.sh", \
"--", \
"docker-entrypoint.sh" \
]
CMD ["mysqld"]
** entrykit을 통해 mysql 서비스 시작 이전에 해야할 일(add-server-id.sh)을 실행시킬 수 있다.
todo-mysql.yml
version: "3"
services:
master:
image: registry:5000/ch04/tododb:latest
deploy:
replicas: 1
placement:
constraints: [node.role != manager]
environment:
MYSQL_ROOT_PASSWORD: gihyo
MYSQL_DATABASE: tododb
MYSQL_USER: gihyo
MYSQL_PASSWORD: gihyo
MYSQL_MASTER: "true"
networks:
- todoapp
slave:
image: registry:5000/ch04/tododb:latest
deploy:
replicas: 2
placement:
constraints: [node.role != manager]
depends_on:
- master
environment:
MYSQL_MASTER_HOST: master
MYSQL_ROOT_PASSWORD: gihyo
MYSQL_DATABASE: tododb
MYSQL_USER: gihyo
MYSQL_PASSWORD: gihyo
MYSQL_REPL_USER: repl
MYSQL_REPL_PASSWORD: gihyo
networks:
- todoapp
networks:
todoapp:
external: true
04-01에서 정의했듯이 마스터는 레플리카 수를 1개만, 슬레이브는 2개로 설정하여 스택정의 파일을 작성한 후, 아래 명령어로 스택을 스웜 클러스터에 배포한다.
docker container exec -it manager \
docker stack deploy -c /stack/todo-mysql.yml todo_mysql
아래와 같이 배포 현황을 확인할 수 있다.
docker container exec -it manager \
docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
nd3qtgaf6yr1 todo_mysql_master replicated 1/1 registry:5000/ch04/tododb:latest
d3vkcoroi2nl todo_mysql_slave replicated 2/2 registry:5000/ch04/tododb:latest
go를 이용해 간단한 CRUD operation을 수행할 수 있는 api 서버를 만들고, 이를 도커 스웜으로 배포한다.
FROM golang:1.13.6
WORKDIR /
ENV GOPATH /go
COPY . /go/src/github.com/gihyodocker/todoapi
RUN go get github.com/go-sql-driver/mysql
RUN go get gopkg.in/gorp.v1
RUN cd /go/src/github.com/gihyodocker/todoapi && go build -o bin/todoapi cmd/main.go
RUN cd /go/src/github.com/gihyodocker/todoapi && cp bin/todoapi /usr/local/bin/
CMD ["todoapi"]
todo-app.yml
version: "3"
services:
api:
image: registry:5000/ch04/todoapi:latest
deploy:
replicas: 2
environment:
TODO_BIND: ":8080"
TODO_MASTER_URL: "gihyo:gihyo@tcp(todo_mysql_master:3306)/tododb?parseTime=true"
TODO_SLAVE_URL: "gihyo:gihyo@tcp(todo_mysql_slave:3306)/tododb?parseTime=true"
networks:
- todoapp
networks:
todoapp:
external: true
(생략)
nginx는 웹 UI 서비스 앞단에 1개, api 서버 앞단에 1개로 총 2개를 배치한다. nginx와 같은 웹 서버를 api의 앞단에 배치하는 이유는 접근 로그를 생성하기 편리하다는 점,
캐시 제어, 애플리케이션을 수정하지 않고도 임의로 라우팅 설정이 가능하다는 점 등을 들 수 있다.
FROM nginx:1.13
RUN apt-get update
RUN apt-get install -y wget
RUN wget https://github.com/progrium/entrykit/releases/download/v0.4.0/entrykit_0.4.0_linux_x86_64.tgz
RUN tar -xvzf entrykit_0.4.0_linux_x86_64.tgz
RUN rm entrykit_0.4.0_linux_x86_64.tgz
RUN mv entrykit /usr/local/bin/
RUN entrykit --symlink
RUN rm /etc/nginx/conf.d/*
COPY etc/nginx/nginx.conf.tmpl /etc/nginx/
COPY etc/nginx/conf.d/ /etc/nginx/conf.d/
ENTRYPOINT [ \
"render", \
"/etc/nginx/nginx.conf", \
"--", \
"render", \
"/etc/nginx/conf.d/upstream.conf", \
"--", \
"render", \
"/etc/nginx/conf.d/public.conf", \
"--" \
]
CMD ["nginx", "-g", "daemon off;"]
04-03에서 작성했던 api 서버의 스택 정의 파일에 다음 내용을 추가한다.
nginx:
image: registry:5000/ch04/nginx:latest
deploy:
replicas: 2
placement:
constraints: [node.role != manager]
depends_on:
- api
environment:
WORKER_PROCESSES: 2
WORKER_CONNECTIONS: 1024
KEEPALIVE_TIMEOUT: 65
GZIP: "on"
BACKEND_HOST: todo_app_api:8080
BACKEND_MAX_FAILS: 3
BACKEND_FAIL_TIMEOUT: 10S
SERVER_PORT: 80
SERVER_NAME: todo_app_nginx
LOG_STDOUT: "true"
networks:
- todoapp
정적 파일은 node.js를 통해 제공할 필요가 없기 때문에 nginx에서 바로 제공하도록 설정할 필요가 있다. nginx의 conf 파일에 다음과 같은 내용을 추가한다.
server {
location /_nuxt/ {
alias /var/www/_nuxt/$1;
{{ if var "LOG_STDOUT" }}
access_log /dev/stdout json;
error_log /dev/stderr;
{{ else }}
access_log /var/log/nginx/assets_access.log json;
error_log /var/log/nginx/assets_error.log;
{{ end }}
}
}
이렇게 하면 /_nuxt/ 경로로 들어오는 요청은 api 서버를 통하지 않고 nginx 단에서 바로 처리한다. 그리고 api 서버 단에서 생성되는 정적 에셋 파일들을 nginx와 공유할 수 있도록 하기위해
volume 설정을 스택 정의 파일에 넣어야 한다.
todo-frontend.yml
version: "3"
services:
nginx:
image: registry:5000/ch04/nginx-nuxt:latest
deploy:
replicas: 2
placement:
constraints: [node.role != manager]
depends_on:
- web
environment:
SERVICE_PORTS: 80
WORKER_PROCESSES: 2
WORKER_CONNECTIONS: 1024
KEEPALIVE_TIMEOUT: 65
GZIP: "on"
BACKEND_HOST: todo_frontend_web:3000 (이 nginx 서비스의 백엔드는 web 서비스임)
BACKENT_MAX_FAILS: 3
BACKENT_FAIL_TIMEOUT: 10s
SERVER_PORT: 80
SERVER_NAME: localhost
LOG_STDOUT: "true"
networks:
- todoapp
volumes:
- assets:/var/www/_nuxt
web:
image: registry:5000/ch04/todoweb:latest
deploy:
replicas: 2
placement:
constraints: [node.role != manager]
environment:
TODO_API_URL: http://todo_app_nginx
networks:
- todoapp
volumes:
- assets:/todoweb/.nuxt/dist
networks:
todoapp:
external: true
volumes:
assets:
driver: local
db, api, web, nginx 까지 해서 스웜 클러스터 구축이 끝났기 때문에, 이 애플리케시연을 스웜 외부로 노출시키기 위한 인그레스가 필요하다. 이 또한 스택을 ㅗ만들어 클러스터에 배포한다.
todo-ingress.yml
version: "3"
services:
haproxy:
image: dockercloud/haproxy
networks:
- todoapp
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
mode: global
placement:
constraints:
- node.role == manager
ports:
- 80:80
- 1936:1936
networks:
todoapp:
external: true