1. 程式人生 > Docker入門教學 >容器核心技術--Namespace

容器核心技術--Namespace

上文提到,容器技術的核心有兩個:Namespace 和 Cgroup。本節先來介紹 Namespace 是什麼。

簡單來說,Namespace 可以為容器提供系統資源隔離能力

當然,這樣講過於籠統,我們來舉個例子:假如一個容器中的程序需要使用 root 許可權,出於安全考慮,我們不可能把宿主機的 root 許可權給他。但是通過 Namespace 機制,我們可以隔離宿主機與容器的真實使用者資源,謊稱一個普通使用者就是 root,過這個程式。從這個角度看,Namespace 就是核心對程序說謊的機制,目前(Linux最新的穩定版本為5.6),核心可以說的謊話有 8 種:

Namespace 系統呼叫
Mount CLONE_NEWNS
UTS CLONE_NEWUTS
IPC CLONE_NEWIPC
PID CLONE_NEWPID
Network CLONE_NEWNET
User CLONE_NEWUSER
Cgroup CLONE_NEWCGROUP
Time CLONE_NEWTIME

1. Namespace 詳解

1.1 Mount Namespace

Mount Namespace 用來隔離檔案系統的掛載點,不同的 Mount namespace 擁有各自獨立的掛載點資訊。在 Docker 這樣的容器引擎中,Mount namespace 的作用就是保證容器中看到的檔案系統的檢視。

1.2 UTS Namespace

UTS Namespace 用來隔離系統的主機名、hostname 和 NIS 域名

1.3 IPC Namespace

IPC 就是在不同程序間傳遞和交換資訊。IPC Namespace 使得容器內的所有程序,進行的資料傳輸、共享資料、通知、資源共享等範圍控制在所屬容器內部,對宿主機和其他容器沒有干擾。

1.4 PID Namespace

PID namespaces用來隔離程序的 ID 空間,使得不同容器裡的程序 ID 可以重複,相互不影響

1.5 Network Namespace

Network namespace 用來隔離網路,每個 namespace 可以有自己獨立的網路棧,路由表,防火牆規則等

1.6 user namespace

user namespace 是例子中講到的,控制使用者 UID 和 GID 在容器內部和宿主機上的一個對映,主要用來管理許可權。

1.7 Time namespace

這個 Namespace 允許作業系統為程序設定不同的系統時間

1.8 Cgroup Namespace

這個 Namespace 用來限制 CGroup 根目錄下不同層級目錄的許可權,使得 CGROUP 根目錄下的子目錄的程序無法影響到父目錄。

2. 實踐出真知

2.1 感受 Namespace

講了那麼多理論,Namespace 能不能讓我們直接觀察到呢?

讓我們進入 Linux 環境,執行如下操作:

# 進入/proc/目錄
cd /proc/
# 檢視當前目錄下有哪些檔案或目錄
ls
# 隨便進入一個以數字(程序號)命名的目錄,比如1
cd 1
# 檢視ns(Namespace)目錄下的內容
ls -al ns

圖片描述

當前目錄下紅色的連結,就是這個程序對應的 Namespace。

有興趣的讀者可以看看其他不同程序的 Namespace,比對下是否有差異。如果你找到某個程序的Namespace 與其他的不一致,就說明這個程序指定了 Namespace 隔離。

2.2 使用 Namespace 自制簡易容器

將以下程式碼儲存到/root/test/container.c

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/capability.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char container_stack[STACK_SIZE];
char* const container_args[] = {
        "/bin/bash",
        NULL
};

int pipefd[2];

void set_map(char* file, int inside_id, int outside_id, int len) {
        FILE* mapfd = fopen(file, "w");
        if (NULL == mapfd) {
                perror("open file error");
                return;
        }
        fprintf(mapfd, "%d %d %d", inside_id, outside_id, len);
        fclose(mapfd);
}

void set_uid_map(pid_t pid, int inside_id, int outside_id, int len) {
        char file[256];
        sprintf(file, "/proc/%d/uid_map", pid);
        set_map(file, inside_id, outside_id, len);
}

void set_gid_map(pid_t pid, int inside_id, int outside_id, int len) {
        char file[256];
        sprintf(file, "/proc/%d/gid_map", pid);
        set_map(file, inside_id, outside_id, len);
}

int container_main()
{
        char ch;
        close(pipefd[1]);
        read(pipefd[0], &ch, 1);
        sethostname("container",10);

        /* Mount Namespace */
        mount("proc", "/proc", "proc", 0, NULL);
        mount("none", "/tmp", "tmpfs", 0, "");

        execv(container_args[0], container_args);
        return 1;
}

int main()
{
        const int gid=getgid(), uid=getuid();

        pipe(pipefd);

        int container_pid = clone(container_main, container_stack+STACK_SIZE,
            CLONE_NEWCGROUP|CLONE_NEWIPC|CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | SIGCHLD, NULL);
        set_uid_map(container_pid, 0, uid, 1);
        set_gid_map(container_pid, 0, gid, 1);
        close(pipefd[1]);
        waitpid(container_pid, NULL, 0);
        return 0;
}

我們不用讀懂這個程式碼,只需要留意下 main 主函式中這部分

int container_pid = clone(container_main, container_stack+STACK_SIZE,
                    CLONE_NEWCGROUP|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | SIGCHLD, NULL);

這段程式碼 呼叫 clone 實現執行緒的系統呼叫,用來建立一個新的程序,並可以通過設計上述引數達到隔離。

執行下面的操作

# 安裝可能需要的依賴
sudo dnf install -y libcap-devel
# 編譯這個檔案
cc container.c -o container
# 執行
./container

執行我們編譯好的container程式後,發現我們處於一個新的環境的終端中,你可以在這裡驗證你的猜測,比如檢視當前環境的程序 ps,當前登入的使用者 whoami,網路狀況 ip a等等,使用exit 可以退出回到原來的環境。
我們確實通過系統呼叫,建立了一個與宿主機資源隔離的容器環境。

3. 小結

本節我們介紹了 Namespace 機制,和它的 8 種隔離型別,並實現了一具有名稱空間隔離功能的“容器”,在這個過程中,希望大家對容器和 Namespace 機制有了更深入的理解。