How to dockerize

Dockerize를 한다는 것은 기존에 유지하고 있던 service를 docker 기반으로 바꾼다는 것을 의미한다. 작은 의미에서는 단순히 docker image 를 만드는 것이고 큰 의미는 docker 를 활용해서 cluster 를 꾸미고 여기에 기존의 서비스를 빌드/배포할 체계를 만드는 것이다. 조심해야 할것은 dockerize가 단순히 vm에 service를 올리는 것처럼 간단치 않다는 것이다. docker는 vm과는 다르기 때문에 vm에 서비스를 배포 하는 것처럼 생각하면 큰 낭패를 보기 쉽다. 따라서 서비스의 성격에 따라 어떻게 dockerize를 할것인지 전략을 수립하는 것이 좋다.

docker를 쓰는 형태

docker clustering에는 어떤 방식이 있는가?

docker를 활용해서 cluster를 구성하는 방법에는 다음과 같은 방식이 있을수 있다.

  1. docker를 단순히 배포 툴로 보고 준비된 머신에 docker image를 배포하는 형식
  2. docker를 여러개 묶어서 docker image을 cluster상의 임의에 node에 띄우는 방식
    1. docker 외부의 scheduler/resource manager를 쓰는 방식
      • kubernetes
      • classic swarm
    2. docker 내부의 scheduler/resource manager를 쓰는 방식
      • swarmkit(docker service)
  3. docker를 여러개 묶어서 docker cluster를 구성하고 이 cluster를 아울러서 mulit-cluster를 구성하고 그 multi cluster에 배포하는 방식

이 방식들 외에도, 다른 방식들이 더 있을수 있으나 대표적으로 위의 방식을 사용한다1. 각 방식은 각자의 장단점이 존재한다.

1의 방식은 가장 간단하게 시도할 수 있는 방법이다. 뒤에서 설명하겠지만 docker run만 사용하며, process를 실행하는 대신 docker container를 실행하는 방법이다. 따라서 기존의 배포 프로세스에 가장 잘 녹아들수 있다. 그렇기 때문에 docker에서 얻을수 있는 이득도 어디서나 실행이 가능하다는 것 외에는 없다. 각 개별 container를 사용자가 직접 관리 해야 하며, 각 서버도 사용자가 직접 관리해야한다. failover에 대한 대응도 제한적이다(restart외에는 제공하지 않는다). 리소스 관리도 사용자가 직접 해야 하며, 실행할때 사용자가 모든 정보를 신경써서 docker에 넘겨줘야 한다.

2의 방식은 최근에 가장 많이 시도되는 방식이다. cluster로 묶어둔 node중에 어느 노드에서 container 가 뜰지 사용자가 제어하기 힘들지만, 그렇기 떄문에 node 가 fail되거나 resource(CPU, MEM) 가 소모되면 다른 node(machine)로 밸런싱을 할수 있다. 이과정에서 schedule 가 container를 재시작 할수 있으며2,따라서 container가 state를 가지게 되면 그 state가 언제든 reset 될 수 있다고 생각하는 것이 옳다. 그래서 이 단계에서는 state를 저장할수 있는 volume, 서로간에 discovery를 할수 있는 기능, 등등을 신경 써야 하며, 이 관리 기법과 알고리즘, 가정하고 있는 machine구성 상태(network 구성, rack)에 따라서 서로 다른 scheduler/resource manager 를 선택하게 되고, 각 plugin들을 사용하게 된다. docker 팀도 이것이 필요하다는 것을 인지하여 1.12 버전 이후로 swarmkit을 통합하여 docker service명령을 추가 하였으며 docker-ce 17.03 에 이르러서는 docker engine 을 제외한 부분을 자유롭게 통합하여 쓸수 있는 구조로 바꾸어 가고 있는 중이다. 현재 docker main stream은 이 수준을 고려하고 있으며 적어도 향후 몇개월간은 이수준의 기능들이 추가/수정 될것으로 보인다.

3의 방식은 아직 본격적으로 검토되지는 않았다. 다만 GCP나 AWS에서 서비스로 일정부분 제공하고 있다. cluster가 IDC수준을 넘어서 구성되기에는 cluster가 관리하는 정보가 너무 많아지고 schedule 알고리즘도 복잡해지기 때문에, 이부분을 별도로 분리하여 관리하는 것이다. 일반적으로 필요한 기능이 아니고 Global service를 하는 급이 아니라면 고려할만한 것이 아니고, 실제 machine의 구성에 디펜던시가 크게 걸리기 때문에 클라우드 서비스 제공자가 각자 custom 버전을 제공할뿐 어떤 opensource 가 득세를 하는 상황은 아니다.

그럼 어떤 것을 써야 하나?3

예상은 했겠지만, 정답은 어떻게 사용하느냐에 따라 다르다. 기존의 레거시를 포용해야 하는게 필수라면 1의 방식을 택하고 점차 2의 방식으로 나가야하고 그렇다면 classic swarm이 가장 적합한 선택일수 있다. GCP에 올려야 한다면 kubernetes를 쓰는 것이 가장 좋은 방식이다. network 구성을 내 마음대로 할수있고, cluster자체를 만들어야 한다면 가장 트렌디하고 발전이 빠를 것으로 예상되는 2-2 를 따라가는 것이 좋다.

모든 경우에 수를 여기에 적을수 는 없으니 크게 분류를 나누어서 적어볼까 한다.

만약 DB form과 같이 local Disk에 많은 량의 데이터를 읽고 쓰고, 그 자체의 접속 정보(IP, port)등이 바뀌면 안되는 경우라면, 필연적으로 1과 같이 따로 사용자가 컨트롤 하는 것이 좋다. 왜냐면 local disk에 많은 량을 읽고 쓴다는 것은, 그 container가 다른 node로 옯겨 갈수 없음을 뜻한다. 따라서 local disk의 성능만큼 나오는 remote volume manager와 그 플러그인 없이는 container가 cluster 내부에서 옯겨갈 방법이 없음을 뜻한다. 이런경우에는 그냥 docker는 배포 툴로만 사용하고, 나머지 컨트롤은 수동으로 하는 것이 좋다. 조금이나마 자동화 해보고 싶다면 classic swarm 정도를 적용해볼수 있다. 하지만 매번 DB를 띄울때마다 어느 노드에 띄울지 수동으로 지정하는 것과 다름없으므로 Shell Script로 관리하는 것과 큰 차이가 없게 된다. 엘라스틱 서치와 같은 경우에도 이런 경우에 해당한다.

Web Application 과 같이 매번 새로 떠도 상관 없는 경우라면4, 통합된 docker service를 사용 하면 된다. docker swarm kit으로 관리 되는데, docker swarmkit 에서 가정하고 있는 상황이 바로 이러한 상황이다. 최근에 추가된 docker service event 를 활용하면 auto-scaling도 손쉽게 구현할수 있다. docker api로 관리가 가능하고, docker api는 단순 http request들 이기 때문에 chatbot등과 연계하기도 쉽다.

복잡한 application을 파일 하나 command 하나로 띄우고 싶다면, kubernetes가 좋은 선택일수 있다. docker-compose가 최근에 비슷한 기능들을 추가하고 있지만, kubernetes는 오래 전부터 해당 기능들을 제공했고 그에 따른 신뢰도도 꽤나 높기 때문에 해당 상황에서는 가장 쓰기 편한 선택지이다. 다만 docker service와 연계를 해야한다면 docker-compse를 활용하는것을 고려하는 것이 낫다.

Docker 의 특징

Docker 는 VM과는 다른 특징을 지니고 있기 때문에 docker container가 어떤 특징을 지니는지 알아야 dockerize를 원활히 할수 있다.

docker는 cgroup으로 isolation을 한다. 즉 kernel의 기능을 이용해서 컨테이너를 만든다. 따라서 가상화 와는 다른 기술이며 단순히 OS scheduler의 기능을 활용하는 것뿐이다. 좀더 쉽게 설명하면 docker는 linux process가 할수 있는 동작을 제한 하고, 해당 프로세스가 접근할수 있는 root를 조작(?) 함으로써 process 가 마치 격리된 리눅스 상에 뜬것처럼 해줄 뿐이다. 따라서 docker는 태생적으로 linux 상에서 실행될수 밖에 없으며, window와 osx는 단순히 가상화 기술을 이용해서 가벼운 linux를 띄운후 그 위에서 docker를 동작시키는 방식으로 실행된다. 그래서 docker container 내부에서 사용할 수 있는 것들은 전부 linux에 기반을 두고 있다. 따라서 docker를 사용하려면 linux 기반의 환경을 받아 들여야만 한다.

docker는 process 하나를 기준으로 동작한다. linux container기술 자체는 한 container에 여러개의 process 를 띄우는 것을 허용한다. docker container 도 여러개의 process를 띄우는 것 자체를 막지는 않는다. 다만 docker container의 동작은 모두 container 내의 처음 뜬 process를 기준으로 이루어 진다. 즉 container에서 처음 뜬 프로세스가 종료되면 모든 process가 종료된것으로 판단하고 해당 컨테이너를 지워버린다. 에러 코드도 처음 뜬 프로세스의 에러코드로 판단하고, STDOUT 또한 해당 프로세스의 내용만 활용한다. 이처럼 docker에서는 한 컨테이너에 왠만하면 한 프로세스만 실행하는 것이 편하게끔 설계 되어 있다. 이 부분이 레거시를 전환 할때 큰 문제가 되는 경우가 많다.

docker는 기본적으로 process의 STDIN에 뭔가를 줄수있는 구조로 되어 있지 않다. docker attach 와 같이 일부 방안들이 있긴 하지만, docker의 기본 동작은 처음 실행하자 마자 STDIN을 그냥 닫아버리는 것이다. 만약 사용자에게 입력을 받는 부분이 있다면 해당 process는 dockerize 하기 힘들다.

docker는 stateless 하다. 이는 docker가 단순히 process를 실행하는 것과 다르지 않다는 것에서 기인하는 사실이다. 일반 process를 종료하면 메모리상에 존재하는 모든 데이터는 날아가 버리고 다시 실행하면 이전의 state를 모두 잃어 버린 상태에서 시작한다. docker container도 같은 방식으로 종료하면 모든 데이터(파일시스템에 있더라도)는 날아가 버리고 다시 실행하면 이전의 state를 모두 잃어 버린 상태에서 시작한다. 따라서 일반 process에서 상태를 file이나 DB에 써서 영속성을 확보하듯이 docker 는 volume에 상태를 기록하므로써 영속성을 확보 하게 된다. 즉 제한된 방식으로 state을 저장할수 밖에 없다. 이 stateless함이 cluster를 꾸밀때 장애물로 다가오게 되는데, docker container가 stateless함을 가정하기 떄문에 docker 는 대부분에 상황에서 container 를 재시작 함으로써 문제를 해결한다. 예를들어 특정 한 노드에 문제가 생겨서 옆 노드로 container를 옯겨야 한다면, 실행중에 옯기는 대신 단순히 현재 container를 종료한후 옆 container에서 시작하는 것으로 대응한다. state가 없다고 가정하기 때문이다. 만약 여러 node에서 state를 공유하고 싶다면, remote volume plugin이 필수가 된다(여러 노드에서 같은 volume을 공유해야 하므로).

Docker Image

docker image는 실행할 binary와 system lib들을 하나로 뭉쳐논것이다. 전체 시스템의 파일시스템을 압축한 다음 tar로 묶은것에 지나지 않는다. 다만, 진짜로 하나로 뭉쳤다가는 몇기가에 달하는 이미지를 새로 받을때마다 수기가를 받아야 하는 상황에 직면하기 떄문에 약간의 곰수를 썼다. 바로 공통된 부분을 별도의 레이어로 작성하는 것이다. 이를 overlay file system이라고 한다. overlay file system 자체는 별로 색다른 것이 아니다. 기존에도 높은 가용성을 확보하거나 주기적인 백업이 중요시 될때는 overlay file system을 사용하기도 한다. 이 overlay system은 각 layer 별로 변경사항을 기록한다는 검에서 git 과 유사한 면이 있다.


TBD


  1. 이 중에서 google 에서 쓰는 방식은 2-1. 의 방식이다. kubernetes는 docker service 가 출시되기 전에 나왔으며 현재까지 product에서 사용하는 방식중에 가장 대표적인 방식이라고 할수 있다. 하지만 docker swarmkit 이 통합되면서 최근에는 docker service 기반으로 가는 것이 더 좋을지도 모른다.
  2. node(machine을 옯겨야 하니까)
  3. 이 글은 2017년 8월 말에 쓰고 있다. 미래 시점에는 바뀔지도 모른다.
  4. 사실 대부분 application이 DB만 제외할수 있다면 이 형태에 해당한다.