手把手教你寫Docker(下)

寫程序的康德fireflyc2018-01-31 23:50:05

寫在前面

上次的願望沒有實現,但是我還是更新了。沒辦法,誰讓我愛你們呢。

的網絡部分是最簡單的部分(沒辦法,哥們是最懂網絡的程序員),Docker預留的一大段IP地址(在我的機器上是172.17.0.0/16,一共65536個IP地址),藉助Linux bridge為每個容器提供一個IP地址,這樣容器之間通訊就直接通過Linux bridge實現。外部網絡通訊部分是基於IPTables實現的DNAT(導出端口到Host)和SNAT(藉助Host訪問外部網絡)。

涉及到的技術

其實你可能已經猜到了,我們會用到Linux bridge、network namespace、IPtables三個東西,這三個都是Linux內置的工具。雖然上次我只講了一個API,但是居然還有人聲稱沒看懂,非要看我表演胸口碎大石。所以這次我決定——一個API也不用,全部用Linux命令行工具來實現。

涉及到三個工具

  1. iproute2,你可以通過輸入ip link擁有這個工具。這是Linux主張使用的網絡管理工具,旨在代替routeifconfig這些命令。 (iproute2是基於netlink實現的,這是內核空間提供的標準網絡控制接口,vishvananda/netlink是golang對netlink的封裝,Docker是基於vishvananda/netlink的)

  2. bridge-utils,Linux Bridge工具集,通過brctl show來判斷是否已經擁有它。

  3. IPTables,Linux提供了數據包處理的擴展點(Hook),這個模塊叫netfilter。IPTables是一組用戶空間工具,通過這組工具為Hook增加一些“轉發規則”。比如實現數據包過濾(防火牆),SNAT、DNAT。通過輸入iptables -V判斷是否已經安裝。

學“手動擋”,開“自動擋”

先把上次做的“容器”啟動,centos7提供的rootfs沒有相關的網絡管理工具,只提供了一個ping命令,待會我們會用這個“簡陋”的命令判斷網絡是否聯通。

導出容器的network namespace

新啟動一個Shell,查看上面容器中的namespace下面有什麼。1243是我上面容器的PID。

這裡的net就是上面容器對應的network namespace,我們把它link到/var/run/netns/下。(ip netns命令只會管理/var/run/netns/下的文件,所以我們必須link到這個目錄下面)

注意/var/run/netns目錄如果不存在請自己創建,然後我們就可以通過ip netns管理容器的網絡了。

掛載VETH到容器

我們先創建一對VETH。這是一種虛擬設備,一端連接容器,一端連接我們稍後創建的Linux Bridge。ip netns exec表示在指定的namespace中執行網絡命令。

創建Linux Bridge

通過brctl創建一個名叫“mini-docker-br0”的bridge。

把之前創建的veth的“另一端”加入到bridge裡。

啟動

把veth的兩端和Linux Bridge都設置為up狀態

為容器的“eth0”和mini-docker-br0設置IP地址(我們也預留一大段IP地址為容器使用)

然後就是在容器裡面用ping測試一下到網關(mini-docker-br0)

現在只能實現兩個容器之間互訪,如果要讓容器訪問公網必須在Host上通過IPTable做SNAT映射。

首先是修改Forward表,容器訪問公網的數據包首先會經過Forward表;同樣公網返回到容器的數據包也會經過Forward表。這裡直接放行。

其次是NAT表,源地址10.100.0.0/16的數據包都用網卡ens33對應的IP地址完成SNAT。

最後為容器設置默認路由信息,指向網關(mini-docker-br0)。

然後在容器中ping一個公網IP

通過修改/etc/resolv.conf指定DNS服務器,就可以通過域名訪問了。

當然yum也完全沒有問題

自動化

總結一下,我們上面使用iproute工具創建了veth,為容器分配網卡,管理容器的網卡、路由表。通過bridge-utils創建了橋作為所有容器的網關。使用iptables為容器設置NAT。

剩下的事情其實很簡單,只要把上面的命令變成程序就行了,我在代碼中封裝了command.py用於調用外部命令;把iproute相關的命令都封裝在ip_link中;iptables相關的iptables.py中。

相關代碼已經放到github上了

https://github.com/fireflyc/mini-docker/tree/v0.2

後記

其實還可以寫overlayfs和Docker的鏡像文件對接,以後直接使用Docker Hub上的鏡像。但是——畢竟這僅僅是一個“玩具”,就像我前面說的那樣,因為依賴Python運行庫所以沒有辦法用Centos以外的“鏡像”。至於代碼,出於簡單性考慮我會盡量“粗獷”一些。所以它僅僅是一個玩具。

如果你真的需要自己寫一個容器那麼我建議你看一下runC和libcontainer,它完整的實現了namespace和cgroup,而且用golang編寫僅僅需要libc就可以了。


歡迎關注公眾賬號瞭解更多信息“寫程序的康德——思考、批判、理性”


閱讀原文

TAGS: