linux container (LXC)內核知識碎記

linux container (LXC)內核知識碎記

linux namespace資源隔離

在linux內核中一共有下面六種namespace

namespace 系統調用參數 隔離內容
UTS CLONE_NEWUTS 主機名與域名
IPC CLONE_NEWIPC 信號量、消息隊列和貢享內存
PID CLONE_NEWPID 進程編號
NETWORK CLONE_NEWNET 網絡設備、網絡棧
Mount CLONE_NEWNS 掛載點(文件系統)
USER CLONE_NEWUSER 用戶和用戶組

在linux 3.8以後的內核版本中,就對這六種namespace有了全面支持,還提供了相關的api函數,一共有四種方式進行namespace API操作。

  1. 通過clone()在創建新進程時創建namespace
int clone(int (*child_func)(void * args),void *child_stack,int flags,void * arg);
//clone()實際上是linux系統調用fork()的一種更通用的實現方式,他可以通過flags來控制使用了多少功能,一共有20多種CLONE_*標誌位來控制clone進程的方方面面。
/**
四個參數說明:
child_func 傳入子進程運行的程序主函數
child_stack 子進程使用棧空間的大小
flags 表示使用了哪些CLONE_*標誌位。
args 用於傳入主函數的參數.
*/
  1. 查看/proc/[pid]/ns文件

從linux內核3.8起,用戶就可以通過查看/proc/[pid]/ns文件下看到指向不同namespace號的文件,如果兩個進程的編號相同,就說明他們在同一個namespace下。/proc/[pid]/ns裏設置這些link的目的還有一個就是,一旦上述link文件被打開,只要打開的文件描述符在,那麼就算該namespace下所有的進程都已經結束,這個namespace也會存在,這樣後續的進程依然可以加進來。

  1. 通過setns()加入一個已經存在的namespace
int setns(int fd,int nstype);
//參數fd表示要加入namespace的文件描述符,
//參數nstype讓調用者可以檢查fd指向的namespace是否符合實際要求,參數設爲0表示不檢查。
  1. 通過unshare()在原進程中進行namespace隔離

該系統調用與clone類似,但是不創建新進程,而是在原進程中進行namespace隔離

int unshare(int flags);
//fllags參數同clone系統調用的flags參數

UTS namespace

UTS namespace 地宮了主機名和域名的隔離,這樣每個容器就有了獨立的主機名和域名,在網絡上被視爲一個獨立的節點,而非宿主機上一個進程。

#define _GNU_SOURCE

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024*1024)

static char child_stack[STACK_SIZE];
char * const child_args[] = {
        "/bin/bash",
        NULL
};
int child_main(void * args){
    printf("在子進程中\n");
    sethostname("NewNamespace",12);
    execv(child_args[0],child_args);
    return 1;
}

int main(){
    printf("程序開始\n");
    int child_id = clone(child_main,child_stack+STACK_SIZE,CLONE_NEWIPC|CLONE_NEWUTS|SIGCHLD,NULL);
    waitpid(child_id,NULL,0);
    printf("已退出\n");
    return 0;
}

IPC Namespace

進程將通信涉及的ipc資源通常包括常見的信號量,消息隊列和貢享內存。申請IPC資源就申請了一個去哪聚唯一的32位ID,所以IPC namespace中實際上包含了系統IPC標識符以及實現POSIX消息隊列的文件系統。在同一個IPC namespace下的進程彼此可見,不同的IPC namespace下的進程互不可見。

PID namespace

pid namespace隔離非常實用,他對進程pid進行重新編號,即兩個不同namespace下的進程可以有相同的pid。有四點需要記住:

  1. 每個pid namespace中的第一個進程“PID 1”,都會像傳統的linux中的init進程一樣擁有特權,起特殊作用。
  2. 一個namespace中的進程,不可能通過kill或者ptrace影響父節點或者兄弟節點中的進程,因爲其它節點的pid在這個namespace中沒有意義。
  3. 如果你在新的pid namespace重新掛載/proc文件系統,會發現其下只顯示同屬於一個pid namespace中的進程。
  4. 在root namespace中可以看到所有的進程,並且遞歸包含所有的子節點中的進程。

pid namespace 中的init進程

傳統的init進程是所有的進程的父進程,維護一張進程表,不斷檢查進程的狀態,一旦有某個子進程因爲父進程退而變成孤兒進程,init就會負責收養這個子進程並最終回收資源,結束進程。所以在容器中啓動的第一個進程也需要實現類似與init的功能。

信號和init進程

內核爲pid namespace 中的init進程賦予了其他的特權-屏蔽信號。如果init進程中沒有編寫處理某個信號的代碼邏輯。,那麼與init在同一個pid namespace 中的其他進程向其發送的該信號都會被屏蔽。這個功能主要是防止init進程被殺死。.

父節點pid namespace中的進程發送除了SIGKILL或者SIGSTOP同樣也會被忽略。.

一旦pid namespace中的init進程被殺死,那麼其他的子進程也隨之收到SIGKILL信號而被銷燬。

掛載/proc

我們知道在linux中ps,top等進程控制命令,實際上是讀取的/proc/[pid]中的文件信息。如果不掛載新的/proc文件系統,我們看到依然是宿主機上的進程信息

在使用setns和unshare系統調用時PID Namespace的特殊性。

setns和unshare允許用戶在原有的進程中建立namespace進程隔離,但創建了PID namespace後,原來的調用者並不進入namespace,因爲一旦pid被創建後,它的pid namespace也就確定了,進入新的pid namespace會使進程pid發生變化,將導致進程出錯。而創建其他的namespace後,調用者會進入新的namespace

mount namespace

mount namespace通過隔離文件系統掛載點對隔離文件系統進行支持,他時歷史上第一個linux namespace。不同的mount namespace文件發生變化也不會相互影響。可以通過/proc/[pid]/mounts查看到所有掛載到當前namespacce下的文件系統.

進程在執行mount namespace時會把當前的文件結構複製給新的namespace。新的namespace中的所有操作都只會影響當前的文件系統。

mount propagation(掛載傳播)

掛載傳播定義了掛載對象之間的關係。這樣的關係包括共享關係和從屬關係,系統通過這些關係確定任何掛載對象中的掛載事件會如何傳播到其他的掛載對象。

  • 共享關係。如果兩個對象之間是共享關係,那麼一個掛載對象中的掛載事件會傳播到另一個掛載對象,反之依然。
  • 從屬關係。如果兩個掛載對象形成從屬關係,那麼一個掛載對象中的掛載事件會傳播到另外一個掛載對象,但是反之不行;在這種關係中,從屬對象是事件的接受者。

一個掛載狀態可能爲以下的一種:

  1. 共享掛載
  2. 從屬掛載
  3. 共享/從屬掛載
  4. 私有掛載
  5. 不可綁定掛載

具體的相關信息,可以查看mount的manual

network namespcae

network namespace 主要提供了關於網絡資源的隔離,包括網絡設備、網絡協議棧,ip路由表,防火牆,/proc/net目錄,/sys/class/net目錄,套接字等。一個網絡設備一般只存在與一個network namespace。

如果宿主機存在多塊網卡,可以將其中一塊或者多塊分配給新創建的network namespace。而一般而言如果沒有多餘的網卡,我們常用的方法是創建一個虛擬網橋,將網絡橋接到一塊物理網卡中,同時通過虛擬的veth pair實現虛擬容器的網絡通信。關於這部分,後續會有新的筆記來介紹。

user namespace

user namespace主要隔離了安全相關的表示符和屬性,包括用戶id,用戶組id,root目錄,key(密鑰)以及特殊權限。通俗的講,一個普通用戶的進程通過clone創建的新進程在新的user namespace中可以擁有不同的用戶和用戶組。需要記住一下幾點:

  1. user namespace被創建後,第一個進程被賦予了該namespace中的全部權限,這樣該init進程就可以完成必要的初始化工作,而不會出現權限不足而出錯。
  2. 從namespace內部觀察到的uid和gid已經和外部不同了,默認顯示爲65534,表示尚未與外部namespace用戶映射。此時需要對user namespace內部的這個初始化user和他外部namespace的某個用戶建立映射關係,這樣可以保證當涉及到一些對外部namespace操作時,系統可以檢查其權限。同樣用戶組也需要建立起映射關係。
  3. 用戶在新的namespace中具有所有權限,但是它創建的父namespace中不含任何權限,就算條用和創建他的進程具有全部權限,也是如此。
  4. user namespace的創建其實是一個層層嵌套的樹狀結構,最上面就是root namespace,新創建的namespace都有一個父節點user namespace,以及零個或者多個子節點user namespace,這一點與pid namespac非常類似。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章