鄭昀編著,文字資料來自於張帆、白俊華、劉飛宇以及網絡資料 建立於2015/10/21 最後更新於2015/10/29
關鍵詞:
Docker,容器,持續集成,持續發佈,CI,私有云
本文檔適用人員:廣義上的技術人員
首先,你要明白容器並非虛擬機,雖然它能夠解決虛擬機可以解決的問題,同時也可以解決虛擬機因爲資源要求太高而沒法解決的問題,但它真的不是虛擬機。以往咱們的開發、配置管理、部署發佈、監控報警思路都要跟着變。
其次,一開始註定只有一少部分工程遷移到容器私有云上,既然還有大多數應用服務還在虛擬機或物理機上,那麼它們之間如何通信就成了一個必須解決的問題。
那麼,咱們在構建基於容器的私有云以及相應的持續發佈時,遇到並解決了哪些問題呢?
0x00 集裝箱仍是卷掛載?
先拋出問題,下面這個選擇題你怎麼選:
- 代碼不放入 Image(鏡像) 裏,而是放在 Volume(卷) 上,這樣鏡像只須要維護程序運行的環境(如 Resin+JDK1.7)便可,準備幾種 Java、PHP、Python 運行時環境的鏡像便可,不一樣的應用運行不一樣的容器掛載不一樣的代碼;
- 代碼打包放入 Image 裏,典型場景以下圖(圖源自出處3)所示:
|
咱們再來看一下 Docker 的 Logo,它隱含天機:
一艘鯨魚大船,載着無數集裝箱。操做系統就是這艘貨輪,每個容器就是一個集裝箱,交付運行環境如同海運。你們知道嗎,集裝箱的英文單詞就是 container!
集裝箱有什麼好處?
- 規格標準,在港口和船上能夠層疊擺放,節省大量空間,
- 能夠進行快速裝卸,並可從一種運輸工具方便地換裝到另外一種運輸工具,
- 途中轉運不用移動箱內貨物,就能夠直接換裝,
- 貨物的裝滿和卸空很方便。
那麼,上面的選擇題如何回答呢?
觸控科技運維負責人蕭田國認爲,把代碼放在宿主機上,讓容器經過卷組映射來讀取,不建議採用這種方案,緣由是,將代碼拆分出容器,這違背了 Docker 的集裝箱原則:
- 從貨運工人角度考慮,總體纔是最經濟的。
- 這也致使裝卸複雜度增長。
或者說,容器時代,一切版本化,拋棄過去文件分發的思想,纔是正途,這樣也才能實現真正意義上的容器級遷移。
咱們是怎麼考慮的呢?
假定是代碼和 Image 分開的場景,主要有兩種實現方式:
- 代碼放到 slave 節點的本地磁盤:那麼必需要有一種機制,來確保每臺 slave 上代碼包版本的一致性,並且對於磁盤空間和網絡分發來說也是一種浪費(考慮一下 100 臺 slave,其中可能只有 10 臺須要運行這個版本的代碼)
- 代碼放到分佈式共享文件系統(如 ceph):它卻是解決了數據過分冗餘以及一致性的問題,但分佈式共享文件系統自己成爲了『單點』,雖然能夠設置多個副本。它的性能和可靠性都必須獲得充分保障才行。
在容器雲以前,咱們採用的是基於 OpenStack 的虛擬機管理方案。在線下環境用 OpenStack 時,咱們將全部的虛擬機放置到 ceph(注:Linux PB 級分佈式文件系統) 上,可是因爲線下 ceph 節點太少,單節點出問題時影響較大。每次 ceph 出現問題,全部的虛擬機都特別慢。
來到了容器時代,若是還把代碼放置到 ceph 上,對 ceph 的依賴過高了,ceph 一旦有問題,有可能使用了 ceph 的全部容器都會出問題 。線上業務主要考慮的是性能(容器本地運行代碼 vs 經過網絡獲取代碼)和可靠性(每一個 slave 都本地運行容器 vs 全部容器依賴共享文件系統),結論不言而喻。因此一開始決策時,沒敢把代碼放在 ceph 上,而是放在鏡像裏。
因此咱們選的是方案2,代碼在鏡像裏。
在咱們的場景裏,鏡像會被下載到 mesos slave 物理機的本地磁盤上,因此啓動容器時讀的是本地磁盤。
多說一句,Qunar 等公司在2014年選擇的是卷掛載方案,業界也有人認爲,這事兒得分環境具體問題具體分析,開發集成環境和生產環境可能就不同。
0x01 Host Networking 仍是 Bridge Networking?
網絡基礎
Docker 現有的網絡模型主要是經過使用 Network namespace、Linux Bridge、iptables、veth pair 等技術實現的。(
出處8)
- Network namespace:它主要提供了網絡資源的隔離,包括網絡設備、IPv4/IPv6 協議棧、IP 路由表、防火牆、/proc/net 目錄、/sys/class/net 目錄、端口(socket)等。
- Linux Bridge:功能至關於物理交換機,爲連在其上的設備(容器)轉發數據幀,如 docker0 網橋。
- Iptables:主要爲容器提供 NAT 以及容器網絡安全。
- veth pair:兩個虛擬網卡組成的數據通道。在 Docker 中,用於鏈接 Docker 容器和 Linux Bridge。一端在容器中做爲 eth0 網卡,另外一端在 Linux Bridge 中做爲網橋的一個端口。
容器的網絡模式
用來設置網絡接口的 docker run --net 命令,它的可用參數有四個:
- none:關閉了 container 內的網絡鏈接。容器有獨立的 Network namespace,但並無對其進行任何網絡設置,如分配 veth pair 和網橋鏈接,配置 IP 等。
- bridge:經過 veth 接口來鏈接其餘 container。這是 docker 的默認選項。
- host:容許 container 使用 host 的網絡堆棧信息。容器和宿主機共享 Network namespace。
- container:使用另一個 container 的網絡堆棧信息。kubernetes 中的 pod 就是多個容器共享一個 Network namespace。
咱們須要從中選一個做爲咱們的網絡方案,實際上只有 bridge 和 host 兩種模式可選。(想了解這四個參數,請翻到附錄B之 Network settings。)
在 docker 默認的網絡環境下,單臺主機上的容器能夠經過 docker0 網橋直接通訊,以下圖(圖做者馮明振)所示:
而不一樣主機上的容器之間只能經過在主機上作端口映射進行通訊。這種端口映射方式對集羣應用來講極不方便。(
出處8)咱們重點要解決這個問題。
先看一下其餘學生怎麼作這道選擇題的:
公司
|
網絡方案
|
備註
|
網易(2014)
|
tinc+quagga+pipework
|
Pipework 是對 Docker Bridge 的擴展,它由 200 多行 shell 腳本實現。經過使用 ip、brctl、ovs-vsctl 等命令來爲 Docker 容器配置自定義的網橋、網卡、路由等。
|
CoreOS(2014)
|
flannel
|
flannel 屬於隧道方案,UDP 廣播,VxLan。
|
大衆點評網(2015) |
Bridge Networking 工做在 level 2 的模式,使公共 IP 得以暴露出來,這部分是作了定製的
|
|
汽車之家(2015) |
Bridge Networking
|
|
去哪兒(2015)
|
Host Networking
|
『大吞吐量平臺下,bridge 模式性能測試都偏低,因而選擇了 host 模式』——20150915,徐磊
|
芒果TV(2015)
|
Macvlan
|
屬於路由方案。『從邏輯和 Kernel 層來看隔離性和性能最優的方案,基於二層隔離,因此須要二層路由器支持,大多數雲服務商不支持,因此混合雲上比較難以實現』——20150505,彭哲夫
|
新浪微博(2015)
|
Host Networking
|
|
先說一下 host 模式。
容器的網絡直接和外部網絡打通,解決了不一樣 slave 物理機之間容器互聯互通的問題,減小了 NAT 轉換的損耗,但須要自行來維護容器的 IP 地址和端口,不然會衝突,這增長了管理上的複雜度。請考慮這個場景:在同一個 slave 物理機上運行兩個 80 端口 Nginx 容器的狀況,除非分配兩個不一樣的 IP 地址,這種方案咱們以前在 shipyard 方案中使用過。
Docker 部署被詬病最多就是網絡,平臺目前採用的是 host 模式,爲何沒有采用 NAT 或者 Bridge 呢?因爲涉及的技術細節比較繁冗,這裏僅分享一些踩過的坑。例如 NAT 使用 iptables 底層流量轉發依靠內核 netfilter 模塊,其默認僅保持 65536 個連接,在服務有大量連接的場景下,會出現大量拒絕連接的現象。再如 Bridge 的 MAC 地址默認是選擇其子接口中最早的一個,這樣就會致使一個宿主機下多個容器啓停時出現網絡瞬斷。還有不少問題不一一列舉,平臺將來計劃採用 vlanif 的方案來解決容器網絡部署難題。
再看 bridge 模式,它是 docker 默認的網絡模式,爲了解決容器間互通問題,一般有兩種解決方案:
- 採用 SDN(軟件定義網絡) 技術,如 flannel,weave 等,直接將容器內網打通;
- 採用 NAT(網絡地址轉換) 技術,直接使用 slave 主機的 IP 地址互通。
咱們認爲,bridge 模式加 NAT 方式,是當前 mesos+marathon 支持的最好的模式(採用的是 container port,host port,service port 的對應關係)。咱們的容器雲主要構建於 mesos+marathon 之上,封裝了咱們的業務邏輯,採用一些方式解決了容器與現存非容器架構之間的互通問題, 如 consul 的服務註冊和發現機制,dubbo 的應用註冊和發現機制等。
最終咱們選擇了 Bridge Networking。
Libnetwork 是 Docker 官方 2015年初推出的項目,旨在將 Docker 的網絡功能從 Docker 核心代碼中分離出去,造成一個單獨的庫。 Libnetwork 以插件的形式爲 Docker 提供網絡功能,使得用戶能夠根據本身的需求實現本身的 Driver 來提供不一樣的網絡功能。 Libnetwork 所要實現的網絡模型基本是這樣的: 用戶能夠建立一個或多個網絡(一個網絡就是一個網橋或者一個 VLAN ),一個容器能夠加入一個或多個網絡。 同一個網絡中容器能夠通訊,不一樣網絡中的容器隔離。 Docker 網絡的發展之後就都在這個項目上了。
0x02 容器要固定IP嗎?
默認狀況下,當 docker 啓動時,它會在宿主機器上建立一個名爲 docker0 的虛擬網絡接口。它會從 RFC 1918 定義的私有地址中隨機選擇一個主機不用的地址和子網掩碼,並將它分配給 docker0。(
出處9)因此,
容器的 IP 是動態變化的。
因而乎你們一開始接觸 Docker 就紛紛提出給容器分配靜態 IP 的需求。看一下
各大公司容器雲技術棧對比,幾家歷史包袱較重的公司都選擇不讓上層應用感知到底層是 VM 仍是容器,如360/點評/汽車之家,都改了docker 代碼,固定了容器 IP。
docker 官方對此需求的態度是,Docker maintainers prefers a more abstract way to separate user intent from operational intent. Based on this feedback and various other discussions on a flexible ip address management, we feel that having a pluggable IPAM will help a great deal.
上一章節裏提到的
Pipework 能夠作到讓容器有一個能夠直接訪問的靜態 IP 地址,just like this:
If you’re using named Docker instances, then adding the IP address 10.40.33.21 to a Docker instance bind is as simple as: php
pipework br0 bind 10.40.33.21/24
If you want to route out of 10.40.33.1, change it to: html
pipework br0 bind 10.40.33.21/24@10.40.33.1
(出處:https://opsbot.com/advanced-docker-networking-pipework/)
|
這樣的話,須要在容器啓動後在宿主機上運行 pipework 來設置容器的 ip,這樣就增長了自動化的難度。
咱們的考慮是,第一,容器應該是無狀態的,分配固定 IP 違背了這個理念,第二,Docker 剛出來一兩年,還在飛速發展中,如今你改了內核代碼,未來它大版本升級,你怎麼跟隨?第三,使用固定 IP 很大程度上是爲了讓技術人員能像之前同樣直接登陸虛擬機操做,但咱們的持續集成管理平臺裏使用 webconsole 也能登到容器裏面操做。
咱們的實踐是:首先,咱們內部調用都走內部域名,有專門的 DNS Server,其次,咱們很早之前就引入了服務治理框架 Dubbo,基本作到了服務的註冊和發現。再次,咱們在容器雲裏引入了 consul 的註冊和發現服務,配合 slave 節點上的 registrator 容器,以及 haproxy 和 consul-template 組件,實現了完整的容器服務的註冊和發現架構。
第一,dubbo 能幫咱們解決什麼容器問題:
進駐容器雲後 java 工程之間的調用:直接將物理機的 ip 和容器 java 工程對外提供的隨機端口,註冊到 dubbo 裏。例如, 容器 javaA 要調用容器 javaB,容器 javaB 已經將它對外提供的 ip 和隨機端口註冊到了 dubbo 裏,容器 javaA 能夠從 dubbo 裏找到容器 javaB 相關的信息直接調用。
第二,dubbo 不能解決的問題有:
- 咱們的線上還有 php 或者其餘開發語言的應用,它怎麼調用容器化的 java 工程呢?
- 容器化的工程如何對外網提供服務 ,如何把它放入 nginx 和 F5中呢?
基於以上兩個需求、以及同一個工程的容器也要作負載均衡 ,咱們引入了 consul + consul-template + registrator + haproxy,來作服務註冊和發現。
實施詳細流程以下:
第一步,每一臺 slave 節點上都會啓動一個 registrator 的容器,該容器檢測 docker 引擎的 unix socket 地址:/var/run/docker.sock,從中得到該 slave 上全部容器的啓動、中止以及其餘運行時相關的信息;
第二步,registrator 容器同時會把它獲取的其餘容器的信息(IP、端口等)註冊到 consul 集羣中,服務信息以下面的輸出:
curl 172.28.128.3:8500/v1/catalog/service/python-micro-service
"Address":"172.28.128.3",
"ServiceID":"registrator:service1:5000",
"ServiceName":"python-micro-service",
第三步,consul-template 組件從 consul 中得到全部容器註冊的服務信息,應用相應的規則(如,區分爲鏡像環境和生產環境)後,將容器提供的服務信息寫入 haproxy 中,並 reload haproxy 以生效配置。
第四步,client 端經過 DNS 中註冊的域名指向 haproxy 的地址,訪問到具體提供服務的容器。
0x03 容器內部如何獲取宿主機的IP?
容器啓動後,宿主機 IP 會被寫入容器的環境變量裏。
容器內的程序能夠從環境變量裏讀取。
——未完待續——
附錄A:參考資源
3,2015,Docker持續部署圖文詳解;
附錄B:術語簡單介紹
Registry/Repository/Image/Tag:
Registry 存儲鏡像數據,而且提供拉取和上傳鏡像的功能。Registry 中鏡像是經過 Repository 來組織的,而每一個 Repository 又包含了若干個 Image。
- Registry 包含一個或多個 Repository
- Repository 包含一個或多個 Image
- Image 用 GUID 表示,有一個或多個 Tag 與之關聯
好比,你在本地機器上運行 docker image 命令,可能會獲得這樣的輸出結果:
咱們常說的「ubuntu」鏡像其實不是一個鏡像名稱,而是表明了一個名爲 ubuntu 的 Repository,同時在這個 Repository 下面有一系列打了 tag 的 Image,Image 的標記是一個 GUID,爲了方便也能夠經過 Repository:tag 來引用。
——《玩轉Docker鏡像,2014,孫宏亮》
|
Network settings:
docker run --net 的四個參數進一步解釋以下:
None:
將網絡模式設置爲 none 時,這個 container 將不容許訪問任何外部 router。這個 container 內部只會有一個 loopback 接口,並且不存在任何能夠訪問外部網絡的 router。
Bridge:
默認爲 bridge 模式。此時在 host 上面將存在一個 docker0 的網絡接口,同時會針對 container 建立一對 veth 接口。其中一個 veth 接口是在 host 充當網卡橋接做用,另外一個 veth 接口存在於 container 的命名空間中,而且指向 container 的 loopback。docker 會自動給這個 container 分配一個 IP,而且將 container 內的數據經過橋接轉發到外部。
以下圖(圖做者馮明振)所示:
容器 eth0 網卡從 docker0 網橋所在的 IP 網段中選取一個未使用的 IP,容器的 IP 在容器重啓的時候會改變。docker0 的 IP 爲全部容器的默認網關。容器與外界通訊爲 NAT。
Host:
此時,這個 container 將徹底共享 host 的網絡堆棧。host 全部的網絡接口將徹底對 container 開放。container 的主機名也會存在於 host 的 hostname 中。這時,container 全部對外暴露的 port 和對其它 container 的 link,將徹底失效。
Container:
此時,這個 container 將徹底複用另外一個 container 的網絡堆棧。
|
附錄C:
目前,咱們容器管理集羣的技術棧包括如下內容: java
- mesos(資源調度)
- marathon(服務編排)
- chronos(分佈式計劃任務)
- docker(容器引擎)
- consul+registrator(服務註冊和發現)
- haproxy(負載均衡)
- prometheus(服務監控)
- nagios/zabbix(節點監控)
- salt(節點配置管理)
- cobbler(節點自動化裝機)
- ELK(日誌收集分析)
窩窩持續集成管理平臺在這些技術的基礎上,實現了咱們的集羣管理、容器管理、應用管理等業務流程。 python
歡迎訂閱個人微信訂閱號『老兵筆記』,請掃描二維碼關注:
-EOF-