컨테이너 기술 이해하기
컨테이너는 샌드박스 기술의 한 유형입니다. 이름에서 알 수 있듯이, 샌드박스는 애플리케이션을 컨테이너처럼 '패키징’하여 설정된 경계 덕분에 애플리케이션들이 서로 간섭하지 않도록 보장하는 기술입니다. 또한 이러한 ‘컨테이너’ 안에 담긴 애플리케이션은 다양한 시스템 환경에서 마이그레이션되어 실행될 수 있습니다.
컨테이너 기술의 원리를 살펴보기 전에, 프로세스의 개념을 이해하는 것이 필수적입니다. 프로세스의 정적 표현은 프로그램이며, 일반적으로 디스크에 조용히 저장되어 있습니다. 실행되면 컴퓨터 내의 데이터와 상태의 총합, 즉 동적 표현으로 변환됩니다. 컨테이너 기술의 핵심 기능은 프로세스의 동적 동작을 제한하고 수정하여 '경계’를 만드는 것입니다.
Docker 컨테이너 기술
Docker를 포함한 대부분의 Linux 컨테이너에서는 Cgroups 기술이 주로 제약을 가하는 데 사용되며, Namespace 기술이 프로세스 뷰를 수정하는 주요 방법입니다.
여러분이 Ubuntu 22.04와 같은 Linux 운영 체제에서 Docker 프로젝트를 실행 중이라고 상상해 보세요. 실험을 위해 컨테이너를 만들어 보겠습니다:
$ docker run -it busybox /bin/sh
이 Docker 명령은 busybox 이미지를 기반으로 컨테이너를 시작하고 그 안에서 대화형 셸 세션을 실행합니다.
docker run: 새 컨테이너 인스턴스를 생성하고 시작합니다.-i: 터미널이 연결되지 않은 상태에서도 STDIN을 열어두어 컨테이너와 상호작용할 수 있게 합니다.-t: 의사 터미널(pseudo-terminal)을 할당하여 대화형 셸 환경을 만듭니다./bin/sh: 컨테이너 시작 시 실행할 명령을 지정합니다. 이 경우 컨테이너 내에서 셸 세션을 시작합니다.
컨테이너 내부의 프로세스
따라서 Ubuntu 머신은 호스트가 되고, /bin/sh를 실행하는 컨테이너가 그 안에서 동작합니다. 이 예시와 그 기본 원리는 숙련된 Docker 사용자에게는 익숙할 것입니다. 컨테이너 내부에서 ps 명령을 실행하면 흥미로운 점을 발견할 수 있습니다:
/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh
10 root 0:00 ps
여기서 Docker 내에서 실행된 초기 /bin/sh는 컨테이너 내부에서 프로세스 번호 1(PID=1)이며, 두 개의 프로세스만 실행 중입니다. 이는 우리가 방금 실행한 /bin/sh와 ps 명령이 호스트와는 다른 환경으로 격리되었음을 의미합니다.
Namespace 메커니즘
이것은 어떻게 이루어질까요? 일반적으로 호스트에서 /bin/sh 프로그램이 실행되면 운영 체제는 고유하게 식별하기 위해 PID=100과 같은 프로세스 ID를 할당합니다. Docker 컨테이너 내에서 이 프로그램을 실행하면 Docker는 '착각’을 적용하여 실제로 PID=100인 프로세스가 자신이 첫 번째 프로세스(PID=1)라고 믿게 만듭니다. 이 메커니즘은 격리된 애플리케이션의 프로세스 공간을 조작하여 다시 계산된 프로세스 ID를 볼 수 있게 합니다.
Linux의 Namespace 기술
Namespace의 사용은 매우 흥미롭습니다. Linux에서 새 프로세스를 생성하기 위한 선택적 매개변수에 불과합니다. Linux에서 프로세스를 생성하는 시스템 호출은 clone()이며, 다음과 같습니다:
int pid = clone(main_function, stack_size, SIGCHLD, NULL);
이 호출은 새 프로세스를 생성하고 해당 프로세스 ID인 pid를 반환합니다. clone() 시스템 호출을 사용하여 새 프로세스를 생성할 때 CLONE_NEWPID 인수를 지정할 수 있습니다:
int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);
그러면 새로 생성된 프로세스는 자신의 PID가 1인 새로운 프로세스 공간을 ‘보게’ 됩니다. 이 '착각’은 호스트의 프로세스 공간에서 실제 PID를 변경하지 않으며, 실제 값(예: 100)은 그대로 유지됩니다.
여러 Namespace의 작동
위의 clone() 호출을 반복 실행하면 여러 PID Namespace가 생성됩니다. 각 Namespace의 애플리케이션 프로세스는 자신이 각 컨테이너 내에서 첫 번째 프로세스라고 믿으며, 호스트의 실제 프로세스 공간이나 다른 PID Namespace의 세부 사항을 알지 못합니다. PID Namespace 외에도 Linux는 Mount, UTS, IPC, Network 및 User Namespace를 제공하여 다양한 프로세스 컨텍스트를 ‘가립니다’.
Mount Namespace
- Mount Namespace: 격리된 프로세스가 현재 Namespace와 관련된 마운트 포인트만 볼 수 있도록 합니다. 즉, 컨테이너 내의 프로세스는 호스트의 다른 마운트 포인트를 인식하지 못합니다.
Network Namespace
- Network Namespace: 격리된 프로세스에게 현재 Namespace에 특화된 네트워크 장치 및 구성을 볼 수 있는 뷰를 제공합니다. 각 Network Namespace는 호스트 및 다른 Namespace와 별개로 자체 네트워크 장치, IP 주소, 라우팅 테이블 및 포트 번호를 가집니다.
기타 Namespace
- UTS Namespace: 노드 이름(호스트네임) 및 네트워크 호스트네임 정보를 격리합니다.
- IPC Namespace: 메시지 큐, 세마포어 등 프로세스 간 통신을 위한 리소스를 분리합니다.
- User Namespace: 사용자 및 그룹 ID를 격리하여 컨테이너화된 사용자 ID를 호스트의 다른 ID에 매핑할 수 있게 하여 보안을 강화합니다.
Docker 컨테이너의 구현 원리
이것이 Linux 컨테이너의 기본 구현 원리입니다. 따라서 겉보기에는 복잡해 보이는 Docker 컨테이너의 개념은 본질적으로 컨테이너 프로세스를 생성할 때 Namespace 매개변수 세트를 지정하는 것을 포함합니다. 결과적으로 컨테이너는 현재 Namespace에 의해 제한된 리소스, 파일, 장치, 상태 또는 구성만 ‘볼’ 수 있으며, 호스트 및 관련 없는 프로그램에 대해 완전히 인식하지 못합니다.
컨테이너와 가상 머신 비교
따라서 컨테이너는 특별한 종류의 프로세스입니다. 프로세스에 독립적인 공간을 할당하는 아이디어를 고려할 때 가상 머신이 떠오릅니다. 가상 머신은 Hypervisor 소프트웨어를 통해 하드웨어를 시뮬레이션하고 완전한 게스트 운영 체제를 실행하여 애플리케이션 격리를 달성합니다. 반면, Docker 컨테이너는 호스트의 운영 체제 커널을 공유하면서 Namespace 및 Cgroups 기술을 통해 격리를 달성합니다.
Docker의 경량 가상화
Docker는 하드웨어 시뮬레이션이나 추가 운영 체제 실행 없이 호스트 리소스를 직접 활용하기 때문에 ‘경량’ 가상화 기술로 자주 언급됩니다. 이로 인해 전통적인 가상 머신에 비해 시작 속도, 리소스 활용 및 성능 측면에서 상당한 이점을 제공합니다.
결론
Docker 컨테이너 기술은 Namespace 및 Cgroups 기술을 통해 애플리케이션 프로세스에 격리된 경량 환경을 제공합니다. 이 기술은 컨테이너가 빠르게 시작되고 효율적으로 실행되며 호스트 및 다른 컨테이너와의 격리를 유지할 수 있게 하여, 현대 클라우드 컴퓨팅 및 마이크로서비스 아키텍처에서 필수적인 기술이 되었습니다.
Novita AI, 무한한 창의성을 위한 원스톱 플랫폼으로 100개 이상의 API에 액세스할 수 있습니다. 이미지 생성, 언어 처리, 오디오 향상 및 비디오 조작에 이르기까지 저렴한 사용량 기반 요금제로, 자신의 제품을 구축하면서 GPU 유지 관리의 번거로움에서 해방됩니다. 지금 무료로 사용해 보세요.
