컨테이너 개발자
나의 첫 Docker
내가 처음으로 Docker 에 대해 들었던 건 16년도 즈음이었을 거다, 당시 CTO 는 새로운 기술, 기술 트렌드에 미쳐있었는데 (아주 중요한 덕목이라고 생각한다)
그는 나에게 입사 후 첫 임무로 CentOS 6 설치를 지시 했다. 나는 당시에 윈도우나 좀 깔아봤지, 우분투도 아니고 처음 들어보는 CentOS 를 설치하라니 참 생소했었던 기억이 난다.
USB 문제였는지 나는 한참 동안 PC 를 붙잡고 헤메었는데, 당시 사수였던 수석님이 직접 USB 를 구워준 덕분에 하루만에서야 설치를 마무리 할 수 있었다.
CentOS 를 설치한 데스크탑에 CTO 는 Docker 를 깔기 시작하더니 이거저거 만져보고는 나에게 관리를 맡겼다. 리눅스 명령어는 조금 알고 있었지만, 갑자기 큰 책임을 가지게 된 거 같아 부담스러웠던 기억이 난다.
고객은 그런거 원하지 않아.
Docker를 접한지는 조금 되었지만, 나의 컨테이너 지식은 얕디 얕았다. 이미지를 빌드할 줄도 몰랐고 그냥 이거저거 만져본게 다였으니까.
19 년도 부터 다시 웹 서비스를 직접 개발할 기회가 생기면서 자연스럽게 Docker 도 다시 시작할 수 있었는데, Spring 서버를 올릴 이미지를 직접 만들어 보면서 이거저거 성장할 수 있었던 거 같다.
하지만 실제 프로덕션 경험은, 자사 네트워크에 DB 나 WAS 몇 개 올리는 거가 전부였는데, 실제 SI 프로젝트에서는 Docker 나 컨테이너에 대한 환경이 아직 어색했기 때문이다.
많은 개발자들이 착각하는 것이, 현장에서 말하는 "레거시" 는 기술에 뒤떨어졌다는 이야기가 아니다, 검증되었고 신뢰성 높은 비즈니스 로직이 녹여져 있다는 얘기다.
거기에 Docker 는 신뢰성, 검증과는 아직 거리가 먼 솔루션이었고, 고객은 더더욱 거리를 두었다.
그럼 왜 써야 하는가?
단순히 힙스터나 얼리어답터라서 쓴다기에는 지금 쏟아지는 모든 솔루션에는 컨테이너화가 진행되고 있다. 어플리케이션을 환경에 구애받지 않고 Hypervisor 가 지원되는 환경이라면 문제없이 서비스가 가능한데 마다할 이유가 없는 셈이다.
단순히 Jenkins 나 Gitlab 을 설치하더라도, 당신이 portable 하게 구성하지 않는 이상 OS 와 권한 등에 의해 매번 다른 구성을 만들어야 한다.
게다가 HA/DR 을 위해 여러 서버를 구성하고 있을 경우, 이것을 업데이트 하고 유지보수 하는 과정에서 각 서버가 무언가 불일치 한 모습을 보일 때가 있다. 이것을 Snowflake Servers 라고 일컫는데 "A 장비는 잘되는데 B 장비는 안돌아간다" 라는 상황을 피하기 위해서 백엔드 개발자와 인프라 엔지니어는 수많은 가능성을 통제해야 한다.
단순히 당신이 latest 한 소프트웨어를 설치 할 경우, 일년 아니 하루 뒤에라도 최신본이 업데이트 된 경우, 새로이 설치하는 B 장비와 이전에 설치한 A 장비의 소프트웨어는 불일치 하다. 이럴 경우 아예 같은 상황은 아니지만서도 500마일 문제 와 같은 버젼관리에 대한 이슈를 피할 수 없게 된다.
게다가 서버를 관리하는 사람이 당신 혼자라면 모를까, 대기업은 순환 근무나 보직변경 등으로 이전에 관리하던 사람이 하루 아침에 없어질 수 있다. 이럴 경우 당신은 수많은 장비들에 대한 모든 History 를 꿰고 있지 않는 이상 서로 다른 환경에 처한 서버를 다루기 어려울 것이다.
IaC 의 등장
코드형 인프라, Infrastructure as code, IaC 는 위의 문제점을 보고 수작업을 최대한 배제하기 위해 Terraform, Ansible 등의 서버를 코드로 관리하는 해결책을 내놓았다. 코드로 서버를 관리하는 관점에서 보면 Docker 의 DockerFile도 이와 같은 결에 있다고 볼 수 있다.
기본 이미지를 끌어오고 필요한 의존성을 설치하고 어플리케이션을 실행 가능하도록 이미지를 빌드해놓으면, 나중에 다시 실행 하더라도, 아니면 새로운 컨테이너를 올리더라도, 이전과 같은 환경을 가지고 서버를 구축할 수 있다.
Dockerfile 이 주는 안정
나는 Dockerfile 을 하나의 레시피라고 생각한다. 하나의 요리를 위한 요리법이 아니라, 얼마든지 재료와 조리시간을 바꾸어서 새로운 요리를 만들 수 있는 만능 레시피.
물론 이미지를 한번에 빌드하지는 못한다. 우리가 요리를 매번 실패하고 버리면서 실력을 성장해 나갈 수 있는 것처럼, Dockerfile 도 빌드를 거쳐가면서 테스트를 실패하며 완성되어진다.
잘 만들어진 하나의 Dockerfile 은 심적 안정감 까지 주기도 한다, 단순히 Controller 만을 수행하는 자바 클래스를 보더라도, 어떤 이상한 요청이 들어올지 몰라 걱정되어 failover를 걱정해야 하는데, Dockerfile 의 이미지는 한번 잘 빌드되면 얼마든지 다시 올릴 수 있다.
이렇게 만들어진 골든 이미지 는 일관되고 신뢰성 있는 시스템 유지보수를 보장해준다.
컨테이너를 왜 안 쓰는가?
앞선 설명이 와 닿았을지는 모르겠지만, 나는 Docker 를 접하면서 왜 이 좋은 걸 쓰려하지 않는지 의문이었다.
물론 Docker 자체가 주는 입문의 턱이 높은 것도 한 몫 하는데, 일단 리눅스를 대충이라도 쓸줄 알아야 하고 네트워크와 보안 측 지식도 얕게나마 두루두루 알아야 한다.
그렇더라도 개인으로서의 성장에 있어서도 솔직히 배제할 수 없는게 컨테이너다. 잘 만들어진 어플리케이션을 누구나 쓸 수 있도록 만드는건 많은 개발자들의 성장동력이기 때문이다.
기업 입장에서야 기존의 레거시에 대한 경로의존성이나, 교육등에 매우 인색하다면 컨테이너 도입이 어려운 결정일 수 있다. 하지만 새로운 생태계에 적응하지 못하면 성장하지 못하는 건 기업도 마찬가지일 것이다.
컨테이너는 앞으로의 웹 생태계에 있어서 필수 불가결한 존재가 될 것이다, 물론 이전의 베어메탈 기반의 서비스가 멸하지는 않으리라, 그 분야도 나름의 이유가 있으니까.
컨테이너 런타임에 대해
Docker 를 기준으로 하여 컨테이너 생태계는 리눅스의 LXC 가 가진 기술적 성숙도와 OCI 덕분에 아주 큰 성장을 이루어왔다.
OCI(Open Container Initiative) 는 컨테이너의 포맷, 런타임에 대한 표준을 만들기 위해 구성된 명세이다. OCI 를 준수하는 이미지와 런타임을 통해 도커는 많은 신뢰성을 확보할 수 있게 된다.
실제 컨테이너를 실행하는 컨테이너 런타임은 runC 에서 담당하는데, OCI 에서 정의 한 명세에 따라 컨테이너를 생성, 실행하는 역할을 가진다.
runC 는 저수준 영역에서의 컨테이너 실행을 담당하는데, 컨테이너의 namespace (fs 등 시스템 리소스 가상화, 자원 격리), networking, cgroups (Control groups, 프로세스들의 자원의 사용을 제한하고 격리시키는 리눅스 커널 기능) 을 할당한 뒤 이를 이용해 명령을 처리한다.
Docker 의 경우 containerd 라고 하는 고수준 영역의 데몬을 통해서 runC 를 사용하여 컨테이너 라이플사이클을 관리한다.
containerd 같은 고수준 컨테이너 런타임은 원격지에서 컨테이너를 관리하고 모니터링 하는데 필요한 기능을 제공하며, runC 등의 저수준 컨테이너 런타임에게 명령을 전달하는 등 상위 layer와 컨테이너간 중간 다리 역할을 한다.
Docker 는 추가로 Socket 으로 컨테이너를 관리하는 dockerd 전용 데몬을 가지는데, containerd 와의 Socket 연결로, 클라이언트 측에 RestAPI 와 모니터링 기능을 제공한다
CRI(Container Runtime Interface) 은 쿠버네티스 측에서 개발한 컨테이너 런타임 인터페이스인데, CRI 가 직접 Contrainerd 등의 OCI 런타임과 통신하고 동시에, dockerd 등의 클라이언트 측 기능도 제공한다. CRI 가 개발되면서 OCI 를 구성했던 수많은 벤더들은 CRI-O (Container Runtime Interface - Open Container Initiative) 라고하는 오픈된 표준 명세를 만들어냈다.
지금의 컨테이너 생태계는?
CRI-O 가 만들어지면서 기존의 dockerd, dockershim 등의 도커 기반의 솔루션이 필요하지 않게 되었지만, 이미지 빌드와 컨테이너의 생성에 대해서는 도커에 의존성을 가지고 있었다.
이미 컨테이너 생태계의 명세는 정해졌고 오픈되었기 때문에 Docker 를 벗어나는건 시간 문제나 다름 없었다.
Buildah, Podman, Skopeo 등의 툴은 위와 같은 이유로 탄생하였는데, Docker가 가진 클라이이언트 Docker CLI 와 서버 Dockerd (Docker Daemon) 가 너무 과다한 기능을 한꺼번에 가지고 있었기 때문에 이를 분리하기 위함도 있다.
dockerd 가 데몬으로서 모든 컨테이너를 자식 프로세스로 가지고 있었기 때문에, 프로세스 자체의 모놀리스에 대한 문제, root 탈취, Single point of failure 이슈를 피할 수 없었다.
Buildah는 Dockerfile 없이도 타 스크립트 언어를 통해 컨테이너 이미지를 빌드할 수 있도록 지원한다.
Podman은 pull 및 tag 지정 등 이미지를 유지관리하는 데 필요한 명령 및 기능을 제공한다. 또한 컨테이너 작성, 실행도 가능하며, Podman CLI 가 Docker CLI 를 대체한다.
Skopeo는 이미지 저장소 명령줄 도구이다. 도커가 다른 레지스트리에 이미지를 복사하기 위해 pull, tag, push를 사용하는것에 비해, Skopeo는 copy 명령만으로 해당 기능을 제공한다.
컨테이너 생태계는 지금 의존성을 버리고 명세를 준수하는 성숙기 단계에 들어섰다고 볼 수 있다. 많은 운영 경험과 레퍼런스를 가지고 앞으로는 백엔드, 인프라 엔지니어들에게 필수 덕목이 될 것으로 보인다.