네임스페이스와 cgroup은 무엇이며 어떻게 작동합니까?

최근에는 오픈소스 다중 언어 앱 서버인 NGINX Unit에 대해 조사하고 있습니다. 조사의 일환으로 Unit이 프로세스 격리를 가능하게 하는 네임스페이스와 cgroup을 모두 지원한다는 사실을 알게 되었습니다. 이 가이드에서는 컨테이너의 기반이 되는 두 가지 주요 Linux 기술을 살펴보겠습니다.

Docker 및 Kubernetes와 같은 컨테이너 및 관련 도구는 얼마 동안 사용되었습니다. 그들은 현대적인 애플리케이션 환경에서 소프트웨어가 개발되고 전달되는 방식을 바꾸는 데 도움을 주었습니다. 컨테이너를 사용하면 개별 VM(가상 머신)을 구축할 필요 없이 분리된 자체 환경에서 각 소프트웨어를 빠르게 배포하고 실행할 수 있습니다.

대부분의 사람들은 컨테이너가 은밀하게 작동하는 방식에 대해 거의 생각하지 않을 것입니다. 그러나 저는 기본 기술을 이해하는 것이 중요하다고 생각합니다. 이는 의사 결정 프로세스에 정보를 제공하는 데 도움이 됩니다. 그리고 개인적으로 어떻게 작동하는지 완전히 이해하는 것만으로도 행복합니다!

목차

1. 네임스페이스(Namespaces)란?
  1-1. 네임스페이스 유형
  1-2. 상위 및 하위 PID 네임스페이스의 예
  1-3. 네임스페이스 생성
  1-4. 외부에서 네임스페이스 보기
  1-5. 네임스페이스 및 컨테이너
2. cgroup이란 무엇입니까?
  2-1. cgroup 버전
  2-2. cgroup 만들기
3. 결론

1. 네임스페이스(Namespaces)란?

네임스페이스는 약 2002년부터 Linux 커널의 일부였으며 시간이 지남에 따라 더 많은 도구와 네임스페이스 유형이 추가되었습니다. 그러나 실제 컨테이너 지원은 2013년에만 Linux 커널에 추가되었습니다. 이것이 네임스페이스를 정말 유용하게 만들고 대중에게 제공한 것입니다.

하지만 네임스페이스가 정확히 무엇인가요? 다음은 Wikipedia의 장황한 정의입니다.

“네임스페이스는 한 프로세스 집합이 한 리소스 집합을 보고 다른 프로세스 집합이 다른 리소스 집합을 볼 수 있도록 커널 리소스를 분할하는 Linux 커널의 기능입니다.”

즉, 네임스페이스의 주요 기능은 프로세스를 서로 격리한다는 것입니다. 다양한 서비스를 실행하는 서버에서 각 서비스 및 관련 프로세스를 다른 서비스와 격리하면 변경 사항에 대한 폭발 반경이 줄어들고 보안 관련 문제에 대한 설치 공간도 줄어듭니다. 그러나 대부분 격리 서비스는 Martin Fowler가 설명한 대로 마이크로서비스의 아키텍처 스타일을 충족합니다.

개발 프로세스 중에 컨테이너를 사용하면 개발자에게 완전한 VM처럼 보이고 느껴지는 격리된 환경을 제공합니다. VM이 아니라 어딘가에 있는 서버에서 실행 중인 프로세스입니다. 개발자가 두 개의 컨테이너를 시작하면 어딘가의 단일 서버에서 두 개의 프로세스가 실행되고 있지만 서로 격리되어 있습니다.

1-1. 네임스페이스 유형

Linux 커널에는 다양한 유형의 네임스페이스가 있습니다. 각 네임스페이스에는 고유한 속성이 있습니다.

  • 사용자 이름 공간에는 프로세스에 할당하기 위한 고유한 사용자 ID 및 그룹 ID 세트가 있습니다. 특히, 이는 프로세스가 다른 사용자 이름 공간에 있지 않고 사용자 이름 공간 내에서 루트 권한을 가질 수 있음을 의미합니다.
  • 프로세스 ID(PID) 네임스페이스는 다른 네임스페이스의 PID 집합과 독립적인 프로세스에 PID 집합을 할당합니다. 새 네임스페이스에서 생성된 첫 번째 프로세스에는 PID 1이 있고 자식 프로세스에는 후속 PID가 할당됩니다. 자식 프로세스가 자체 PID 네임스페이스로 생성되면 해당 네임스페이스에는 PID 1이 있고 부모 프로세스의 네임스페이스에는 PID가 있습니다. 예는 아래를 참조하십시오.
  • 네트워크 네임스페이스에는 자체 개인 라우팅 테이블, IP 주소 세트, 소켓 목록, 연결 추적 테이블, 방화벽 및 기타 네트워크 관련 리소스와 같은 독립적인 네트워크 스택이 있습니다.
  • 마운트 네임스페이스에는 네임스페이스의 프로세스에서 볼 수 있는 독립적인 마운트 지점 목록이 있습니다. 이는 호스트 파일 시스템에 영향을 주지 않고 마운트 네임스페이스에서 파일 시스템을 마운트 및 마운트 해제할 수 있음을 의미합니다.
  • IPC(프로세스 간 통신) 네임스페이스에는 자체 IPC 리소스(예: POSIX 메시지 대기열)가 있습니다.
  • UNIX 시분할(UTS) 네임스페이스를 사용하면 단일 시스템이 서로 다른 프로세스에 대해 서로 다른 호스트 및 도메인 이름을 갖는 것처럼 보일 수 있습니다.

1-2. 상위 및 하위 PID 네임스페이스의 예

아래 다이어그램에는 세 개의 PID 네임스페이스가 있습니다. 부모 네임스페이스와 두 개의 자식 네임스페이스입니다. 상위 이름 공간에는 PID1에서 PID4까지 이름이 지정된 4개의 프로세스가 있습니다. 이들은 모두 서로를 보고 리소스를 공유할 수 있는 정상적인 프로세스입니다.

부모 네임스페이스에 PID2 및 PID3이 있는 자식 프로세스는 PID가 1인 자체 PID 네임스페이스에도 속합니다. 자식 네임스페이스 내에서 PID1 프로세스는 외부를 볼 수 없습니다. 예를 들어 두 자식 네임스페이스의 PID1은 부모 네임스페이스의 PID4를 볼 수 없습니다.

이것은 (이 경우) 서로 다른 네임스페이스 내의 프로세스 사이에 격리를 제공합니다.

1-3. 네임스페이스 생성

이 모든 이론을 바탕으로 실제로 새로운 이름 공간을 만들어 이해를 강화합시다. Linux unshare 명령은 시작하기에 좋은 곳입니다. 매뉴얼 페이지는 그것이 우리가 원하는 것을 정확히 수행함을 나타냅니다:

NAME
          unshare - run program in new name namespaces

저는 현재 고유한 사용자 ID, 그룹 등이 있지만 루트 권한이 없는 일반 사용자인 svk로 로그인했습니다.

svk $ id
uid=1000(svk) gid=1000(svk) groups=1000(svk) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c.1023

이제 다음 unshare 명령을 실행하여 자체 사용자 및 PID 네임스페이스가 있는 새 네임스페이스를 생성합니다. 루트 사용자를 새 네임스페이스에 매핑하고(즉, 새 네임스페이스 내에서 루트 권한이 있음), 새 proc 파일 시스템을 마운트하고, 새로 생성된 네임스페이스에서 내 프로세스(이 경우 bash)를 분기합니다.

svk $ unshare --user --pid --map-root-user --mount-proc --fork bash

(컨테이너에 익숙한 사용자의 경우 실행 중인 컨테이너에서 exec -it /bin/bash 명령을 실행하는 것과 동일한 작업을 수행합니다.)

ps -ef 명령은 bash와 ps 명령 자체의 두 프로세스가 실행 중임을 보여주고 id 명령은 내가 새 네임스페이스의 루트임을 확인합니다(변경된 명령 프롬프트에서도 표시됨).

root # ps -ef
UID         PID     PPID  C STIME TTY        TIME CMD
root          1        0  0 14:46 pts/0  00:00:00 bash
root         15        1  0 14:46 pts/0  00:00:00 ps -ef
root # id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c.1023

주목해야 할 중요한 점은 내 네임스페이스에서 두 개의 프로세스만 볼 수 있고 시스템에서 실행 중인 다른 프로세스는 볼 수 없다는 것입니다. 나는 내 이름 공간 내에서 완전히 고립되어 있습니다.

1-4. 외부에서 네임스페이스 보기

네임스페이스 내에서 다른 프로세스를 볼 수는 없지만 lsns(list namespaces) 명령을 사용하면 사용 가능한 모든 네임스페이스를 나열하고 부모 네임스페이스(새 네임스페이스 외부)의 관점에서 이에 대한 정보를 표시할 수 있습니다.

출력은 위에서 실행한 unshare 명령의 인수에 해당하는 user, mnt 및 pid 유형의 세 가지 네임스페이스를 보여줍니다. 이러한 외부 관점에서 각 네임스페이스는 루트가 아닌 사용자 svk로 실행되는 반면 네임스페이스 내부의 프로세스는 예상되는 모든 리소스에 액세스할 수 있는 루트로 실행됩니다. (출력은 읽기 쉽도록 두 줄로 나뉩니다.)

root # lsns --output-all | head -1; lsns --output-all | grep svk
        NS TYPE   PATH                   NPROCS    PID   PPID ...
4026532690 user   /proc/97964/ns/user         2  97964  97944 ...                
4026532691 mnt    /proc/97964/ns/mnt          2  97964  97944 ...        
4026532692 pid    /proc/97965/ns/pid          1  97965  97964 ...

  ... COMMAND                                                       UID USER               
  ... unshare --user --map-root-user --fork –pid --mount-proc bash  1000 svk               
  ... unshare --user --map-root-user --fork –pid --mount-proc bash  1000 svk               
  ... bash         

1-5. 네임스페이스 및 컨테이너

네임스페이스는 컨테이너가 구축되는 기술 중 하나로 리소스 분리를 시행하는 데 사용됩니다. 수동으로 네임스페이스를 생성하는 방법을 보여주었지만 Docker, rkt 및 podman과 같은 컨테이너 런타임은 사용자를 대신하여 네임스페이스를 생성하여 작업을 더 쉽게 만듭니다. 마찬가지로 NGINX Unit의 격리 응용 프로그램 개체는 네임스페이스와 cgroup을 생성합니다.

2. cgroup이란 무엇입니까?

제어 그룹(cgroup)은 프로세스 모음의 리소스 사용량(CPU, 메모리, 디스크 I/O, 네트워크 등)을 제한, 설명 및 격리하는 Linux 커널 기능입니다.

Cgroup은 다음과 같은 기능을 제공합니다.

Resource limits – 프로세스가 사용할 수 있는 특정 리소스(예: 메모리 또는 CPU)의 양을 제한하도록 cgroup을 구성할 수 있습니다.

Prioritization – 리소스 경합이 있을 때 다른 cgroup의 프로세스와 비교하여 프로세스가 사용할 수 있는 리소스(CPU, 디스크 또는 네트워크)의 양을 제어할 수 있습니다.

Accounting – 리소스 제한은 cgroup 수준에서 모니터링되고 보고됩니다.

Control – 단일 명령으로 cgroup에 있는 모든 프로세스의 상태(고정, 중지 또는 다시 시작)를 변경할 수 있습니다.

따라서 기본적으로 cgroup을 사용하여 프로세스 또는 프로세스 집합에서 액세스하거나 사용할 수 있는 지정된 키 리소스(CPU, 메모리, 네트워크 및 디스크 I/O)의 양을 제어합니다. Cgroup은 컨테이너에서 함께 제어해야 하는 여러 프로세스가 실행되는 경우가 많기 때문에 컨테이너의 핵심 구성 요소입니다. Kubernetes 환경에서 cgroup을 사용하여 파드(Pod) 수준에서 리소스 요청 및 제한 및 해당 QoS 클래스를 구현할 수 있습니다.

다음 다이어그램은 사용 가능한 시스템 리소스의 특정 비율을 cgroup(이 경우 cgroup‑1)에 할당할 때 나머지 비율을 시스템의 다른 cgroup(및 개별 프로세스)에서 사용할 수 있는 방법을 보여줍니다.

2-1. cgroup 버전

Wikipedia에 따르면 cgroups의 첫 번째 버전은 2007년 말이나 2008년 초에 Linux 커널 메인라인에 병합되었으며 “cgroups‑v2 문서는 2016년에 Linux 커널에 처음 등장했습니다.” 버전 2의 많은 변경 사항 중 가장 큰 변경 사항은 훨씬 단순화된 트리 아키텍처, cgroup 계층 구조의 새로운 기능 및 인터페이스, “루트 없는” 컨테이너(0이 아닌 UID 포함)의 향상된 조정입니다.

v2에서 내가 가장 좋아하는 새 인터페이스는 PSI(압력 스톨 정보)용입니다. 이전에 가능했던 것보다 훨씬 세분화된 방식으로 프로세스별 메모리 사용 및 할당에 대한 통찰력을 제공합니다(이는 이 블로그의 범위를 벗어나지만 매우 멋진 주제입니다).

2-2. cgroup 만들기

다음 명령은 foo라는 v1 cgroup(경로 이름 형식으로 알 수 있음)을 만들고 메모리 제한을 50,000,000 bytes(50MB)로 설정합니다.

root # mkdir -p /sys/fs/cgroup/memory/foo
root # echo 50000000 > /sys/fs/cgroup/memory/foo/memory.limit_in_bytes

이제 cgroup에 프로세스를 할당하여 cgroup의 메모리 제한을 적용할 수 있습니다. 나는 cgroup 테스팅 도구를 화면에 출력하고 아무 것도 하지 않고 기다리는 test.sh라는 쉘 스크립트를 작성했습니다. 내 목적을 위해, 그것은 내가 멈출 때까지 계속 실행되는 프로세스입니다.

백그라운드에서 test.sh를 시작하고 PID는 2428로 보고됩니다. 스크립트는 출력을 생성한 다음 PID를 cgroup 파일 /sys/fs/cgroup/memory/foo/cgroup.procs에 파이프하여 cgroup에 프로세스를 할당합니다.

root # ./test.sh &
[1] 2428
root # cgroup testing tool
root # echo 2428 > /sys/fs/cgroup/memory/foo/cgroup.procs

내 프로세스가 실제로 cgroup foo에 대해 정의한 메모리 제한의 적용을 받는지 확인하기 위해 다음 ps 명령을 실행합니다. -o cgroup 플래그는 지정된 프로세스(2428)가 속한 cgroup을 표시합니다. 출력은 메모리 cgroup이 foo임을 확인합니다.

root # ps -o cgroup 2428
CGROUP
12:pids:/user.slice/user-0.slice/\
session-13.scope,10:devices:/user.slice,6:memory:/foo,...

기본적으로 운영 체제는 cgroup에서 정의한 리소스 제한을 초과할 때 프로세스를 종료합니다.

3. 결론

네임스페이스와 cgroup은 컨테이너 및 최신 애플리케이션을 위한 빌딩 블록입니다. 애플리케이션을 보다 현대적인 아키텍처로 리팩토링할 때 작동 방식을 이해하는 것이 중요합니다.

네임스페이스는 시스템 리소스의 격리를 제공하고 cgroup은 해당 리소스에 대한 세분화된 제어 및 제한 적용을 허용합니다.

컨테이너는 네임스페이스와 cgroup을 사용할 수 있는 유일한 방법이 아닙니다. 네임스페이스 및 cgroup 인터페이스는 Linux 커널에 내장되어 있으므로 다른 애플리케이션에서 이를 사용하여 분리 및 리소스 제약을 제공할 수 있습니다.

NGINX Unit에 대해 자세히 알아보고 소스를 다운로드하여 직접 사용해 보십시오.