VLCP的Docker network插件
(題圖為Docker Engine結構圖)
關注專欄的各位讀者大家久等了,前一段時間在公司搭建了一個SDN控制器VLCP相關的應用項目,利用VLCP的SDN功能為Docker Swarm提供網路服務。對於第一次閱讀專欄的讀者,VLCP是開源的SDN控制器,它的項目地址是:
GitHub - hubo1016/vlcp: A full stack framework for SDN Controller, support Openflow 1.0, Openflow 1.3, and Nicira extensions.
本次發布的Docker Plugin也已經開源,地址為:
GitHub - hubo1016/vlcp-docker-plugin: Docker network plugin for VLCP
傳統上Docker使用一種NAT的方式對外提供服務,將容器內部的埠通過物理主機IP對外映射或者負載均衡,訪問外網也通過物理主機的IP地址進行nNAT,這在服務請求數比較高、帶寬比較大的時候可能會出現性能問題,引起如conntrack表耗盡、埠號耗盡等問題。使用SDN技術,我們可以為每n個容器提供一個獨立的、外網可直接訪問的IP地址,這樣就大大拓展了容器的應用方式,簡化了容器中服務的設計。
除了可以在Docker以及Docker Swarm環境當中實用以外,Docker也是一個理想的SDN以及NFV(網路虛擬化)的學習平台:有方便的CLI工具,網路相關的參數比較透明,很容易測試,而且對環境要求不高,物理機、虛擬機上均可以運行,還可以在很少量的伺服器上創建大量的docker容器用於測試。本文簡單介紹了在物理伺服器或者虛擬機上搭建一個SDN測試環境的方法。
目標
- 在兩個或更多節點上部署Docker Engine
- 在這些節點上部署VLCP Docker plugin,同時介紹VLCP的配置方式
- 創建簡單的網路與容器進行測試
- 部署Docker Swarm進行統一管理
如果你希望跟著本文的步驟進行操作,你應當準備兩台或以上的物理伺服器或虛擬機,確保它們的網路可以互通。由於許多組件的API介面並沒有安全認證,為了安全起見它們應該位於一個私有網路中。作為練習或測試目的的話,同一台PC上建立的兩台Linux虛擬機足夠完成這個目標。如果希望使用公有雲中的伺服器,請確保它們正確配置了防火牆或iptables,保證只有內部地址才能訪問額外開放的埠。其中一些組件如Redis,如果配置不正確,會導致嚴重的安全問題,外網訪問者可以通過Redis修改你的伺服器的安全配置並獲取ROOT許可權。如果你只有一台伺服器,也可以部署,但不能進行跨節點網路的測試。
在本文中使用的操作系統為CentOS 7。Docker要求比較新版本的內核,Ubuntu和CentOS 7 一般是可以的,如果希望使用Ubuntu可以參考各個組件的官方文檔。確保yum源正確配置,extras源啟用,並安裝好epel-release的額外的源備用:
yum install epel-releasen
部署Docker Engine
官方的部署方式: Installation on CentOS
推薦使用最新版,不要使用CentOS鏡像中的docker包,而是安裝docker-engine。在CentOS上實際上只有兩步:
sudo tee /etc/yum.repos.d/docker.repo <<-EOFn[dockerrepo]nname=Docker Repositorynbaseurl=https://yum.dockerproject.org/repo/main/centos/7/nenabled=1ngpgcheck=1ngpgkey=https://yum.dockerproject.org/gpgnEOFnyum install docker-enginen
對於跨多節點的網路,需要配置一個集群KV存儲,可以選用etcd、zookeeper、consul,這裡使用etcd,因為可以通過yum安裝:yum install etcd
安裝etcd後,修改/etc/etcd/etcd.conf,將
ETCD_LISTEN_CLIENT_URLS="http://localhost:2379"nETCD_ADVERTISE_CLIENT_URLS="http://localhost:2379"n
中的localhost替換成本機IP地址,這個IP地址應當是其他節點可以訪問的。
再次強調:請注意埠的安全性,不要在公網上開啟內部服務埠!
生產環境中的etcd或其他KV存儲應當以高可用集群的方式部署,這裡為了簡便起見,只部署一台作為演示用。
修改docker的配置來使用集群存儲,官方文檔參見:Control and configure Docker with systemd
# Start etcdnsystemctl enable etcdnsystemctl start etcdn# Create configurationnmkdir -p /etc/systemd/system/docker.service.d/ncd /etc/systemd/system/docker.service.d/ntee cluster.conf <<-EOFn[Service]nExecStart=nExecStart=/usr/bin/docker daemon -H 0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://127.0.0.1:2379/nEOFn# Enable/start dockernsystemctl daemon-reloadnsystemctl enable dockernsystemctl start dockern
我們同時額外開啟了:2375埠上的docker API服務,這是為了以後部署Docker Swarm準備的。將etcd中的127.0.0.1替換成實際的IP地址。
部署VLCP Docker Plugin
部署VLCP有兩個前提條件:正確安裝Python(2.6+或3.x或PyPy),正確安裝OpenvSwitch。Python 2.7是CentOS自帶的解釋器,你也可以選擇創建virtualenv,如果想要嘗試PyPy可以使用官方提供的Portable的版本:GitHub - squeaky-pl/portable-pypy: Portable 32 and 64 bit x86 PyPy binaries for many Linux distributions. 或者使用其他編譯的CPython版本。對系統自帶的Python,需要安裝好pip,可以使用get-pip.py,也可以使用yum源中的pip(yum install python-pip,最好在安裝後使用pip install --upgrade pip wheel setuptools升級)
安裝OpenvSwitch要困難一些,CentOS官方和epel-release中並沒有OpenvSwitch的RPM,需要自己進行編譯打包,為此需要安裝gcc等必要組件(一般對於開發者來說都是通過yum groupinstall "Development Tools"一次全部裝上)。可以通過官方地址下載最新的OpenvSwitch Release tarball:
Download OpenvSwitch
RPM打包的過程可以參考官方文檔:INSTALL.Fedora.md (Open vSwitch 2.5.90) (CentOS 7使用和Fedora相同的打包方式)。簡單來說,首先將源代碼的tarball複製到rpm編譯目錄中,然後運行rpmbuild就可以了(將x.y.z替換成實際的文件名):
mkdir -p $HOME/rpmbuild/SOURCESncp openvswitch-x.y.z.tar.gz $HOME/rpmbuild/SOURCESnmkdir openvswitch-sourcencd openvswitch-sourcentar axf ../openvswitch-x.y.z.tar.gznrpmbuild -bb rhel/openvswitch-fedora.specn
如果成功運行,RPM就會生成在$HOME/rpmbuild/RPMS/x86_64目錄下,在CentOS中我們一般只需要安裝openvswitch-x.y.z-1.el7.centos.x86_64.rpm這一個rpm文件,通過yum localinstall命令安裝:
yum localinstall $HOME/rpmbuild/RPMS/x86_64/openvswitch-x.y.z-1.el7.centos.x86_64.rpmnsystemctl enable openvswitchnsystemctl start openvswitchn
然後是安裝VLCP與VLCP Docker plugin,這一步可以通過pip進行:
pip install --upgrade vlcp-docker-pluginn
在CentOS 7中,服務可以通過systemd啟動,因此python-daemon並不是必須的。可以選擇額外安裝hiredis,如果選用CPython,可以考慮安裝vlcp_event_cython來提升性能。不要在PyPy中安裝vlcp_event_cython(hiredis是可以的)。
pip install vlcp_event_cython hiredisn
VLCP目前使用Redis作為中心存儲節點,目前正在開發支持使用ZooKeeper作為中心存儲的方案,到時可以與Docker的集群存儲與Docker Swarm復用一個ZooKeeper集群。安裝redis:
yum install redisn
修改/etc/redis.conf,找到bind 127.0.0.1這一行,將其修改為bind 0.0.0.0或者具體的IP地址。在真實使用中可以考慮配置主備,開啟AOF等方式來提高數據可靠性(appendonly yes)
配置VLCP
創建VLCP的配置文件在/etc/vlcp.conf,參考的配置文件也可以在github上找到:vlcp-docker-plugin/vlcp.conf at master · hubo1016/vlcp-docker-plugin · GitHub
#server.debugging=Truenmodule.jsonrpcserver.ovsdb=Truenmodule.jsonrpcserver.client=Truenmodule.jsonrpcserver.url=unix:///var/run/openvswitch/db.socknmodule.httpserver.url=Nonenmodule.httpserver.vhost.api.url=tcp://localhost:8081nmodule.httpserver.vhost.docker.url=unix:///run/docker/plugins/vlcp.socknmodule.redisdb.url=tcp://localhost/n#module.console.startinconsole=Truenserver.logging.version=1nserver.logging.formatters={fileFormatter:{format:%(asctime)s %(levelname)s %(name)s: %(message)s}}nserver.logging.handlers={fileHandler:{class:logging.handlers.TimedRotatingFileHandler,n formatter:fileFormatter,n filename:/var/log/vlcp.log,n when:midnight,n interval:1,n backupCount:7}}nserver.logging.root={level:INFO,n handlers:[fileHandler]}n#protocol.openflow.debugging = Truen#protocol.redis.debugging = Truenmodule.l2switch.learning=Falsenmodule.l2switch.nxlearn=Falsenmodule.vxlancast.learning=Falsenmodule.vxlancast.prepush=Truenserver.startup = (vlcp.service.sdn.viperflow.ViperFlow,n vlcp.service.sdn.ioprocessing.IOProcessing,n vlcp.service.sdn.l2switch.L2Switch,n vlcp.service.manage.modulemanager.Manager,n vlcp.service.manage.webapi.WebAPI,n vlcp.service.utils.autoload.AutoLoad,n vlcp.service.sdn.vxlancast.VXLANCast,n vlcp.service.sdn.arpresponder.ARPResponder,n vlcp.service.sdn.dhcpserver.DHCPServer,n vlcp_docker.dockerplugin.DockerPlugin)n
VLCP的配置文件每一行是一項配置,等號之前由一個點號分隔的字元串組成,代表配置項名稱;等號之後是一個Python的常量表達式,可以使用整數、字元串、常量、元組、字典、列表等。#之後的內容是注釋。如果下一行的開頭是空格字元,則這一行會被當作是上一行配置的延續。server.logging項是logging模塊的字典方式配置。module開頭的是各個模塊的配置。server.startup是需要啟動的模塊的列表,這些模塊的依賴項會自動被載入。WebAPI將各個模塊的API映射到了一個HTTP的服務上,這個服務通過
module.httpserver.vhost.api.url=tcp://localhost:8081n
配置到了本機的8081埠上,我們後面會使用這個HTTP服務來進行初始化配置。
ViperFlow提供了一組標準的創建網路與埠的API。vlcp.service.sdn前綴的模塊是相應的SDN功能模塊,每個模塊負責一部分流表的項目的維護,它們會自動將OpenFlow和OVSDB的服務作為依賴項載入起來,開啟在默認埠上,由於和OpenvSwitch部署在同一台物理主機上,OVSDB通過默認的UNIX socket進行連接。DockerPlugin是Docker的網路插件,它是從vlcp-docker-plugin包中提供的。
我們需要修改其中的一項,將Redis的地址修改為實際的地址:
module.redisdb.url=tcp://<your-server-address>[:yourport]/n
我們希望將VLCP部署成為服務,首先創建一個簡單的啟動腳本並保存為/usr/sbin/vlcp:
tee /usr/sbin/vlcp <<-EOFn#!/bin/bashnmkdir -p /run/docker/pluginsnrm -f /run/docker/plugins/vlcp.socknexec /bin/python -m vlcp.startnEOFnchmod +x /usr/sbin/vlcpn
腳本中創建插件目錄,如果UNIX socket殘留則進行清除,然後啟動VLCP。如果使用其他解釋器,替換為解釋器的絕對地址(必須使用絕對地址)。
創建一個systemd的服務項並保存為/usr/lib/systemd/system/vlcp.service:
tee /usr/lib/systemd/system/vlcp.service <<-EOFn[Unit]nDescription=VLCPnAfter=network.targetnBefore=docker.servicen[Service]nExecStart=/usr/sbin/vlcpn[Install]nWantedBy=multi-user.targetnEOFnsystemctl daemon-reloadnsystemctl enable vlcpnsystemctl start vlcpn
OpenvSwitch與VLCP服務都啟動之後,我們在OpenvSwitch中創建初始網橋,並使其連接到我們的控制器。OpenvSwitch是一個軟體的OpenFlow交換機,它的每個橋在OpenFlow協議中都是一個獨立的交換機,對於我們的SDN系統來說,只需要有一個橋就足夠了,所有的docker容器可以連接到同一個橋上,通過流表進行控制。我們創建的新橋起名為dockerbr0:
ovs-vsctl add-br dockerbr0novs-vsctl set-fail-mode dockerbr0 securenovs-vsctl set-controller dockerbr0 tcp:127.0.0.1n
set-fail-mode設置了當橋無法連接到控制器時的默認行為,設置為保持流表(secure)可以幫助我們在控制器維護或意外退出的情況下,保證交換行為正確,這是很重要的。set-controller設置控制器地址,將橋變為OpenFlow模式。設置完成稍等片刻後使用ovs-vsctl show命令查看橋,應該可以看到控制器已經成功連接(is_connected: true):
在其他節點上部署軟體
完成第一個節點上組件的部署之後,其他節點上的部署方式與前面的方法相同,注意etcd不需要再次部署,Redis不需要再次部署,而是將所有相應的地址指向第一台伺服器的IP地址或域名。
規劃網路並初始化
在實際環境中,我們應當提前對網路的使用有所規劃,包括使用什麼樣的技術進行跨節點連接, 規劃多少個網路,分別使用什麼IP地址段。VLCP目前支持的跨節點網路技術主要包括VLAN與VXLAN,VLAN即通過trunk介面的特定VLAN子介面進行通信,需要外層交換機配合進行相應的設置;VXLAN則是通過VXLAN隧道的模式進行節點之間的通信,它只需要節點之間能進行IP層的通信即可,因此建立條件大大放寬,這種模式也叫做Overlay網路,是現在SDN發展的熱點。Docker也有自帶的Overlay網路支持,但使用VLCP提供的VXLAN網路,可以支持通過SDN分散式網關進行IP層通信(三層功能目前基本開發完成,處於測試階段,尚未發布)。VLAN與VXLAN可以在相同的節點上混用。
VLCP使用ViperFlow模塊提供一組標準的網路模型:
- 物理網路代表一種對流量的歸類,同一個物理網路中的流量是經過相同的網路設備進行交換的,它們內部通過VLAN或者VXLAN的技術進行隔離
- 物理埠是OpenvSwitch網橋上的用於與外部網路進行交換的埠,相當於交換機的上聯口。一般來說每個OpenvSwitch的每個物理網路上有一個物理埠,每個OpenvSwitch上的物理埠配置在一定程度上是對稱的(比如埠名稱相同)
- 邏輯網路是物理網路通過隔離技術劃分出的二層交換域,同一個邏輯網路中的埠可以互通,不同邏輯網路中的埠二層不能互通。通常一個物理網路中有多個邏輯網路,它們在跨節點通信時會共用物理網路中的物理埠。
- 子網是邏輯網路上的三層概念,用來表示IP段、網關、路由等信息。
- 邏輯埠是連接虛擬網路中的實體(如虛擬機或容器)的埠,每個虛擬機或容器通過一個邏輯埠與SDN網路相連。邏輯埠屬於某個邏輯網路,它只能跟同邏輯網路中的埠互通,不是同一個邏輯網路中的埠,即使位於同一個節點上也不能互通。
本文中我們創建一個VXLAN的物理網路,並在下面劃分出兩個邏輯網路,IP段分別是192.168.1.0/24和192.168.2.0/24,使用VNI10000和VNI10001進行隔離。VNI是VXLAN網路的一個編號,作用類似於VLAN tag,可以用於不同流量的分類與隔離。創建物理網路的過程通過ViperFlow API進行,這就用到我們前面的WebAPI模塊提供的HTTP介面,從任意一個節點上執行:
curl -g http://localhost:8081/viperflow/createphysicalnetwork?type=vxlan&vnirange=`[[10000,11000]]`&id=vxlann
我們通過curl的GET方式提交請求給WebAPI模塊,URL路徑格式是模塊名稱全小寫作為目錄,API名稱作為文件名,參數會作為key-value參數提供給相應的API。使用反引號的`[[10000,11000]]`是WebAPI提供的擴展,對於反引號引起來的參數會自動翻譯成Python的常量表達式,比如這個表達式會翻譯成嵌套的兩層list,內層是[10000,11000],它代表預先分配VNI 10000-11000作為這個物理網路的VNI範圍,邏輯網路的VNI可以在這個範圍內取;為了不跟bash與curl的功能衝突,需要用單引號引起來,然後加上-g選項(關閉curl對[]的擴展)。API的返回值是JSON格式,可以通過一個簡單的技巧——管道輸出到python模塊json.tool來顯示成格式化的結果:
curl -g http://localhost:8081/viperflow/createphysicalnetwork?type=vxlan&vnirange=`[[10000,11000]]`&id=vxlan | python -m json.tooln
在這個API中,我們創建了一個物理網路,類型是vxlan,VNI範圍從10000到11000,同時起了一個好記的ID就叫做vxlan。如果不指定ID,VLCP會自動分配一個UUID。
其他模塊也有暴露的API,可以通過WebAPI模塊用相同的方法調用,也可以用discover方法來查看支持的API的列表、參數和說明:
curl -g http://localhost:8081/viperflow/discover?details=True | python -m json.toolncurl -g http://localhost:8081/manager/discover?details=True | python -m json.tooln
物理網路只需要在任意節點上創建一次,配置會通過Redis同步到所有需要的節點上。
接下來我們創建物理埠,對VXLAN隔離類型來說,物理埠是OpenvSwitch上一個類型為vxlan的port,可以用下面的命令創建:
ovs-vsctl add-port dockerbr0 vxlan0 -- set interface vxlan0 type=vxlan options:local_ip=10.9.1.2 options:remote_ip=flow options:key=flown
ovs-vsctl命令用--隔離的多個子語句形成一個transact(事務),它可以在創建port的同時指定port的屬性。我們使用vxlan0作為新創建埠的名稱,這個名稱後面要用。在每台機器上,將10.9.1.2替換成你這台機器的實際的IP地址(注意每台機器都應當是不同的),這對於正確建立VXLAN隧道至關重要。如果伺服器有多個IP地址,選擇其他節點可達的IP地址。remote_ip=flow與key=flow指定port的遠端IP地址與隧道VNI由流表控制。
接下來在VLCP中創建物理埠的配置:
curl -g http://localhost:8081/viperflow/createphysicalport?physicalnetwork=vxlan&name=vxlan0n
我們指定了物理網路為vxlan,埠名稱為vxlan0,這會在所有加入的節點上識別名為vxlan0的埠為相應物理網路的物理埠,這樣我們不需要為每個新加入的節點調用API,只需要在部署時正確創建相應的vxlan0埠即可,節點會自動通過中心伺服器(Redis)進行服務發現邏輯,在長時間無連接後被移除,相應節點的VXLAN端點地址則通過OpenvSwitch埠的配置進行識別。
在Docker中創建網路
在Docker Plugin的模式中,邏輯網路通過docker命令或API進行創建。按照規劃,我們要創建192.168.1.0/24與192.168.2.0/24兩個網路,可以通過以下命令進行:
docker network create -d vlcp --subnet 192.168.1.0/24 --gateway 192.168.1.1 --internal -o physicalnetwork=vxlan -o vni=10000 -o mtu=1450 vxlan1ndocker network create -d vlcp --subnet 192.168.2.0/24 --gateway 192.168.2.1 --internal -o physicalnetwork=vxlan -o vni=10001 -o mtu=1450 vxlan2n
創建了兩個網路,名稱分別為vxlan1與vxlan2,指定了相應的子網網段,將子網中第一個IP地址作為網關分配,指定--internal的作用是禁用默認的通過docker宿主機NAT的網路(由SDN網路提供互聯功能),-o的選項則分別指定了物理網路(和createphysicalnetwork時指定的ID一致)、VNI以及MTU值。設置MTU為1450是因為我們的外層網路的MTU為1500,而Overlay網路需要一個額外的二層、三層、四層(UDP)包頭,合計14 + 20 + 8 = 42位元組,再留下8位元組給可能的IP選項。如果外層網路支持至少1550的MTU,則可以使用默認的1500位元組的MTU。
現在我們在新網路中創建一個docker容器,使用官方的centos鏡像:
docker pull centosndocker run -d -it --net=vxlan1 --ip 192.168.1.2 centosn
一切順利的話這個容器就成功啟動並運行了,這是個空的bash。我們接下來啟動另一個容器,測試網路是否聯通:
docker run -it --rm --net=vxlan1 centos ping 192.168.1.2n
順利的話就會發現ping可以通。在其他節點上也可以嘗試這個命令,通過VXLAN網路,任意節點都可以與我們之前創建的容器互通。
創建Docker Swarm集群
Docker Swarm是Docker官方提供的組件,可以將一個集群通過統一的API入口進行管理,自動進行容器創建的調度。配合SDN網路,Docker Swarm的能力會更加強大,用戶完全不需要關心容器實際創建在了哪個節點上,只需要統一使用容器的IP地址進行訪問就行了。
Docker Swarm的安裝部署方式非常有意思,可以以Docker容器的方式直接部署在Docker集群中。在所有節點上執行:
docker pull swarmndocker run -d swarm join --advertise=<machine-ip>:2375 etcd://<etcd-server-ip>:2379/n
這個容器進行服務發現,將本機的2375埠(我們之前在docker服務當中啟動了這個埠)通過etcd公告出去。
在某台主機上創建一個swarm manage的容器,將埠號4000開放為Swarm API埠,通過傳統方式經過映射對外暴露:
docker run -d -p 4000:4000 swarm manage -H :4000 --replication --advertise <local-ip>:4000n
可以創建多個swarm manage節點,它們會自動經過競選選出一個節點作為主節點,主節點故障時由從節點接管。現在我們通過Swarm API來查看或者創建、刪除容器:
docker -H :4000 psn
與之前相比,只是增加了-H的選項,用於指定使用Swarm API埠。但效果是創建的容器會自動分布到集群中的合適的主機上,很方便管理。再加上網路全部互通,管理大規模分散式的業務也很輕鬆了。
總結
本文總結了部署一套SDN+Docker的環境的步驟,雖然看上去組件比較多有些複雜,但每個組件的安裝都可以通過腳本完成,也可以用ansible等部署工具進行部署,具備實用的潛力。為了簡便,本文並沒有對Docker或DockerSwarm或其他組件進行最佳的配置,實際使用中應當結合需要進行規劃。
對這套系統的壓力測試顯示,在三台物理伺服器構成的集群上創建容器,容器中啟動ping命令保證有網路流量存在,在三台伺服器上至少可以創建2000個容器,仍然可以保持網路暢通,足以滿足任何現實的業務需要。目前L3功能仍然處於測試階段,未來通過L3的分散式路由,所有Docker容器都可以獲得順暢的東西和南北網路。
推薦閱讀:
※OpenStack Neutron概述
※BGP漫談
※SDN 技術指南(二):OpenFlow
※SDN閑聊
※OpenStack容器網路項目Kuryr(libnetwork)