$ docker run -it busybox /bin/sh
-it 參數(shù)告訴了 Docker 項目在啟動容器后,需要給我們分配一個文本輸入/輸出環(huán)境,也就是TTY,跟容器的標準輸入相關聯(lián),這樣我們就可以和這個Docker容器進行交互了,而/bin/sh就是我們要在Docker容器里運行的程序。
所以,上面這條指令翻譯成人類的語言就是:請幫我啟動一個容器,在容器里執(zhí)行/bin/sh,并給我分配一個命令行終端跟這個容器交互。
這樣,我的linux機器就變成了一個宿主機,而一個運行著/bin/sh的容器,就跑在這個宿主機里面。
/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh
10 root 0:00 ps
可以看到,我們在Docker里最開始執(zhí)行的/bin/sh,即使這個容器內部的第一號進程(PID=1),而這個容器里一共只有兩個進程在運行,這就意味著,前面執(zhí)行的/bin/sh,以及我們剛剛執(zhí)行的ps,已經被Docker隔離在了一個跟宿主機完全不同的世界當中。
本來,每檔我們在宿主機上運行一個/bin/sh程序,操作系統(tǒng)都會給它分配一個進程編號,比如PID=100,這個編號是進程的唯一標識,就像員工的工牌一樣,所以PID=100,可以粗略地理解這個/bin/sh是我們公司里的地100個員工,而第一個員工就自然是老板這樣統(tǒng)領全局的人物。
而現(xiàn)在,我們要通過Docker把這個/bin/sh程序運行在一個容器當中,這時候,Docker就會在這個第100個員工入職時給施一個“障眼法”,讓他永遠看不到前面的99個員工,更看不到老板,這樣,他就會錯誤地以為自己就是公司里的第一個員工。
這種機制,其實就是對被隔離應用的進程空間做了手腳,使得這些進程只能看到重新計算過的進程編號,比如PID=1,可實際上,他們在宿主機的操作系統(tǒng)里,還是原來的第100號進程。
這種技術,就是linux里面的namespace機制,而namespace的使用方法也非常有意思:它其實只是linux創(chuàng)建新進程的一個可選參數(shù),我們知道,在Linux系統(tǒng)中創(chuàng)建線程的系統(tǒng)調用是clone(),比如:
int pid = clone(main_function, stack_size, SIGCHLD, NULL);
這個系統(tǒng)調用就會為我們創(chuàng)建一個新的進程,并且返回它的進程號PID。
而當我們用clone()系統(tǒng)調用創(chuàng)建一個新進程時,就可以在參數(shù)重指定CLONE_NEWPID參數(shù),比如:
int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);
這時,新創(chuàng)建的這個繼承將會“看到”一個全新的進程空間,在這個進程空間里,它的PID是1,之所以說“看到”,是因為這只是一個”障眼法“,在宿主機真實的進程空間里,這個進程的PID還是真實的數(shù)值,比如:101
當然,我們還可以多次執(zhí)行上面的 clone() 調用,這樣就會創(chuàng)建多個 PID Namespace,而每個 Namespace 里的應用進程,都會認為自己是當前容器里的第 1 號進程,它們既看不到宿主機里真正的進程空間,也看不到其他 PID Namespace 里的具體情況。
除了我們剛剛用到的 PID Namespace,Linux 操作系統(tǒng)還提供了 Mount、UTS、IPC、Network 和 User 這些 Namespace,用來對各種不同的進程上下文進行“障眼法”操作。
比如,Mount Namespace,用于讓被隔離進程只看到當前 Namespace 里的掛載點信息;Network Namespace,用于讓被隔離進程看到當前 Namespace 里的網絡設備和配置。
所以,Docker 容器這個聽起來玄而又玄的概念,實際上是在創(chuàng)建容器進程時,指定了這個進程所需要啟用的一組 Namespace 參數(shù)。這樣,容器就只能“看”到當前 Namespace 所限定的資源、文件、設備、狀態(tài),或者配置。而對于宿主機以及其他不相關的程序,它就完全看不到了。
所以說,容器其實是一種特殊的進程而已。