이번 글에서는 드디어 docker compose를 다루게 되었습니다. 도커를 사용하면 사실상 compose를 사용하는게 당연하게 생각 되듯이 널리 사용되고 있습니다. 이번 글에서 기본적인 사용법과 실습을 해보면서 익숙해 지도록 해봅시다.
멀티 컨테이너 서비스 구성
[실습] wordpress와 mysql을 이용한 web application
cd ~/fastcampus/ch10/my-webdb docker volume create mydb_data docker volume create myweb_data docker network create my-webdb-net docker run -itd --name=mysql_app -v mydb_data:/var/lib/mysql --restart=always -p 3306:3306 --net=my-webdb-net -e MYSQL_ROOT_PASSWORD=password# -e MYSQL_DATABASE=wpdb -e MYSQL_USER=wpuser -e MYSQL_PASSWORD=wppassword arm64v8/mysql:8.1.0 docker run -itd --name=wordpress_app -v myweb_data:/var/www/html -v ${PWD}/myweb-log:/var/log --restart=always -p 8888:80 --net=my-webdb-net -e WORDPRESS_DB_HOST=mysql_app:3306 -e WORDPRESS_DB_NAME=wpdb -e WORDPRESS_DB_USER=wpuser -e WORDPRESS_DB_PASSWORD=wppassword --link mysql_app:arm64v8/mysql wordpress:5.7 docker exec -it mysql_app bash mysql -uroot -p show databases; use wpdb; show tables select * from wp_users;
볼륨 두개를 생성하고 네트워크를 하나 생성해서 mysql, wordpress를 각각 run명령어로 실행하고 글 하나를 인서트하고 디비에서 확인하는 내용입니다. 아래에서는 이걸 compose로 한번에 하는 방법을 실습으로 하겠습니다.
[docker compose installation or version update]
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker} mkdir -p $DOCKER_CONFIG/cli-plugins curl -SL https://github.com/docker/compose/releases/download/v2.23.1/docker-compose-linux-aarch64 -o $DOCKER_CONFIG/cli-plugins/docker-compose chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose docker compose version
m1 실리콘이라서 강의와는 다른 버전의 compose를 설치했습니다.https://docs.docker.com/compose/install/linux/
링크를 참고해서 진행하시면 설치가 됩니다. docker compose version을 실행시 버전 표기가 안되면 잘못된 버전을 설치한거니 다른 버전으로 재시도 하시면 됩니다.
[실습2] docker compose를 이용한 web application(wordpress+mysql)
docker stop mysql_app wordpress_app docker rm mysql_app wordpress_app vi docker compose.yaml version: "3.9" services: # 첫 번째 서비스 정의. mydb: # 도커 허브에서 제공하는 mysql:8.0 이미지 선택. image: arm64v8/mysql:8.1.0 # 서비스 컨테이너 이름 지정. container_name: mysql_app # 최상위 레벨에 정의 mydb_data 볼륨 지정. volumes: - mydb_data:/var/lib/mysql # 수동 제어를 제외한 컨테이너 종료 시 자동 재시작. restart: always # 호스트 운영체제와 컨테이너의 3306 포트를 바인드한다. # workbench 같은 클라이언트 도구와 연결하기 위해 필요하다. ports: - "3306:3306" # 최상위 레벨에 정의한 backend-net을 기본 네트워크로 지정한다. networks: - backend-net # 서비스가 사용할 환경 변수 지정한다. environment: MYSQL_ROOT_PASSWORD: password# MYSQL_DATABASE: wpdb MYSQL_USER: wpuser MYSQL_PASSWORD: wppassword # 두 번째 서비스를 작성한다. myweb: # myweb 서비스가 실행되기 전에 mydb 서비스를 먼저 실행하는 의존성을 설정한다. depends_on: - mydb # wordpress:5.7 이미지 지정한다. image: wordpress # 서비스 컨테이너 이름을 지정한다. container_name: wordpress_app # 호스트 운영체제의 8888 포트와 컨테이너의 80 포트를 바인드한다. ports: - "8888:80" # backend-net으로 mydb 서비스와 동일 네트워크로 지정하고, # 외부 연결을 위한 네트워크를 위해 fronetend-net 지정을 가정한다. # docker network connect ~ 명령으로 두 번째 네트워크를 추가하는 것과 같다. networks: - backend-net - frontend-net # 컨테이너 데이터 지속성을 위해 도커 볼륨기법과 바인드 마운트 기법을 사용한다. volumes: - myweb_data:/var/www/html - ${PWD}/myweb-log:/var/log \ # 수동 제어를 제외한 컨테이너 종료 시 자동 재시작한다. restart: always # 서비스가 사용할 환경 변수를 지정한다. environment: WORDPRESS_DB_HOST: mydb:3306 WORDPRESS_DB_USER: wpuser WORDPRESS_DB_PASSWORD: wppassword WORDPRESS_DB_NAME: wpdb # 도커 컴포즈 애플리케이션이 사용할 네트워크 생성. docker network create와 동일하다. networks: frontend-net: {} backend-net: {} # 도커 컴포즈 애플리케이션이 사용할 볼륨 생성. docker volume create와 동일하다. volumes: mydb_data: {} myweb_data: {} docker compose up
첫번째 실습을 compose로 옮겨 놓았다. 주의 사항으로는 파이선처럼 띄어쓰기로 계층을 구분하므로 띄어쓰기를 신경써야 하고 탭 대신 스페이스바 2번을 추천한다. OS에 때라 탭은 다르게 적용되는 경우도 있기때문이다.
docker compose YAML 코드 작성과 CLI
docker compose
- docker compose는 KUBERNETES와 같이 컨테이너 오케스트레이션 및 컨테이너화된 애플리케이션 관리에 사용되는 널리 사용되는 도구다.
- docker compose YAML 코드는 여러 개의 docker run 실행과 유사하며 네트웤, 볼륨 등을 한번에 생성할 수 있다.
- docker compose 3단계 프로세스
- Dockerfile: 배포하고자 하는 애플리케이션 환경 정의(선택)
- docker-compose.yaml(yml): 하나 이상의 컨테이너 서비스를 실행 할수 있도록 서비스 정의
- docker compose up: 명령으로 YAML코드로 정의된 서비스를 시작하고 실행한다.
- 장단점
- 장점
- 서로 다른 OS 환경이라도 동일한 환경구성이 가능하다.(이식성)
- 동일한 환경을 사용하기 때문에 개발환경에 이슈가 발생해도 팀간 소통이 쉽다.
- 복잡한 환경도 YAML 코드로 스크립트화 할 수 있기 때문에 자동화가 가능하다.
- docker compose CLI를 이용하여 쉽게 “멀티 컨테이너 애플리케이션”을 관리할 수 있다.
- 단점
- 동시에 다수의 컨테이너 서비스를 수행하는 경우(MSA) 자원 활용률이 순간 높아질 수 있다.
- docker container기술에 대한 이해도가 필요하다.
- 장점
[실습] docker compose CLI 활용을 위한 “flask+redis” 서비스 배포
cd ~/fastcampus/ch10/flask-redis tree docker compose up docker compose up --scale server_db=3 --scale server_web=3 -d docker compose ps docker compose up --scale server_db=1 --scale server_web=1 -d docker compose ps docker compose down --rmi all
docker compose up –scale server_db=3 –scale server_web=3 -d 이 명령어를 사용해서 쉽게 컨테이너의 크케일을 확장할수 있는것을 알수 있다.
docker compose 활용, 다양한 애플리케이션 배포
[실습1] nginx proxy와 연결되는 docker compose 애플리케이션 배포하기
tree -a vi Dockerfile.alb FROM nginx:1.25.1-alpine RUN rm /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf vi nginx.conf upstream web-alb { server 172.17.0.1:5001; server 172.17.0.1:5002; server 172.17.0.1:5003; } server { location / { proxy_pass http://web-alb; } } vi Dockerfile.app1 FROM python:3.10-slim COPY requirements.txt /requirements.txt WORKDIR / RUN pip install -r requirements.txt COPY . / ENTRYPOINT [ "python3" ] CMD [ "/pyfla_app1.py" ] vi pyfla_app1.py from flask import request, Flask import json app1 = Flask(__name__) @app1.route('/') def hello_world(): return 'Flask Web Application [1]' + '\n' if __name__ == '__main__': app1.run(debug=True, host='0.0.0.0') vi requirements.txt Flask vi docker-compose.yaml version: '3.8' services: pyfla_app1: build: context: ./pyfla_app1 dockerfile: Dockerfile.app1 ports: - "5001:5000" pyfla_app2: build: context: ./pyfla_app2 dockerfile: Dockerfile.app2 ports: - "5002:5000" pyfla_app3: build: context: ./pyfla_app3 dockerfile: Dockerfile.app3 ports: - "5003:5000" nginx: build: context: ./nginx_alb dockerfile: Dockerfile.alb ports: - "8080:80" depends_on: - pyfla_app1 - pyfla_app2 - pyfla_app3 docker compose up --build docker compose ps docker compose down --rmi all 새
새로고침을 하면 라운드로빙으로 3개의 서버로 분배 되는 것을 알 수 있다.
[실습2] 웹 페이지 방문 count하는 docker compose 애플리케이션 배포하기
cd ~/fastcampus/ch10/web-count tree -a vi app.py from flask import Flask, render_template from redis import Redis import os,random app = Flask(__name__) redis = Redis(host='redis', port=6379) images = [ "cloud-01.png", "cloud-02.png", "cloud-03.png", "cloud-04.png", "cloud-05.png", "docker_logo.png", "k8s_logo.png" ] @app.route('/') def index(): image_path = "/static/images/" + random.choice(images) count = redis.incr('count') return render_template('index.html', image_path=image_path, visit_count=count) if __name__ == "__main__": app.run(host="0.0.0.0", port=8899) vi Dockerfile FROM python:3.10-slim LABEL maintainer "kevin.lee" RUN pip install --upgrade pip RUN mkdir -p /cloud-web/image ENV APP_PATH /cloud-web/image COPY requirements.txt $APP_PATH/ RUN pip install --no-cache-dir -r $APP_PATH/requirements.txt COPY app.py $APP_PATH/ COPY templates/ $APP_PATH/templates/ COPY static/ $APP_PATH/static/ EXPOSE 8899 CMD ["python", "/cloud-web/image/app.py"] vi docker-compose.yml version: '3.8' services: webserver: build: . ports: - "8899:8899" depends_on: - redis redis: image: redis:6.0 vi templates/index.html <html> <head> <meta charset="utf-8"> <title>Fastcampus Docker Container Service</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <link rel="stylesheet" href="./css/bootstrap.css"> </head> <body> <nav class="navbar navbar-expand-sm" style="background-color:#2684FF;"> <a class="navbar-brand" href="https:///hub.docker.com" target="_blank" style="color:#fff;"> <img src="/static/images/fastcampus.png">Docker Container Application by kevin.lee "hylee@dshub.cloud"</a> </nav> <div class="container" style="padding:20px 0 0 0"> <div class="row"> <div class="alert alert-danger" role="alert"> [Currently you have {{visit_count}} visits.] </div> </div> <div class="row"> <img class="img-fluid" src="{{image_path}}" /> </div> </div> </body> </html> docker compose up --build [-d] docker compose ps docker compose down --rmi all
docker compose 활용한 3-tier 애플리케이션 배포
[실습] docker CLI를 이용한 3-tier web application(나의 수강 일기)
cd my-diary-front/ docker build -t mydiary-front:1.0 . cd my-diary-back/ docker build -t mydiary-back:1.0 . docker network create fastapp-net docker run -d --name rolling-db --net fastapp-net -p 13306:3306 -e MYSQL_ROOT_PASSWORD=pass123# -e MYSQL_DATABASE=paperdb -e MYSQL_ROOT_HOST=% -e MYSQL_USER=user -e MYSQL_PASSWORD=user arm64v8/mysql:8.1.0 --character-set-server=utf8 --collation-server=utf8_general_ci docker run -d --name rolling-server --net fastapp-net -p 8080:8080 -e SPRING_DATASOURCE_URL=jdbc:mysql://rolling-db:3306/paperdb?serverTimezone=Asia/Seoul -e SPRING_DATASOURCE_USERNAME=user -e SPRING_DATASOURCE_PASSWORD=user mydiary-back:1.0 docker run -d --name rolling-front --net fastapp-net -p 3000:3000 mydiary-front:1.0
run 명령어를 사용해서 db, server, front 컨테이너를 올렸다.
[실습2] docker compose를 이용한 3-tier web application(나의 수강 일기)
vi docker-compose.yaml version: '3.3' services: mydiary-db: image: arm64v8/mysql:8.1.0 container_name: rolling-db environment: MYSQL_ROOT_PASSWORD: pass123 MYSQL_DATABASE: paperdb MYSQL_ROOT_HOST: '%' MYSQL_USER: user MYSQL_PASSWORD: user ports: - '13306:3306' networks: - rolling-be-db restart: always command: - --character-set-server=utf8 - --collation-server=utf8_general_ci mydiary-back: image: mydiary-back:1.0 container_name: rolling-server restart: always depends_on: - mydiary-db ports: - '8080:8080' environment: SPRING_DATASOURCE_URL: jdbc:mysql://rolling-db:3306/paperdb?serverTimezone=Asia/Seoul SPRING_DATASOURCE_USERNAME: user SPRING_DATASOURCE_PASSWORD: user networks: - rolling-be-db - rolling-fe-be mydiary-front: image: mydiary-front:1.0 container_name: rolling-front restart: always depends_on: - mydiary-back ports: - '3000:3000' networks: - rolling-fe-be networks: rolling-be-db: {} rolling-fe-be: {} docker compose up
위에서 docker build로 만든 이미지를 사용해서 docker-compose.yaml를 작성하고 실행해 보았다.
[실습3] 실습1의 image build + 실습2를 한번에 수행
cd ~/fastcampus/ch10//my-diary-3 vi docker-compose.yaml version: '3.3' services: mydiary-db: image: arm64v8/mysql:8.1.0 container_name: rolling-db environment: MYSQL_ROOT_PASSWORD: pass123 MYSQL_DATABASE: paperdb MYSQL_ROOT_HOST: '%' MYSQL_USER: user MYSQL_PASSWORD: user ports: - '13306:3306' networks: - rolling-be-db restart: always command: - --character-set-server=utf8 - --collation-server=utf8_general_ci mydiary-back: build: context: ./my-diary-back dockerfile: Dockerfile container_name: rolling-server restart: always depends_on: - mydiary-db ports: - '8080:8080' environment: SPRING_DATASOURCE_URL: jdbc:mysql://rolling-db:3306/paperdb?serverTimezone=Asia/Seoul SPRING_DATASOURCE_USERNAME: user SPRING_DATASOURCE_PASSWORD: user networks: - rolling-be-db - rolling-fe-be mydiary-front: build: context: ./my-diary-front dockerfile: Dockerfile container_name: rolling-front restart: always depends_on: - mydiary-back ports: - '3000:3000' networks: - rolling-fe-be networks: rolling-be-db: {} rolling-fe-be: {} docker compose up docker compose ps docker compose down --rmi all
server, front image를 빌드해서 컨테이너를 올리는 작업을 같이 수행하도록 했다.
[실습4] Nginx Proxy(load balancer)를 결합한 애플리케이션 배포
이번 실습에서는 front, backend 앞에 로드밸런서를 두고 round roubin 방식으로작동하도록 실습해보겠습니다.
cd ~/fastcampus/ch10/my-diary-3-proxy tree vi docker-compose.yaml version: '3.8' services: mydiary-db: image: arm64v8/mysql:8.1.0 container_name: rolling-db environment: MYSQL_ROOT_PASSWORD: pass123 MYSQL_DATABASE: paperdb MYSQL_ROOT_HOST: '%' MYSQL_USER: user MYSQL_PASSWORD: user ports: - '3306:3306' networks: - mydiary-net restart: always command: - --character-set-server=utf8 - --collation-server=utf8_general_ci mydiary-back: build: context: ./my-diary-back dockerfile: Dockerfile deploy: replicas: 3 restart: always depends_on: - mydiary-db ports: - '8081-8083:8080' environment: SPRING_DATASOURCE_URL: jdbc:mysql://rolling-db:3306/paperdb?serverTimezone=Asia/Seoul SPRING_DATASOURCE_USERNAME: user SPRING_DATASOURCE_PASSWORD: user networks: - mydiary-net mydiary-front: build: context: ./my-diary-front dockerfile: Dockerfile deploy: replicas: 3 restart: always depends_on: - mydiary-back ports: - '3000-3002:3000' networks: - mydiary-net proxy-be: image: nginx:1.21.5-alpine container_name: rolling-server-lb restart: always depends_on: - mydiary-back ports: - '8080:80' volumes: - ${PWD}/proxy/nginx-be.conf:/etc/nginx/nginx.conf networks: - mydiary-net proxy-fe: image: nginx:1.21.5-alpine container_name: rolling-front-lb restart: always ports: - '80:80' volumes: - ${PWD}/proxy/nginx-fe.conf:/etc/nginx/nginx.conf networks: - mydiary-net networks: mydiary-net: driver: bridge ipam: driver: default config: - subnet: 172.20.0.0/24 ip_range: 172.20.0.0/24 gateway: 172.20.0.1 docker compose up --build docker compose ps docker compose down --rmi all