Image by Bo-Yi Wu from Flicker
도커는 지정한 어플리케이션에 해당하는 바이너리와 실행환경 정보를 포함하여 이미지를 생성해주고, 해당 이미지 실행시에 ‘컨테이너’ 라고 불리는 격리된 환경에서 어플리케이션이 실행되는 것이 가능하도록 만들어줍니다. 윈도우 버전을 배포하지 않는 코드체인의 경우에도 윈도우버전 도커를 통해서 노드를 실행할 수 있습니다. 기존의 ‘가상머신’ 과 기능적으로 비슷한 점들이 많죠. 하지만 ‘가상머신’은 운영체제를 포함한 완전한 컴퓨터를 가상화하는 방식이므로 성능면에서의 부하가 크고, 한 가지 어플리케이션을 위해서 실행시키기에는 용량과 배포 관리 측면에서 불리합니다.
도커는 보다 가벼운 용량으로 이미지를 배포할 수 있고, 호스트와 컨테이너 사이의 성능차이가 크지 않다는 장점이 있습니다. 또한 깃허브(github)을 통해서 코드의 버전을 쉽게 관리하듯이 도커허브(Docker hub)를 통해서 이미지의 버전을 간편하게 관리할 수 있습니다.
왜 도커 이미지에서만 커밋해쉬가 오류났을까?
현재 코드체인에서 지원하는 JSON 형식의 원격 프로시저 호출(RPC) 종류 중에 커밋해쉬(commitHash)라는 명령이 있습니다. 이 명령을 통해서 현재 실행되고 있는 코드체인 바이너리의 깃 저장소 내에서의 커밋 해쉬를 가져올 수 있습니다. 그런데 같은 배포 버전의 바이너리이더라도 로컬 저장소에서 직접 빌드하여 실행한 바이너리와 달리 도커 이미지를 통해서 배포된 바이너리의 경우는 원격 프로시저 호출에서 커밋 해쉬를 가져오지 못하는 오류가 있었습니다.
원인은 생각보다 간단했는데, .dockerignore파일에서 .git 을 등록하여 도커 빌드에서 제외했던 것이 문제였습니다. 개발자들이 .gitignore 에 IDE 관련 디렉토리인 .idea 나 .vscode 같은 것들을 등록하는 것은 매우 습관적인 일인데 , 편집이력이 담겨있는 .git 은 특히 그 정보가 보안적으로 중요할 수도 있기 때문에 도커 빌드에서 제외하는 것은 어쩌면 당연한 일 중 하나입니다. 코드체인에서는 커밋해쉬 원격 프로시저 호출을 처리하기 위해서 이 글의 소개와 같이 버젠(vergen) 이라는 라이브러리를 사용했습니다. 이 라이브러리에서 커밋해쉬를 등록하기 위해서 빌드 당시의 .git 정보를 필요로 하기 때문에 해당 정보 없이 빌드 된 도커 이미지에서는 커밋해쉬 호출이 제대로 동작할 수 없었던 것이죠.
다단계 빌드로 문제 해결하기
이와 동일한 문제가 고-이더리움(go-ethereum) 프로젝트에서도 역시 제기된 적이 있을 만큼(이슈 15346번) 흔히 일으킬 수 있는 실수에 기반해 있습니다. 하지만 .git 정보를 외부로 유출하지는 않으면서 빌드에는 포함할 수 있을까요? 이것의 실현을 위해서 도커의 다단계 빌드(Multi-stage build)를 활용할 수 있습니다. 빌드에 단계를 설정하고 각 단계에서 다음 단계로 넘어갈 때에 필요한 정보만을 복사하여 사용할 수 있는 것이 이 기능의 특징 중 하나입니다. 따라서 두 단계의 빌드를 사용하여 .dockerignore 에서는 .git 디렉토리를 등록하지 않으면서, 두 번째 단계의 빌드에서는 첫 번째에서 포함된 이 디렉토리의 내용은 복사하지 않음으로써 빌드 당시에는 .git 정보는 이용하고 배포에는 포함시키지 않아 문제를 해결할 수 있었습니다. 완성된 도커파일로 빌드된 이미지들은 코드체인 도커허브 에서 찾아볼 수 있습니다.
어떻게 데이터의 지속성을 유지할 수 있을까?
코드체인은 로컬에 저장되는 데이터베이스(database) 그리고 키모음(keys)에 의존하여 실행됩니다. 하지만 도커 이미지를 통해 컨테이너 내에서 어플리케이션이 실행되는 경우에는 그 데이터는 컨테이너 층(layer)에 저장되고, 그 환경이 컨테이너를 들고 있는 호스트와는 별개로 고립되는 특징이 있습니다. 새로운 코드체인 버전의 이미지를 받아서 컨테이너를 가동하는 경우 혹은 심지어 같은 이미지더라도 새로운 컨테이너를 가동하는 경우 코드체인이 의존하고 있는 두 데이터가 지속적으로 유지되지 않습니다. 컨테이너의 쓰기 가능한 층의 데이터는 호스트 머신과 강하게 연결되어 있기 때문에 컨테이너의 데이터 또한 바깥으로 내보내기 힘들다는 문제가 있습니다.
호스트의 공간 마운트 하기
도커에서 제공하는 호스트의 공간을 컨테이너 내의 공간에 마운트 하는 방법은 Volumes, Bind mounts, tmpfs mounts 3 가지가 있습니다. tmpfs mounts는 오직 호스트의 메모리에만 저장을 하기 때문에, 호스트의 파일시스템에 쓰는 일이 없고 디스크에 지속적으로 정보가 존재하지 않아 코드체인에서 데이터를 지속성을 유지하고자 하는 목적과는 맞지 않습니다. Bind mounts는 호스트 시스템의 어떤 곳이든지 컨테이너 내의 공간에 마운트 할 수 있습니다. 그것이 호스트의 시스템 파일이나 디렉토리 일 수도 있고 도커 혹은 도커가 아닌 프로세스가 언제든 수정할 수 있다는 특징을 가집니다. 성능면에서는 훌륭하지만, 한 가지 부작용으로 호스트 시스템의 중요한 파일이나 디렉토리를 컨테이너 내에서 동작하는 프로세스가 삭제하거나 수정할 가능성이 있습니다. 그래서 코드체인은 Volumes 를 사용하여 데이터를 지속성 있게 관리할 것을 권장합니다.
Volumes 활용하기
Volumes옵션은 호스트 파일 시스템 중 도커에 의해서 관리되고 있는 /var/lib/docker/volumes내의 공간을 컨테이너 위에 마운트합니다. 볼륨 옵션은 크게 빌드 전과 컨테이너 실행 시 두 가지 방법으로 지정할 수 있는데, 빌드 이전에 지정한 경우에는 실행하는 호스트에 대한 어떤 정보도 없으므로 미리 볼륨의 이름을 명시하여 빌드할 수 없다는 문제가 있습니다. 볼륨의 이름은 새로운 코드체인 컨테이너의 데이터를 연결하거나, 백업하는 등의 유지 관리에 자주 쓰이게 되는데, 빌드 이전에 도커파일에 지정한 볼륨들의 경우 이름이 해쉬를 사용하여 지어지게 되어 길고 접근성이 떨어집니다. 따라서 코드체인 도커 이미지는 배포 전에 볼륨을 지정하지 않습니다. 볼륨 옵션은 컨테이너 실행시에 -v host_dir_path:container_dir_path와 같이 지정할 수 있는데, 지정한 호스트 디렉토리 경로( host_dir_path) 는 /var/lib/docker/volumes하위에 위치하게 되고, 컨테이너 내 경로( container_der_path)는 컨테이너 내에서의 독립된 경로를 나타냅니다. 이 옵션으로 실행하면 호스트 디렉토리가 컨테이너 내의 디렉토리 위에 마운트(mount) 됩니다.
이렇게 호스트의 파일시스템에 데이터베이스와 키모음을 기록하게 되면, 새로운 버전이 나왔을 때 혹은 새로운 컨테이너 실행시에 기존의 공간을 마운트 함으로써 지속적으로 데이터를 유지해가며 코드체인을 운영할 수 있습니다. 더 자세하게는 아래와 같은 명령을 실행시킬 경우, codechain-db-vol 과 codechain-keys-vol 이 도커의 볼륨들에 존재했다면, 기존의 것들을 불러오고 아니라면 새로 볼륨을 생성하게 됩니다.
$ docker run -it -v codechain-db-vol:/app/codechain/db -v codechain-keys-vol:/app/codechain/keys codechain-io/codechain:branch_or_tag_name
컨테이너 내의 경로를 위와 같이 지정해준 까닭은 도커 이미지의 빌드 설정에 기본 작업 디렉토리가 /app/codechain 으로 설정되어있고, 그 하위의 db 디렉토리와 keys 디렉토리가 기본적으로 활용되기 때문입니다. 코드체인 실행 옵션을 통해서 아래 둘의 경우로 맞춤 설정 할 수 있습니다.
1. base-path 옵션 활용하기
코드체인의 명령줄 실행 옵션 중에서 base-path 를 명시할 수 있는 옵션이 있는데, 이를 활용하면 데이터베이스와 키모음을 한 볼륨에 다음과 같이 기록할 수 있습니다.
$ docker run -it -v codechain-data-vol:custom_base_path codechain-io/codechain:branch_or_tag_name — base-path custom_base_path
2. db-path 와 key-path 옵션 활용하기
위와 다르게, db-path와 keys-path를 별개로 명시하여 서로 다른 볼륨에 기록할 수 있습니다.
$ docker run -it -v codechain-db-vol:custom_db_path -v codechain-keys-vol:custom_keys_path codechain-io/codechain:branch_or_tag_name — db-path custom_db_path — keys-path custom_keys_path
볼륨을 지정하고 난 뒤에는 docker volume ls를 활용하여 현재 등록된 볼륨들을 아래와 같이 확인할 수 있습니다.
$ docker volume ls
DRIVER VOLUME NAME
local codechain-db-vol
local codechain-keys-vol
또한 docker volume inspect를 통해서 각각의 볼륨이 어떤 정보를 갖고 있는지 아래와 같이 자세하게 확인할 수 있습니다.
docker volume inspect codechain-db-vol
[{
“CreatedAt”: “2019–04–29T14:48:41+09:00”,
“Driver”: “local”,
“Labels”: null,
“Mountpoint”: “/var/lib/docker/volumes/codechain-db-vol/_data”,
“Name”: “codechain-db-vol”,
“Options”: null,
“Scope”: “local”
}]
현재 코드체인은 도커만 설치되어 있다면 쉽게 실행할 수 있도록 도커 허브를 통해서 도커 이미지 또한 배포하고 있습니다. 코드체인 저장소의 README.md의 설명을 따라한다면 누구든 도커 이미지를 활용한 코드체인 노드를 운영할 수 있을 것입니다.
관련 스택