Docker如何實現隔離
Linxu內核實現Namespace的主要目的是為了實現輕量化的虛擬化,就是為了支持容器
查看隔離
Docker每一個容器中有獨立的IP、端口、路由,共有六項隔離
我們通過一個簡單的Apache來查看Docker有哪六項隔離
[root@localhost ~]# yum -y install httpd
[root@localhost ~]# systemctl start httpd
[root@localhost ~]# netstat -anpt | grep 80
tcp6 0 0 :::80 :::* LISTEN 68305/httpd
可以看到關於80端口的pid是68305,這是重點,接下來就要去看68305這個pid中有哪些東西
這寫pid的相關目錄都在/proc
目錄中,也理解為在內存中運行的內容
[root@localhost ~]# ls /proc
1 10524 10676 1286 289 484 67448 861 cgroups modules
10 10529 10701 1289 29 485 67589 862 cmdline mounts
10042 10535 10727 1293 290 49 67590 863 consoles mpt
101 10552 10730 13 314 50 67591 864 cpuinfo mtrr
10112 10562 10735 1301 316 51 67628 865 crypto net
10117 10570 10742 1307 317 53 677 867 devices pagetypeinfo
10120 10581 10745 1308 318 569 68058 868 diskstats partitions
....
所有正在運行的進程id號都會在這里以目錄的形式體現,同樣剛才啟動的80端口的pid68305也在這里,如果要看該程序占用多大,查看目錄相關pid目錄的大小即可
其中的ns目錄就是namespace
的縮寫
[root@localhost ~]# cd /proc/68305/ns/
[root@localhost ns]# ll
total 0
lrwxrwxrwx. 1 root root 0 Mar 25 09:06 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 Mar 25 09:06 mnt -> mnt:[4026532505]
lrwxrwxrwx. 1 root root 0 Mar 25 09:06 net -> net:[4026531956]
lrwxrwxrwx. 1 root root 0 Mar 25 09:06 pid -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 Mar 25 09:06 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 Mar 25 09:06 uts -> uts:[4026531838]
ns目錄中的六項內容,就是namespace
的隔離數據包,每個程序的空間都要保持唯一性,否則就會產生沖突
我們通過另一個隨機程序的隔離機制來查看,當兩個程序的每一項后面的數字相同時,表示存在沖突,也就是他們在同一個空間內,但是觀察發現,只有mnt是不沖突的
[root@localhost ns]# ll ../../20/ns/
total 0
lrwxrwxrwx. 1 root root 0 Mar 25 09:09 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 Mar 25 09:09 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 Mar 25 09:09 net -> net:[4026531956]
lrwxrwxrwx. 1 root root 0 Mar 25 09:09 pid -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 Mar 25 09:09 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 Mar 25 09:09 uts -> uts:[4026531838]
mnt:是掛載點和文件系統的隔離,也就是程序所在的根目錄不同,也就是在不同的mnt命名空間
六項隔離
Linux的namespace是在內核版本3.8以后引進的,如果內核是之前的也就代表不支持虛擬化
編寫一個C語言的腳本來啟動一個進程模擬命名空間
[root@localhost ~]# vim example.c # 添加 #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"); execv(child_args[0], child_args); return 1; } int main() { printf("程序開始: \n"); int child_pid = clone(child_main, child_stack + STACK_SIZE, SIGCHLD, NULL); waitpid(child_pid, NULL, 0); printf("已退出\n"); return 0; }
編譯該腳本
gcc -Wall example.c -o test.o
將編譯后的腳本程序運行起來
./test.o
驗證該進程是否好用
可以看到運行腳本后多了一個終端test.o
,執行ps命令之后,查看到執行的ps是在test.o
的bash環境里面執行的,其實就是我們在c腳本程序中讓他開啟了一個子進程bash
當exit
退出test.o
,再次執行時,發現只有一個bash了,也就是我們最開始使用的環境
[root@localhost ~]# ./test.o
程序開始:
在子進程中!
[root@localhost ~]# ps
PID TTY TIME CMD
68102 pts/3 00:00:01 bash
68651 pts/3 00:00:00 test.o
68652 pts/3 00:00:00 bash
68695 pts/3 00:00:00 ps
[root@localhost ~]# exit
exit
已退出
[root@localhost ~]# ps
PID TTY TIME CMD
68102 pts/3 00:00:01 bash
68697 pts/3 00:00:00 ps
這個程序中的子進程bash和原始bash是對稱空間的關系
UTS:主機和域名
提供了主機名和域名的隔離,每個容器都有自己的主機名和域名,可以理解為一個獨立的節點,並不是一個進程
修改c腳本來實現UTS的隔離,在腳本中我們子進程的bash設置了一個主機名為Changed Name
,希望在執行程序進入子進程后,它的主機名是我們設置的這個,又添加了CLONE_NEWUTS
,用來克隆一個當前空間來啟動一個UTS空間
[root@localhost ~]# vim example.c #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("Changed Name", 12); execv(child_args[0], child_args); return 1; } int main() { printf("程序開始: \n"); int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWUTS | SIGCHLD, NULL); waitpid(child_pid, NULL, 0); printf("已退出\n"); return 0; }
編譯修改后的腳本
gcc -Wall example.c -o uts.o
運行成功后是這樣的,運行之前的主機名是localhost
,之后就是我們在腳本中設置的一樣Changed Name
[root@localhost ~]# ./uts.o 程序開始: 在子進程中! [root@Changed Name ~]#
現在這台電腦里就有兩個主機名
查看進程也可以看到有兩個/bin/bash
環境在運行
[root@Changed Name ~]# ps -ef | grep bash
root 973 1 0 06:25 ? 00:00:00 /bin/bash /usr/sbin/ksmtuned
root 68102 68098 0 08:53 pts/3 00:00:01 -bash
root 70128 70127 0 09:52 pts/3 00:00:00 /bin/bash
root 70181 70128 0 09:54 pts/3 00:00:00 grep --color=auto bash
IPC:信號量、消息列隊、共享內存
容器當中進程間通訊,使用消息列隊,共享內存等,和虛擬機不同的是容器內的進程通訊對宿主機來說實際上是具有相同的pid的,因此需要一個標識符來區別,因此需要在 IPC 資源申請時加入命名空間信息,每個 IPC 資源有一個唯一的 32 位 id。
IPC命名空間包含了系統中的標識符和實現消息列隊的系統文件,所以在同一個ipc命名空間的進程彼此間是可以感知或者可見的,其他的ipc命名空間互不可見
ipcs -q
用來查看當前的列隊
ipcs
查看計算機中所有的列隊
由於一開始已經啟動過httpd,所以可以看到關於apache的進程信息,最下方顯示的Semaphore Arrays
就是信號量數組,apache的信息都在同一個信號量組
[root@localhost ~]# ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 524288 root 777 16384 1 dest
0x00000000 1015814 root 777 3932160 2 dest
0x13143901 1966090 root 600 1000 6
------ Semaphore Arrays --------
key semid owner perms nsems
0x00000000 131072 apache 600 1
0x00000000 163841 apache 600 1
0x00000000 196610 apache 600 1
0x00000000 229379 apache 600 1
0x00000000 262148 apache 600 1
當還沒有進行IPC隔離時,進入剛才的uts.o的子進程中,也會看到apache的信號量組
在不同空間中實際是不應該存在相同的ipc信號量的
[root@localhost ~]# ./uts.o
程序開始:
在子進程中!
[root@Changed Name ~]# ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 524288 root 777 16384 1 dest
0x00000000 1015814 root 777 3932160 2 dest
0x13143901 1966090 root 600 1000 6
------ Semaphore Arrays --------
key semid owner perms nsems
0x00000000 131072 apache 600 1
0x00000000 163841 apache 600 1
0x00000000 196610 apache 600 1
0x00000000 229379 apache 600 1
0x00000000 262148 apache 600 1
修改c腳本,增加ipc信號量的隔離CLONE_NEWIPC
[root@localhost ~]# vim example.c #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("Changed Name", 12); execv(child_args[0], child_args); return 1; } int main() { printf("程序開始: \n"); int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL); waitpid(child_pid, NULL, 0); printf("已退出\n"); return 0; }
編譯輸出為ipc.o
gcc -Wall example.c -o ipc.o
現在執行ipc.o
進入另一個空間后,再次查看ipcs
,這時已經與他相對的空間的ipc已經隔離開了
[root@localhost ~]# ./ipc.o
程序開始:
在子進程中!
[root@Changed Name ~]# ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Semaphore Arrays --------
key semid owner perms nsems
然后可以在這個空間中創建一個屬於它自己的列隊
[root@Changed Name ~]# ipcmk -Q
Message queue id: 0
[root@Changed Name ~]# ipcmk -Q
Message queue id: 32769
[root@Changed Name ~]# ipcmk -Q
Message queue id: 65538
[root@Changed Name ~]# ipcmk -Q
Message queue id: 98307
[root@Changed Name ~]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0xea398e26 0 root 644 0 0
0xf7fe5f0c 32769 root 644 0 0
0xc1cc43e6 65538 root 644 0 0
0x47762e70 98307 root 644 0 0
然后在驗證物理機的環境中有沒有這些創建的隊列
[root@Changed Name ~]# exit
exit
已退出
[root@localhost ~]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
生產環境中大部分會去使用消息列隊去替代ipc,但是docker要用到這個ipc隔離機制
Docker的BUG—PID
pid隔離非常使用,對進程pid編號進行重新編號,不同空間中的進程可以有相同的pid號,內核為所有的pid命名空間維護各樹狀結構,最頂層是root的命名空間,在它之下創建的命名空間被稱為child命名空間,不同的pid命名空間形成了等級體系,所有的父節點可以看到子節點中的進程,並通過信號的方式對子節點的進程產生影響,反過來說,子節點不能對父節點產生任何影響。
結論:每個pid命名空間的第一個進程pid,一定是1
當一個系統中查看pid時,會看到第一個進程pid就是1,在linux中,第一個進程是init進程,版本不同,可能有的是systemd進程,在linux中運行的所有進程都都是init的子進程
ps -aux
查看所有進程,查看第一個進程
pstree
查看進程樹,可以清晰的看到進程之間的關系,最上面的就是systemd或者init進程衍生了所有的進程,一個命名空間中的父進程不會影響物理機或者和它對等空間的父進程
在沒有pid命名空間隔離之前,進入一個新的空間,echo $$
查看進行當前shell的進程號
[root@localhost ~]# ./ipc.o
程序開始:
在子進程中!
[root@Changed Name ~]# echo $$
70752
[root@localhost ~]# echo $$
68102
雖然pid不是緊跟在宿主機的后面,但是也會在宿主機的pid號周圍
修改c腳本代碼,增加pid命名空間的隔離
[root@localhost ~]# vim example.c #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("Changed Name", 12); execv(child_args[0], child_args); return 1; } int main() { printf("程序開始: \n"); int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL); waitpid(child_pid, NULL, 0); printf("已退出\n"); return 0; }
編譯輸出為pid.o
gcc -Wall example.c -o pid.o
運行程序,再去查看當前shell的pid編號,變為了1,但是物理機的還是沒有改變的
[root@localhost ~]# ./pid.o
程序開始:
在子進程中!
[root@Changed Name ~]# echo $$
1
[root@Changed Name ~]# exit
exit
已退出
[root@localhost ~]# echo $$
68102
mnt:掛載點和文件系統
上一步對pid命名空間進行了隔離,但是進入空間中查看進程時ps -aux
,還是可以看到宿主機的所有進程,這是因為沒有對文件系統進行隔離
ps
和top
之類的命令是直接調用宿主機的/proc
目錄進行查看的
無論在宿主機還是一個命名空間中,查看proc目錄中的內容是相同的。
[root@localhost ~]# ls /proc
1 10524 10676 1286 289 484 67591 861 cgroups modules
10 10529 10701 1289 29 485 67628 862 cmdline mounts
10042 10535 10727 1293 290 49 677 863 consoles mpt
101 10552 10730 13 314 50 68058 864 cpuinfo mtrr
10112 10562 10735 1301 316 51 68098 865 crypto net
10117 10570 10742 1307 317 53 68102 867 devices pagetypeinfo
10120 10581 10745 1308 318 569 68305 868 diskstats partitions
1013 10586 10746 14 320 583 68306 869 dma sched_debug
...
[root@localhost ~]# ./pid.o
程序開始:
在子進程中!
[root@Changed Name ~]# ls /proc
1 10524 10676 1286 289 484 67591 861 cgroups modules
10 10529 10701 1289 29 485 67628 862 cmdline mounts
10042 10535 10727 1293 290 49 677 863 consoles mpt
101 10552 10730 13 314 50 68058 864 cpuinfo mtrr
10112 10562 10735 1301 316 51 68098 865 crypto net
10117 10570 10742 1307 317 53 68102 867 devices pagetypeinfo
10120 10581 10745 1308 318 569 68305 868 diskstats partitions
1013 10586 10746 14 320 583 68306 869 dma sched_debug
...
與其他的命名空間不同的是,為了實現一個穩定安全的容器,pid命名空間還需要進行一些額外的工作,才能確保其中進程運行順利
pid為1的進程,用來維護命名空間中的所有進程,進行資源的監控和回收
內核也賦予了1號進程特殊的權利,叫信號屏蔽,如果進程中沒有寫處理某個信號的代碼邏輯,那么1號進程在同一個命名空間下的所有進程都會被屏蔽。
Docker一旦啟動,就有進程在運行,不存在不包括任何進程的Docker
如果想在pid命名空間中只想要看到該命名空間內運行的pid進程,則需要掛載proc目錄到命名空間
如圖所示,物理機中的1號進程是
init
,而容器中的1號進程是/bin/bash
,他倆的proc目錄需要同步,需要使用mnt命名空間來實現我們傳統的掛載,屬於共享式(share)的掛載,無論是掛載點還是掛載源,改變任何一個都會想另一個點去同步
現在就是不需要兩端去同步寫數據,需要有一個主從的概念,一端寫入,另外一端可以看到,但是不會傳播給另外一端
mount支持的掛載操作如下:
Supported operations:
mount --make-shared mountpoint # 默認使用
mount --make-slave mountpoint # 有主從結構,master掛載到從,master寫,slave看,適用於只讀
mount --make-private mountpoint # 私有掛載,各自是各自的,互補影響,經常用來掛載proc目錄,適用於隔離
mount --make-unbindable mountpoint # 不能被二次掛載,如:root目錄
mount是歷史上第一個命名空間,它的標識比較特殊,CLONE_NEWNS
,隔離之后不同的mnt命名空間中的文件結構都會發生變化,也不會互相影響
cat /proc/68305/mounts # 可以查看到所有掛載到當前命名空間的文件系統
# 查看命名空間中文件系統的掛載狀態,包括了,什么東西掛載了哪個目錄上 cat /proc/68305/mountstats
進程在創建新的mnt命名空間的時候,會把當前的文件系統結構復制給新的命名空間,新的命名空間中的所有mount操作都值影響自己命名空間中的文件系統,嚴格的實現了文件系統隔離
實現/proc/文件系統的隔離掛載,需要在物理機和命名空間中都進行隔離掛載
[root@localhost ~]# vim example.c #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("Changed Name", 12); execv(child_args[0], child_args); return 1; } int main() { printf("程序開始: \n"); int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL); waitpid(child_pid, NULL, 0); printf("已退出\n"); return 0; }
編譯輸出為mount.o
gcc -Wall example.c -o mount.o
執行mount.o
,進入mnt命名空間
[root@localhost ~]# ./mount.o
[root@Changed Name ~]# mount --make-private -t proc proc /proc
[root@Changed Name ~]# ls /proc # 發現已經和剛才做mnt隔離之前不一樣了
1 cpuinfo fs kmsg mounts self timer_stats
33 crypto interrupts kpagecount mpt slabinfo tty
acpi devices iomem kpageflags mtrr softirqs uptime
asound diskstats ioports loadavg net stat version
buddyinfo dma irq locks pagetypeinfo swaps vmallocinfo
bus driver kallsyms mdstat partitions sys vmstat
cgroups execdomains kcore meminfo sched_debug sysrq-trigger zoneinfo
cmdline fb keys misc schedstat sysvipc
consoles filesystems key-users modules scsi timer_list
然后使用ps查看進程,也只剩下了兩個,1號進程為/bin/bash
[root@Changed Name ~]# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 116752 3340 pts/3 S 11:31 0:00 /bin/bash
root 34 0.0 0.0 155372 1844 pts/3 R+ 11:35 0:00 ps aux
退出查看物理機
[root@Changed Name ~]# exit
exit
已退出
[root@localhost ~]# ls /proc/
ls: cannot read symbolic link /proc/self: No such file or directory
acpi devices iomem kpageflags mtrr softirqs uptime
asound diskstats ioports loadavg net stat version
buddyinfo dma irq locks pagetypeinfo swaps vmallocinfo
bus driver kallsyms mdstat partitions sys vmstat
cgroups execdomains kcore meminfo sched_debug sysrq-trigger zoneinfo
cmdline fb keys misc schedstat sysvipc
consoles filesystems key-users modules scsi timer_list
cpuinfo fs kmsg mounts self timer_stats
crypto interrupts kpagecount mpt slabinfo tty
發現proc目錄的內容和命名空間中一樣,被命名空間所影響了,所以物理機也需要變為隔離掛載
[root@localhost ~]# mount --make-private -t proc proc /proc
[root@localhost ~]# ls /proc
1 10505 10652 10891 21 448 644 70002 870 devices mpt
10 10514 10659 10912 22 449 645 70625 882 diskstats mtrr
10042 10518 10662 10971 23 46 656 70937 884 dma net
101 10524 10669 11 24 460 65815 71085 888 driver pagetypeinfo
10112 10529 10673 11030 25 461 66 71324 892 execdomains partitions
10117 10535 10674 11039 26 474 66041 71384 895 fb sched_debug
...
這個時候命名空間內還是會被影響,還需要再進一次,隔離掛載就好
[root@localhost ~]# ./mount.o
程序開始:
在子進程中!
[root@Changed Name ~]# ls /proc
1 10524 10676 1286 289 484 68102 861 cgroups modules
10 10529 10701 1289 29 485 68305 862 cmdline mounts
10042 10535 10727 1293 290 49 68306 863 consoles mpt
101 10552 10730 13 314 50 68307 864 cpuinfo mtrr
10112 10562 10735 1301 316 51 68308 865 crypto net
10117 10570 10742 1307 317 53 68309 867 devices pagetypeinfo
10120 10581 10745 1308 318 569 68310 868 diskstats partitions
1013 10586 10746 14 320 583 7 869 dma sched_debug
...
# 再次進行隔離掛載 [root@Changed Name ~]# mount --make-private -t proc proc /proc [root@Changed Name ~]# ls /proc 1 cpuinfo fs kmsg mounts self timer_stats 33 crypto interrupts kpagecount mpt slabinfo tty acpi devices iomem kpageflags mtrr softirqs uptime asound diskstats ioports loadavg net stat version buddyinfo dma irq locks pagetypeinfo swaps vmallocinfo bus driver kallsyms mdstat partitions sys vmstat cgroups execdomains kcore meminfo sched_debug sysrq-trigger zoneinfo cmdline fb keys misc schedstat sysvipc consoles filesystems key-users modules scsi timer_list # 退出后查看物理機的proc目錄,已經完全不影響了 [root@Changed Name ~]# exit exit 已退出 [root@localhost ~]# ls /proc 1 10505 10652 10891 21 448 644 70002 882 diskstats mtrr 10 10514 10659 10912 22 449 645 70625 884 dma net 10042 10518 10662 10971 23 46 656 71085 888 driver pagetypeinfo 101 10524 10669 11 24 460 65815 71451 892 execdomains partitions 10112 10529 10673 11030 25 461 66 71463 895 fb sched_debug
NET:網絡隔離
當物理機已經運行一個apache進程時,再進入命名空間去運行一個apache,會報出端口被占用的錯誤,就需要進行網絡隔離
NET網絡隔離包括了設備,ipv4/ipv6協議棧,防火牆,路由表、端口、socket等。
物理的網絡設備最多存在一個net命名空間中,可以通過虛擬網絡端對端,
不同的net命名空間創建通道,達到網絡通訊的目的
如果有多塊網卡,可以將網卡分配給新建net命名空間,當net命名空間被釋放時,所有的內部進程都會中止,物理網卡就會返回到root的命名空間中
不同的net命名空間會通過物理機的網橋實現路由轉發功能
使用代碼建立net命名空間時較為復雜,所以接下來使用命令來測試
創建network namespace
# 創建test_ns的network命名空間 [root@localhost ~]# ip netns add test_ns # 查看test_ns中的網卡,只有一個lo網卡,且狀態為DOWN [root@localhost ~]# ip netns exec test_ns ip link list 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 # lo的DOWN狀態下是無法進行iso封裝數據的 [root@localhost ~]# ip netns exec test_ns ping 127.0.0.1 connect: Network is unreachable # 開啟lo網卡 [root@localhost ~]# ip netns exec test_ns ip link set dev lo up [root@localhost ~]# ip netns exec test_ns ping 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.161 ms
創建網絡設備對為命名空間添加網卡
[root@localhost ~]# ip link add veth0 type veth peer name veth1
[root@localhost ~]# ip a # 宿主機的網卡編號是全局的,無論哪個空間編號不會重復
12: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 3a:3e:0e:0a:1f:bf brd ff:ff:ff:ff:ff:ff
13: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 56:83:20:7a:c5:3a brd ff:ff:ff:ff:ff:ff
[root@localhost ~]# ip link set veth1 netns test_ns # 將veth1放入test_ns命名空間
[root@localhost ~]# ip netns exec test_ns ip a
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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
12: veth1@if13: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether be:a8:bd:76:bd:71 brd ff:ff:ff:ff:ff:ff link-netnsid 0
配置ip地址
# 為test_ns中的veth1配置ip為10.1.1.1,並啟動 [root@localhost ~]# ip netns exec test_ns ifconfig veth1 10.1.1.1/24 up # 網絡設備對的另一個配置ip,作為和網絡命名空間網橋地址 [root@localhost ~]# ifconfig veth0 10.1.1.2/24 [root@localhost ~]# ping 10.1.1.1 [root@localhost ~]# ip netns exec test_ns ping 10.1.1.2 [root@localhost ~]# ip netns # 查看現有的網絡命名空間 test_ns (id: 1)
路由表隔離查看
[root@localhost ~]# ip netns exec test_ns route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
10.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 veth1
[root@localhost ~]# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default gateway 0.0.0.0 UG 100 0 0 ens33
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
192.168.122.0 0.0.0.0 255.255.255.0 U 0 0 0 virbr0
防火牆隔離查看
# net命名空間防火牆列表 [root@localhost ~]# ip netns exec test_ns iptables -L Chain INPUT (policy ACCEPT) target prot opt source destination Chain FORWARD (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination # 宿主機防火牆列表 [root@localhost ~]# iptables -L # 由於太多就不復制了
刪除網絡命名空間
[root@localhost ~]# ip netns delete test_ns # 刪除網絡命名空間
演示容器中的1號進程不能去啟動其他進錯誤
這就是經過pid隔離以后引出的大bug
命名空間中的pid為1的程序為/bin/bash
,它是沒有權限去管理其他的進程的,但前面說到,id為1的進程可以管理所有的進程,這個時候需要去做什么。
容器還存在一個bug,不能運行yum所使用的東西
如:運行一個容器
[root@localhost ~]# docker run -it --rm centos /bin/bash
[root@68dfc0967784 /]# yum -y install httpd
[root@68dfc0967784 /]# systemctl start httpd
Failed to get D-Bus connection: Operation not permitted
在啟動httpd時報錯,沒有操作權限,就是說明容器中的1號進程bash是無法啟動其他進程的
我們要做到讓它可以去管理這些進程
[root@68dfc0967784 /]# ll /sbin/init # 鏈接到1號物理機父進程
lrwxrwxrwx. 1 root root 22 Oct 1 01:15 /sbin/init -> ../lib/systemd/systemd
[root@68dfc0967784 /]# exit
exit
方法一
給容器提權:--privileged
運行/sbin/init
環境,當操作完之后,建議立即退出容器,時間長會占用權限,使系統鎖定。
[root@localhost ~]# docker run -d --rm --name test --privileged centos /sbin/init
eea69300721c847ca67d3961ca222f75565b920b70459be01a050649c85d36d0
[root@localhost ~]# docker exec -it test /bin/bash
[root@eea69300721c /]# yum -y install httpd
[root@eea69300721c /]# systemctl start httpd
[root@eea69300721c /]# exit
exit
方法二
這個才是正確的用法
[root@localhost ~]# docker run -itd --name sshd centos /bin/bash
cd3c512d6cd959263b1c94e19781d7213831aad714a2e962ade7c0adc28c510e
[root@localhost ~]# docker exec -it sshd /bin/bash
[root@cd3c512d6cd9 /]# yum -y install openssh-server openssh-clients password iproute net-tools
[root@cd3c512d6cd9 /]# passwd root # 設置root密碼
[root@cd3c512d6cd9 /]# cat /usr/lib/systemd/system/sshd.service
# 找到以下 ExecStart=/usr/sbin/sshd -D $OPTIONS # /usr/sbin/sshd -D這個用來啟動yum安裝的服務,幾乎所有yum安裝的都有這個 [root@5c46b791e8d2 /]# /usr/sbin/sshd -D # 執行之后發現報錯,找不到3個密鑰文件 Could not load host key: /etc/ssh/ssh_host_rsa_key Could not load host key: /etc/ssh/ssh_host_ecdsa_key Could not load host key: /etc/ssh/ssh_host_ed25519_key sshd: no hostkeys available -- exiting. # 生成密鑰,分別存放到它要找到三個路徑中 [root@cd3c512d6cd9 /]# ssh-keygen -q -t rsa -b 2048 -f /etc/ssh/ssh_host_rsa_key -N '' [root@cd3c512d6cd9 /]# ssh-keygen -q -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -N '' [root@cd3c512d6cd9 /]# ssh-keygen -q -t dsa -f /etc/ssh/ssh_host_ed25519_key -N '' # 可以通過cat去查看這三個密鑰 [root@cd3c512d6cd9 /]# vi /etc/ssh/sshd_config # 修此文件是得ssh以進程方式運行 # 這是pam模塊使用sshd,容器中沒有這個模塊,所以需要注釋 # UsePAM no # 解開以下注釋並修改值為no UsePrivilegeSeparation sandbox //將sandbox改為no # 以下解開注釋,允許超級用戶登錄 PermitRootLogin yes [root@5c46b791e8d2 /]# /usr/sbin/sshd -D & [root@5c46b791e8d2 /]# exit exit
然后就可以通過ssh來管理docker的容器了
[root@192 ~]# ssh root@172.17.0.2
The authenticity of host '172.17.0.2 (172.17.0.2)' can't be established.
ECDSA key fingerprint is SHA256:8M08usrRzTfDZjM9cfZhM+DAMn8d4O6/xW3ULlpM17o.
ECDSA key fingerprint is MD5:8b:72:3b:0f:15:ac:f4:8f:24:f0:ed:fa:40:12:c3:ae.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '172.17.0.2' (ECDSA) to the list of known hosts.
root@172.17.0.2's password:
[root@5c46b791e8d2 ~]#
pid隔離后引發的兩個bug
- 容器中的1號pid不能去管理其他進程,容器提權解決
- pid隔離后,進入命名空間還是可以看到物理機的所有進程,私有掛載文件系統解決