最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

手工模擬實(shí)現(xiàn) Docker 容器網(wǎng)絡(luò)?。ǔ敿?xì)~)

2022-07-04 20:11 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿
  • 如今服務(wù)器虛擬化技術(shù)已經(jīng)發(fā)展到了深水區(qū)。現(xiàn)在業(yè)界已經(jīng)有很多公司都遷移到容器上了。我們的開發(fā)寫出來的代碼大概率是要運(yùn)行在容器上的。因此深刻理解容器網(wǎng)絡(luò)的工作原理非常的重要。只有這樣將來遇到問題的時(shí)候才知道該如何下手處理。

  • 網(wǎng)絡(luò)虛擬化,其實(shí)用一句話來概括就是用軟件來模擬實(shí)現(xiàn)真實(shí)的物理網(wǎng)絡(luò)連接。比如 Docker 就是用純軟件的方式在宿主機(jī)上模擬出來的獨(dú)立網(wǎng)絡(luò)環(huán)境。我們今天來徒手打造一個(gè)虛擬網(wǎng)絡(luò),實(shí)現(xiàn)在這個(gè)網(wǎng)絡(luò)里訪問外網(wǎng)資源,同時(shí)監(jiān)聽端口提供對外服務(wù)的功能。



  • 看完這一篇后,相信你對 Docker 虛擬網(wǎng)絡(luò)能有進(jìn)一步的理解。

一、基礎(chǔ)知識回顧

1.1 veth、bridge 與 namespace

  • Linux 下的 veth 是一對兒虛擬網(wǎng)卡設(shè)備,和我們常見的 lo 很類似。在這兒設(shè)備里,從一端發(fā)送數(shù)據(jù)后,內(nèi)核會尋找該設(shè)備的另一半,所以在另外一端就能收到。不過 veth 只能解決一對一通信的問題。

  • 如果有很多對兒 veth 需要互相通信的話,就需要引入 bridge 這個(gè)虛擬交換機(jī)。各個(gè) veth 對兒可以把一頭連接在 bridge 的接口上,bridge 可以和交換機(jī)一樣在端口之間轉(zhuǎn)發(fā)數(shù)據(jù),使得各個(gè)端口上的 veth 都可以互相通信。參見

  • Namespace 解決的是隔離性的問題。每個(gè)虛擬網(wǎng)卡設(shè)備、進(jìn)程、socket、路由表等等網(wǎng)絡(luò)棧相關(guān)的對象默認(rèn)都是歸屬在 init_net 這個(gè)缺省的 namespace 中的。不過我們希望不同的虛擬化環(huán)境之間是隔離的,用 Docker 來舉例,那就是不能讓 A 容器用到 B 容器的設(shè)備、路由表、socket 等資源,甚至連看一眼都不可以。只有這樣才能保證不同的容器之間復(fù)用資源的同時(shí),還不會影響其它容器的正常運(yùn)行。參見

  • 通過 veth、namespace 和 bridge 我們在一臺 Linux 上就能虛擬多個(gè)網(wǎng)絡(luò)環(huán)境出來。而且它們之間、和宿主機(jī)之間都可以互相通信。



  • 我們還剩下一個(gè)問題沒有解決,那就是虛擬出來的網(wǎng)絡(luò)環(huán)境和外部網(wǎng)絡(luò)的通信。還拿 Docker 容器來舉例,你啟動(dòng)的容器里的服務(wù)肯定是需要訪問外部的數(shù)據(jù)庫的。還有就是可能需要暴露比如 80 端口對外提供服務(wù)。例如在 Docker 中我們通過下面的命令將容器的 80 端口上的 web 服務(wù)要能被外網(wǎng)訪問的到。

  • 我們今天主要就是解決這兩個(gè)問題的,一是從虛擬網(wǎng)絡(luò)中訪問外網(wǎng),二是在虛擬網(wǎng)絡(luò)中提供服務(wù)供外網(wǎng)使用。解決它們需要用到路由和 nat 技術(shù)。

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)??

1.2 路由選擇

  • Linux 是在發(fā)送數(shù)據(jù)包的時(shí)候,會涉及到路由過程。這個(gè)發(fā)送數(shù)據(jù)包既包括本機(jī)發(fā)送數(shù)據(jù)包,也包括途徑當(dāng)前機(jī)器的數(shù)據(jù)包的轉(zhuǎn)發(fā)。

  • 先來看本機(jī)發(fā)送數(shù)據(jù)包。

  • 所謂路由其實(shí)很簡單,就是該選擇哪張網(wǎng)卡(虛擬網(wǎng)卡設(shè)備也算)將數(shù)據(jù)寫進(jìn)去。到底該選擇哪張網(wǎng)卡呢,規(guī)則都是在路由表中指定的。Linux 中可以有多張路由表,最重要和常用的是 local 和 main。

  • local 路由表中統(tǒng)一記錄本地,確切的說是本網(wǎng)絡(luò)命名空間中的網(wǎng)卡設(shè)備 IP 的路由規(guī)則。

  • 其它的路由規(guī)則,一般都是在 main 路由表中記錄著的??梢杂?ip route list table local 查看,也可以用更簡短的 route -n



  • 再看途徑當(dāng)前機(jī)器的數(shù)據(jù)包的轉(zhuǎn)發(fā)。除了本機(jī)發(fā)送以外,轉(zhuǎn)發(fā)也會涉及路由過程。如果 Linux 收到數(shù)據(jù)包以后發(fā)現(xiàn)目的地址并不是本地的地址的話,就可以選擇把這個(gè)數(shù)據(jù)包從自己的某個(gè)網(wǎng)卡設(shè)備上轉(zhuǎn)發(fā)出去。這個(gè)時(shí)候和本機(jī)發(fā)送一樣,也需要讀取路由表。根據(jù)路由表的配置來選擇從哪個(gè)設(shè)備將包轉(zhuǎn)走。

  • 不過值得注意的是,Linux 上轉(zhuǎn)發(fā)功能默認(rèn)是關(guān)閉的。也就是發(fā)現(xiàn)目的地址不是本機(jī) IP 地址默認(rèn)是將包直接丟棄。需要做一些簡單的配置,然后 Linux 才可以干像路由器一樣的活兒,實(shí)現(xiàn)數(shù)據(jù)包的轉(zhuǎn)發(fā)。

1.3 iptables 與 NAT

  • Linux 內(nèi)核網(wǎng)絡(luò)棧在運(yùn)行上基本上是一個(gè)純內(nèi)核態(tài)的東西,但為了迎合各種各樣用戶層不同的需求,內(nèi)核開放了一些口子出來供用戶層來干預(yù)。其中 iptables 就是一個(gè)非常常用的干預(yù)內(nèi)核行為的工具,它在內(nèi)核里埋下了五個(gè)鉤子入口,這就是俗稱的五鏈。

  • Linux 在接收數(shù)據(jù)的時(shí)候,在 IP 層進(jìn)入 ip_rcv 中處理。再執(zhí)行路由判斷,發(fā)現(xiàn)是本機(jī)的話就進(jìn)入 ip_local_deliver 進(jìn)行本機(jī)接收,最后送往 TCP 協(xié)議層。在這個(gè)過程中,埋了兩個(gè) HOOK,第一個(gè)是 PRE_ROUTING。這段代碼會執(zhí)行到 iptables 中 pre_routing 里的各種表。發(fā)現(xiàn)是本地接收后接著又會執(zhí)行到 LOCAL_IN,這會執(zhí)行到 iptables 中配置的 input 規(guī)則。

  • 在發(fā)送數(shù)據(jù)的時(shí)候,查找路由表找到出口設(shè)備后,依次通過 __ip_local_out、 ip_output 等函數(shù)將包送到設(shè)備層。在這兩個(gè)函數(shù)中分別過了 OUTPUT 和 POSTROUTING 開的各種規(guī)則。

  • 如果是轉(zhuǎn)發(fā)過程,Linux 收到數(shù)據(jù)包發(fā)現(xiàn)不是本機(jī)的包可以通過查找自己的路由表找到合適的設(shè)備把它轉(zhuǎn)發(fā)出去。那就先是在 ip_rcv 中將包送到 ip_forward 函數(shù)中處理,最后在 ip_output 函數(shù)中將包轉(zhuǎn)發(fā)出去。在這個(gè)過程中分別過了 PREROUTING、FORWARD 和 POSTROUTING 三個(gè)規(guī)則。

  • 綜上所述,iptables 里的五個(gè)鏈在內(nèi)核網(wǎng)絡(luò)模塊中的位置就可以歸納成如下這幅圖。



  • 數(shù)據(jù)接收過程走的是 1 和 2,發(fā)送過程走的是 4 、5,轉(zhuǎn)發(fā)過程是 1、3、5。有了這張圖,我們能更清楚地理解 iptable 和內(nèi)核的關(guān)系。

  • 在 iptables 中,根據(jù)實(shí)現(xiàn)的功能的不同,又分成了四張表。分別是 raw、mangle、nat 和 filter。其中 nat 表實(shí)現(xiàn)我們常說的 NAT(Network AddressTranslation) 功能。其中 nat 又分成 SNAT(Source NAT)和 DNAT(Destination NAT)兩種。

  • SNAT 解決的是內(nèi)網(wǎng)地址訪問外部網(wǎng)絡(luò)的問題。它是通過在 POSTROUTING 里修改來源 IP 來實(shí)現(xiàn)的。

  • DNAT 解決的是內(nèi)網(wǎng)的服務(wù)要能夠被外部訪問到的問題。它在通過 PREROUTING 修改目標(biāo) IP 實(shí)現(xiàn)的。

二、 實(shí)現(xiàn)虛擬網(wǎng)絡(luò)外網(wǎng)通信

  • 基于以上的基礎(chǔ)知識,我們用純手工的方式搭建一個(gè)可以和 Docker 類似的虛擬網(wǎng)絡(luò)。而且要實(shí)現(xiàn)和外網(wǎng)通信的功能。

1. 實(shí)驗(yàn)環(huán)境準(zhǔn)備

  • 我們先來創(chuàng)建一個(gè)虛擬的網(wǎng)絡(luò)環(huán)境出來,其命名空間為 net1。宿主機(jī)的 IP 是 10.162 的網(wǎng)段,可以訪問外部機(jī)器。虛擬網(wǎng)絡(luò)為其分配 192.168.0 的網(wǎng)段,這個(gè)網(wǎng)段是私有的,外部機(jī)器無法識別。



  • 這個(gè)虛擬網(wǎng)絡(luò)的搭建過程如下。先創(chuàng)建一個(gè) netns 出來,命名為 net1。

創(chuàng)建一個(gè) veth 對兒(veth1 - veth1_p),把其中的一頭 veth1 放在 net1 中,給它配置上 IP,并把它啟動(dòng)起來。

創(chuàng)建一個(gè) bridge,給它也設(shè)置上 ip。接下來把 veth 的另外一端 veth1_p 插到 bridge 上面。最后把網(wǎng)橋和 veth1_p 都啟動(dòng)起來。

  • 這樣我們就在 Linux 上創(chuàng)建出了一個(gè)虛擬的網(wǎng)絡(luò)。

2. 請求外網(wǎng)資源

  • 現(xiàn)在假設(shè)我們上面的 net1 這個(gè)網(wǎng)絡(luò)環(huán)境中想訪問外網(wǎng)。這里的外網(wǎng)是指的虛擬網(wǎng)絡(luò)宿主機(jī)外部的網(wǎng)絡(luò)。

  • 我們假設(shè)它要訪問的另外一臺機(jī)器 IP 是 10.153.*.* ,這個(gè) 10.153.*.* 后面兩段由于是我的內(nèi)部網(wǎng)絡(luò),所以隱藏起來了。你在實(shí)驗(yàn)的過程中,用自己的 IP 代替即可。


  • 我們直接來訪問一下試試

  • 提示網(wǎng)絡(luò)不通,這是怎么回事?用這段報(bào)錯(cuò)關(guān)鍵字在內(nèi)核源碼里搜索一下:


  • 在 ip_route_output_flow 這里的返回值判斷如果是 ENETUNREACH 就退出了。這個(gè)宏定義注釋上來看報(bào)錯(cuò)的信息就是 “Network is unreachable”。

  • 這個(gè) ip_route_output_flow 主要是執(zhí)行路由選路。所以我們推斷可能是路由出問題了,看一下這個(gè)命名空間的路由表。

  • 怪不得,原來 net1 這個(gè) namespace 下默認(rèn)只有 192.168.0.* 這個(gè)網(wǎng)段的路由規(guī)則。我們 ping 的 IP 是 10.153.*.* ,根據(jù)這個(gè)路由表里找不到出口。自然就發(fā)送失敗了。

  • 我們來給 net 添加上默認(rèn)路由規(guī)則,只要匹配不到其它規(guī)則就默認(rèn)送到 veth1 上,同時(shí)指定下一條是它所連接的 bridge(192.168.0.1)。

再 ping 一下試試。

額好吧,仍然不通。上面路由幫我們把數(shù)據(jù)包從 veth 正確送到了 bridge 這個(gè)網(wǎng)橋上。接下來網(wǎng)橋還需要 bridge 轉(zhuǎn)發(fā)到 eth0 網(wǎng)卡上。所以我們得打開下面這兩個(gè)轉(zhuǎn)發(fā)相關(guān)的配置

  • 不過這個(gè)時(shí)候,還存在一個(gè)問題。那就是外部的機(jī)器并不認(rèn)識 192.168.0.* 這個(gè)網(wǎng)段的 ip。它們之間都是通過 10.153.*.* 來進(jìn)行通信的。設(shè)想下我們工作中的電腦上沒有外網(wǎng) IP 的時(shí)候是如何正常上網(wǎng)的呢?外部的網(wǎng)絡(luò)只認(rèn)識外網(wǎng) IP。沒錯(cuò),那就是我們上面說的 NAT 技術(shù)。

  • 我們這次的需求是實(shí)現(xiàn)內(nèi)部虛擬網(wǎng)絡(luò)訪問外網(wǎng),所以需要使用的是 SNAT。它將 namespace 請求中的 IP(192.168.0.2)換成外部網(wǎng)絡(luò)認(rèn)識的 10.153.*.*,進(jìn)而達(dá)到正常訪問外部網(wǎng)絡(luò)的效果。

來再 ping 一下試試,耶,通了!

  • 這時(shí)候我們可以開啟 tcpdump 抓包查看一下,在 bridge 上抓到的包我們能看到還是原始的源 IP 和 目的 IP。


  • 再到 eth0 上查看的話,源 IP 已經(jīng)被替換成可和外網(wǎng)通信的 eth0 上的 IP 了。


  • 至此,容器就可以通過宿主機(jī)的網(wǎng)卡來訪問外部網(wǎng)絡(luò)上的資源了。我們來總結(jié)一下這個(gè)發(fā)送過程



3. 開放容器端口

  • 我們再考慮另外一個(gè)需求,那就是把在這個(gè)命名空間內(nèi)的服務(wù)提供給外部網(wǎng)絡(luò)來使用。

  • 和上面的問題一樣,我們的虛擬網(wǎng)絡(luò)環(huán)境中 192.168.0.2 這個(gè) IP 外界是不認(rèn)識它的。只有這個(gè)宿主機(jī)知道它是誰。所以我們同樣還需要 NAT 功能。

  • 這次我們是要實(shí)現(xiàn)外部網(wǎng)絡(luò)訪問內(nèi)部地址,所以需要的是 DNAT 配置。DNAT 和 SNAT 配置中有一個(gè)不一樣的地方就是需要明確指定容器中的端口在宿主機(jī)上是對應(yīng)哪個(gè)。比如在 docker 的使用中,是通過 -p 來指定端口的對應(yīng)關(guān)系。


我們通過如下這個(gè)命令來配置 DNAT 規(guī)則

  • 這里表示的是宿主機(jī)在路由之前判斷一下如果流量不是來自 br0,并且是訪問 tcp 的 8088 的話,那就轉(zhuǎn)發(fā)到 192.168.0.2:80 。

  • 在 net1 環(huán)境中啟動(dòng)一個(gè) Server

外部選一個(gè)ip,比如 10.143.*.*, telnet 連一下 10.162.*.* 8088 試試,通了!


  • 開啟抓包, # tcpdump -i eth0 host 10.143.*.*??梢娫谡埱蟮臅r(shí)候,目的是宿主機(jī)的 IP 的端口。



編輯切換為居中


  • 但數(shù)據(jù)包到宿主機(jī)協(xié)議棧以后命中了我們配置的 DNAT 規(guī)則,宿主機(jī)把它轉(zhuǎn)發(fā)到了 br0 上。在 bridge 上由于沒有那么多的網(wǎng)絡(luò)流量包,所以不用過濾直接抓包就行,# tcpdump -i br0。

  • 在 br0 上抓到的目的 IP 和端口是已經(jīng)替換過的了。



  • bridge 當(dāng)然知道 192.168.0.2 是 veth 1。于是,在 veth1 上監(jiān)聽 80 的服務(wù)就能收到來自外界的請求了!我們來總結(jié)一下這個(gè)接收過程




三、總結(jié)

  • 現(xiàn)在業(yè)界已經(jīng)有很多公司都遷移到容器上了。我們的開發(fā)寫出來的代碼大概率是要運(yùn)行在容器上的。因此深刻理解容器網(wǎng)絡(luò)的工作原理非常的重要。這有這樣將來遇到問題的時(shí)候才知道該如何下手處理。

  • 本文開頭我們先是簡單介紹了 veth、bridge、namespace、路由、iptables 等基礎(chǔ)知識。Veth 實(shí)現(xiàn)連接,bridge 實(shí)現(xiàn)轉(zhuǎn)發(fā),namespace 實(shí)現(xiàn)隔離,路由表控制發(fā)送時(shí)的設(shè)備選擇,iptables 實(shí)現(xiàn) nat 等功能。

  • 接著基于以上基礎(chǔ)知識,我們采用純手工的方式搭建了一個(gè)虛擬網(wǎng)絡(luò)環(huán)境。



  • 這個(gè)虛擬網(wǎng)絡(luò)可以訪問外網(wǎng)資源,也可以提供端口服務(wù)供外網(wǎng)來調(diào)用。這就是 Docker 容器網(wǎng)絡(luò)工作的基本原理。

  • 整個(gè)實(shí)驗(yàn)我打包寫成一個(gè) Makefile,放到了這里:https://github.com/yanfeizhang/coder-kung-fu/tree/main/tests/network/test07

  • 最后,我們再擴(kuò)展一下。今天我們討論的問題是 Docker 網(wǎng)絡(luò)通信的問題。Docker 容器通過端口映射的方式提供對外服務(wù)。外部機(jī)器訪問容器服務(wù)的時(shí)候,仍然需要通過容器的宿主機(jī) IP 來訪問。

  • 在 Kubernets 中,對跨主網(wǎng)絡(luò)通信有更高的要求,要不同宿主機(jī)之間的容器可以直接互聯(lián)互通。所以 Kubernets 的網(wǎng)絡(luò)模型也更為復(fù)雜。




手工模擬實(shí)現(xiàn) Docker 容器網(wǎng)絡(luò)?。ǔ敿?xì)~)的評論 (共 條)

分享到微博請遵守國家法律
金平| 开原市| 昔阳县| 屏南县| 商河县| 达州市| 四会市| 孟州市| 武平县| 文昌市| 天津市| 手游| 白水县| 达孜县| 通州市| 芒康县| 永定县| 吉首市| 托里县| 衡山县| 亳州市| 洛隆县| 墨竹工卡县| 铜鼓县| 安多县| 西贡区| 博白县| 永城市| 桐庐县| 阿巴嘎旗| 高雄市| 琼海市| 乐清市| 珠海市| 大化| 富裕县| 张家口市| 荣成市| 五华县| 河曲县| 滨海县|