Web/Django-python

docker-compose를 이용해 Django와 Mysql 컨테이너 연결하기

1. env 파일을 이용한 docker-compose 작성

docker를 이용해 여러 컨테이너를 묶으려고 하는데, 그 중 mysql DB 컨테이너가 포함되는 경우 mysql 계정명과 비밀번호 등을 설정해야한다.

허나 비밀번호가 docker-compose.yml 파일에 하드코딩되는건 안될일, 다른 파일에 넣고 읽어오는 식으로 만드는게 보안상 안전하다.

그러나 yaml 문법에서는 include나 import를 지원하지 않는다. 따라서 docker-compose에서 독자적으로 지원하는 방법을 이용해야 한다.

기본적으로 docker-compose는 같은 디렉토리에 있는 .env 파일을 환경변수 세팅 파일로써 가져온다. 따라서, mysql 등과 같은 컨테이너에서 environment 인자로 계정명과 비밀번호를 세팅할 경우에는 이 .env 파일을 이용하는것이 적합하다.

docker-compose.yml

  mysql:
    image: mysql
    container_name: mysql_service
    env_file :
      - ../envs/mysqlenv
    volumes:
     -- ../volumes-mysql/mysql/:/var/lib/mysql/
    command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci
    ports:
      - "3306:3306"

총 두가지 방식이 있는데, 첫번째는 컨테이너에서 env_file 을 명시하는 것. 같은 디렉토리가 아니더라도 env 파일을 여러개 만들어 두었다가 해당 파일의 경로를 읽어오는 식으로 환경변수 세팅이 가능하다.

../envs/mysqlenv

MYSQL_ROOT_PASSWORD=rootpass
MYSQL_DATABASE=dbname
MYSQL_USER=username
MYSQL_PASSWORD=dbpass

이때 환경변수 명은 실제로 사용할 변수명과 동일하게 설정해야한다.

docker-compose.yml

  mysql:
    image: mysql
    container_name: mysql_service
    volumes:
      - ../volumes-mysql/mysql/:/var/lib/mysql/
    environment: 
      MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
      MYSQL_DATABASE: "${DB_DATABASE}"
      MYSQL_USER: "${DB_USER}"
      MYSQL_PASSWORD: "${DB_PASSWORD}"
    command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci
    ports:
      - "3306:3306"

두번째 방법은 .env 파일을 이용하는건데, docker-compose 는 같은 디렉토리에 있는 .env 파일을 자동으로 가져와 환경변수 인자로 사용하게끔 한다.

.env

DB_ROOT_PASSWORD=rootpass
DB_DATABASE=dbname
DB_USER=username
DB_PASSWORD=dbpass

위와같은 형태로 변수명=값 형태로 .env 파일을 세팅해두면, compose 파일에서 해당 변수명에 맞게 값을 세팅해서 사용할 수 있다.

두가지 방식 다 사용시 주의점은 띄어쓰기가 없어야한다. 띄어쓰기가 들어가면 제대로 인식이 안되더라.

docker-compose config 명령어로 결과를 확인해볼 수 있다.

또 여기서 주의해야 할게, 저기에 있는 저 환경변수들은 mysql 컨테이너를 처음 생성할때만 적용되어 이후로 쭉 유지된다. 즉, docker-compose 파일에 mysql 세팅을 다 끝내고 *첫 실행할때 정했던 패스워드가 앞으로 해당 컨테이너에서 쓰이고, 이후로는 환경변수 값을 아무리 바꿔도 적용이 안된다는것. *

저걸 몰라서 한참을 삽질했다. 비밀번호를 미리 세팅해두고 컨테이너를 새로 생성하니까 잘 되더라.

2. 장고 컨테이너 설정

mysql 컨테이너 세팅을 위해 docker-compose와 env 파일 작성이 끝났으면 이제 장고에서 mysql과 연결시킬 준비를 해야한다.

settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db이름',
        'USER': '유저이름',
        'PASSWORD': '패스워드'
        'HOST': 'mysql_service',
        'PORT': '3306',
    }
}

django의 프로젝트 설정파일을 열어 DATABASES 항목을 위와 같이 수정해준다. 기본적으로 장고에서 지원하는 sqlite 대신 mysql를 사용하기 위한 설정이다.

그 다음, 장고 컨테이너에서 mysql을 실행하기 위해 mysql 설치가 필요하다. 아래와 같은 내용을 추가하여 도커파일을 수정하고 이미지를 새로 빌드해주자.

dockerfile

RUN apt-get install -y libmysqlclient-dev=5.7.31-0ubuntu0.18.04.1
RUN pip3 install mysqlclient

이렇게 DB 컨테이너와 장고 컨테이너가 연동이 되면 장고 앱의 models.py 에서 작성하여 마이그레이트한 내용이 DB에도 동일하게 반영이 된다.

그 다음에 docker-compose를 할시 열이면 아홉은 에러가 발생한다.

이는 sql 컨테이너가 준비가 끝나기 전 장고 컨테이너가 실행되서 그런것이다. sql 컨테이너 준비가 완료되어야 장고에서 정상적으로 연결할 수 있을텐데, 그렇지 못하면 정상적인 연결이 불가능해 에러가 발생할 수 밖에 없다.

따라서 장고 컨테이너가 sql 컨테이너 준비가 끝날때까지 대기하는 과정이 필요하다.

인터넷에서 찾았을때는 여러가지 방법이 있었는데, 나는 그중에서 dockerize를 사용하기로 했다. dockerize는 도커 컨테이너들의 관리를 돕는 일종의 유틸이다.

dockerfile

ENV DOCKERIZE_VERSION v0.6.1
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 \
    && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz

ENTRYPOINT ["dockerize", "-wait", "tcp://mysql_service:3306", "-timeout", "20s"]

해당 내용도 도커파일에 추가해주면 된다.

내용만 보면 이해가 되기에 굳이 설명이 필요할까 싶다. 깃허브에서 dockerize 패키지를 받아와 설치하고, 컨테이너를 시작할때 실행되는 예약명령인 docker entrypoint를 지정해주는 내용을 추가하였다. 해당 예약 명령은 dockerize를 이용해 mysql_service 컨테이너의 3306 포트에 20초동안 tcp 연결을 걸며 대기하는 명령어다.

이렇게 하면 장고 컨테이너가 먼저 실행되더라도 mysql 컨테이너 준비가 끝나 포트가 열리기 전까지는 20초동안 대기를 진행하기 때문에 디비에 연결을 실패해 컨테이너가 종료되는 불상사는 발생하지 않는다.

 

장고 컨테이너가 대기하면서 계속 재연결을 시도하는 로그가 보인다. 그리고 mysql 컨테이너의 준비가 끝나 포트가 열리면 연결에 성공하고 이후 작업을 계속 진행한다. 

 

장고 models.py에서 추가한 모델들의 내용도 마이그레이션 이후 DB에 잘 반영되는걸 볼 수 있다. 

컨테이너간 연동이 겨우 끝났으니 이제 웹페이지에서 디비를 활용해보자.