이 글의 목표는 원격 배포 환경과 최대한 유사한 환경에서 애플리케이션을 개발하는 것입니다. Docker Machine과 Compose를 활용해 앱을 로컬 개발 단계에서 원격 배포 단계까지 이동시키는 전 과정을 함께 살펴보겠습니다.
Docker, Docker, Docker
먼저 Docker를 설치해야 합니다. 이 글은 작성 시점 기준으로 가장 최신 버전의 Docker 구성 요소들을 사용합니다.
- Docker 1.7.0
- Docker Machine 0.3.0
- Docker Compose 1.3.0
이후 내용은 VirtualBox에서 Docker를 실행한다고 가정하고 진행합니다.
방명록에 서명해 주세요
여기서 배포할 애플리케이션은 간단한 방명록(Guestbook)입니다. 기본적인 3계층(3-Tier) 앱 구조를 보여주기에 충분한 예제입니다. 먼저 다음 기술 스택을 사용해 웹 앱을 개발할 로컬 개발 환경부터 시작합니다.
- Nginx : 웹 서버
- 앱 서버 :
- Python : 프로그래밍 언어
- Flask : 웹 프레임워크
- Gunicorn : WSGI 서버
- MySQL : 데이터베이스
GitHub 저장소에서 작업할 예정이므로 먼저 저장소를 클론합니다.
git clone https://github.com/rackerlabs/guestbook.git -b dmc guestbook
cd guestbook
Docker 호스트가 여러 개 생기면 어떤 호스트에 접속 중인지 헷갈리기 쉽습니다. 아래와 같은 별칭(alias)을 설정해 두면 현재 사용 중인 호스트를 쉽게 확인할 수 있습니다.
alias de='env | grep DOCKER_'
이제 de 명령어만 입력하면 설정된 Docker 환경 변수를 바로 확인할 수 있습니다.
다음은 애플리케이션을 로컬 개발 환경에서 원격 배포 환경으로 옮기는 전체 과정의 최종 결과 모습입니다.
로컬 개발
로컬 개발 환경(dev env)은 자격 증명 설정이나 보안을 신경 쓸 필요가 없어 매우 간단합니다. 원격 배포 환경과 혼동되지 않도록 개발 환경 관련 리소스에는 모두 "-dev" 접미사를 붙입니다.
환경 초기화
docker-machine으로 컨테이너가 실행될 Docker 호스트를 생성합니다. 그다음 Docker가 해당 호스트를 사용하도록 설정하고, 마지막으로 docker-compose로 개발 환경 전체를 띄웁니다.
docker-machine create --driver virtualbox guestbook-dev
docker-machine ip guestbook-dev # note the Guestbook Dev IP Address
eval "$(docker-machine env guestbook-dev)"
docker-compose up
실행되는 동안 docker-compose.yml 파일을 읽어보며 어떤 작업을 대신 처리해 주는지 파악해 보세요. 주석이 충분히 달려 있어 스스로 이해하는 데 어려움이 없을 것입니다. 공통 컴포넌트의 기반으로 docker-compose-common.yml을 확장해 사용하는 구조도 눈여겨보세요. Docker Compose 파일 형식에 대한 전체 설명은 공식 docker-compose.yml 레퍼런스 문서에서 확인할 수 있습니다.
모든 이미지가 다운로드되고 컨테이너가 실행되면 데이터베이스를 초기화해야 합니다. 앱을 일회성(one-off) 컨테이너로 실행해 데이터베이스에 테이블을 생성하는 Python 명령을 수행합니다.
새 터미널을 열고 guestbook 디렉터리로 이동합니다.
eval "$(docker-machine env guestbook-dev)"
docker-compose run --rm --no-deps app python app.py create_db
왜 굳이 일회성 컨테이너를 실행할까요? docker-compose가 데이터베이스 연결에 필요한 환경 변수를 자동으로 주입해 주기 때문입니다. 별도의 설정 작업이 전혀 필요 없습니다.
이제 브라우저를 열고 앞서 기억해 둔 Guestbook Dev IP 주소로 접속하세요. 방명록에 서명해 주세요!
변경 사항 실시간 반영하기
애플리케이션 코드를 빠르게 계속 개발할 수 있는지 확인해 봅시다. docker-compose.yml에는 이를 가능하게 하는 핵심 설정 옵션들이 있습니다. app 서비스의 volumes와 command 항목의 주석을 다시 한 번 읽어보세요.
선호하는 텍스트 편집기로 app/templates/index.html 파일을 열고 코드를 수정한 뒤 저장합니다. 브라우저로 돌아가 새로고침하면 짜잔! 변경 사항이 즉시 반영됩니다.
원격 배포
이번 파트는 조금 더 까다롭습니다. 자격 증명을 설정해야 하고 보안도 고려해야 하기 때문입니다. Rackspace 계정이 없다면 developer+에 가입해 무료 크레딧을 받을 수 있습니다. 계정 준비가 끝나면 API 키를 찾아 터미널에 다음 환경 변수를 설정하세요.
export OS_USERNAME=your-rackspace-username
export OS_API_KEY=your-rackspace-api-key
export OS_REGION_NAME=IAD
환경 초기화
docker-machine으로 원격 Docker 호스트를 생성합니다.
docker-machine create --driver rackspace guestbook
docker-machine ip guestbook # note the Guestbook IP Address
환경 보안 강화
주의: 이 구성은 가능한 가장 안전한 설정과는 거리가 멉니다. 이 스크립트는 기본 설정보다 조금 더 안전해지기 위한 목적일 뿐이며, 서버 침입이나 데이터 유출을 완전히 막아준다고 보장하지 않습니다. 보안 위협은 언제나 존재하며, 이 스크립트를 실행했다고 해서 안전한 애플리케이션 코드를 작성할 책임이 사라지는 것도 아닙니다!
fail2ban을 설치해 서버에 대한 무차별 대입(Brute Force) 로그인 시도를 차단하고, ufw로 방화벽을 구성해 필요한 포트만 허용합니다. 명령이 완전히 끝날 때까지 화면에 출력이 나타나지 않으므로 약간의 인내심이 필요합니다.
docker-machine ssh guestbook "apt-get update"
docker-machine ssh guestbook "apt-get -y install fail2ban"
docker-machine ssh guestbook "ufw default deny"
docker-machine ssh guestbook "ufw allow ssh"
docker-machine ssh guestbook "ufw allow http"
docker-machine ssh guestbook "ufw allow 2376" # Docker
docker-machine ssh guestbook "ufw --force enable"
환경 초기화 계속하기
Docker Compose가 원격 배포를 구축하기 전에 데이터베이스에 사용할 환경 변수를 먼저 설정해야 합니다. 이 값들은 docker-compose-prod.yml 파일에서 사용되며, 민감한 정보가 GitHub에 커밋되는 사고를 방지하기 위함입니다.
export MYSQL_USER=guestbook-admin
export MYSQL_PASSWORD=$(hexdump -v -e '1/1 "%.2x"' -n 32 /dev/random)
export MYSQL_ROOT_PASSWORD=$(hexdump -v -e '1/1 "%.2x"' -n 32 /dev/random)
echo "MYSQL_USER=$MYSQL_USER"
echo "MYSQL_PASSWORD=$MYSQL_PASSWORD"
echo "MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD"
이제 Docker가 해당 호스트를 사용하도록 설정합니다. 마지막으로 docker-compose가 백그라운드에서 컨테이너를 빌드하고 실행하므로, 로컬 개발 때처럼 화려한 로그 메시지는 화면에 나타나지 않습니다.
eval "$(docker-machine env guestbook)"
docker-compose --file docker-compose-prod.yml build
docker-compose --file docker-compose-prod.yml up -d
실행되는 동안 docker-compose-prod.yml 파일을 읽어보세요. 이 파일은 기본 docker-compose.yml 이름으로 지정되어 있어 docker-compose 명령에 파일 이름을 별도로 지정하지 않아도 됩니다. 역시 주석이 잘 정리되어 있고, 공통 컴포넌트의 기반으로 docker-compose-common.yml을 확장하는 구조입니다.
모든 것이 빌드되고 실행되면 데이터베이스를 초기화합니다. 앱을 일회성 컨테이너로 실행해 테이블을 생성하는 Python 명령을 수행합니다.
docker-compose --file docker-compose-prod.yml run --rm --no-deps app python app.py create_db
이제 브라우저를 열고 앞서 기억해 둔 Guestbook IP 주소로 접속하세요. 방명록에 서명해 주세요!
변경 사항 배포하기
원격 환경에서 직접 코드를 개발하지는 않겠지만, 변경 사항은 손쉽게 배포하고 싶을 것입니다.
편집기로 app/templates/index.html 파일을 열고 수정한 후 저장합니다. 브라우저에서 Guestbook Dev IP 주소로 접속해 새로고침하며 로컬 개발 환경에서 의도한 대로 변경되었는지 먼저 확인합니다.
이제 익숙한 몇 가지 명령만으로 해당 변경 사항을 원격 환경에 배포합니다.
docker-compose --file docker-compose-prod.yml build
docker-compose --file docker-compose-prod.yml up -d
브라우저에서 Guestbook IP 주소로 돌아가 새로고침하세요. 짜잔! 변경 사항이 반영되었고, 기존 데이터도 그대로 잘 보존되어 있습니다.
다음 단계는?
이 예제를 한 단계 더 발전시키려면 무엇을 해야 할까요?
데이터는 애플리케이션에서 가장 중요한 자산입니다. 반드시 안전하게 보호해야 하며, 우선 백업 계획부터 세우는 것이 좋습니다. cron 잡을 통해 데이터베이스 덤프를 Cloud Files에 저장하는 솔루션을 직접 구축할 수도 있고, 더 간편한 방법으로 Cloud Backup을 활용할 수도 있습니다. 좀 더 나아가 고가용성 Cloud Database를 생성한 뒤 docker-compose.yml을 수정해 해당 데이터베이스를 사용하도록 변경하는 방법도 있습니다.
결론
이 글을 통해 원격 배포 환경과 최대한 유사한 환경에서 애플리케이션을 개발하는 방법을 배우셨기를 바랍니다. 진행 과정에서 문제가 발생했다면 아래 댓글로 알려주세요.