這裡的 "namespace" 指的是 Linux namespace 技術,它是 Linux 核心實現的一種隔離方案。簡而言之,Linux 作業系統能夠為不同的程序分配不同的 namespace,每個 namespace 都具有獨立的資源分配,從而實現了程序間的隔離。如果你的 Linux 安裝了 GCC,可以通過執行 man namespaces
命令來檢視相關檔案,或者你也可以存取線上手冊獲取更多資訊。
下圖為各種 namespace 的引數,支援的起始核心版本,以及隔離內容。
Namespace | 系統呼叫引數 | 核心版本 | 隔離內容 |
---|---|---|---|
UTS (Unix Time-sharing System) | CLONE_NEWUTS | Linux 2.4.19 | 主機名與域名 |
IPC (Inter-Process Communication) | CLONE_NEWIPC | Linux 2.6.19 | 號誌、訊息佇列和共用記憶體 |
PID (Process ID) | CLONE_NEWPID | Linux 2.6.19 | 程序編號 |
Network | CLONE_NEWNET | Linux 2.6.24 | 網路裝置、網路棧、埠等等 |
Mount | CLONE_NEWNS | Linux 2.6.29 | 掛載點(檔案系統) |
User | CLONE_NEWUSER | Linux 3.8 | 使用者和使用者組 |
PID Namespace:
Net Namespace:
IPC Namespace:
MNT Namespace:
UTS Namespace:
User Namespace:
涉及到三個系統呼叫(system call)的 API:
lsns –t <type>
ls -la /proc/<pid>/ns/
nsenter -t <pid> -n ip addr
Test:
# Linux命令列中,可以使用`unshare`命令結合`clone()`建立一個新的程序,並在其中使用名稱空間隔離引數。
# 建立一個新的程序,並在其中使用名稱空間隔離引數
unshare --pid --net -- sleep 600
ps -ef|grep sleep
root 37915 34572 0 08:53 pts/1 00:00:00 sudo unshare --pid --net -- sleep 600
root 37916 37915 0 08:53 pts/3 00:00:00 sudo unshare --pid --net -- sleep 600
root 37917 37916 0 08:53 pts/3 00:00:00 sleep 600
zhy 37919 37896 0 08:53 pts/2 00:00:00 grep --color=auto sleep
sudo lsns -t net
[sudo] password for zhy:
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531840 net 277 1 root unassigned /sbin/init
4026532656 net 1 37347 root 0 /run/docker/netns/c986b82be683 bash
4026532718 net 1 37917 root unassigned sleep 600
sudo nsenter -t 37917 -n ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
docker run --rm -it docker.m.daocloud.io/ubuntu:22.10 bash
ps -ef|grep ubuntu
# zhy 37247 34017 0 08:20 pts/0 00:00:00 docker run --rm -it docker.m.daocloud.io/ubuntu:22.10 bash
ls -la /proc/37247/ns/
total 0
dr-x--x--x 2 zhy zhy 0 May 27 08:24 .
dr-xr-xr-x 9 zhy zhy 0 May 27 08:23 ..
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 net -> 'net:[4026531840]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 time -> 'time:[4026531834]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 user -> 'user:[4026531837]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 uts -> 'uts:[4026531838]'
sudo lsns -t pid
NS TYPE NPROCS PID USER COMMAND
4026531836 pid 275 1 root /sbin/init
4026532654 pid 1 37347 root bash
sudo lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531840 net 275 1 root unassigned /sbin/init
4026532656 net 1 37347 root 0 /run/docker/netns/c986b82be683 bash
為什麼查出來執行 bash
的 pid 和 ps -ef
的不一樣?
一個是docker run
的程序 PID
一個是 容器內部 'bash' 程序的 PID 這個程序是由docker run
的程序通過程序複製(process cloning)建立的子程序。
ip addr
在主機執行 nsenter -t <pid> -n ip addr
# 容器內
ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
11: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:23 pts/0 00:00:00 bash
root 360 1 0 09:12 pts/0 00:00:00 ps -ef
# 主機
sudo nsenter -t 37347 -n -- ip addr # -n 進入網路namespace執行
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
11: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
sudo nsenter -t 37347 -a -- ps -ef # -a 進入所有namespace執行
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:23 pts/0 00:00:00 bash
root 359 0 0 09:12 ? 00:00:00 ps -ef
Linux cgroups 的全稱是 Linux Control Groups,它是 Linux 核心的特性,主要作用是限制、記錄和隔離行程群組(process groups)使用的物理資源(cpu、memory、IO 等)。
可以做到對 cpu,記憶體等資源實現精細化的控制,容器技術就使用了 cgroups 提供的資源限制能力來完成cpu,記憶體等部分的資源控制。
subsystem 是一組資源控制的模組,一般包含有:
Cgroup v2手冊
是否載入了Cgroup v2核心模組
cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc
執行一段go程式碼
package main
func main() {
go func() { for{} }()
for {}
}
/*
執行 go run test.go
top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
39268 zhy 20 0 709572 868 584 R 200.0 0.0 2:12.27 test
可以看到使用了2個cpu 因為開個兩個goroutine for阻塞
*/
限制cpu
sudo mkdir /sys/fs/cgroup/test
sudo echo "100000 100000" | sudo tee /sys/fs/cgroup/test/cpu.max >/dev/null
sudo echo "39268" | sudo tee /sys/fs/cgroup/test/cgroup.procs >/dev/null
# top
# PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
# 39268 zhy 20 0 709572 868 584 R 100.3 0.0 7:45.04 test
# 馬上就只佔用一個cpu了
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define BLOCK_SIZE (100 * 1024 * 1024)
#define NUM_ALLOCATIONS 10
#define SLEEP_SECONDS 30
char* allocMemory(int size) {
char* out = (char*)malloc(size);
memset(out, 'A', size);
return out;
}
int main() {
int i;
for (i = 1; i <= NUM_ALLOCATIONS; i++) {
char* block = allocMemory(i * BLOCK_SIZE);
printf("Allocated memory block of size %dMB at address: %p\n", i * 100, block);
sleep(SLEEP_SECONDS);
}
return 0;
}
/*
ps -p 3243 -o rss=,unit=M,cmd=
M
308512 session-4.scope ./test2
*/
限制記憶體
sudo echo "300000000" |sudo tee /sys/fs/cgroup/test/memory.max >/dev/null
sudo echo "64417" | sudo tee /sys/fs/cgroup/test/cgroup.procs >/dev/null
#cat memory.current
#299839488
聯合檔案系統(UnionFS)是一種分層、輕量級並且高效能的檔案系統,它支援對檔案系統的修改作為一次提交來一層層的疊加,同時可以將不同目錄掛載到同一個虛擬檔案系統下 (unite several directories into a single virtual filesystem)。
聯合檔案系統是 Docker 映象的基礎。映象可以通過分層來進行繼承,基於基礎映象(沒有父映象),可以製作各種具體的應用映象。
另外,不同 Docker 容器就可以共用一些基礎的檔案系統層,同時再加上自己獨有的改動層,大大提高了儲存的效率。
最新版 Docker 使用的是 overlay2。
現在主流基本都是 overlayFS
OverlayFS 屬於檔案級的儲存驅動,包含了最初的 Overlay 和更新更穩定的 overlay2。
Overlay 只有兩層:upper 層和 lower 層,Lower 層代表映象層,upper 層代表容器可寫層。
mkdir test && cd test
mkdir upper lower merged work
echo "file1 from lower" > lower/file1.txt
echo "file2 from lowerr" > lower/file2.txt
echo "file3 from lower" > lower/file3.txt
echo "file2 from upper" > upper/file2.txt
echo "file4 from upper" > upper/file4.txt
current_dir=$(pwd)
sudo mount -t overlay -o lowerdir="$current_dir/lower",upperdir="$current_dir/upper",workdir="$current_dir/work" overlay "$current_dir/merged"
cat merged/file1.txt
file1 from lower
cat merged/file2.txt
file2 from upper
cat merged/file3.txt
file3 from lower
cat merged/file4.txt
file4 from upper
每一條指令是一層, 下層可以共用
典型的Linux檔案系統組成如下:
Linux
Docker 啟動
由於映象具有共用特性,所以對容器可寫層的操作需要依賴儲存驅動提供的寫時複製和用時分配機制,以此來 支援對容器可寫層的修改,進而提高對儲存和記憶體資源的利用率。