[Vol.11 No.3] 도커 컨테이너 이관 (Docker container migration): Part I

  • 작성자

    관리자
  • 작성일자

    2021-09-10 00:00
  • 조회수

    336

도커 컨테이너 이관 (Docker container migration): Part I

 


한림대학교 소프트웨어융합대학 김태운(taewoon@hallym.ac.kr)

 

 

 

 

1. 엣지 컴퓨팅(Edge Computing) 소개

 클라우드 컴퓨팅 기술은 인터넷에 접속된 단말이 언제 어디서든 컴퓨팅 자원을 유연하게 사용할 수 있도록 한다. 컴퓨팅 자원을 중앙 집중 방식으로 관리하고(예: 데이터 센터), 가상화 및 멀티-태넌시(multi-tenancy) 기술에 기반한 클라우드 컴퓨팅 기술은 컴퓨팅 자원의 활용도를 비약적으로 향상시켰다. 특히, 연산 자원이 제한적이고 배터리로 구동하는 사용자 단말은 클라우드 컴퓨팅으로의 연산 오프로딩 기술에 힘입어 복잡한 연산을 빠르게 수행할 수 있고 배터리 사용량도 줄일 수 있다. 하지만, 제한된 수의 데이터 센터로 운영되는 클라우드 컴퓨팅 서비스는 데이터 센터와 사용자 단말 간 물리적 거리에 따라 지연시간이 증가하며, 지연 시간에 민감한 서비스(예: 자율주행, 건강/안전 모니터링, 재난/재해/구조 활동 등)의 품질을 저하시킬 수 있다.

연산 오프로딩 서비스를 제공하는 동시에 짧은 지연시간을 보장하기 위해 엣지 컴퓨팅 기술이 등장했다. 엣지 컴퓨팅은 연산을 처리할 수 있는 호스트 머신(엣지 서버)을 네트워크의 가장자리에 배치하여 서비스 지연시간을 최소화한다. 특히, 무선 액세스 포인트 또는 기지국에 엣지 서버를 배치하면 서비스 지연을 줄이고 및 네트워크 혼잡을 피할 수 있다. 여기서 한가지 문제점은, 사용자가 이동하여 다른 기지국으로 핸드오프 될 경우, 서비스 중인 엣지 서버와의 거리가 멀어지고 지연시간 또한 증가한다는 것이다. 이 문제를 해결하기 위해 서비스 이관 기술이 연구 및 개발되었다. 본 고에서는 Stateful and Live Container Migration 기술을 구현하기 위한 요소 기술을 살펴보고, 다음 고에서는 Stateful and Live Container Migration 구현 방법을 알아본다. 본 고는 총 두 개로 기획된 글의 첫 번째 파트이다.

 

 


2. Stateful and Live Container Migration(상태를 보존하는 실시간 컨테이너 이관) 이란? 

 

  가상화 기술은 호스트 가상화, 하이퍼바이저 가상화, 컨테이너 가상화 등으로 분류할 수 있는데, 이 중에서 컨테이너 가상화 기술은 호스트 운영체제의 커널을 공유하는 방식으로, 경량화가 가능하고 네이티브 서비스와 거의 유사한 성능을 가지는 장점이 있다. 경량의 컨테이너는 서비스 마이그레이션의 복잡도를 감소시키는 장점이 있어 엣지 컴퓨팅을 위한 가상화 기술로 주목받고 있으며, 컨테이너 가상화 솔루션 중 도커(Docker)[1]는 전체 시장의 25%를 점유할 정도로 널리 사용되고 있다[2].

Container Migration(이관)이란 하나의 엣지 서버에서 구동중인 컨테이너 형 서비스를 다른 엣지 서버로 옮기는 것을 의미한다. 이 때, 구동중인 서비스(컨테이너)의 상태를 유지한 채로 이관하면 Stateful Migration이라 하고, 이관 중에 서비스 단절이 발생하지 않는다면 Live Migration이라 한다. Stateful and Live Migration을 도커 컨테이너 가상 환경에서 구현하기 위한 요소기술을 살펴보자.

 

 

 

3. Docker 기본

  본 고에서 소개하는 실습은 체크포인트 기능을 원활히 사용하기위해 Ubuntu Desktop 18.04 LTS 리눅스 운영체제 및 Docker 17.03.2 버전(Community Edition)에서 수행되었으며, 현재 시점(2021년 8월)을 기준으로 Ubuntu 리눅스는 20.04 LTS가 최신 버전이며, Docker는 20.10.8이 최신 버전이다. 도커를 설치하는 방법은 공식 문서를 참고하자[3].

서비스를 구동하기 위해 필요한 어플리케이션과 실행 환경을 패키지화 한 것이 도커 이미지이며, 이미지를 실행하여 도커 컨테이너를 생성 및 실행할 수 있다. 도커 이미지는 직접 제작할 수도 있지만, 공개 저장소인 Docker Hub[4]을 통해 사전에 제작된 다양한 이미지를 다운받아 사용할 수 있다. 도커 이미지를 실행하여 컨테이너를 생성하는 가장 손쉬운 방법은 $ docker run <이미지 이름> 명령을 사용하는 것이다. 예를 들어, hello-world 이미지를 실행하여 컨테이너를 만들기 위해 $ docker run hello-world를 입력한 결과는 아래와 같다. 이 때, 로컬 파일 시스템에서 hello-world 이미지를 찾을 수 없어서 공개 저장소로부터 해당 이미지를 자동으로 다운받아(pull) 실행한다.
 

  

다음으로, Ubuntu 16.04 이미지를 사용하여 컨테이너를 실행하고, 종료 시 자동으로 삭제하며, 컨테이너 실행 후 Bash Shell을 실행하고 컨테이너에 터미널로 접속하는 명령은 $ docker run -it --rm ubuntu:16.04 /bin/bash이다. 실행 옵션에 대한 상세 정보는 공식 매뉴얼[5]을 참고하자.
 

 
 

Ubuntu 16.04 이미지가 로컬 파일 시스템에 존재하지 않았고, 공개 저장소에서 자동으로 이미지를 다운받아 실행되었다. 직전의 hello-world 이미지와는 달리 여러 번의 pull이 수행된 것을 볼 수 있는데, 이것은 Ubuntu:16.04 이미지가 여러 개의 레이어(layer)로 구성[6]되어 있다는 것을 의미한다. 여러 레이어를 쌓는 방식으로 이미지가 구성된다는 것을 기억하자! 차곡차곡 쌓인 레이어는 컨테이너의 파일 시스템을 구성하고, 모든 레이어는 읽기 전용(read-only) 속성을 가진다. 즉, 컨테이너는 로컬 파일 시스템 내의 파일 또는 디렉토리를 생성/삭제/수정할 수 없다...? (사실은 가능하다). 레이어 형태로 이미지를 구성하는 도커의 특성 덕분에 개별 레이어의 재사용성을 높이고, 로컬 파일 시스템의 저장공간을 절약할 수 있다.

이번에는 Ubuntu 16.04 이미지를 구동하고 간단한 어플리케이션을 실행해보자. ‘App’이라고 부를 어플리케이션(또는 서비스)은 두 가지 동작을 한다. 먼저, 10MB 크기의 test.file 이라는 이름의 파일을 컨테이너 내부 파일 시스템의 /tmp 디렉토리 아래에 생성하고(truncate 명령), 0에서 시작하여 +1씩 증가하는 정수형 카운터를 1초에 한번씩 STDOUT에 출력한다(echo 명령). App 어플리케이션을 구동하는 컨테이너 시작 명령은 다음과 같다: $ docker run -d --name App ubuntu:16.04 /bin/sh -c ‘truncate -s 10m /tmp/test.file; i=0; while true; do echo $i; i=$(expr $i + 1); sleep 1; done’

 
 

참고로, 컨테이너 실행 직후에 출력되는 값은 컨테이너의 ID이며, $ docker ps 명령을 통해 알 수 있듯이, 컨테이너 이름이 App으로 지정되었다(--name 옵션). 로컬 파일 시스템에 이미 저장된 이미지(ubuntu:16.04)를 사용하므로 다운로드 과정을 건너뛰고 즉시 실행된다. 분리된(detached) 형태로 실행된 컨테이너(-d 옵션)는 Background에서 동작하는 프로세스로 볼 수 있고, 컨테이너의 STDOUT 스트림을 직접 확인할 수 없다. 단, $ docker attach <컨테이너 이름> 명령을 사용하면 컨테이너에 연결되고(Foreground 프로세스로 만들기), 직접 확인이 가능하다.

컨테이너가 실행된 이후, 파일 시스템상의 변경 내역을 확인하는 명령은 $ docker diff <컨테이너 이름>이다. 이를 통해 컨테이너에서 생성/삭제/변경한 파일, 디렉토리 등을 확인할 수 있다. 직전에 언급한 바와 같이, 컨테이너는 read-only 레이어로 구성된 파일 시스템을 변경할 수 없지만 실제로는 가능한 이유는 copy-on-write 방식의 writable layer(또는 container layer) 때문이다. Ubuntu:16.04 이미지로 컨테이너를 실행하면 Ubuntu:16.04 이미지를 구성하는 레이어로 App 컨테이너의 파일 시스템이 구성된다. 이 때, 최 상위 레이어 위에 writable layer(쓰기 가능 레이어)가 추가로 생성되며, 컨테이너 내에서 파일 시스템에 가하는 모든 변경 사항은 writable layer에 저장된다. Writable layer에 저장된 모든 변경 사항은 이미지에 저장된 것과 달라진(different) 부분이며, 이를 diff 명령으로 확인할 수 있다. App 컨테이너 파일 시스템 내의 변경 이력을 살펴보면 아래와 같이 /tmp/test.file 파일이 생성되었다는 기록을 확인할 수 있다.
 


 
 

다음으로, 백그라운드에서 동작하는 컨테이너가 1초에 한 번씩 STDOUT으로 출력하는 정수형 숫자를 확인해 보자. 이는 $ docker logs <컨테이너 이름> 명령으로 확인할 수 있는데, logs 명령은 컨테이너의 STDOUT 및 STDERR 출력을 보여준다.
 

 


 

 

4. 도커 체크포인트

 

  상태 기반 서비스의 경우, 엣지 서버 1번(ES-1)에서 구동중인 컨테이너형 서비스를 엣지 서버 2번(ES-2)으로 이관할 때 서비스의 현재 상태를 동시에 이관해야 끊김 없는 서비스를 제공할 수 있다. 여기서 ‘상태’란, 서비스 사용 중에 생성/변경된 파일, 메모리에 저장된 정보 등 다양하게 정의할 수 있다. 파일 시스템 내의 변경 내역은 writable layer에 기록되어 있으며, diff 명령으로 확인할 수 있다. 이를 이용해, 컨테이너 이관 시 파일 시스템 변경사항을 ES-2로 전송해 줄 수 있다. 하지만, 메모리 상에 저장된 정보는 어떻게 ES-2로 이관할 수 있을까? 이를 위해 도커는 체크포인트[7] 기능을 지원한다. 체크포인트 기능은 아직 실험적(experimental)기능이며, 리눅스 CRIU[8] 유틸리티가 설치되어 있어야 한다. 체크포인트를 생성하는 명령은 $ docker checkpoint <컨테이너 이름> <체크포인트 이름> 이다. App 컨테이너의 체크포인트를 check1이라는 이름으로 생성하고, 체크포인트가 저장될 경로를 지정해 보자: $ docker checkpoint create --checkpoint-dir=/home/daniel/checkpoints App check1. 이 명령을 실행하면 checkpoint-dir로 지정한 경로에 check1 이라는 디렉토리가 생성되었음을 알 수 있고, 디렉토리 내에는 이진 형식의 파일이 다수 저장되어 있다. 체크포인트를 생성하면 실행중인 컨테이너는 정지하는데, 별도의 옵션(--leave-running)을 사용하면 정지하지 않게 할 수 있다. ES-1에서 구동중인 App 컨테이너의 체크포인트를 생성하고, 체크포인트를 ES-2로 전송한 후, ES-2에서 체크포인트를 이용해서 컨테이너를 실행하면, 메모리 상태 정보를 유지한 채로 서비스를 재시작 할 수 있다. 이 때, ES-1에서 App 컨테이너를 실행할 때 사용했던 이미지가 ES-2에도 저장되어 있어야 한며, 그렇지 않다면 자동으로 다운로드(pull)된다. ‘scp’ 명령으로 체크포인트를 ES-2로 전송하고, ES-2에서 컨테이너를 재시작해 보자. 체크포인트가 저장된 폴더 전체를 원격으로 전송하는 명령은 $ scp -r /home/daniel/checkpoints/check1 <USER ID>@<ES-2 IP>:/home/daniel/checkpoints이고, 전송 후에는 ES-2에서 아래의 명령을 순차적으로 실행한다:

1) $ docker create --name App-New ubuntu:16.04

2) $ docker start --checkpoint-dir=/home/daniel/checkpoints --checkpoint=check1 App-New

ES-2에서 logs 명령으로 App-New 컨테이너의 출력을 확인하면, STDOUT 출력이 초기값 0부터 시작하지 않고, ES-1에서 마지막으로 출력한 숫자부터 이어서 출력하는 것을 알 수 있다. 다음으로, App-New 컨테이너에 diff 명령을 사용해 보면, 아무것도 출력되지 않고, 이것은 test.file이 ES-2에서는 생성되지 않았다는 것을 의미한다. 즉, 체크포인트는 컨테이너의 실행 상태를 보존해 주기는 하나, 파일 시스템에서의 변경 사항은 보존하지 않는 것을 알 수 있다.

 
 

 

5. 맺음말

이것으로 도커 이관에 필요한 요소 기술에 대해 살펴보았다. Stateful Docker Migration을 위해서는 상태 보존이 반드시 필요하며, 파일 시스템 상의 변경내역 및 메모리에 저장된 정보 등을 유지하는 방식으로 컨테이너를 이관해야 끊김 없는 서비스를 제공할 수 있다. Stateful Live Docker Migration을 구현하는 방법은, 이어지는 “도커 컨테이너 이관 (Docker container migration): Part II”에서 살펴보기로 한다.

 

<참고자료>

[1] Docker, https://www.docker.com/

[2] Containerization software market share, Datanyze, https://www.datanyze.com/market-share/containerization--321

[3] Install Docker Engine on Ubuntu, Docker, https://docs.docker.com/engine/install/ubuntu/

[4] DockerHub, https://hub.docker.com/

[5] Docker run reference, https://docs.docker.com/engine/reference/run/

[6] About storage drivers, https://docs.docker.com/storage/storagedriver/

[7] Docker checkpoint, https://docs.docker.com/engine/reference/commandline/checkpoint/

[8] Checkpoint/Restore In Userspace, or CRIU, https://criu.org/Main_Page

 

도커 컨테이너 이관 (Docker container migration): Part II 보기