11장 컨테이너

컨테이너 기술을 사용한 소프트웨어라 고 하면, 컨테이너 애플리케이션을 관리하는 도커, 도커 등을 활용한 컨테이너 오케스트 레이션 시스템 쿠버네티스Nikeimeier'가 유명합니다.

🧱 컨테이너는 왜 가볍고 빠른 가상화인가?

1. 컨테이너란 무엇인가?

1) 컨테이너의 정의

컨테이너는 운영체제 수준의 가상화 기술로, 프로세스 격리와 자원 분리를 통해 마치 독립된 시스템처럼 작동하는 실행 환경입니다.

가상 머신각 가상 머신 전용의 가상 하드웨어와 커널을 사용하는 반면에, 가상 머신과 달리 전체 OS를 복제하지 않고, 호스트 OS의 커널을 공유한다는 특징이 있습니다.

2) 컨테이너 vs 가상 머신

항목
가상 머신
컨테이너

커널

별도 OS 커널 필요

호스트 OS의 커널 공유

기동 방식

전체 OS 부팅

단일 프로세스 실행

기동 속도

느림 (수십 초)

빠름 (0.6초 이내)

기동 시간

평균 10~30초 이상

밀리초~1초 내외

자원 사용량

무거움

가벼움

하드웨어 접근

완전 가상화로 성능 손실 있음

네이티브와 유사한 속도 가능

주 사용 사례

OS 격리 / 이기종 OS 필요 시

마이크로서비스 / 빠른 배포 환경용

호환성

이기종 OS 가능 (Windows 위에 Linux 등)

동종 커널만 가능 (Linux 위에 Linux)

가상 머신은 윈도에서 리눅스를 실행하는 등 완전히 다른 호스트 OS를 쓸 수 있지만, 리눅스 컨테이너는 리 눅스 커널에서만 동작하는 시스템(우분투 레드햇 엔터프라이즈 리눅스 등)만 사용할 수 있습 니다.

3) 가상 머신(Virtual Machine)의 기동 단계

가상 머신에서 우분투 20.04와 같은 OS가 완전히 부팅되기까지는 다음과 같은 과정을 거칩니다.

  1. 호스트 OS의 가상화 소프트웨어(QEMU, VirtualBox 등)가 가상 머신을 기동한다.

  2. 부트 로더(예: GRUB)가 시작된다.

  3. 부트 로더가 리눅스 커널을 로드한다.

  4. 커널이 init 프로그램(systemd 등)을 기동한다.

  5. systemd백그라운드 서비스들(systemctl 서비스)를 순차적으로 기동한다.

📌 이 흐름은 실제 물리 시스템이 부팅되는 방식과 거의 동일하며, 초기화에 수 초~수십 초가 소요

4) 컨테이너(Container)의 기동 단계

컨테이너에서는 전체 OS 부팅이 아닌, 애플리케이션 프로세스만 기동합니다.

  1. 컨테이너 런타임(Docker 등)이 컨테이너를 생성한다.

  2. 런타임이 최초 프로세스(예: bash, nginx 등)를 바로 실행한다.

📌 커널, 부트로더, 시스템 서비스 기동이 없기 때문에 수 밀리초 수준의 빠른 기동이 가능합니다.

5) 결과 비교

환경
기동 시간 (초)

가상 머신

14.0 초

컨테이너

0.670 초

20배 이상 차이 발생! 컨테이너는 가상 머신보다 훨씬 빠르게 실행됩니다.

왜 이런 차이가 날까요?

  1. 가상 머신의 느린 기동 원인

  • 부트로더 → 커널 → 시스템 초기화 → 데몬 등 전통적인 OS 부팅 절차 전부 수행

  • 하드웨어 접근 시 VMX 모드 전환 비용이 계속 발생

  1. 컨테이너의 빠른 기동 비결

  • 커널은 이미 호스트 OS와 공유

  • 필요한 건 애플리케이션 프로세스 1개 실행이면 끝

  • 별도의 OS 초기화 필요 없음

2. 컨테이너의 두 얼굴

1) 시스템 컨테이너

  • init(systemd 등)을 실행해 전통적인 리눅스 환경처럼 작동

  • 다양한 애플리케이션 실행 가능

  • LXD 등에서 사용

2) 애플리케이션 컨테이너

  • 하나의 앱만 실행

  • systemd 없이 시작 프로그램만 실행

  • 도커(Docker)의 대표적인 컨테이너 방식

3. 컨테이너 내부 구조 – 커널이 컨테이너를 모른다?

1) 커널에는 ‘컨테이너’라는 개념이 없다

사실 커널에는 컨테이너라고 부르는 기능이 없습니다. 컨테이너는 단지 네임스페이스와 cgroup을 조합한 결과물입니다.

2) 네임스페이스란?

네임스페이스는 시스템에 있는 다양한 종류의 자원에 사용할 수 있는데, 모두가 이런 자원을 공 유하는 것이 아니라 소속된 프로세스에 독립된 자원인 것처럼 만들어 주는 기능입니다.

리눅스에서 자원을 논리적으로 격리하는 방법입니다. 컨테이너는 다음과 같은 네임스페이스를 조합해서 사용합니다:

3) 컨테이너는 네임스페이스의 조합

  • 프로세스 ID 공간 분리 (PID 네임스페이스) : 독립된 PID 이름 공간 제공

  • 사용자 계정 분리 (User NS) : 독립된 UID, GID를 제공

  • 파일 시스템 마운트 분리 (Mount NS) : 독립된 파일 시스템 마운트 제공

  • 네트워크 스택 분리 (Network NS)

  • IPC, UTS(호스트명 등)도 분리 가능

🧠 리눅스의 네임스페이스 – 프로세스 ID(PID) 네임스페이스 완전 이해

1) 개념: 프로세스 ID 네임스페이스란?

✅ 핵심 정의

  • PID 네임스페이스는 각 프로세스 집단이 서로 독립적인 PID 테이블을 가질 수 있게 해주는 기능입니다.

  • 컨테이너 기술의 핵심 구성 요소이며, 격리된 실행 환경을 구현하는 데 사용됩니다.

✅ 계층 구조

  • PID 네임스페이스는 부모-자식 관계를 가질 수 있으며, 자식 네임스페이스는 부모의 프로세스를 볼 수 없지만, 부모는 자식의 프로세스를 볼 수 있습니다.

2) 네임스페이스 구조 이해

아래는 루트 PID 네임스페이스와 그 자식인 foo 네임스페이스의 구조를 도식화한 그림입니다.

특징

  • 루트는 foo의 프로세스를 볼 수 있음

  • foo는 루트의 프로세스를 볼 수 없음

3) 실습: 네임스페이스 확인하기

현재 bash의 네임스페이스 ID 확인

$ ls -l /proc/$$/ns/pid
lrwxrwxrwx 1 ... -> 'pid: [4026531836]'
  • 이 ID는 루트 PID 네임스페이스를 의미합니다.

  • 별도로 지정하지 않는 한 init를 비롯한 모든 프로세스는 루트 프로세스ID의 네임스페이스에 소속

4) 새로운 네임스페이스 생성 - unshare 명령어로 PID 네임스페이스 실습

“하나의 리눅스 시스템에서, 새로운 프로세스 ID 네임스페이스를 만들어 그 안에서 bash를 실행하면 어떤 일이 벌어지는가?”

이를 통해 PID 네임스페이스의 격리 효과를 직접 확인할 수 있습니다.

1. 실습 명령어와 의미

$ sudo unshare --fork --pid --mount-proc bash

각 옵션 설명:

옵션
설명

--pid

새로운 PID 네임스페이스를 생성

--fork

bash를 새 네임스페이스에서 실행

--mount-proc

/proc 파일시스템을 새로 마운트 (PID 정보 확인 가능)

이 명령어로 실행된 bash는 새로운 PID 네임스페이스에 속하게 되며, 그 안에서 PID가 1번이 됩니다.

2. 내부에서 확인하기

bash 셸이 열리면 다음을 입력해 봅니다:

# echo $$
1
# ls -l /proc/1/ns/pid
lrwxrwxrwx ... -> 'pid: [4026532814]'
  • $$는 현재 프로세스 ID (1)를 보여줍니다.

  • /proc/1/ns/pid현재 bash가 속한 PID 네임스페이스의 고유 ID를 보여줍니다.

ps 명령어로 내부 상태 확인

# ps ax
  PID TTY      STAT   TIME COMMAND
    1 pts/1    S      0:00 bash
    9 pts/1    R+     0:00 ps ax
  • PID가 1인 bash와, 9인 ps만 존재

  • 루트 네임스페이스와 완전히 격리된 새로운 PID 공간

3. 외부(호스트 OS)에서 확인

pstree 명령어로 구조 확인

$ pstree -p | grep unshare
sshd(14126)---bash(14193)---sudo(14382)---unshare(14384)---bash(14385)
  • 이 구조에서 PID 14385의 bash가 unshare 명령어로 실행된 셸입니다.

해당 bash의 네임스페이스 확인

$ sudo ls -l /proc/14385/ns/pid
lrwxrwxrwx ... -> 'pid: [4026532814]'

PID 14385는 루트 네임스페이스에선 14385로 보이지만, 내부에서는 PID 1

4. 전체 구조 그림

항목
설명

unshare --pid

새로운 PID 네임스페이스 생성

$$ 출력값

새로운 공간에서는 항상 1

/proc/1/ns/pid

새로운 네임스페이스 ID로 확인 가능

ps ax 실행 결과

격리된 내부 프로세스만 나옴

호스트에서 보이는 PID

실제로는 일반 PID(예: 14385)로 보임

네임스페이스 격리 효과

PID 1인 프로세스를 루트에서 직접 실행하지 않아도 생성 가능


5) 서로 다른 두 PID 네임스페이스

이제 bar라는 또 다른 PID 네임스페이스를 만들면, foo와 bar는 서로의 프로세스를 볼 수 없습니다.

$ sudo unshare --fork --pid --mount-proc bash
# echo $$
1
# ls -l /proc/1/ns/pid
-> 'pid: [4026532816]'  ← bar 네임스페이스 ID

🖼️ 그림: 여러 개의 PID 네임스페이스


6) 네임스페이스 격리 정리

네임스페이스
볼 수 있는 프로세스
ID 예시

루트

foo, bar 포함 전체

4026531836

foo

자기 자신만

4026532814

bar

자기 자신만

4026532816

→ foo와 bar는 서로를 볼 수 없다!

7) 마무리: 컨테이너와 PID 네임스페이스

컨테이너는 독립적인 네임스페이스로 구성된 ‘프로세스 격리된 세계’입니다.

  • 프로세스는 자신이 속한 네임스페이스 안에서만 보이고, 관리되며, 조작됩니다.

  • 따라서 컨테이너 내부에서는 시스템 전체의 상태를 파악할 수 없습니다.

  • 모니터링 시 호스트에서의 접근이 필요합니다.

🐳 컨테이너란 무엇인가 – 네임스페이스로 본 정체와 한계

🧱 1) 컨테이너의 본질: 네임스페이스 격리

컨테이너 = 독립된 네임스페이스에서 실행되는 프로세스 또는 프로세스 집합

리눅스 커널의 핵심 기능인 네임스페이스(Namespace)를 이용해 다른 프로세스와 프로세스 ID, 사용자, 파일 시스템, 네트워크 등 실행 환경을 격리합니다.

▶ 그림 11-08의 의미 (컨테이너와 네임스페이스)

컨테이너 A
컨테이너 B

PID ns: 1

PID ns: 2

User ns: 1

User ns: 2

Mount ns: 1

Mount ns: 2

  • 각 컨테이너는 독자적인 PID/User/Mount 네임스페이스를 가짐

  • 서로의 프로세스를 절대 볼 수 없음

  • 그래서 top 명령어로 봐도 "자기 안의 프로세스만" 보여요!

  • 컨테이너가 되려면 어떻게 네임스페이스를 분리해야 하는지 명확하게 정해져 있지 않습니다. 그건 각각의 컨테이너 런타임 제작자 또는 사용자가 구현하고 싶어하는 내용에 따라 달라지기 때문입니다.

🧠 2) 네임스페이스란 무엇인가?

리눅스에서 자원을 논리적으로 격리하는 방법입니다. 컨테이너는 다음과 같은 네임스페이스를 조합해서 사용합니다:

네임스페이스 종류
격리 대상

PID

프로세스 ID

UTS

호스트명 및 도메인명

MOUNT

파일 시스템 구조

NET

네트워크 인터페이스와 포트

IPC

프로세스 간 통신 자원

USER

사용자 ID, 그룹 ID

CGROUP

자원(CPU, 메모리) 사용량 제한

모든 네임스페이스를 다 쓴다고 “컨테이너”가 되는 것은 아닙니다. “어떤 네임스페이스를 조합하고 실행하느냐”는 컨테이너 런타임의 구현 방식에 따라 달라집니다.

🔍 3) 컨테이너 내부에서 보이는 세상은 제한적이다

예를 들어, 컨테이너 안에서 top 명령어를 실행했을 때 CPU 사용률이 100%라고 해봅시다. 하지만 보이는 프로세스는 자기 컨테이너 안의 것뿐이기 때문에 진짜 원인이 호스트 OS다른 컨테이너에 있다면 확인할 수 없습니다.

컨테이너에서는 동일한 컨테이너에 있는 프로세스밖에 보이지 않으므로, CPU 부하 원인이 호스트 OS 또는 다른 컨테이너의 프로세스라면 손쓸 방법 이 없습니다.

🔐 그래서 이런 말이 가능해요

“컨테이너 안에서는 CPU 부하 원인이 자신 밖에 있을 경우 손쓸 방법이 없다.”

✅ 해결 방법은 없을까?

있긴 합니다. 대표적으로:

1. 호스트 OS에서 전체 프로세스를 모니터링하기 (`htop`, `top`, `ps -aux` 등)

2. cgroup을 통해 자원 제한 걸기 (CPU 제한, 메모리 제한 등)

3. 컨테이너 런타임에서 metrics/log 수집 설정하기 (예: Prometheus, cadvisor)

🔐 4) 보안 위험성 – 컨테이너는 커널을 공유한다

컨테이너는 가볍지만, 가상 머신보다 보안에는 불리합니다.

▶ 그림 11-09 의미 요약

항목
가상 머신
컨테이너

커널 공유 여부

각자 다른 커널 (분리됨)

하나의 커널 공유

보안 경계

가상 하드웨어 단위

커널까지 뚫리면 전체 영향 가능

대표 취약점

VM Escape (어렵고 드묾)

커널 익스플로잇 (위험성 존재)

🛠️ 5) 컨테이너 런타임 종류

▶ 대표적인 컨테이너 런타임 비교

런타임
특징

runC

Docker에서 기본으로 사용하는 런타임. 네임스페이스 기반

Kata Containers

컨테이너를 경량 가상 머신에서 실행. 보안성과 격리 우수

gVisor

시스템 콜을 사용자 공간에서 에뮬레이션. Google이 개발

▶ 그림 11-10 요약

런타임 종류
사용자 공간
커널 공간
시스템 콜 처리 방식

runC

네임스페이스 + 애플리케이션

호스트 리눅스 커널 공유

직접 호출

Kata Containers

VM 내 사용자 공간 + 애플리케이션

VM 내부 게스트 커널 → 호스트 커널과 분리

가상머신을 통한 격리

gVisor

애플리케이션 + 자체 커널

자체 커널이 유저 공간에서 처리

사용자 공간에서 처리

런타임 종류
격리 수준
시스템 콜 처리 방식
보안성
속도
커널 공유 여부

runC

낮음

직접 호출 → 호스트 커널

낮음

빠름

O

Kata Containers

높음

VM 내 게스트 커널에서 처리

높음

느림

X

gVisor

중간

사용자 공간 자체 커널에서 처리

매우 높음

중간

X (논리적 분리)

1. runC

도커의 기본 런타임입니다.

✅ 특징

  • 컨테이너는 호스트 커널을 공유합니다.

  • 네임스페이스 + cgroup을 이용해 격리.

  • 시스템 콜은 컨테이너 → 바로 호스트 커널로 전달됨.

⚠️ 단점

  • 커널 취약점이 있으면 호스트에 영향을 줄 수 있음.

  • 보안적으로 민감한 환경엔 부적합.

2. Kata Containers

✅ 특징

  • 컨테이너 하나마다 경량 VM(Virtual Machine)을 띄움.

  • VM 안에서 별도의 게스트 커널을 사용.

  • 시스템 콜은 → VM 내부 커널에서 처리되므로 호스트와 분리됨.

🧱 장점

  • 보안 강화: 호스트 커널과 완전히 격리됨.

  • 가상 머신 수준의 고립성 제공.

❗ 단점

  • VM 부팅 오버헤드 때문에 기동 속도 느림.

  • 리소스 사용량 증가.

3. gVisor

✅ 특징

  • 구글에서 만든 보안 특화 컨테이너 런타임.

  • 시스템 콜을 자체 구현된 커널에서 처리.

  • 이 커널은 **사용자 공간(User Space)**에 존재.

🌈 장점

  • 보안성 매우 뛰어남: 실제 리눅스 커널에 접근하지 않음.

  • VM보다 빠름.

❗ 단점

  • 시스템 콜 구현이 제한적 → 호환성 문제가 있을 수 있음.

  • 성능은 약간 떨어질 수 있음.

Last updated