news banner

很多人會認為同一台主機下,通常會有很多容器。且這些容器彼此都靠網路溝通,各種服務都需要放在容器裡,例如:資料庫放在容器下且暴露在外網底下,不就很不安全嗎?

以下就介紹幾個Linux下容器的隔離技術,來說明資料庫放在容器下是安全的。

從前面幾篇的部落格文章中(容器技術革命:Docker帶動IT轉型與軟體開發革命的Kubernetes),我們可以知道利用容器化技術可以在同一台主機上很快速的模擬成多台主機跑許多不同應用互相隔離的假象,那它裡面是透過什麼方式辦到的?

 

命名空間(Namespace)

在Linux世界中,docker使用六種不同的Namespace,來實現資源隔離的容器。下面簡單的介紹一下,這六種Namespace:

1. UTS namespace(Unix Time-sharing):每台不同的主機都會有不同的主機名稱,然而UTS namespace提供了主機名與域名的隔離,這樣每個docker容器就可以擁有獨立的主機名和域名了,在網路上可以被視作一個獨立的節點,而不會與宿主機的主機名稱互相干擾。

2. IPC namespace(Inter-Process Communication):是Unix/Linux下行程之間通信的方式,例如IPC有共享記憶體、訊號量、消息隊列等方法。既然是模擬多台主機的方式,那你的宿主機與容器裡面的行程總不可能共用同一份記憶體空間吧?所以才需要透過此方式來隔離。

3. PID namespace(Process ID):不管在Windows或是Linux上,在同一個主機上,你上面跑的Process的ID是不可能一樣的,例如:主機A上面跑了兩個行程tomcat(PID 10000)、mongodb(PID 10001),這邊的tomcat PID與mongodb PID在同一台主機下永遠是不可能一樣的,否則當你要以PID號碼發訊號殺掉一個行程時,作業系統怎麼會知道要殺掉tomcat還是mongodb?但是如果在另外一台B主機下,PID就可能會重複。那我們有沒有辦法既在同一台主機下,可以模擬成多台主機下,彼此可以擁有相同的PID又不互相干擾?沒錯,就是使用劃分不同的PID namespace,每個PID namespace,裡面的process可以和另外一個PID namespace,擁有重複的PID名稱,彼此不互相干擾,就像在多台主機上。

4. mount namespace:顧名思義,就是用來隔離檔案系統的掛載點,使得不同的mount namespace擁有自己的獨立掛載點訊息,宿主機與容器內互相使用不同的mount namespace而不會互相干擾。

5. network namespace:這個namespace算得上是容器技術的精華,主要提供了關於網路資源的隔離,包含網路設備、IPv4、IPv6協定、IP路由表、防火牆等資源的隔離,否則如果你在容器內執行一個apache的行程,宿主機上也執行apahce,你很可能會得到80端口已經被佔用的錯誤。就是靠這個network namespace才有辦法使得我們在容器的世界裡,宿主機與各個不同容器內,都使用不同的network namespace,彼此才可以使用相同的行程相同的端口不互相被干擾。

6. user namespace:主要隔離了user權限相關的Linux資源,包括user IDs and group IDs,keys,和capabilities。這是目前namespace中最複雜的一個,因此在這邊並不打算提太多,有興趣可以參考下列連結:

參考資料1  / 參考資料2 / 參考資料3  

因為user和權限息息相關,而權限又事關容器的安全,所以稍微有不慎就會出現安全問題。這邊只打算舉例一個比較直覺的例子,假設你在容器內所使用的root(超級使用者,Linux世界中擁有至高的權力),與外面宿主機上的root是同樣的?試想,這是一件非常可怕的一件事,如果今天有心人拿到你任何一個容器root權限,那他不就可以對你的宿主機做任何事情了,很可怕吧。但這個安全問題在設計Linux與容器的專家也會想到這件事,因此他透過這個user namespace的機制,讓容器內的root,映設回去宿主機的某一個普通的帳號,而這個帳號剛好是容器內那個偽root權限剛好可以做的事情,一來你在容器內,只會無感的覺得你仍是root身份,二來就算這個容器被駭了,它在宿主機上也僅僅只是一個普通user的身份,大大提高了安全性。

最後再提一下,除了用上面那六種namespace來達到隔離的效果之外,容器的技術也用了chroot來更改容器根目錄的位置,舉例來說,我在宿主機外有個目錄是/a/b/c,在c目錄中其實就是放了容器執行時所需要的設定檔以及相關的binary檔,在執行chroot,把容器內的根目錄的位置改成宿主機的/a/b/c。也因此透過那六個namespace,再加上chroot更改容器根目錄的位置,當你登入容器時,就會像是真的在一台實體主機上,就是這樣由來的。

前面講了六個namespace,我用一張圖來做個小結,如圖:

Linux namespace

這張圖是在同一台主機上,底層是硬體設備,再來是那六個namespace,你應該可以發現一件事,原來那六個namespace是直接Linux Kernel可以支援,因此在Linux幾乎是隨手就可以用容器,非常的快速與方便,那右邊的Control group是什麼?他也是在Linux kernel裡,用來控制容器內的資源限制,舉例來說,有人在名叫movie容器執行了一個看影片的行程,為了看這個影片,把我的宿主機上所有的CPU、Memory資源都吃光光了?那其他人不就都拿不到資源都不用寫code做事了,如果你是老闆允許這種事發生嗎?Control group就是用來控制容器使用宿主機上的硬體資源。再來最上面的就是我們關注的容器,由圖可知,用Namespace的技術,來達到隔離的效果,就像是有三台主機CentOS、Ubuntu Precise、Ubuntu Trusty,底層分別跑apache、MySQL,tomcat、Rails,MongoDB、Nginx。

上面不管是資源限制、安全性考量等等,容器技術都幫你考慮到了,這也是為什麼現在容器化技術這麼火紅的原因了。

 

Docker容器網路(單機)

如下圖:

Docker 3

同一台主機下,有三個不同的network namespace(Host、Container1、Container2),也因此彼此像是都在不同的實體的主機下,互相隔離,綠色的部分是由docker虛擬出來的網路介面,在Container1與Container2中的eth0像是這兩台主機中自己的網卡介面,而docker0這個介面就像是我們實體的網路設備Switch(交換器),vethxxxxxx、vethyyyyyy就像是這台Switch上的某兩個port網路孔,所以vethxxxxxx與Container1的eth0是一對的,而vethyyyyyy與Container2的eth0是一對的,你可以把它們整體想成我有兩台主機分別為Container1與Container2,實體線路接在docker0這台Switch上,加上這兩台主機又是在同一個網路(172.17.0.0/16)上,所以他們彼此可以互相溝通。

那個紫色的”實體”網卡介面eth0是做什麼的?這也很容易理解,如果你的ContainerX需要出去網際網路的話,自然就是透過這張卡出去的。

這裡稍微再補充一下,這張圖的容器網路網段是172.17.0.0/16,但是紫色那張eth0的實體網路介面的網段是192.168.1.0/24,顯然是不同的網段,實際出去時會把封包表頭來源位址172.17.0.5改成192.168.1.123再從紫色的eth0出去,這用了NAT的技術,更多細節請參考 wiki

 

Kubernetes網路(多機)

以下的範例架構圖Kubernetes + calico (Kubernetes所使用的第三方網路插件) + ipip協定為例,如下圖:

Kubernetes 5

左邊的Pod web-service-0001嘗試寫資料進去Pod database-0001,那它們之間是怎麼從容器再透過host1傳送到host2中的容器的?如果忘記Pod是什麼的話,可以參考之間部落格文章 ”帶動IT轉型與軟體開發革命的Kubernetes”。

因為容器技術的關係,Pod web-service-0001像是獨立的一台主機,所以它有自己獨立的路由表,當web-service-0001要傳送資料給database-0001的時候,會根據自己的路由表把封包傳送到外面的host1的caliXXXXXX介面,這個介面是沒有IP位址的,這段路由的過程使用了proxy arp技術,更多可以參考 wiki

然而封包送到caliXXXXXX之後,host1這台實體主機也有自己的路由表,會再根據自己的路由表把封包往下送到tunl0最後再由紫色的eth0實體介面出去,這邊再提一下,因為host1的tunl0是172.17.0.0/16的網段,而紫色的eth0是192.168.1.0/24的網段,兩個很明顯的是不同的網段,在上個例子Docker單機網路這邊是使用NAT的技術,而calico是使用IP in IP的技術出去,等host2收到來自host1的封包之後,也是根據host2路由表往database-0001裡面送,最終database-0001就收到由web-service-0001送來的資料了!

 

用個比較生活化的例子來大概描述這個過程,假設小博哥(web-service-0001)想從台灣寫一封情書給美國的小美(database-0001),小博哥把情書的內容寫完之後,放入信封,附上小美他家的地址,並還要註明小美的大名,最後放入郵筒(web-service-0001 eth0),等郵差車來運載。當郵差來收完這封信的時候,會先載到郵局(caliXXXXXX),然後再把這封信運到地區的大型郵件集散地(host1 tunl0),並根據信封上面的地址進行包裹的分類,原來是要送往國外的,再把信件往海關(host1 eth0)送,最後以空運出去(途中LAN那條線),然而對方國家也是會有海關(host2 eth0),再送往大型郵件集散地(host2 tunl0),對方的大型郵件集散地將貨櫃打開之後,依據各包裹的地址進行分類送往郵局(host2 caliYYYYYY),由郵差將分類過後的信件運載到正確地址的郵筒(database-0001 eth0),最後小美從郵筒取出收到情書。

根據上述的技術可以看出,只要善用容器網路隔離技術,資料庫放在容器中是非常安全的。容器中的服務是躲在宿主機裡面的,沒有直接暴露在宿主機上,畢竟它與宿主機的網段根本是不同的網段!

註:參考更多calico資訊 以及 IP in IP是一種網路協定,由calico所採用,想要瞭解請參考:https://en.wikipedia.org/wiki/IP_in_IP

 

Kubernetes的網路有非常多作法,然而上面舉的例子只是其中一個例子(calico)的一個網路協定(IP in IP),當然還有其他不只Calico的第三方插件,舉例還有Flannel、Linen、Open vSwitch、Weave等其他第三方插件這麼多種,Calico只是冰山一角而已,有興趣的人可以上網找資料。

 

作者:德鴻科技 研發部 Andrew