읽기 설정
도커를 적어도 한 번쯤 사용해 보셨을 거라고 짐작합니다. 하지만 그 내부 동작 원리에 대해 궁금해하신 적 있으신가요?00:00
도커 파일을 작성하시고, 이미지를 빌드하시고, 컨테이너를 실행하시죠.00:05
이제 여러분의 코드는 시스템의 나머지 부분과 완전히 분리되어 실행되고 있습니다.00:09
자신만의 파일 시스템과 네트워크 인터페이스를 가지고 있습니다.00:13
그리고 자체 프로세스 트리도 가지고 있습니다. 코드는 여러분의 노트북에서 프로덕션 서버에서 실행될 때와 똑같은 방식으로 동작합니다. 보통 그것만으로도 충분합니다.00:16
컨테이너가 실행되면 크게 상관하지 않습니다.00:21
무엇이 내부에서 일어나는지 살펴볼 건데요. 하지만 이번 영상에서는 실제로 그 Docker run 명령어 뒤에 어떤 일이 발생하는지 자세히 살펴보겠습니다.00:26
컨테이너를 가상 머신과 자주 비교합니다.00:31
가상 머신은 하이퍼바이저를 사용하여 완전히 시뮬레이션된 컴퓨터를 만듭니다.00:35
커널을 부팅하고, 하드웨어 드라이버를 로드하며, 처음부터 끝까지 완전한 운영체제를 실행합니다.00:39
만약 단일 물리 서버에 세 개의 가상 머신을 실행한다면, 메모리에 세 개의 별도 커널이 로드됩니다.00:45
그것은 강력한 격리를 보장하지만, 막대한 양의 메모리를 소비하고 오랜 시간이 걸립니다.00:50
부팅을 합니다. 하지만 컨테이너는 다릅니다. 컨테이너는 운영체제를 부팅하지 않으며 커널을 필요로 하지 않습니다. 단순히 일반적인 리눅스 프로세스입니다. 호스트의 커널을 공유하며00:55
이것은 일반적인 Linux 프로세스일 뿐입니다. 호스트에서 직접 실행되며 커널을 공유합니다.01:01
기본적으로, 같은 시스템에 있는 다른 컨테이너에도 영향을 미칩니다. 운영체제 부팅 과정 전체를 건너뛰기 때문에, 컨테이너는01:06
거의 즉시 시작되며, 실제로 애플리케이션에서 사용하는 메모리 양만큼만 차지합니다.01:10
컨테이너가 일반적인 프로세스라면 문제가 발생할 수 있습니다.01:14
일반적으로 리눅스 머신에서 프로세스들은01:18
서로 볼 수 있습니다. 동일한 파일 시스템, 네트워크 인터페이스, 그리고 호스트 이름을 공유합니다.01:21
단일 프로세스를 독립적인 환경처럼 만들려면 커널에서 이를 처리해야 합니다.01:26
커널은 호스트의 나머지 부분을 완전히 차단합니다. 이를 위해 리눅스에서 네임스페이스라는 기능을 사용합니다.01:30
네임스페이스는 프로세스가 무엇을 볼 수 있는지 제어합니다. 먼저 프로세스 ID를 관리하는 PID 네임스페이스부터 살펴보겠습니다.01:36
일반적으로 운영체제는 실행 중인 모든 프로그램의 거대한 목록 하나를 유지합니다.01:41
웹 서버를 시작하면 커널이 프로세스 4500과 같이 고유한 번호를 할당합니다.01:46
이것이 전역 목록에 존재하기 때문에, 웹 서버는 원칙적으로 기계에서 실행 중인 다른 모든 애플리케이션을 볼 수 있습니다.01:52
PID 네임스페이스를 프로세스에 연결하면, 커널이 그 주위에 장벽을 세웁니다.01:58
이제 웹 서버가 운영체제에 다른 어떤 프로그램이 실행 중인지 물어보면, 커널이 응답을 필터링합니다.02:03
프로세스는 시스템이 비어 있는 것처럼 보고, 부팅된 최초의 프로그램 중 하나라고 가정합니다.02:09
자신을 레이블로 표시합니다.02:14
다른 네임스페이스들이 함께 작동하고 있습니다.02:27
마운트 네임스페이스는 프로세스가 파일 시스템에 대한 자체 격리된 뷰를 갖도록 해 줍니다.02:30
네트워크 네임스페이스는 프로세스에게 격리된 네트워크 스택을 제공하고, UTS 네임스페이스를 통해 프로세스는 자체 호스트 이름을 가질 수 있습니다.02:34
프로세스 간 통신과 사용자 ID를 위한 네임스페이스도 있습니다.02:41
컨테이너 내 루트 사용자를 호스트의 권한 없는 사용자로 매핑할 수 있습니다.02:45
이러한 네임스페이스들을 결합하면, 프로세스는 호스트 시스템 전체로부터 완전히 격리됩니다.02:50
정말로 자신이 이 컴퓨터 전체를 가지고 있다고 믿는 것 같습니다.02:54
네임스페이스는 가시성을 제어하지만, 자원에 대해서는 아무런 영향을 주지 않습니다.02:59
네임스페이스로 격리된 프로세스는 여전히 호스트의 모든 CPU를 사용하고 시스템을 멈추게 할 수 있습니다.03:02
리눅스 커널은 cgroup이라고 불리는 제어 그룹을 통해 이 문제를 해결합니다.03:07
네임스페이스는 프로세스가 볼 수 있는 것을 제한하지만, cgroup은 프로세스가 사용할 수 있는 것을 제한합니다.03:11
도커 명령어에 메모리나 CPU 플래그를 전달할 때, C 그룹을 설정하게 됩니다.03:17
이전 과정에서, 커널은 가상 파일 시스템을 통해 이를 관리합니다.03:23
특정 디렉터리 구조를 생성합니다.03:27
단순히 파일에 숫자를 쓰면, 프로세스에 대한 정확한 자원 제한을 설정할 수 있습니다.03:29
그 순간부터 커널은 컨테이너가 사용하는 RAM과 CPU 시간의 양을 정확히 추적합니다.03:34
컨테이너가 허용된 메모리 제한을 초과하려고 할 때, 커널은 공간을 확보하려고 시도합니다.03:40
방이 먼저 필요합니다. 컨테이너가 이미 사용하고 있는 메모리를 회수하여 확보하는 방식으로요. 만약 그것으로 충분하지 않다면,03:44
프로세스를 종료합니다. 이렇게 하면 한 개의 폭주 컨테이너가 서버의 나머지 부분을 먹통으로 만들지 않도록 보장할 수 있습니다.03:50
앞서 말씀드린 것처럼, 마운트 네임스페이스는 컨테이너에게 파일 시스템을 자신만의 시각으로 보게 해줍니다.03:54
하지만 실제로 시작하려면, 프로세스가 루트 디렉토리가 필요합니다.03:59
호스트를 사용할 수 없도록 해야 하므로, 완전히 분리된 호스트를 제공해야 합니다.04:02
도커가 존재하기 훨씬 이전부터 유닉스 시스템은...04:06
이것은 'chi root'라는 명령어를 통해 처리했습니다. 프로세스를 특정 폴더를 가리키게 하고, 커널은 그 폴더를 파일 트리의 최상위 디렉토리로 취급합니다. 그래서 프로세스는 다른 것을 볼 수 없게 됩니다.04:10
그 폴더를 절대 최상위 파일 트리로 취급하기 때문에, 프로세스가 다른 것을 볼 수 없게 됩니다.04:15
그 폴더 바깥은 안전하지만, chi root에는 주요한 문제가 하나 있었습니다. 그것은 보안 경계로 사용되도록 설계되지 않았습니다.04:19
루트 권한으로 실행되는 프로세스는 이 경계를 벗어나 호스트 파일에 접근할 수 있습니다.04:24
컨테이너 엔진은 더 강력한 기능이 필요하기 때문에, pivot-root라는 시스템 호출을 사용합니다.04:30
단순히 보기를 제한하는 것 대신, pivot-root는 루트 디렉터리를 완전히 대체합니다.04:34
컨테이너는 새로운 격리된 파일 시스템을 받게 되고, 호스트 머신의 원래 파일 트리 구조는 안전하게 분리됩니다.04:39
하지만 pivot-root는 단순히 교체 메커니즘일 뿐입니다. 경계를 보호하지만, 새로운 루트 디렉토리는 비어 있을 수 없습니다.04:45
시스템 라이브러리와 어플리케이션으로 미리 채워져 있어야 하고, 완전히 채워진 환경을 제공하는 것이 바로 도커 이미지의 역할입니다.04:52
하지만 이미지는 하나의 큰 파일이 아닙니다.04:59
이는 독립적인 레이어들을 유니온 파일 시스템을 통해 결합한 구조입니다. 흔히 오버레이 FS를 사용합니다.05:02
도커 파일을 작성하실 때, 파일 시스템을 변경하는 각 명령어는 레이어를 생성합니다.05:09
기본 운영체제는 한 레이어입니다. 의존성(dependency)을 설치하면 또 다른 레이어가 추가됩니다.05:13
소스 코드를 복사하면 레이어가 하나 더 늘어납니다.05:18
env나 cmd처럼 메타데이터만 설정하는 명령어는 파일 시스템에 아무런 변화를 주지 않습니다.05:20
그냥 이미지에 설정을 연결하는 것 뿐입니다.05:27
오버레이 FS는 이러한 디렉터리들을 모두 합쳐서 컨테이너에 단일 통합 파일 시스템으로 제공합니다.05:29
이 이미지 레이어들은 엄격하게 읽기 전용입니다. 컨테이너를 시작하면 Docker는 그 위에 얇고 쓰기 가능한 레이어를 추가합니다.05:36
컨테이너가 기존 파일을 수정하면, 원래 파일은 절대로 건드리지 않습니다.05:43
Overlay FS는 파일을 쓰기 가능 레이어로 복사하고, 복사본을 변경합니다.05:47
컨테이너는 파일을 수정했다고 생각하지만, 실제로는 단순히 새로운 버전을 위에 쌓고 이전 버전을 숨기는 것 뿐입니다.05:52
이러한 설계 덕분에 상당한 양의 디스크 공간을 절약할 수 있습니다.05:57
같은 이미지에서 10개의 컨테이너를 실행한다면, 이들은 메모리와 디스크에서 완전히 동일한 읽기 전용 레이어를 공유합니다.06:00
이것 또한 Dockerfile 내에서의 명령어 순서가 중요하다는 것을 설명해 줍니다.06:07
만약 파일 상단 근처의 줄을 수정하면, 도커는 해당 레이어와 그 아래의 모든 후속 레이어를 다시 빌드해야 합니다.06:10
아직 컨테이너가 외부 세계와 어떻게 통신하는지 설명해야 합니다. 네트워크 네임스페이스는 컨테이너를 완전히 네트워크에서 분리시켰습니다.06:16
자신만의 네트워크 스택은 가지고 있지만, 물리적인 네트워크 인터페이스는 없습니다. 도커는 이것을 해결하기 위해 가상 이더넷 페어를 생성합니다. 마치 ~처럼 생각할 수 있습니다.06:22
가상 이더넷 페어를 생성하여 이 문제를 해결합니다. 이를 ~라고 생각하시면 됩니다.06:25
가상 네트워크 케이블의 한쪽 끝은 컨테이너 네트워크 네임스페이스에 연결되고, 다른 쪽 끝은 호스트 머신에 있는 가상 스위치, 즉 브리지 네트워크에 연결됩니다.06:30
기본적으로는...06:36
이것은 'docker zero'라고 이름이 붙습니다. 컨테이너가 요청을 하면 트래픽은 통과합니다.06:40
호스트 머신은 그런 다음 해당 트래픽을 물리적 네트워크 어댑터로 라우팅합니다.06:46
만약 두 컨테이너가 동일한 브리지에 연결되어 있다면, 내부 IP 주소를 사용하여 서로 직접 통신할 수 있습니다.06:50
컨테이너를 공개 인터넷에 노출시키려면 포트 매핑을 해야 합니다.06:57
호스트에서 8080번 포트를 컨테이너의 80번 포트에 바인딩하면, Docker는 호스트 운영체제에게 8080번 포트로 들어오는 트래픽을 가로채도록 설정합니다.07:01
8080번 포트로 들어오는 트래픽을 가로채도록 설정합니다.07:08
호스트는 네트워크 주소 변환(NAT)을 사용하여 패킷들을 브릿지를 통해 전달하고, 그 가상 케이블을 거쳐 컨테이너로 전달합니다.07:10
핵심적인 리눅스 기능에 대해서는 다루었습니다. 이제 Docker run 명령에 의해 실행되는 실제 아키텍처를 살펴보겠습니다.07:18
명령어를 입력하시면, Docker 명령줄 인터페이스가 컨테이너를 직접 시작하지 않습니다.07:24
대신 백그라운드에서 실행되는 Docker 디먼이라는 서비스에 API 호출을 합니다.07:28
데몬은 이미지, 네트워크, 그리고 스토리지 볼륨을 관리합니다.07:32
그런 다음 컨테이너 사양을 컨테이너디라고 불리는 구성 요소에 전달합니다.07:36
컨테이너드(Containerd)는 컨테이너의 생명주기를 관리합니다. 이미지 다운로드와 레이어 압축 해제도 담당합니다.07:41
컨테이너드(Containerd)조차도 프로세스를 직접 생성하지 않습니다. 최종 설정을 저수준 도구인 런(Runc)에 전달합니다.07:47
런씨의 유일한 역할은 리눅스 커널과 통신하는 것입니다. 네임스페이스를 설정하고, c 그룹을 구성하며,07:53
루트 파일 시스템을 풀어헤치고, 프로세스를 시작합니다. 그리고 프로세스가 실행되는 즉시, 런씨는 즉시 종료됩니다.08:00
컨테이너를 생성하고 그냥 종료됩니다. 하지만 리눅스에서는 모든 프로세스가 부모 프로세스를 필요로 합니다.08:07
RunC가 종료되었으므로, 쉼(shim)이라고 불리는 작은 프로세스가 대신하여 컨테이너를 채택합니다.08:12
컨테이너가 살아있는 동안 계속 존재합니다.08:17
컨테이너 D가 재시작되더라도 프로세스를 계속 실행하고, 컨테이너가 최종적으로 멈출 때 종료 코드를 가져옵니다.08:19
정리하자면, 컨테이너는 일반적인 리눅스 프로세스일 뿐입니다.08:25
네임스페이스는 컨테이너가 볼 수 있는 것을 격리하고, c 그룹은 컨테이너가 소비할 수 있는 자원을 제한합니다.08:30
오버레이 FS는 레이어를 활용하여 파일 시스템을 구축하고, 가상 브리지는 호스트 네트워크와 연결해 줍니다.08:34
제 애니메이션 제작 방법에 대해 자주 질문을 받습니다. 직접 해보고 싶으시다면, 설명란에 있는 링크를 확인해 주세요.08:40
이 영상이 재미있으셨다면, 좋아요와 구독 부탁드리고, 다음에는 어떤 주제를 보고 싶으신지 댓글로 알려주세요.08:47
가장 많은 좋아요를 받은 주제를 선택해서 영상으로 만들 예정입니다. 시청해주셔서 감사합니다, 다음 영상에서 만나요.08:53
AI Summary
이 글은 도커 컨테이너의 내부 동작 원리에 대해 설명합니다. 가상 머신과는 달리 호스트 커널을 공유하여 가볍고 빠르게 실행되는 도커는 네임스페이스, Cgroup, OverlayFS, Chi Root & Pivot Root 등 다양한 리눅스 기능을 활용합니다. 또한, docker run 명령어를 통해 Docker 데몬, Containerd, Runc와 같은 구성 요소들이 협력하여 컨테이너를 생성하고 관리하는 아키텍처도 살펴봅니다.
Key Highlights
- •도커 컨테이너는 가상 머신과 달리 호스트 커널을 공유하여 더 가볍고 빠르게 실행됩니다.
- •네임스페이스는 프로세스 환경을 격리하여 컨테이너 내부에서 고유한 환경을 제공합니다.
- •Cgroup은 컨테이너의 자원 사용량을 제한하여 안정적인 운영을 지원합니다.
- •OverlayFS는 이미지 레이어를 활용하여 효율적인 파일 시스템을 구축합니다.
- •Docker 아키텍처는 `docker run` 명령어, Docker 데몬, Containerd, Runc, Shim 프로세스가 상호 협력하여 컨테이너를 관리합니다.
![Docker Crash Course for Absolute Beginners [NEW]](https://i.ytimg.com/vi/pg19Z8LL06w/mqdefault.jpg)
![Docker Tutorial for Beginners [FULL COURSE in 3 Hours]](https://i.ytimg.com/vi/3c-iBn73dDE/mqdefault.jpg)
