Linux와 Docker의 기술적 관계 (1/3) - Namespace

이 글은 리눅스의 Namespace 기능의 일부를 다룬다. 도커의 내용도 포함되겠으나 리눅스 자체의 기능에 주안점을 둔다. 우선 PID, UTS를 다루고, chroot에 대해서도 소개한다.

기준 환경:

Ubuntu 20.04.1 LTS (WSL2 by Windows 10 x64) (Docker Desktop)

일부 기능은 Native Docker에서만 정상 작동하여 아래 환경도 활용하였다.
Ubuntu 20.04.1 LTS (GCP Compute Engine) (Native Docker)


리눅스 컨테이너는 리눅스 네임스페이스와 루트 파일 시스템 격리 등의 기능을 사용해 격리시킨 프로세스를 의미한다.

namespace라는 기술에 대해

리눅스 네임스페이스(Linux Namespace)란?

Namespace는 한 개의 특정 프로세스에 대해 시스템 리소스를 논리적으로 격리하는 기능이다. 아래와 같이 여러 가지의 Namespace가 존재한다.

1
2
3
4
5
6
7
8
lrwxrwxrwx 1 root root 0 Apr  9 07:53 cgroup -> 'cgroup:[4026531835]' # cgroup = control group = CPU,RAM,NET 할당
lrwxrwxrwx 1 root root 0 Apr 9 07:53 ipc -> 'ipc:[4026532188]'
lrwxrwxrwx 1 root root 0 Apr 9 06:41 mnt -> 'mnt:[4026532186]'
lrwxrwxrwx 1 root root 0 Apr 9 07:53 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Apr 9 07:53 pid -> 'pid:[4026532189]'
lrwxrwxrwx 1 root root 0 Apr 9 08:35 pid_for_children -> 'pid:[4026532189]'
lrwxrwxrwx 1 root root 0 Apr 9 07:53 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr 9 07:53 uts -> 'uts:[4026532187]' # uts=Unix Time-Sharing / Host, Domain name 할당

Docker에서 사용하는 Namespace의 목록은 아래와 같다.

  • ipc, mnt, net, pid, pid_for_children, uts

PID Namespace

  1. 분리된 새로운 네임스페이스에서는 PID=1부터 시작하나, 기존 네임스페이스에서도 (다른 PID로) 해당 프로세스를 볼 수 있으며, 이는 PID=1부터 시작하지 않는다.

  2. ls -alh /proc/{PID}/ns - PID에 대응되는 프로세스가 속한 네임스페이스를 출력한다. (sym link 형태의 구성)

  • 위의 결과는 같은 1번 프로세스에서 생성된 경우 모두 같은 값을 갖는다.

    1
    2
    $ diff <(ls -Al /proc/1/ns | awk '{ print $11 }') \ 
    <(ls -Al /proc/{임의의_프로세스ID}/ns | awk '{ print $11 }')
  1. User namespace는 UID 0을 줘서 root 같아 보이는 - 어느 정도 제한된 - 권한을 제공할 수 있다. User namespace는 initial이 있고, 매 생성마다 Parent-Child 구조를 갖는다.

  2. PID namespace 격리를 위해선 격리된 디렉토리에서의 모든 의존성이 준비 돼야 한다. - 의존성? docker Image가 하는 일이 바로 그것이니, docker Image를 활용해 mount한 폴더를 활용할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    $ docker run ~
    $ docker export ~ > ~.tar
    $ tar xf ~
    $ sudo unshare -p -f --mount-proc chroot . ./image_extracted_dir /bin/sh

    # echo $$
    > 1
  3. unshare 명령어를 써서 PID를 격리해 1번 프로세스가 되는 예시:

    1
    2
    3
    4
    5
    $ unshare -p -f --mount-proc chroot . /bin/sh
    > echo $$ : 1

    $ unshare -p /bin/sh
    > echo $$: 3481
  4. 각종 옵션들 정리

    1. p: PID 네임스페이스는 분리가 되었을텐데 로 짐작해보건데 PID 네임스페이스를 의미하는 듯하다.
    2. f: --fork의 의미로, unshare의 child process로 실행한다고 한다.
    3. --mount-proc: /proc 이 기본값이어서, 기존 proc과 격리될 수 없다. 위 명령어에선 현재 폴더(.)를 넘겼다.
    • man page에선 The new proc filesystem is explicitly as private라고 하는데, 여기서 private이 무슨 뜻인지 모르겠다. 그리고, mount proc filesystem한다는 표현도 무슨 뜻인지 잘 모르겠다.
    • shell이 지원하는 명령어가 아니라 바이너리를 실행하려고 하면 2: Cannot fork라고 뜬다. Why?
    1. Host Shell에서 ps -ef하면 unshare도 하나의 프로세스로 뜨고, sudo kill -9 3481 하면 unshare도 종료된다.
    2. 프로세스 3481 == 프로세스 1 (unshare도 단지 하나의 프로세스)
    3. /proc 이 기본 PID namepsace이고, init 프로세스(pid 1)에 의해 pid namespace가 다 동일하다.
    4. unshare로 다른 디렉토리에 PID namespace를 mount하면 그 때부터 pid namepsace 값이 다른 값으로 할당됨

UTS Namepsace

출처: UTS 네임스페이스를 사용한 호스트네임 격리 - 컨테이너 네트워크 기초 1편

UTS Namespace는 호스트 네임을 분리한다. 딱 이것만 한다. 별로 기능은 없다.

docker는 컨테이너마다 컨테이너ID로 호스트네임을 부여한다. 이 기능을 구현하는 방법은 무엇일까? nsenter로 가능하다. nsenter는 unshare로 지정한 네임스페이스 정보를 기반으로 프로세스를 실행하는 프로그램이다.

  1. unshare --uts=/tmp/utsns1 hostname utsns1 명령으로 utsns1로 새로운 namespace를 정의하고 실행할 수 있다.
  2. /tmp/utsns1 파일에 unshare 설정을 저장한 후,
  3. nsenter --uts=/tmp/utsns1 hostname 수행 시 utsns1를 출력할 수 있다.

Network Namepsace

Network Namespace는 프로세스의 네트워크 환경을 분리한다. 실제로 docker 컨테이너마다 네트워크 인터페이스와 IP가 부여된다.

  • (2편에서 확인할 예정)

TODO:

  • 네트워크 부분 더 공부하기

Linux와 Docker의 기술적 관계 (1/3) - Namespace

https://jsqna.com/linux-namespaces-1/

Author

Seongbin Kim

Posted on

21-04-11

Updated on

21-05-16

Licensed under

댓글