Post

컨테이너와 Docker

이 포스팅에서는 컨테이너가 무엇인지 알아보고, Docker가 각광받게 된 이유를 살펴본다.

컨테이너는 무엇이냐?

격리 기술을 이용해, 논리적으로 격리된 환경을 제공하는 기술이다. “그렇다면 거기서 작동하는 애플리케이션은 결국 무엇이냐?” 답은 프로세스이다.

만약, 해당 프로세스에서 추가로 필요한 것이 있으면 자식 프로세스를 생성한다.

1
2
3
4
5
6
7
8
docker top docker-nginx-1
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                1623524             1623498             0                   03:40               ?                   00:00:00            nginx: master process nginx -g daemon off;
uuidd               1623600             1623524             0                   03:40               ?                   00:00:00            nginx: worker process
uuidd               1623601             1623524             0                   03:40               ?                   00:00:00            nginx: worker process
uuidd               1623602             1623524             0                   03:40               ?                   00:00:00            nginx: worker process
uuidd               1623603             1623524             0                   03:40               ?                   00:00:00            nginx: worker process
uuidd               1623604             1623524             0                   03:40               ?                   00:00:00            nginx: worker process

위에는 nginx 컨테이너의 프로세스를 확인한 것이다. master, worker 프로세스가 있는 것을 확인할 수 있다.

컨테이너 격리 방법

  1. 네임스페이스를 통해 네트워크, 파일시스템 격리
  2. cgroup을 통해 리소스 양을 제한
  3. chroot를 통해 컨테이너가 바라볼 수 있는 디렉터리를 한정
    1. 탈옥 이슈로 현재 chroot를 사용하지 않고, privot root를 사용한다고 한다. (카카오 기술블로그)

위와 같은 방법으로 컨테이너를 관리하는 것이 컨테이너 엔진이다. 가장 유명한 컨테이너 엔진은 Docker이다. 하지만, Docker만 있었던 것은 아니다. 그렇다면 어떻게 Docker가 선택받을 수 있게 되었을까?

Docker가 선택받은 이유

Docker 이전에는 Linux Container(LXC)가 있었다. (관련된 내용은 LXC 포스팅에서 확인할 수 있다.) LXC와 비교해서 Docker가 선택받은 이유는 아래와 같다.

  1. LXC와 다르게 Application 수준으로 추상화
    • LXC는 의존성 설치, 네트워크 설정 등이 필요하나, docker는 네트워크도 간단히 설정 가능하다.
    • LXC는 결국 네트워크 설정, 의존성 등 환경설정에 어려움이 많다. 즉, 일관된 환경을 제공하지 못한다.
  2. UFS(Union File System)로 여러 Layer를 합치는 방식으로 이미지를 관리
    • 기존의 Layer를 캐싱하는 방식으로 이용할 수 있음(중복 제거)
  3. DockerHub, OpenSource
    • 모든 커뮤니티와 공유하며, 컨테이너 이미지를 쉽게 사용할 수 있다.

이런 편리함 때문에 Docker는 선택받을 수 있었다. 장점에 대해 살펴보면, Docker가 Application 수준으로 추상화했다는 것과, DockerHub는 바로 이해가 간다. 하지만 UFS에 대해서는 처음 접할 수 있다. 그렇기에 여기서는 UFS에 대해서만 살펴본다. (UFS와 관련된 포스트는 여기에서 확인할 수 있다. 아래에서는 간단하게 설명한다.)

UFS

다른 파일시스템 위에서 존재하는 파일시스템이며 여러 파일시스템을 단일 디렉터리로 마운트한다. 여러 레이어를 쌓아서 통합된 파일시스템을 생성한다.

레이어는 크게 아래와 같이 구분된다.

  • LowerDir: 하위 레이어, 이미지에 해당함
  • UpperDir: 상위 레이어, 현재 내가 컨테이너에서 쓸 수 있는 Layer(컨테이너 삭제 시 소멸된다.)
  • MergeDir: 모든 레이어를 병합한 디렉터리, 컨테이너에서는 MergeDir의 환경을 본다.

만약 내가 LowerDir에 있는 파일 B를 수정했다면? COW(Copy on Write) 방식으로 하위 레이어에서 파일 B를 상위 레이어로 복사한 뒤, 여기서 업데이트를 반영한다.

덕분에 우리는 하위레이어(이미지)를 건드리지 않고 우린 재활용할 수 있다. 또, 레이어로 구분되기에 캐싱이 효율적이다.

통합

여기서는 OCI, CRI에 대해 살펴본다.

쿠버네티스에서는 Docker가 아닌 다른 컨테이너 엔진으로 동작한다. 하지만, 우리는 DockerHub에서 이미지를 가져와 똑같이 컨테이너를 실행할 수 있다. 이는 OCI 표준이 만들어졌기 때문이다. OCI란 무엇일까?

OCI

Open Container Initiative의 약자로 Docker와 redhat 등이 모여 컨테이너에 대한 표준을 만들었다. 덕분에 어떤 엔진을 사용하던, 동일한 규격을 갖추게 된다. 덕분에 우리는 DockerHub에 있는 이미지를 다른 엔진으로도 쉽게 실행할 수 있다.

*당시 사실상 표준은 docker였다고 한다. 지금도 컨테이너 이미지를 빌드할 때, 주로 Dockerfile을 사용한다.

CRI

Docker는 쿠버네티스의 런타임 표준인 CRI에 준수되지 않는다. 그렇기에 Kubernetes에서는 더 이상 Docker를 지원하지 않는다. 관련된 내용은 Kubernetes Docs에서 확인할 수 있다.

docker swarm

쿠버네티스와 같은 컨테이너 오케스트레이션 플랫폼이다. 다만, 쿠버네티스가 사실상 업계 표준이 되면서 많이 사용하진 않는다.

Docker에 묶여있다. 반면 Kubernetes는 CNI, CRI 등 표준을 만들어, 해당 형식에만 만족하면 다양한 기술을 선택할 수 있다.

Docker의 단점

최근에는 Docker의 이런저런 문제들 때문에, 컨테이너를 이용할 때 다른 플랫폼을 이용한다. 예를 들어, 파이프라인에서는 DinD(Docker in Docker)보다 Kaniko와 같은 빌드도구를 사용한다.

여기서는 Docker에는 어떤 문제들이 있고, 비교되는 플랫폼에 대해 설명한다.

문제점

  1. Docker 서버(Daemon)에서 모든 것을 처리한다. 이는 모든 컨테이너를 자식 프로세스로 가지고 있어, 데몬의 장애 발생 시 컨테이너가 위험에 빠진다.
  2. docker는 sudo(root) 명령을 통해 실행할 수 있으며, 이는 DinD와 같은 상황에서 컨테이너에게 특권(privileged)을 부여해야 하는 문제가 생긴다.

vs Podman

간단하게 말하는 데몬을 없앤 컨테이너 엔진이다. Rootless 컨테이너로 권한을 제한할 수 있으며, 빠르고 가볍다.

자세한 내용은 https://podman.io/에서 확인할 수 있다.

Dockerfile Best Practices

마지막으로 컨테이너 이미지를 생성할 때, 모범 관행을 살펴본다.

  1. 이미지의 버전을 명시
  2. 이미지는 최대한 가벼운 것으로
  3. 여러 stage를 나눠서, 이미지를 더 가볍게 설계할 수 있음.
  4. .dockerignore 파일을 통해 불필요한 것들을 제외 가능
  5. 실행하는 유저를 별도로 두어 컨테이너 권한 최소화
  6. dockerfile에서 자주 변경되는 명령어는 최대한 아래로 두어, 캐싱 최적화

정리

이 포스트에서는 간단하게 컨테이너가 무엇이고, Docker는 왜 사람들의 선택을 받게 되었는지, 또 현재 발생하고 있는 Docker의 문제가 무엇이고 비교 엔진은 무엇이 있는지 살펴봤다. 컨테이너와 관련된 역사를 훑어본 느낌이다.

참고자료

https://kubernetes.io/ko/blog/2020/12/02/dont-panic-kubernetes-and-docker/

https://medium.com/@knoldus/unionfs-a-file-system-of-a-container-2136cd11a779

https://docs.docker.com/build/building/best-practices/

https://kubernetes.io/ko/docs/concepts/architecture/cri/

https://opencontainers.org/

https://podman.io/

This post is licensed under CC BY 4.0 by the author.