Docker 멀티 컨테이너 개발 환경

2020년 7월 14일 수정

이 앞의 Docker로 개발환경을 만들어보자 글에서 Docker를 이용해 Flask 웹 서비스 개발 환경을 만들어 봤었다. 아마도 이 정도면 가벼운 웹 서비스를 만드는 데 무리는… 있을거다. 세상에 DB도 없이 동적 웹 서비스를 만든다는 것은 아마도 바보같은 일일 것 같다.

자 그래서 이제 기능을 확장해야 할 시기를 맞이해야 할 것 같다.

MongoDB 컨테이너를 띄워보자

DB서버로 MongoDB를 이용해보자. 당연하게도 이미지가 제공되고 있어서 그냥 컨테이너로 띄우면 된다.

사실 우리의 자작 앱 컨테이너에서 apt-get 으로 MongoDB를 설치해도 된다. 하지만 이 DB를 다른 컨테이너에서도 접속해야 한다면 이런 방식은 귀찮은 문제를 야기시킬 수도 있다. 혹은 설정을 해야 한다거나 혹은 DB 서버만 용량을 늘린다던가 등의 최적화 작업을 할 때도 앱이 영향 받는 등 다른 단점도 많다.

따라서 DB를 별도의 컨테이너로 띄우는 것이 좋은 선택일거라 생각된다.

자 그런 이유로 MongoDB 이미지를 이용해 컨테이너를 띄워보자.

docker run --rm -d mongo

--rm 옵션은 컨테이너가 종료 시 자동으로 정보까지 삭제되도록 한 것으로 순전히 개인적인 취향이다. 없어도 동작에 무리는 없다.

-d 옵션은 백그라운드 데몬으로 띄우는 옵션인데 마찬가지로 취향껏 사용하면 된다. 멀티 탭을 지원하는 터미널이나 Tmux 등의 멀티플렉서를 쓴다면 굳이 백그라운드로 띄우지 않아도 로그도 볼 수도 있고 오히려 편할 수도 있다.

결과적으로 이렇게 하면 손쉽게 MongoDB 서버가 뜬 것과 동일한 효과가 난다. 굳이 별도의 패키지를 설치해서 서버 세팅을 하지 않아도 된다니 참 좋은 세상이다.

앱에서 DB에 접속해보자

이제 우리의 앱 컨테이너에서 DB 컨테이너에 접속되는지 확인해 봐야 한다. 아 참, 그전에 당연하겠지만, MongoDB에 접속해야 하니 앱 컨테이너에서 pymongo 패키지를 설치해두자.

pip install pymongo

그 다음 main.py 파일의 코드를 이렇게 바꾸었다.

from flask import Flask
from pymongo import MongoClient

db = MongoClient('mongo').myapp
app = Flask(__name__)

@app.route('/')
def num_users():
    c = db.user.find({}).count()
    return f'{c} user(s)'

app.run(host='0.0.0.0', port=8888)

코드의 의도는 myapp 데이터베이스의 user 컬렉션의 도큐먼트 갯수를 세어서 그냥 표시만 해 주려는 것이다.

그런데 이 코드는 아마도 제대로 동작하지 않을 것이다. 앱 컨테이너에서 타임아웃이 걸려서 에러가 나는 것을 볼 수 있을 것이다. 왜냐하면 mongo 라는 호스트 이름이 어디의 네트워크인지 알 수 없기 때문이다.

컨테이너에 이름 주기

순서가 반대로 된 것 같지만, 다시 DB 서버의 호스트 이름으로 서버를 찾을 수 있게 설정을 해 주어야 할 것 같다. 앞서 띄운 mongo 컨테이너를 내리고 아래의 커맨드로 새 컨테이너를 띄우자.

docker run --rm -d --name mongo mongo

위 명령에서 mongo 이미지를 실행할 때 --name 옵션을 이용해 이름을 주었다. 이 옵션은 도커 네트워크 상에 호스트 이름을 추가해 주는 것과 비슷한 기능을 한다.

다시 우리의 앱이 잘 동작하는지 시험해보자. 잘…되지는 않고 불행히도 여전히 동작하지 않을 것이다.

네트워크 링크

뭔가 마음대로 안 된다는 점에서 뭔가 빠진게 있다고 느끼는 사람이 있을까? 포트 번호? MongoDB의 기본 포트인 27017을 열어줘야 하는게 아닌가 생각할 수도 있다. 결론부터 말하자면 위 커맨드에 -p 27017:27017 옵션을 붙여도 변화는 없다. 이번에는 포트 문제가 아니기 때문이다.

대신 이번에는 우리의 앱 컨테이너를 새로 띄우면서 네트워크를 링크 시키는 방법을 써 볼 수 있다. 아래와 같은 커맨드로 앱 컨테이너를 다시 띄워보자.

docker run --rm -it -p 8888:8888 --link mongo myapp /bin/bash

--link mongo 라는 옵션이 추가되었다.

이제 다시 pymongo 패키지를 설치하고 시도해보자.

별 문제가 없다면 이제는 동작할 것이다. 웹 브라우저에는 아마도 '0 user(s)' 라고 표시될 것이다.

도커의 네트워크는 특이해서 연결된 컨테이너 끼리는 포트를 열어주지 않아도 알아서 접속할 수 있다. 필요한 것은 --name 옵션으로 호스트 이름을 지어주고 --link 옵션으로 필요한 컨테이너의 네트워크를 연결하는 것이다.

하지만 이제 --link 옵션은 이제 잊자. 기껏 소개했는데 왜냐고? 이 옵션은 사라질지도 모른다니까. 뭔 청천벽력 같은 소리인가.

도커 네트워크의 시작

다행히도 --link 옵션 없이도 컨테이너끼리 네트워킹을 하는 방법이 있다. 할 일은 더 많지만 사용하는 컨테이너가 많다면 각각 링크시킬 필요 없이 더 편할 수도 있는 방법이다.

바로 커스텀 네트워크를 만드는 것이다.

아래 커맨드로 우리의 앱을 위한 전용 도커 네트워크를 만들 수 있다.

docker network create my-network

이렇게하면 my-network 라는 네트워크가 생성된다.

이제 할 일은 이 네트워크로 필요한 컨테이너들을 다시 띄워주는 것이다.

우선 기존 mongo 컨테이너를 내리고 아래 커맨드로 다시 띄워보자.

docker run --rm -d --name mongo --net my-network mongo

나머지 옵션은 비슷하지만 추가로 --net my-network 라는 옵션이 보일 것이다. 이 옵션이 컨테이너가 사용할 네트워크를 지정할 수 있는 명령이다.

이제 우리 앱 컨테이너도 이 네트워크로 다시 띄워보자.

docker run --rm -it --net my-network myapp /bin/bash

다시 pip로 pymongo를 설치하고 앱을 실행시켜서 잘 동작하는지 확인해보자.

아마도 잘 돌아갈 것이다.

잘 돌아간다면 다시 패키지를 freeze 시켜서 새로 이미지를 떠두면 귀찮게 pymongo를 매번 설치하는 귀찮음을 겪지 않아도 된다.

정말 잘 돌아가고 있을까?

이제까지는 0만 표시되는 환경을 만들었다. 과연 내 코드는 제대로 동작하는지 쉽게 확인할 방법이 있을까?

앱을 고쳐서 도큐먼트를 추가하는 기능을 구현하는 것은 뭔가 일이 많을 것 같다. 그냥 MongoDB에 바로 접속해서 도큐먼트를 생성하는 방법으로 테스트해보자.

아래 커맨드로 mongo 컨테이너에 접속해보자.

docker exec -it mongo mongo

exec 명령은 이미 돌아가고 있는 컨테이너에서 특정 명령을 실행시킨다. 자세한 사항은 Docker CLI 명령 정리 글을 참고하자.

어쨌든 위 명령으로 해당 컨테이너에서 mongo 명령이 실행되면서 mongo 인터프리터가 동작한다. 여기서 아래와 같이 DB를 선택하고 도큐먼트를 삽입해보자.

> use myapp
> db.user.insert({name: 'James'})

이후 웹브라우저에서 리프레시를 해서 갯수가 1 올라가는게 보였다면 성공이다. 심심하면 더 추가해보고 또 확인해봐도 된다.

이제 우리의 앱도 잘 동작하고 네트워크도 잘 물려있고, 거기다 돌아가고 있는 컨테이너에 접속해서 별도의 명령을 실행시키는 방법도 해보게 되었다.

네트워크 청소하기

사실 네트워크는 한번 만들어 두면 계속 써도 된다. 그다지 리소스를 먹는 것도 아니고 놔둬도 해롭지는 않을 거라 생각된다.

하지만 슬슬 업무를 종료하고 쉬고자 모든 컨테이너를 종료했는데 네트워크가 남아있다면 그게 찝찝한 사람이 있을지도 모른다. 이렇게 결벽증이 심하다면 안 쓰는 네트워크를 청소해 주는 것도 나쁘지는 않을 것 같다.

일단 ls 커맨드로 자신이 생성한 네트워크가 있는지 확인해보자.

docker network ls

그리고 보인다면 rm 커맨드로 싸악 청소해 줄 수 있다.

docker network rm my-network

그런데 굳이 이렇게 확인하고 지울 필요는 없다. 아래 커맨드 한 방으로 모든 것을 해결하자.

docker network prune

위 명령은 도커 기본 네트워크를 제외한 모든 커스텀 네트워크를 싹 날려버리는 명령이다. 결벽증 환자에게 더없이 좋은 명령이다.

결론

이제 필요한 컨테이너만 골라서 원하는 대로 띄워서 컨테이너끼리 네트워킹을 하는 소프트웨어를 개발할 수 있는 환경을 구축했다. Docker의 장점을 최대한 활용해 마이크로서비스처럼 실제 환경과 흡사한 개발 환경을 만들었다. 그것도 별도의 패키지나 서버 설치 없이 말이다.

필요한 컨테이너를 부왁부왁 늘리면서 신나게 개발해보자. 뭐 귀찮긴 하지만 방법을 알게 되었으니 말이다. 혹은 좀 더 다른 방법을 알고 싶다면 Docker Compose로 개발 환경 단순화하기도 읽어보자.