최근 패스트캠퍼스 스쿨 웹 프로젝트를 진행하면서, Docker Compose를 이용해 Django 어플리케이션과 PostgreSQL를 실행하고 있다.
아래는 이를 위해 내가 작성한 Dockerfile
, docker-compose.yml
, docker-entry.sh
파일들이다.
[Dockerfile]
FROM python:3.5
MAINTAINER Seunghwan Joo <shjoo@fastcampus.co.kr>
ENV PYTHONUNBUFFERED 1
ENV DJANGO_SETTINGS_MODULE school.settings.development
ENV DATABASE_URL postgres://postgres@db:5432/postgres
RUN mkdir /code
ADD requirements /code/requirements
ADD school /code/school
ADD requirements.txt /code/
ADD Procfile /code/
ADD Makefile /code/
ADD docker-entrypoint.sh /code/
WORKDIR /code
RUN pip install -r requirements/development.txt
RUN chmod +x docker-entrypoint.sh
ENTRYPOINT ./docker-entrypoint.sh
EXPOSE 8000
[docker-compose.yml]
version: '2'
services:
db:
image: postgres
container_name: school_db
volumes:
- /data/postgres:/var/lib/postgresql/data
web:
image: school:latest
container_name: school_web
ports:
- '8000:8000'
links:
- db:db
[docker-entrypoint.sh]
#!/bin/bash
# Apply database migrations
echo "Apply database migrations"
make migrate
# Start server
echo "Start server"
python school/manage.py runserver 0.0.0.0:8000
간단히 설명하면, 먼저 내가 만든 Django 어플리케이션을 school:latest
라는 이미지로 빌드하였다. 그리고 이 이미지가 컨테이너로 실행될 때 docker-entrypoint.sh
가 실행되도록 하였다. 이 안에는 데이터베이스를 migration하기 위한 명령어와 서버를 구동하기 위한 명령어들을 적어놓았다.
PostgreSQL 컨테이너를 만들기 위해 Docker에서 기본적으로 제공하는 postgres
이미지를 사용하였고 내 로컬의 /data/postgres/
를 볼륨으로 연결하였다. 그리고 links
를 통해 이를 Django 어플리케이션 컨테이너와 연결하였다.
하지만 $ docker-compose up
명령어를 실행하면,
위와 같은 에러가 발생하였다. 그리고 이 에러의 원인이, 데이터베이스가 미처 startup 되기 전에 web 컨테이너가 연결을 시도했기 때문이라는 것을 알게 되었다. links
(또는 depends_on
)로 두 컨테이너를 연결했는데도 왜 이와 같은 문제가 발생한 걸까?
나는 처음에 links
나 depends_on
가 이런 dependency 문제를 당연히 해결해주리라고 생각했었다. 하지만 Docker 공식 문서에 아래와 같이 적혀있었다.
Note: depends_on will not wait for db and redis to be “ready” before starting web - only until they have been started. If you need to wait for a service to be ready, see Controlling startup order for more on this problem and strategies for solving it.
links
나 depends_on
은 단지 컨테이너가 시작되는 순서만 컨트롤할 뿐, 컨테이너 안의 서비스가 "ready"(실제 실행 가능한 상태)인지 아닌지에 대해서까지는 관여하지 않는다. 어떻게 보면 얘들은 컨테이너들을 일단 순서에 맞게 시작만 시켜놓고 그 뒤는 나몰라라하는 무책임한 녀석이었다.
따라서 이를 컨트롤하기 위해서는 다른 방법을 사용하여야 한다. stackoverflow에 이를 위한 여러 답변들이 있었다.
1. restart: always
사용하기
restart: always
사용하기컨테이너에 restart: always
를 설정하면, 컨테이너가 실행 중 중단됐을 때(여기서는 어떤 특정 서비스가 ready 상태가 아직 안 돼 중단된 경우) 컨테이너를 다시 알아서 재시작을 해준다.
아래와 같이 docker-compose.yml
에서 web
서비스에 restart: always
를 추가해주었다.
version: '2'
services:
db:
image: postgres
container_name: school_db
volumes:
- /data/postgres:/var/lib/postgresql/data
web:
image: school:latest
container_name: school_web
ports:
- '8000:8000'
links:
- db:db
restart: always
하지만 $ docker-compose up
를 실행하였을 때, 그전과 똑같은 에러가 발생하였다.
그 원인을 확실히는 잘 모르겠지만, 어떤 분의 답변에 의하면 실행 중 에러가 발생했을 때 "exit" 되지 않는 컨테이너에서는 작동되지 않는다고 한다.(실제로 web
컨테이너에서 에러가 발생했을 때 web
컨테이너는 exit되지 않았고, $ docker ps
를 해보면 여전히 실행 중임을 볼 수 있다.
2. wait-for-it이나 dockerize와 같은 툴 이용하기
이런 문제를 해결하기 위해 만들어진 여러 툴들이 있다. 나는 여기서 dockerize라는 툴을 사용해 보았다.
먼저 web
컨테이너에 dockerize를 사용할 수 있게 설치를 해주어야 한다. Dockerfile
에 dockerize를 설치하기 위한 명령어를 추가해주었다.
FROM python:3.5
MAINTAINER Seunghwan Joo <shjoo@fastcampus.co.kr>
ENV PYTHONUNBUFFERED 1
ENV DJANGO_SETTINGS_MODULE school.settings.development
ENV DATABASE_URL postgres://postgres@db:5432/postgres
ENV DOCKERIZE_VERSION v0.2.0
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
&& tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
...이하 생략...
그리고 docker-entrypoint.sh
에서 데이터베이스를 migration 하기 전, db
컨테이너의 실제 PostgreSQL 서비스가 실행가능한 상태가 될 때까지 기다리기 위한 코드를 추가해주어야 한다. timeout
은 기본적으로 10s인데, 10초보다 더 걸리는 경우가 가끔씩 발생하여 안전하게 20s로 늘려주었다.
#!/bin/bash
dockerize -wait tcp://db:5432 -timeout 20s
# Apply database migrations
echo "Apply database migrations"
make migrate
...이하 생략 ...
$ docker-compose up
를 다시 실행해보면, web 컨테이너는 db 컨테이너의 서비스가 실행가능할 때까지 기다리다가
PostgreSQL 서비스가 ready 상태가 되면 그제서야 다음 명령어인 migration을 실행한다.
3. script에서 Netcat을 이용하여 해당 서비스가 실행이 될 때까지 강제로 기다리게 하기
Netcat은 TCP나 UDP 프로토콜을 사용하는 네트워크 연결에서 데이터를 읽고 쓰는 간단한 유틸리티 프로그램이다. 이를 이용하면 PostgreSQL 서비스의 실행 유무를 네트워크 연결 상태로 체크할 수 있다.(앞에서 다루었던 dockerize의 원리와 동일하다.)
먼저 web
컨테이너에 netcat를 사용할 수 있게 설치를 해주어야 한다. Dockerfile
에 netcat를 설치하기 위한 명령어를 추가해주었다.
FROM python:3.5
MAINTAINER Seunghwan Joo <shjoo@fastcampus.co.kr>
ENV PYTHONUNBUFFERED 1
ENV DJANGO_SETTINGS_MODULE school.settings.development
ENV DATABASE_URL postgres://postgres@db:5432/postgres
RUN apt-get update && apt-get install netcat-openbsd -y
...이하 생략...
그리고 docker-entrypoint.sh
에서 데이터베이스를 migration 하기 전, netcat를 이용한 별도의 script를 넣어주었다.
#!/bin/bash
postgres_host="db"
postgres_port=5432
# Wait for the postgres docker to be running
while ! nc $postgres_host $postgres_port; do
>&2 echo "Postgres is unavailable - sleeping"
sleep 1
done
# Apply database migrations
echo "Apply database migrations"
make migrate
...이하 생략 ...
이렇게 하면 Host 이름이 db이고, Port가 5432번인 연결이 확인될 때까지 무한반복문을 돌게 된다.
$ docker-compose up
를 다시 실행해보면, web 컨테이너는 db 컨테이너의 서비스가 실행가능할 때까지 기다린 후, migration을 실행함을 볼 수 있다.