• Docker Swarm으로 고래엮기

    Docker Swarm?


    서버 오케스트레이션 이라는 애매모호한 용어가 있다. 혹시 백엔드 쪽에 관심이 있는 분이라면 인터넷 어디선가 한번쯤 들어봤을 수도 있겠다. 이게 쓰는 사람에 따라 의미가 조금씩 달라지는 용어라 위에 ‘애매모호’ 라고 표현을 헀는데, 기본적으로 IT 인프라의 자동화 라는 뜻을 가지고 있다. 오늘 다룰 Docker Swarm이 바로 “IT 인프라의 자동화”를 할 수 있게 도와주는 오케스트레이션 도구다.

    당연히 Docker 컨테이너를 기반으로 오케스트레이션을 구현해주는 도구가 이것만 있는 것은 아니다. 다만, Docker Swarm은 무려 Docker의 내장 기능이라 기존에 사용하던 Docker의 다른 기능들, 그리고 명령어들과 눈물나게 잘 어우러지는 말 그대로의 오케스트라를 보여 주는 덕에 뒤도 안보고 선택하게 되었다. 그런데 이런 내장 기능들이 다 그러하듯, 정말 깔끔하게 필요한 기능들 이상은 제공하지 않는다는 단점 아닌 단점은 존재한다. 혹시 Docker Swarm 이상의 기능이 필요한 분들은 아쉽지만 뒤로가기를 눌러 구글로 돌아가길 바란다.

    백문이 불어일견이라고, 아래 이미지를 보자. 기본이 되는 Docker Swarm 인프라의 구성도다.


    아마 한눈에 어느정도 이해가 갈 것이다. 위의 Swarm Manager가 아래 Docker 데몬이 돌아가는 Worker 들을 중앙 관제하는 방식으로 구성되어 있는데, 관리자는 Swarm Manager에 터미널이나 Docker Compose 등으로 명령을 내려 아래 서버들까지 한번에 컨트롤 할 수 있다. 이러한 Master가 Slave들을 조련하는 중앙 관제 방식은 비단 Docker Swarm 뿐만 아니라 대부분의 오케스트레이션화가 진행된 인프라에 공통적으로 쓰이는 ‘정석’이니 지금 익숙해지면 나중에 편할 것이다.

    설치하기

    아래 과정을 진행하며 이 포스트를 많이 참고했다. 개인적으로 한국어로 작성된 Docker Swarm 관련 포스트 중 가장 잘 쓰여진 글이라 생각한다. 작성자님 감사합니다.

    위에 올려둔 인프라 구성도 (스크린샷)를 그대로 따라갈 것인데, Docker Swarm이 설치될 테스트 환경은 Vagrant라는 VM 자동화 도구를 사용하여 구축하겠다. 필자의 GitHub에 모든 것이 준비된 Vagrant 패키지를 올려뒀으니, 다운로드받아 실행만 하시면 된다. 위 링크를 달아둔 포스트의 작성자님이 미리 만들어둔 것을 가져와 약간 수정한 것이다. 지금 사용한 Vagrant는 곧 따로 포스트를 작성할 예정이다. 완성되면 이곳에 연결해 두겠다.

    구성도처럼 우리 Docker Swarm 클러스터의 Manager는 core-01가 될 것이다. vagrant ssh core-01 명령어로 core-01과 터미널 연결을 한 후, 아래 명령을 실행하여 Docker Swarm을 시작해 보자.

    docker swarm init --advertise-addr 172.17.8.101
    

    자, 순식간에 Docker Swarm 클러스터가 시작되었다. 아마 아래와 유사한 출력값이 나왔을 거다.

    Swarm initialized: current node (y01bcscl168g9npunryt0fv5w) is now a manager.
    
    To add a worker to this swarm, run the following command:
    
        docker swarm join \
        --token SWMTKN-1-2hkjkxqnqt5dmizyr8lr59elc3dsuwoc5c2kgzbzont2pilcua-auleg4b3rtj3hkgudm4gqhj2o \
        172.17.8.101:2377
    
    To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
    

    Docker Swarm이 시작되었고 이 노드(core-01)이 Manager라는 사실, 그리고 Worker들은 어떻게 추가해야 하는지 설명해 주고 있다. 너무 순조롭고 친절해서 눈물이 나올 지경이다. 클러스터링이 이렇게 쉬운 일이 될줄 누가 알았겠는가.

    눈물은 조금 이따 마저 흘리도록 하고, 일단 시키는대로 Worker 노드들도 추가해 보자. 나머지 노드 (core-02, core-03, core-04) 들에 접속하여 위 docker swarm join 명령어를 복사 & 붙혀넣기 해보자. This node joined a swarm as a worker. 이라는 짧고 강력하게 정상적으로 Worker가 되었다는 사실을 알려준다.

    이제 core-01로 돌아가 docker node ls 명령어를 입력해보자. 이 Swarm 클러스터에 어떤 노드들이 있는지 한눈에 확인할 수 있다. 방금 추가한 Worker 노드들이 정상적으로 표시된다면 제대로 진행한 것이다.

    아직 할 일이 하나 남았다. 우리가 core-01로 돌아온 것은 클러스터의 상태를 확인하려는 목적도 있었지만, 위의 구성도에 보이는 것처럼 Manage 노드를 하나 더 추가하려 온 것이다. 혹시 처음 docker swarm init 명령어로 클러스터를 시작했을 때, 출력값 맨 아래에 Manager를 추가하는 법도 알려줬던 것을 눈채챘을지 모르겠다. docker swarm join-token manager 명령어를 입력하면 Worker 추가 명령어와 마찬가지로 아래처럼 인증 토크값과 함께 Manager 추가 명령어를 알려준다.

    To add a manager to this swarm, run the following command:
    
        docker swarm join \
        --token SWMTKN-1-2hkjkxqnqt5dmizyr8lr59elc3dsuwoc5c2kgzbzont2pilcua-7g5mef5gjj8zlr6kesyzqk83i \
        172.17.8.101:2377
    
    

    이걸 복사한 후, core-05로 접속하자.

    core-05에서 위 명령어를 붙혀넣기만 하면 “This node joined a swarm as a manager.” 라는 메시지와 함께 Manager 추가도 끝난다. 축하한다. 3대의 Worker 노드, 그리고 2대의 Manager 노드로 구성된 고가용 클러스터가 완성되었다. 이제 아래로 넘어가 이 덕심을 자극하는 클러스터로 무언가 돌려 보도록 하자.

    애플리케이션 올려보기

    Docker Hub에서 웹 애플리케이션 (Flask 데모)을 하나 끌어와 클러스터에 올려보자. Manager 노드에 접속하여 아래 명령어를 쳐보자. Docker Hub에 올라가 있는 p0bailey/docker-flask을 끌어와 80:80 포트로 실행하는 명령어이다.

    docker service create --name flaskontesting \ # flaskontesting에는 원하는 이름 입력
      -p 80:80 \ # 연결할 포트
      p0bailey/docker-flask # Docker 이미지의 이름
    

    별다른 경고 없이 무언가 알 수 없는 값 (필자의 경우 3pyk3b8bo9t01r8ct2fhtregk)이 출력되었다면 정상적으로 서비스가 실행되고 있다는 뜻이다. 방금 출력된 값은 우리가 이번에 실행한 서비스의 ID값이다. docker service ls 명령어로 현재 실행되고 있는 서비스를 확인할 수 있다.

    조금 더 자세한 정보를 원한다면 docker service ps 서비스이름 명령어를 입력해보자. 필자의 경우 아래같은 출력값이 나왔다.

    ID            NAME              IMAGE                         NODE     DESIRED STATE  CURRENT STATE           ERROR  PORTS
    1iqh5ti6ditt  flaskontesting.1  p0bailey/docker-flask:latest  core-05  Running        Running 10 minutes ago       
    

    현재 서비스의 상태, 서비스의 ID값, 이름, 사용하고 있는 이미지, 그리고 서비스가 돌아가고 있는 Swarm 노드 등의 정보가 자세히 출력된다. 그런데 여기서 궁금한 점이 생긴다. 왜 Manager 노드인 core-05 에서 서비스가 돌아가고 있는 것으로 나오고, 또 접속은 어떻게 해야 하는 건가? 정말 core-05의 IP주소를 일일히 치고 들어가야 하는 걸까?

    필자도 처음에는 어버버 했었다. 지금부터 하나하나 설명할 것이니 너무 어려워 하지 마라. 먼저, 기본적으로 Manager는 단순히 Worker를 관리하는 매니저의 역할만 하는 것이 아니라, 본인이 일도 같이 하는 ‘Manager + Worker’의 상태이다. 따라서 설령 내 Swarm 클러스터에 Worker 노드가 하나도 없다고 해도 정상적으로 애플리케이션을 실행할 수 있다. 물론 Manager 노드가 매니저의 일만 하도록 지시할 수도 있는데, 이 글 을 참고해 보길 바란다. Manager 노드가 Worker의 일도 같이 한다는 것과, 추가로 Manager 노드에게 매니저의 일만 하도록 설정하는 법이 잘 설명되어 있다.

    두번째로, 접속할 때 core-05의 IP를 일일히 확인하고 입력할 필요는 전혀 없다. Docker Swarm은 기본적으로 Ingress라는 오버레이 메시 네트워크를 사용한다. 말이 어려운데, 그냥 Swarm 노드간 가상의 네트워크를 만들어 서로 알아서 연결된다는 소리다. 그리고 이 네트워크에 연결된 노드들에는 신기한 일이 일어나는데, 만약 서비스를 실행하며 80 포트를 열었다면, 모든 노드의 80 포트가 같이 열리는 것이다. 이게 무슨 소리냐면 일단 서비스가 돌아만 가면 core-01로 접속하던 core-02로 접속하던 같은 서비스에 연결된다는 말이다.

    정말 그럴까? 직접 접속해보자. 위에 말했듯 필자의 Flask 데모 애플리케이션은 core-05 (172.17.8.105) 에서 돌아가고 있다. 먼저 core-01 (172.17.8.101) 로 접속을 해 보겠다.


    문제 없이 접속이 된다. 그럼 core-02 (172.17.8.102) 는 어떨까?


    마찬가지로 아무 문제 없이 접속이 되는 것을 확인할 수 있다. 놀랍지 않은가? 이것은 서버 오케스트레이션이 추구하는 일 중 하나이기도 하다. 수백 ~ 수천대의 서버들이 작동하는데 그 중 웹 서버는 대체 어디서 돌아가는지 관리자가 알고 있어야 한다면 상상만 해도 끔찍할 것이다. 오케스트레이션이 잘 진행된 환경이라면 그 모든 과정을 인프라가 스스로 결정하고 사용자에게 매끄럽게 연결해 줄 것이다. 관리자의 흰머리와 피부 상태도 훨씬 좋아지지 않을까.

    부하 분산하기

    그럼 잠깐 다른 상황을 가정해 보겠다. 우리 웹 애플리케이션의 매력을 알아챈 사용자들이 미친듯이 몰리는 바람에, 서버가 골로 가기 직전의 지옥도가 펼쳐지고 있다고 해보자. 이럴 때는 서비스가 돌아가는 컨테이너를 여러개 만들어 부하를 분산시킬 수 있다. Docker Swarm 답게 역시 간단한 명령어 조금으로 구현이 가능하다.

    docker service scale 서비스이름=5
    

    위 명령어로 지정한 서비스의 컨테이너를 5개 더 만들어 부하 분산을 할 수 있다. 서비스이름 scaled to 5 라는 메시지가 출력됬을 것이다. 혹시 내 Swarm 클러스터의 노드가 5개 미만이더라도 한 노드에 컨테이너 2개 이상을 배치해 문제 없이 가동시킬 수 있다. docker service ps 서비스이름 명령어를 입력해서 진행 상태 등을 확인할 수 있다. 아래는 필자의 출력값이다.

    7mgur7xeylq2  flaskontesting.1      p0bailey/docker-flask:latest  core-05  Running        Running 45 seconds ago                                          
    owqgn5g7ognl  flaskontesting.2      p0bailey/docker-flask:latest  core-01  Running        Running 4 minutes ago                                       
    xwwn6blyxs8r  flaskontesting.3      p0bailey/docker-flask:latest  core-02  Running        Running 52 seconds ago                                      
    ypk6ht23mig3  flaskontesting.4      p0bailey/docker-flask:latest  core-03  Running        Running 4 minutes ago                                                                   
    itv3ms2jm3i2  flaskontesting.5      p0bailey/docker-flask:latest  core-04  Running        Running 3 minutes ago                                       
    

    잘 돌아가고 있는 것을 확인할 수 있다. 혹시라도 일부 컨테이너가 시작을 실패한 경우 docker service update 서비스이름 --force 명령어로 강제 재시작 시킬 수 있다. 물론 명령어는 update 이지만 아무 변경점 없이 --force 깃발을 달아 재시작만 할 수 있다. 자세한 내용은 여기 참조.

    DB와 함께 구동되는 웹 서비스 구축

    잠깐 잊고 있었는데, GitHub 페이지 같은 정적 웹 서비스가 아닌 이상 DB 없이 혼자 구동되는 서비스는 거의 없다. DB 컨테이너를 따로 만들어 웹 애플리케이션 자체와 연결되는 구성을 해볼 것인데, 사실 전혀 어려운 건 없다. 그리고 우리는 이미 비슷한 구성을 이전에 해본 적이 있다. DockerFile과 Docker-Compose를 살펴볼 때 Flask와 Redis 컨테이너가 서로 연결되는 웹 애플리케이션을 이미 만들어 보지 않았는가? 이번에도 Flask와 Redis를 가지고 놀아 보겠다. 단지 다른 점은 Swarm 클러스터 위에 올라간다는 것 뿐이다.

    여기서 잠깐 재미있는 구성을 해 보겠다. 조금 전 우리가 올린 Flask 컨테이너는 기본적으로 제공되는 Ingress 네트워크를 통해 외부와 통신했다. 그럼 Redis DB도 Ingress에 배치해 외부에 노출된 상태로 Flask와 통신해야 할까? 아니다. 서버간 통신용 네트워크를 하나 더 만들면 된다. 외부에서 접근하지 못하는 내부 컨테이너간의 네트워크에 DB를 배치해 편리함과 보안을 전부 잡을 수 있다.

    아래의 명령어로 backend 라는 이름의 오버레이 네트워크를 생성할 수 있다.

    docker network create --attachable \
      --driver overlay \
      backend
    

    정상적으로 생성되었다면, 아마 04vxf5jq7v294qha330okd2aw와 같은 네트워크 ID값이 출력될 것이다. docker network ls 명령어로 생성된 네트워크들을 확인할 수 있다.

    NETWORK ID          NAME                DRIVER              SCOPE
    04vxf5jq7v29        backend             overlay             swarm
    d0c72d79896d        bridge              bridge              local
    8817021f578e        docker_gwbridge     bridge              local
    735c71fe7b49        host                host                local
    ul98o5yxor9n        ingress             overlay             swarm
    12767a2f2080        none                null                local
    

    저기 backend가 보인다. 제대로 잘 생성되었다. 덤으로 ingress 네트워크도 잘 지내고 있는 것을 볼 수 있다.

    그럼 바로 Redis 서비스를 생성할 차례다. 아래 명령어로 진행할 수 있다.

    docker service create --name redis \
      --network=backend \
      redis
    

    위에서 진행했던 것처럼, docker service ls 명령어로 생성한 서비스들을 확인할 수 있다.

    ID            NAME            MODE        REPLICAS  IMAGE
    3pyk3b8bo9t0  flaskontesting  replicated  5/5       p0bailey/docker-flask:latest
    5gnyczzqzxq6  redis           replicated  0/1       redis:latest
    

    역시 잘 생성되고 있다.

    그럼 정말 이 Redis를 사용해서 무언가를 해보자. 이전 포스트에서 만들었던 Flask와 Redis가 함께 작동하며 내가 이 사이트에 몇번 방문했는지 알려주는 카운터 애플리케이션을 기억하나? 비슷한 구현을 해 보겠다. 이전 포스트에서는 Docker-Compose를 사용했지만, 이번에는 미리 빌드된 이미지를 사용하겠다. 중요한 것은 이미지가 아니라 DB와의 연동이니까.

    미리 빌드된 이미지는 Subicura 님의 블로그에서 가져와 사용했다. 정말 감사합니다.

    docker service create --name counter \
      --network=backend \
      --replicas 3 \
      -e REDIS_HOST=redis \
      -p 4568:4567 \
      subicura/counter
    

    위 명령어로 3개로 복제된 backend 네트워크에 연결되고 외부로 4568 포트가 열린 counter라는 이름의 애플리케이션을 실행할 수 있다. 중간에 REDIS_HOST를 지정해줬는데, 별도로 IP를 입력하는 것이 아닌 그냥 호스트 이름만 치면 된다. 오버레이 네트워크가 알아서 그 이름을 찾아 연결해준다. 편하다.

    정상적으로 실행이 되었다면, 생성한 컨테이너로 curl이나 웹 브라우저 접근을 해보자. 한 주소로 접속해서 카운터가 1씩, 총 3번 올라가는 것을 볼 수 있다. 정상적으로 컨테이너가 부하분산 되고 있다는 뜻이다.

    마무리

    이 글을 마무리하는데 정말로 오랜 시간이 걸렸다. 17년 3월 말에 쓰기 시작한 글인데, 8월 말에 마무리했다. 여러가지 바쁜 일도 많았고, 정신 상태도 영 좋지 못했다. 거의 잊어버릴 뻔 했지만, 방치하는 것보다는 어떻게는 마무리를 짓는 것이 옳다고 생각해 남은 시간에 꺼내들었다. Docker Swarm은 정말 편리하고 강력한 도구다. 많이 늦었지만, 누군가에게 도움이 되길 바란다.

  • GitHub 페이지에 댓글창 만들기

    포탈사이트 등에서 제공하는 블로그 서비스의 경우 사용자의 자유도가 적은 대신 많은 기능들이 완성된 형태로 한번에 제공된다. 그 중 하나가 댓글 기능이다. 인터넷에서 글쓴이와 독자가 가장 자연스럽게 의견을 주고받을 수 있는 소통 창구가 바로 댓글창이다. 다만 GitHub 페이지의 기반이 되는 Jekyll은 안타깝게도 댓글 기능을 기본적으로 제공하지 않는다. 그렇지만 크게 문제될 것도 없다. 없으면 만들면 될 것 아닌가?

    Disqus 기반 댓글 기능 넣기

    Disqus라는 어느 웹사이트라도 쉽게 댓글 기능을 집어넣을 수 있는 훌륭한 무료 서비스가 있다. 사이트마다 새로 가입할 필요 없이 페이스북같은 SNS 계정과 연동하여 한 계정으로 Disqus를 사용하는 모든 사이트에 댓글을 남길 수도 있고, 사진이나 동영상 같은 콘텐츠도 첨부할 수 있는 등 매력을 한 컨테이너 가지고 있는데 우리가 마다할 이유는 전혀 없다. 당장 설치해 보자.

    위 링크를 클릭해 Disqus에 접속하여 계정을 생성한다. 생성이 끝나면 메인 페이지에 내던져질 것인데, 오른쪽 상단의 톱니바퀴 아이콘을 누르면 ‘Add Disqus To Site’ 라는 메뉴가 있을 것이다.


    클릭하면 바로 사이트 추가 창으로 옮겨지는 것이 아닌 서비스를 소개하는 페이지가 먼저 뜬다. 당황하지 말고 아래로 쭉 내려 ‘Ready to install Disqus?’ 라는 메시지 아래의 파란 버튼을 누르자. 아래 스크린샷의 파란 버튼 맞다.


    다음 창에서 ‘I want to install Disqus on my site’ 를 클릭한다. 자, 이곳에서 내 사이트 이름과 종류(주제) 등을 정의할 것이다. 적당히 입력하고 넘어가면 요금제를 선택하는 창이 나오는데, 당연히 우리는 Free를 선택하면 된다. 이제 Disqus를 연결하고 싶은 사이트를 고르는 단계다. 우리는 GitHub 페이지를 다루고 있으니 Jekyll을 선택하자. 거의 다 왔다. 이제 코드 몇줄만 GitHub 페이지에 삽입만 하면 된다.


    위 스크린샷의 2번 영역에서 ‘Universal Embed Code’ 링크를 누르면 아래같은 코드가 나올 것이다.


    1번 부분의 코드만 전부 복사해서 내 GitHub 페이지의 _layouts 폴더 안 post.html 파일 안에 넣어주면 된다. 아래는 코드를 삽입한 필자의 GitHub 페이지 post.html 이다.


    이걸로 끝이다. GitHub에 푸시한 후 내 사이트로 접속해보면 이제 모든 포스트 아래에 댓글창이 생긴 것을 확인할 수 있다. 수고하셨다. 이제 소통할 독자만 만들면 된다. ㅠㅠ

  • 풀스택 개발자가 정말 옳은 직군일까

    필자는 현재 반 백수와 다름없는 생활을 계속 이어나가고 있다. 스스로 창업하려 한 스타트업은 절대 이것 가지고 못 먹고산다는 현실적인 결론이 나와 차라리 비영리적/실험적 성격을 키우는 거창한 프라이빗 프로젝트로 돌려버렸고, 일자리를 알아보는 것도 영 쉽지가 않다. 구성원 한명 한명이 큰 목소리를 낼 수 있고 자유로운 분위기가 유지되는 국내 스타트업 위주로 계속 알아보고는 있는데, 시스템 엔지니어나 시스템 어드민 같은 서버 관련 일자리는 정말 드물다. 다행히 1~2주 전 정말 마음에 드는 스타트업에서 면접을 보고 왔긴 했지만 아직까지 감감 무소식이다…

    지금 일어나는 일은 특히 한국에서 두드러지는 풀스택 선호 경향 때문에 일어나고 있는 일이라 생각한다. 물론 이건 큰 기업에는 해당되지 않는다. 사람에 투자할 돈이 많을수록 더 세분화된 팀을 구성할 수 있기 때문이다. 다만 필자가 현재 원하는 곳은 비교적 작은 스타트업/벤쳐기업이다. 소수정예로 움직이며 열정을 불태울 수 있는 최적의 장소라고 느꼈기 때문인지 뭔지는 잘 모르겠다. 사실 기업일수록 풀스택을 선호하는 것은 어쩌면 당연하다. 돈은 없어 사람은 최소한으로 뽑아야 하니 자연스럽게 뭐든지 할 수 있는 사람한테 눈이 갈 수밖에 없다. 이해한다. 다만 이들은 동시에 ‘다룰 수 있다’ 와 ‘전념할 수 있다’ 는 전혀 다른 문제라는 것을 간과하고 있다. 조금 극단적으로 비유하자면 “나는 운전 잘 하니 보험따위 필요 없다!” 의 상태일지도 모르겠다.

    모 블로거는 ‘한국형’ 풀스택 개발자를 “막일을 하는 잡부” 라고 표현했다. 필자는 절대 틀린 말이 아니라고 생각한다. 정말 여러가지 일을 모두 미친듯이 잘 할수 있으면 얼마나 좋겠냐먄, 그건 드문 경우다. 어떻게 그렇게 단정할 수 있냐고 물어볼 수도 있다. 필자가 그런 상태였기에 단정할 수 있다. 긴 반 백수 생활을 하면서 여러 기술에 손을 대봤지만, 결국 깊게 공부한 것은 별로 없다. 몸과 마음만 피폐해졌을 뿐이다. 물론 단순히 ‘여러가지 일에 손대는 것’을 비판하는 건 절대 아니다. 사람은 언제나 새로운 것을 배우며 앞으로 나아가지 않는가. 필자도 이것저것 만져보며 더 넓은 분야를 이해하게 됬고, 나중에 누구와 수다를 떨더라도 할 얘기가 왕창 생겼다. 이 뒤죽박죽한 이야기의 결론은 개인/팀 프로젝트 단위에서의 풀스택은 스스로의 발전에 큰 도움이 되겠지만, 본업에서는 적어도 직책에 ‘풀스택’ 이라는 단어는 들어가지 않도록 하자는 것이다. 구성원 모두가 자기가 가장 좋아하는 분야에 전념할 수 있어야 최고의 결과물이 나올 수 있다.

    정말 철저하게 필자의 기준와 입장에 맞추어 작성한 글이지만, 기업들이 당장의 인건비 절감보다는 조금 더 먼 앞을 보고 결과물을 위해 인간에게 투자하는 이상적인 미래로 나아가길 바란다. 그래야 필자도 일자리를 구할 수 있을 것 같다…

  • Docker 네트워크에 관하여

    Docker 컨테이너도 물론 VM처럼 각 공간마다 다른 IP주소를 할당받을 수 있다. 하이퍼바이저의 GUI 창에서 마우스만 깔딱거리다 Docker의 까만 터미널을 마주했다고 해도 사실 크게 다른 점은 없다. 걱정하지 말고 이번 포스트를 쭉 읽으며 실용적인 Docker에서의 네트워크 설정법에 대해 알아가자.

    기본 Docker 네트워크


    위 간단히 만들어본 이미지가 바로 Docker의 기본적인 네트워크 구성이다. Docker를 설치한 호스트 컴퓨터에는 docker0라는 가상 브릿지 인터페이스가 생긴다. 새로운 컨테이너를 생성하면 이 docker0vethxxxx 인터페이스로 시작하는 터널이 쭉 파져서 컨테이너의 eth0 인터페이스와 연결되는 것이다. 컨테이너는 브릿지 네트워크를 통해 결국 호스트 컴퓨터의 네트워크 인터페이스로 외부로 통신함으로 iptables를 사용해 포트 접근 등의 제어가 가능하다.(물론 iptables를 사용하지 않는 환경을 위해 docker-proxy라는 프로세스가 또 존재하긴 한다.)

    Docker에서는 추가로 호스트 컴퓨터와 같은 네트워크 환경을 사용하는 설정이나, 다른 컨테이너의 네트워크 환경을 공유하는 설정 등이 존재하지만, 위의 기본 설정만으로도 많은 서비스 환경을 커버할 수 있으니 이 포스트에서는 따로 설명하지 않겠다. 이 부분 관련하여 자세한 내용을 원하는 분들은 이 블로그 참고를 추천한다.

    Docker 네트워크

    그럼 여기서 하나 궁금증이 생긴다. 평소 필자는 VM을 사용할 때 호스트 컴퓨터의 네트워크 인터페이스와 브릿지로 묶고 같은 네트워크에 접속하여 사용하는 것을 좋아한다. 무슨 말이냐면 내 공유기의 IP 대역이 192.168.0.0/24일때, 호스트 컴퓨터는 192.168.0.1, VM은 192.168.0.2 주소를 사용하는 것이다. 이런 구성방식을 ‘Flat 네트워크’라 한다. 물론 모든 VM이 같은 브로드캐스트 영역에 놓이게 되어 규모가 커지게 되면 절대 사용이 권장되지 않는 구성법이지만, 개인적인 용도로 굴릴때 이것보다 간단한 방법은 없어 자주 애용한다. 그럼 이 Flat 네트워크 구성법을 Docker 컨테이너와는 사용할 수 없는 것인가? 정답은 ‘가능하다’ 이다.

    이 구성을 위해 우리는 브릿지 설정 도구인 bridge-utils을 사용할 것이다. CentOS 기준 sudo yum install -y bridge-utils 명령어로 간단하게 설치할 수 있다. 원래 OpenVSwitch를 이용해 DHCP로 공유기에서 IP를 받아오는 설정을 구성하려 했지만, 어디에서 꼬였는지 계속 필자를 괴롭혀서 결국 고정 대역을 미리 잡아놓고 공유기 네트워크와 연결하는 방법으로 경로를 수정했다. 삽질로 날라간 몇시간과 고통받은 몸에 묵념을.


    필자의 테스트 환경은 아래 위 이미지와 같다. 그럼 바로 시작해보자.

    sudo brctl addbr br0 # br0 브릿지 생성.
    sudo brctl addif br0 enpxxxx # br0에 enpxxxx(연결할 네트워크 어댑터 이름) 연결.
    sudo brctl setfd br0 0 # br0 초기화.
    ifconfig br0 192.168.2.2 netmask 255.255.255.0 # br0의 IP 주소를 192.168.2.1, 서브넷 마스크를 /24로 설정.
    

    여기까지 끝났으면 이제 CentOS 기준 /etc/sysconfig/network-scripts 로 이동해 브릿지와 네트워크 인터페이스의 설정 스크립트를 만져주자. 파일 이름은 브릿지의 경우 ifcfg-br0, 네트워크 인터페이스는 ifcfg-enpxxxx 로 잡혀 있을 것이다. 많이 파일이 보이지 않는다고 해도 걱정하지 말고 새로 하나 만들어주면 된다.

    DEVICE=br0
    TYPE=Bridge
    IPADDR=192.168.2.2
    NETMASK=255.255.255.0
    ONBOOT=yes
    BOOTPROTO=none
    NM_CONTROLLED=no
    DELAY=0
    

    위는 레드헷 홈페이지에서 직접 제공하는 모범적인 브릿지 스크립트의 예시이다. 적당히 참조해 고쳐 쓰면 된다.

    DEVICE=enp0000
    TYPE=Ethernet
    HWADDR=AA:BB:CC:DD:EE:FF
    BOOTPROTO=none
    ONBOOT=yes
    NM_CONTROLLED=no
    BRIDGE=br0
    

    마찬가지로 네트워크 인터페이스 스크립트다. 참고로 저기 적힌 NM_CONTROLLED는 RHEL 7(CentOS 7)부터 달린 골칫덩이 NetworkManager가 손댈 수 없게 해주는 설정이다.

    모두 작성하였으면, sudo systemctl restart network로 네트워크 서비스를 재시작 해준다. 잘 하셨다. 이제 브릿지의 준비는 끝났다. 마지막으로 Docker가 이 브릿지를 받아먹게 설정해주면 끝.

    sudo docker network create --driver=bridge --ip-range=192.168.2.0/24 --subnet=192.168.2.0/24 --aux-address='ip1=192.168.2.10' -o "com.docker.network.bridge.name=br0" br0
    

    내가 사용한 새로운 Docker 네트워크를 생성하는 명령어다. 나머지는 대충 읽어도 무슨 의미인지 알겠는데, --aux 옵션이나 -o는 무슨 의미잇지 햇갈릴 것이다. 전자는 내가 등록한 네트워크 인터페이스 (브릿지)가 사용하는 추가적인 IP 주소. 대충 내 네트워크 대역 안의 안 사용중인 주소를 입력해주면 될 것 같다. 후자는 추가적인 네트워크 인터페이스 옵션이다. 실제 브릿지 br0와 연결해주는 역할이라고 생각하면 편하다. 마지막 br0은 이 Docker 네트워크의 이름을 br0으로 하겠다는 말이다.

    이제 브릿지와 연결되는 Docker 네트워크 생성도 끝났다. 한번 컨테이너를 생성해보자.

    sudo docker run -i -t --network=br0 ubuntu
    

    --network 깃발로 어떤 Docker 네트워크를 사용할 것인지 지정해줄 수 있다. 위처럼 입력하면 당연히 br0을 사용하게 된다. 아무것도 지정하지 않으면 기본 설정인 docker0에 연결한다.

    생성한 컨테이너에 들어가 인터넷 연결은 제대로 되는지, 내 네트워크 안에 있는 다른 기기와 ping은 잘 주고받아 지는지 확인해보자. 아마 별 문제 없을 것이다. 물론 이 방법이 완전히 정답은 아니지만 (오버레이 네트워크를 만들어 다른 서버들과 통신할 수도 있다. 더 확실하고 ‘힙’한 방법. 나중에 이 포스트 아래에 추가해 두겠다.), 중/소규모 환경에서 깔끔하게 하나의 네트워크로 컨테이너와 여러 서버들을 연결시키는 용도로는 훌륭하다. 가장 시스템 엔지니어들이 스트레스 안 받는 방법이라 할 수 있겠다. 하고보면 복잡할 것 없고 금방 끝날 설정인데, 이상하게 필자는 삼천포를 헤매다 와서 몇시간이 걸렸다. 분명 제대로 설정한 것 같은데 외부 연결이 안되고 난리가 아니였다. 브릿지 설정을 모두 밀고 다시 해보았더니 언제 그랬냐는듯 깔끔하게 해결… 다른 분들은 이런 실수를 반복하지 않기를.

    일단 거의 대부분의 환경에서 동작하는 Docker는 위 두가지 설정법으로도 커버가 가능할 것이다. 다만, 아까 언급한 오버레이 네트워크 같은 것도 있고, 조금 더 다루고 싶은 내용들이 존재할 수 있으니 이 포스트는 시간날 때 틈틈히 업데이트 할 예정이다. 삽질에 고통받은 필자와 읽느라 수고한 독자 모두 수고하셨다.

  • DockerFile과 Docker-Compose

    이전 포스트에서 기본적인 Docker의 개념과 설치법, 이미지 등을 다뤘다. 아직 안 읽어보셨다면 참고하시길.

    DockerFile, 그리고 Docker-Compose


    마치 Docker 이미지와 컨테이너의 관계가 그랬듯, DockerFile과 Docker-Compose는 땔래야 땔 수 없는 관계이다. 우선 DockerFile 이미지를 실행하면서 특정 작업까지 같이 처리하게 해주는 도구고, Docker-Compose는 다수의 컨테이너를 쉽게 실행할 수 있게 도와주는 도구이다. 다시 말해, Docker-Compose로 컨테이너를 자동 생성한 후, DockerFile로 생성한 컨테이너 안에 자동으로 세팅 작업까지 돌아가게 할 수 있다는 것이다.

    지금부터 Docker 공식 메뉴얼을 참고해 DockerFile과 Docker-Compose를 모두 사용하는 예제를 작성할 것인데, 아직 Docker-Compose를 설치하지 않은 여러분들은 아래 과정을 먼저 밟고 따라와주시면 고맙겠다.

    Docker-Compose 설치하기

    이전 포스트와 동일하게 필자는 CentOS에서 설치를 진행하였지만, 이번에는 GitHub에서 직접 파일을 끌어올 것이라 다른 리눅스 배포판들도 설치 방법이 동일하다. 그냥 따라오셔도 무방할 것 같다. 원본 메뉴얼은 이곳에서 확인할 수 있다.

    curl -L "https://github.com/docker/compose/releases/download/1.11.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    

    curl로 Docker-Compose의 GitHub 저장소에서 필요한 파일을 끌어온다. 혹시라도 /usr/local/bin 경로에 권한 문제가 있어 접근이 안 되시는 분들은 위 명령어를 입력하기 전 sudo -i 명령어로 root 권한을 받은 후 진행하시길 바란다.

    chmod +x /usr/local/bin/docker-compose
    

    파일 끌어오기가 완료되었다면, chmod 명령어로 해당 경로에 실행 권한을 준다.

    여기까지 끝났다면, docker-compose --version 명령어를 입력해보자. 정상적으로 Docker-Compose의 버전이 출력된다면 설치 성공.

    본론

    이제 정말로 DockerFile과 Docker-Compose를 모두 활용해 무언가 생성해볼 시간이다. Redis가 실행되는 컨테이너 위에 Flask가 돌아가는 환경을 간단히 만들어 보도록 하겠다.

    mkdir composetest
    cd composetest
    

    먼저 composetest라는 실험을 진행할 폴더를 만들고, 그 안으로 들어간다. 이제 원하는 텍스트 에디터로 app.py라는 새로운 파일을 만들어 아래의 내용으로 채워넣는다.

    from flask import Flask
    from redis import Redis
    
    app = Flask(__name__)
    redis = Redis(host='redis', port=6379)
    
    @app.route('/')
    def hello():
        count = redis.incr('hits')
        return 'Hello World! I have been seen {} times.\n'.format(count)
    
    if __name__ == "__main__":
        app.run(host="0.0.0.0", debug=True)
    

    코드를 대충 읽어보면 알겠지만, Flask가 당신에게 인사를 해주며 몇 번 마주쳤다고 친절하게 알려까지 주는 귀여운 코드다. 그대로 저장하면 된다.

    다시 requirements.txt라는 파일을 하나 더 만든 후, 아래 내용을 넣어준다. 무엇이 이 애플리케이션에 필요한지 명시해주는 파일이다.

    flask
    redis
    

    마찬가지로 저장하고 나오면 된다.

    이제 DockerFile을 작성해보자. Dockerfile이라는 파일을 만들어, 아래처럼 내용을 집어넣으면 OK.

    FROM python:3.4-alpine # Python 3.4를 사용하는 이미지를 기준으로 시작.
    ADD . /code # 현재 위치(.)을 이미지의 /code 위치에 넣기.
    WORKDIR /code # 작업 위치를 /code로 설정.
    RUN pip install -r requirements.txt # 아까 requirements.txt에서 명시한 필요한 소프트웨어 설치.
    CMD ["python", "app.py"] # 컨테이너의 기본실행 명령어를 python app.py로 설정.
    

    위 스크립트 내용에 주석을 달아뒀으니 참고 바란다.

    자. 컨테이너가 실행되면 어떤 내부작업이 돌아갈 것인지 DockerFile에 명시해 뒀으니, 이제 컨테이너 자체를 실행하는 Docker-Compose를 설정해 보자. docker-compose.yml이라는 새로운 파일을 생성하고 아래 내용을 넣어보자.

    version: '2' # Docker-Compose 버전 2 사용.
    services: # 컨테이너별 서비스 정의.
      web: # 웹서버 부분.
        build: . # 현 위치의 DockerFile을 사용해 이미지 빌드.
        ports: # 웹서버 포트.
         - "5000:5000"
        volumes: # 웹서버 저장소.
         - .:/code
      redis: # Redis 부분.
        image: "redis:alpine"
    

    Docker는 서비스별로 컨테이너를 분리시키는 것을 좋아한다. 그게 컨테이너 기술의 의의기도 하니까 말이다. 웹서버와 Redis 컨테이너를 따로따로 만들어 보겠다. DockerFile과 마찬가지로 위 스크립트에 주석을 달아뒀으니 참고하길 바란다.

    준비는 모두 끝났다. 바로 가동시켜 보자. docker-compose up 명령어를 치면 끝. 컨테이너가 모든 준비를 스스로 마친 것 같으면 웹 브라우저를 키고 http://localhost:5000 주소로 접속해보자. 아래같은 실행값이 보이면 성공.

    dockercomposeruned

    이곳에서는 간단한 웹 애플리케이션을 실행하는 것만 보여줬지만, 이것을 응용해 활용할 수 있는 분야는 무궁무진하다. 내 개인, 또는 팀의 서비스가 Docker에 담기는 상상을 해봐라. 그리고 그로 얻을 수 있는 모든 이점을 상상해보자. 모 블로그의 표현처럼 Docker는 단순 생산성 뿐만 아니라 인간의 상상력 자체를 자극하는 도구가 분명하다.

    다음 포스트에서는 Docker에서 팍팍 밀어주는 클러스터링 툴인 Docker-Swarm에 대해 다뤄보려 한다. 모두 수고 많으셨다.