Kubernetes 배우기 전에 Container에 대해 알아보기

2020-06-02

우리는 왜 컨테이너가 필요한가

컨테이너에 대해 잘 알지 못했던 Diagnomics 인턴 시절 힘들었던 일 중 하나를 꼽으라면 단연 서버 환경 설정이다. 당시 개발하던 프로그램마다 사용하는 라이브러리 버전이 달라서 설정하는 데 애를 먹곤 했다. 리눅스 초보였던 나는 그러다 설정이 꼬이기라도 하면 OS를 밀고 다시 깔기 위해 주머니에 우분투 부팅 USB를 지니고 다녔다.

이런 문제를 해결하기 위해 도커가 세상에 등장했다.
도커는 분리된 별도의 환경을 가지는 컨테이너에서 각각의 애플리케이션을 실행할 수 있도록 해준다.

  • 이제 개발자는 애플리케이션을 개발하고 도커 컨테이너로 만들기만 하면 된다.
  • 유니온 마운트1) 기반으로 효율적으로 실행 환경을 이미지로 만들고 공유할 수 있다.
  • 초기 버전의 도커는 LXC를 그대로 사용했으나 이후 cgroups, namespace API를 직접 실행하는 libcontainer 라이브러리를 개발하고 LXC없이 동작할 수 있게 됐고 libcontainer 리팩토링을 통해 만들어진 rucC를 사용하고 있다.

컨테이너란 무엇인가

  • 완전히 분리된 환경
  • 가상머신 처럼 각자의 프로세스, 서비스, 네트워크 인페이스, 마운트를 가진다. (OS kernel 을 공유한다는 점이 가상머신과 다르다.)
  • 컨테이너로 실행된 프로세스는 커널을 공유하지만, namespace2), cgroups3), root 디렉터리 격리 등의 커널 기능을 활용해 격리되어 실행된다.
  • Host 머신에게는 프로세스로 인식되고 컨테이너 관점에서는 마치 독립적인 환경을 가진 가상머신처럼 보인다.

그렇다면, 컨테이너와 가상머신은 어떻게 다를까?

container-vs-virtual-machine

  • 가상 머신은 OS를 가진다.
  • 컨테이너는 OS가 없어서 오버헤드가 적고 가볍다. 따라서 이미지 사이즈가 작고 부팅, 배포가 빠르다.
  • Hypervisor는 뭘까?
    • 머신이 가지고 있는 물리적 자원을 가상머신이 사용할 수 있는 가상의 자원으로 변환해주는 중간 레이어이다. 가상화된 Hardware, 가상화된 네트워크 인터페이스, 가상 CPU 등을 제공하여 가상머신을 구현하고 Guest Kernel을 통해 가상 Hardware에 접근한다. (가상화할 부분이 많다...)
    • 가상머신들이 이 위에서 각각의 OS를 가진 상태로 동작한다.
    • Host OS에서 실행된다.
  • 컨테이너는 커널이 완전 격리되지 않기 때문에 같은 커널을 가지는 컨테이너만 사용 가능하다.
    • 예를 들어 커널이 리눅스인 Ubuntu에서 도커를 사용해서 CentOS, Debian, Fedora 등의 리눅스 커널을 가진 컨테이너를 사용할 수 있지만 커널이 다른 Windows는 사용할 수 없다.

컨테이너의 단점

  • 파일 시스템: 가상머신은 블럭 디바이스 위에 ext3, ext4를 이용해서 스토리지를 사용한다. 컨테이너는 AUFS, Device mapper, Overlay Filesystem등과 같은 유니온 파일 시스템(Union filesystem)을 사용하는데, ext4와 같은 파일 시스템 위에 올라가는 데다가 변경된 내용을 기록하고, 기록된 내용으로 부터 원본 데이터를 복원해야 하기 때문에 느릴 수 밖에 없다. 데이터의 읽기와 쓰기를 일반 파일 시스템으로 분리하거나 Btrfs나 ZFS 같은 COW파일 시스템을 이용하는 등으로 문제를 해결 할 수 있기는 하지만, 가상머신에 비해서 신경써야 할게 많다.
  • 네트워크 구성: 호스트 운영체제 레벨에서 네트워크를 한번 더 추상화 해야 해서 가상머신 보다 네트워크 구성에 신경을 써야 할게 많다.

컨테이너는 뭐고 이미지는 뭐지?

  • 하나의 이미지로 여러 컨테이너를 만들 수 있다.
  • 실행 중인 이미지 인스턴스 === 컨테이너

컨테이너에 익숙해질 쯤

컨테이너로 애플리케이션을 개발, 배포하면 너무너무 편해진다.

상황 1 - 새로운 버전의 개발을 했고, 배포를 한다.

  1. docker build 새로운 이미지
  2. 이미지 저장소에 새로운 이미지를 push 한다. docker push 새로운 이미지
  3. 이미지 저장소로부터 서버에 새로운 이미지를 내려받고 docker pull 새로운 이미지
  4. 새로운 이미지를 실행 docker run 새로운 이미지
  5. 서버 수만큼 3~4를 반복한다.

상황 2 - 트래픽이 증가해서 서버를 증설하기로 했다.

  1. 새로운 서버 추가
  2. 새 서버에 docker pull 이미지
  3. docker run 이미지
  4. 트래픽을 분산해주는 로드밸랜서에 새로운 서버를 연결한다.

상황 3 - 트래픽이 감소해서 서버 수를 줄이기로 했다.

  1. 로드밸랜서에서 삭제할 서버 연결 해제
  2. 서버 없애기

업데이트를 할 때마다, 트래픽 변화가 있을 때마다 간단한 작업이 계속 반복된다.... 귀찮다... 트래픽 모니터링 정도는 컴퓨터가 할 수 있으니 적절히 판단해서 서버 수를 늘이거나 줄여줬으면 좋겠다. 업데이트 배포를 할 때도 컴퓨터가 한 서버씩 알아서 잘 무중단 배포해주면 좋겠다... 그렇게 인프라 관리자들의 염원이 모여 쿠버네티스가 탄생했다...!!!!!

물론 다른 Container Orchestration 시스템도 있지만 가장 많이 사용되고 있기 때문에 참고 자료가 많을 것으로 예상되는 쿠버네티스를 열심히 공부해서 편안한 삶을 누리고자 한다.


appendix. 용어 정리

  • 1) 유니온 마운트 (union mount) > 하나의 디렉터리에 여러 개의 디렉터리를 마운트함으로써 마치 하나의 통합된 디렉터리처럼 보이게 하는 것이다. 도커에서 이미지 구현에 사용된다. 효율적인 이미지 구현을 가능하게 한다. 이미지 빌드 과정에서 캐시를 사용해서 속도를 향상시켜준다.
  • 2) 리눅스 네임스페이스 (linux namespaces) > 특정 프로세스의 리눅스 리소스 접근을 제어하기 위해 사용되는 기능이다. 네임스페이스는 리소스 별로 IPC 네임스페이스, 마운트 네임스페이스, 네트워크 네임스페이스, 프로세스 ID 네임스페이스, 사용자 네임스페이스, UTS 네임스페이스, 컨트롤 그룹 네임스페이스 등으로 나뉜다. 시스템 상에서 실행되는 프로세스들은 기본적으로 init 프로세스의 네임스페이스를 공유하지만 시스템콜이나 unshare 명령어를 사용해 리소스 별로 네임스페이스를 분리하는 것이 가능하다.
  • 3) 컨트롤 그룹 (cgroup) > 프로세스에서 사용 가능한 CPU, 메모리, 네트워크 대역폭, 디스크 I/O 등을 그룹 단위로 제어하는 리눅스 커널의 기능이다. 네임스페이스 분리를 통해 각 프로세스를 그룹으로 나누어 통제한다. 상위 계층에서 분리를 통제할 수 있도록 해준다. 각 네임스페이스에 속한 프로세스에게는 하나의 머신 그 자체처럼 작용한다. cgroup을 통해 LXC가 만들어졌다.
  • 리눅스 캐퍼빌리티 (linux capabilities)

    프로세스의 권한을 제어하는 기능. 리눅스의 프로세스는 크게 루트 권한(사용자 ID 0)으로 실행되는 특권 프로세스와 일반 사용자(사용자 ID 0 이외)가 실행하는 비특권 프로세스로 나뉘는데 루트의 권한을 세분화해서 프로세스 적용할 수 있도록 만든 기능이 바로 리눅스 캐퍼빌리티이다. 컨테이너 런타임에서도 일부 루트 권한이 필요한 경우 리눅스 캐퍼빌리티를 사용해 필요한 권한을 지정하는 방식을 지원하고 있다.


글 작성을 위해 참고한 링크 및 서적