도커 컴포즈 기초 및 문법

장후후·2021년 11월 2일
3

Docker Core Guide

목록 보기
4/5
post-thumbnail

도커 컴포즈란?

도커 컴포즈는 위 그림과 같이, 여러개의 컨테이너를 하나로 묶어주는 역할을 하는 툴이다.

특장점

우리가 일반적으로 도커 컨테이너를 실행하려면, docker run 이라는 명령어를 사용하는데,
여러개 컨테이너를 올릴때, 조금 노가다?? 같다라는 느낌을 받지 않았는가?

아마 느꼈다면, 어느정도 도커를 사용해본 사람일 것이다.

우리가 전 실습에서, redisDB, influxDB, telegraf를 컨테이너로 실행시켜봤는데, 그냥 하나 컨테이너 올릴때보다 복잡도가 엄청 올라간것을 체감할 수 있다.

만약에, 더더욱 복잡한 구조로 컨테이너 기반의 시스템을 구성한다면, 매우 큰일이 아닐수가 없는데!
이럴때 우리를 도와주는 툴이 docker compose라고 할 수 있다.

본격적으로 들어가기전에,
내가 개인적으로 생각하는 컴포즈의 장점은 아래와 같다.

  • 컨테이너가 여러개 실행되야 하는 시스템 구성을 할때, 깔끔하게 정리를 할 수 있음
  • 명령어가 굉장히 간단하며 직관적이고, 쉽게 배울 수 있음

예제를 한번 보여주겠습니다.

도커 컴포즈 문법

YAML

yaml이란?

YAML -> Yaml Ain't Markup Language
yaml은 마크업 언어가 아니다?!? 원래는 Yet Another Markup Language의 준말이었는데,
마크업 언어라는게 핵심이 아니라, 안에 있는 데이터를 중점으로 하기 때문에 맨위와 같이 재귀적 말장난이 공식 이름이 되었음

결국 추구하는 것은 사람이 보기 편하게 이해하기 쉽게 하는 형태를 보여주는게 목적이다.

그렇기 때문에, 현재 많은 곳에서 yaml 포맷을 활용하고 있다.

백문이 불여일견 얼마나 쉬운 포맷인지 확인해보겠다.

personal:
  name: jsh
  age: 31
  address: Korea, Hwaseong
  phone: '010001233'
  email: jsh@email
professional:
  skill:
  - javascript: 3Year
  - nodejs: 3Year
  - docker: 3Year
  - vue.js: 2Year

딱보면 엄청 깔끔하지 않은가??

JSON, XML, YAML

얼마나 깔끔한지 JSON, XML 같은 유명한 포맷과 비교도 한번해보자.

yaml은 왜?

docker compose에 대해 얘기하다가 왜 yaml이라는게 나왔을까요?
바로 docker compose를 실행시키기 위한 설정파일이 yaml포맷으로 되어있기 때문입니다.

그러면 이제, docker compose 작성방법에 대해서 알아볼게요

기본 작성법

docker compose 공식 홈페이지에 있는 예제로 설명해 보겠습니다.

https://docs.docker.com/compose/gettingstarted/

python flask, redis 예제

app.py

import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)

requirements.txt

flask
redis

dockerfile 준비

# syntax=docker/dockerfile:1
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]

docker-compose.yaml 파일 준비

version: "3.3"
services:
  web:
    build: .
    ports:
      - "5000:5000"
  redis:
    image: "redis:alpine"

docker-compose 실행하기

 docker-compose up

docker-compose 바인드 마운트하기

version: "3.3"
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
    environment:
      FLASK_ENV: development
  redis:
    image: "redis:alpine"

도커 컴포즈 문법 설명

version

도커 컴포즈 파일의 버전 여기선 3.3로 지정했음

services

컨테이너를 실행하기 위한 단위라고 보면 됨. 하위에는 서비스 이름->서비스의 옵션 순으로 작성하면된다.

version:"3.3"
services:
  web:
    build:
    ...

build

build할 dockerfile의 경로를 지정해주면 된다.

ports

포트포워딩 지정하는 옵션이다. 순서대로 <호스트 포트>:<컨테이너 포트>로 작성하면 된다.

volume

바인드 마운트, 볼륨을 지정할 수 있다.

environment

컨테이너에서 사용할 환경변수를 설정 할 수 있다.

depends_on

실행순서를 보장받고 싶을 때, 사용한다.

도커 컴포즈 CLI 명령어

실행 docker-compose up
백그라운드에서 실행 docker-compose up -d
서비스 중지 docker-compose stop
서비스 다운 docker-compose down
서비스 다운 후 볼륨삭제 docker-compose down --volumes
컨피그 확인하기 docker-compose config
실행중인 서비스 컨테이너 명령어 실행하기 docker exec <서비스이름> <명령어>
실행중인 서비스 확인하기 docker-compose ps
서비스 로그 확인하기 docker-compose logs
서비스 로그 지속적으로 프린트하기 docker-compose logs -f
서비스 로그 지정해서 확인하기 docker-compose logs <서비스이름>
서비스 로그 여러개 지정해서 확인하기 docker-compose logs <서비스이름> <서비스이름> ...

docker-compose라는 명령어가 너무 길어서 사용하기 불편하다면,
윈도우는 set-alias dco docker-compose dco
리눅스는 ~/.bashrc나 ~/.zshrc에 alias dco='docker-compose'를 추가하고
docker-compose를 dco로 대체해서 사용하면 된다.

docker-compose.yaml 파일 병합해서 사용하기

프로덕션 레벨의 docker-compose를 작성할 때, 서비스가 방대해 질 수록, 코드의 길이가 너무 길어져서 보기가 어려울 수 있다. 그럴 경우 설정을 분할해서 작성하는 방법을 설명하겠다.

docker-compose.yaml

version: '3.3'
services:
  api: 
    image: node
    stdin_open: true
    tty: true
    depends_on:
      - db
      - cache
  db:
    image:postgres
  cache:
    image:redis
  web-front:
    image: nginx
    depends_on:
      - api
  

docker-compose.override.yaml

version: '3.3'
services:
  api:
    environment:
      - FLASK_APP_SERVER=app.py
    ports:
      - "8080:80"
  db:
    image:postgres
  cache:
    image:redis
  web-front:
    image: nginx
    depends_on:
      - api

도커 컴포즈 기초 실습

기본 예제로는 로드 밸런싱을 준비하였다.

로드 밸런싱이라는 건, 서버에 웹요청이 너무 많이 한꺼번에 동시에 들어오는 상황을 해결하는 방법이다.

일단은 여러방법이 있겠지만, Nginx를 사용한 방법을 소개하고자 한다.

로드 밸런싱 (기본)

nginx를 proxy server로 한 가장 기본적인 로드밸런싱 방법이다.
예제에서는 nodejs express api 서버 3개를 준비하고,
Nginx 프록시로 연결하는 방법에 대해서 알아보겠다.

먼저 api로 사용할 nodejs express 서버의 이미지를 만들어보자.
dockerfile과 nodejs 소스코드 index.js를 준비한다.

dockefile

FROM node
WORKDIR /app
RUN npm i express uuid
EXPOSE 3000
COPY . .
CMD node index.js

index.js

var express = require('express'); 
var app = express(); 
var uuid = require('uuid'); 
var id = uuid.v4(); 
var port = 3000; 

app.get('/', (req, res) => { 
    res.send(id);
    console.log(id) 
}); 
app.listen(port, function(){ 
    console.log('port : ', + port); 
});

다음으로는 nginx 설정 파일로 사용할 nginx.conf 파일을 준비한다.

nginx.conf

http {
    upstream node-app {
        server node1:3000;
        server node2:3000;
        server node3:3000;
    }
    server {
        listen 80;
        location / {
            proxy_pass http://node-app;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }
    }
}

마지막으로 서비스들을 묶어서 실행해 줄 compose파일을 준비하자.

docker-compose.yaml

version: '3.3'
services:
  node1:
    image: 'node-api-server'
    build: .
  node2:
    image: 'node-api-server'
    build: .
  node3:
    image: 'node-api-server'
    build: .
  loadbalancer:
    image: nginx
    volumes:
      - "./config/nginx.conf:/etc/nginx/nginx.conf"
    ports:
      - '80:80'

폴더 구조는 다음과 같다.

docker-compose up -d를 실행하자
docker-compse ps로 서비스가 실행이 잘 되었는지 확인해보자

loadbalancer 서비스로 접근 (localhost)

잘 나오는 것을 확인할 수 있다.

세 서비스의 로그를 확인해본다.

연속적으로 요청하게 되면, 다음과 같이 순서대로 배분해주는 것을 확인할 수 있다.

기본적으로 nginx에서는 라운드-로빈 방식(순서대로)으로 접근시켜주기 때문이다.

로드 밸런싱 (HA Proxy)

var http = require('http');
var os = require('os');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(`<h1>I'm ${os.hostname()}</h1>`);
    console.log(os.hostname())
}).listen(8080);
FROM node
RUN mkdir -p /usr/src/app
COPY index.js /usr/src/app
EXPOSE 8080
CMD [ "node", "/usr/src/app/index" ]

docker-compose.yaml

version: '3.3'
services:
  node:
    image: api-example
    ports:
      - 8080
    environment:
      - SERVICE_PORTS=8080
    deploy:
      replicas: 5
      update_config:
        parallelism: 5
        delay: 10s
      restart_policy:
        condition: on-failure
        max_attempts: 3
        window: 120s
    networks:
      - web

  proxy:
    image: dockercloud/haproxy
    depends_on:
      - node
    environment:
      - BALANCE=leastconn
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - 80:80
    networks:
      - web
    deploy:
      placement:
        constraints: [ node.role == manager ]

networks:
  web:
    driver: overlay

위의 세개파일을 만들어 놓고, 순서대로 설정해보자

일단 도커파일로 이미지를 만든다.

docker build -t api-example .

다음으로 스택을 배포 할 수 있도록 도커스웜 초기화 명령어인

docker swarm init을 실행해보자

도커 스웜 init이 제대로 실행되었다면,
docker stack deploy --compose-file=docker-compose.yaml study 으로 서비스를 실행시켜보자

docker ps로 확인해보면 5개의 컨테이너와 하나의 haproxy 서버가 올라가 있는것을 확인할 수 있다.
docker service ls로 조금 더 정리된 화면을 확인할 수 있다.

docker service logs -f study_node로 로그를 확인해보자.

profile
Backend Developer, DevOPS Engineer, IIoT, IoT

2개의 댓글

comment-user-thumbnail
2023년 1월 9일

오타많아요 그리고 yaml파일 버전 3.9 -> 3.3으로 변경했습니다 3.9 안되요 최신화 부탁드립니다

1개의 답글