Docker 是“不可變”架構。
當你希望改變一個服務的時候(比如更新版本、修改配置、開放埠),不允許直接登入到伺服器上改變某個檔案,而是應該把這個服務整個刪掉,然後替換成新的版本。你不能改變它,只能替換它,這就是 Docker 的優點。
在服務規模大的時候,這種維護方式能夠保持每個服務版本、配置的一致性。Docker 禁止對容器內部做任何修改,所以只要檢視映象版本和排程引數,就能判斷服務的一致性。系統執行在軟體定義的基礎架構上,這樣就可以使用版本管理工具(比如 Git)管理基礎架構的變化,像管理軟體版本一樣管理整個環境。這是他的優勢。
Docker “還不夠好”。
- 不少同事或者朋友,吐槽了Docker的很多麻煩事兒,簡單說就是拋棄了傳統的作業系統環境,很多原來的東西都要用新的容器工具鏈。Docker的隔離性也沒有虛擬機器級別的好。這些都是客觀存在的。Docker是一套新的承載環境,相對於傳統的虛擬機器需要非常多的新的工具鏈,但遠沒有成熟。帶來的好處,在傳統的模式下也不是沒有方案。所以Docker仍然缺少決定性的優勢。並不能說服大家大規模的遷移和適應。
- 目前docker 映象,沒有統一標準,體現在一下幾個方面。在使用過程中會遇到過各種本班的 OS。包括 alpine, debian, ubuntu, centos, oraclelinux, redhat 等等。即使是映象採用 CentOS 母版,很多映象製作者會給作業系統減肥。經過優化後,已經不是官方版本,在使用過程中你會遇到各種麻煩。例如除錯的時候需要 curl,wget,telnet,nslookup 等工具在映象中沒有。甚至 ps, top, free, find, netstat, ifconfig 命令都沒有。很多容器都不帶 iptables 所以,即使帶有iptables 在容器中修改規則也很麻煩。
-
傳統OS 以 CentOS為例,有嚴格的安裝規範,例如:
/etc/example 配置檔案 /bin/sbin 二進位制檔案 /var/lib/example 資料檔案 /var/log/example 日誌檔案 /var/run/example PID 檔案 /etc/sysconfig/example 啟動引數檔案 /etc/system.d/example 啟動指令碼
或者被安裝在:
/usr/local/etc 配置檔案 /usr/local/bin 可執行檔案 /usr/local/share 文件
最後一種是獨立安裝在:/usr/local/example 下。容器映象那可是五花八門,沒有統一標準,如果不看 Dockerfile 根本不知道作者將檔案安裝到了哪裡。常常儲存目錄被放置在根目錄。例如 /data
- 在我的執業生涯中是遇到過 Linux 系統有BUG的,如果你採用的映象有BUG,你想過怎麼去debug 嗎?
- 在Linux是一般是採用守護程式方式啟動。啟動後進入後臺,啟動採用 systemd 。
- 容器中啟動通常是直接執行,這樣的執行方式,相當於你在linux的Shell 終端直接執行一樣,是在前臺執行,隨時 CTRL + C 或者關閉終端視窗,程式就會退出。容器採用這種方式啟動,就是為了讓 docker 管理容器,docker 能夠感知到容器的當前狀態,如果程式退出,docker 將會重新啟動這個容器。
- 守護程式方式需要記錄 pid 即父程式ID,用於後面管理該程式,例如可以實現 HUP 訊號處理。也就是 reload 操作,不用退出當前程式實現配置檔案重新整理。處理 HUP 訊號,無需關閉 Socker 埠,也不會關閉執行緒或程式,
- 使用者體驗更好。容器是直接執行(前臺執行),所以沒有 PID 也不能實現 reload 操作。 配置檔案更新需要重新啟動容器,容器啟動瞬間TCP Socker 埠關閉,此時使用者會 timeout。甚至該服務可能會引起叢集系統的雪崩效應。
- 很多映象製作者更趨向使用環境變數傳遞啟動引數。當然你也可以在容器中使用 systemd ,這樣做容器不能直接感知到容器的執行狀態,systemctl stop example 後,容器仍然正常。需要做存活和健康檢查。通過健康狀態判斷容器的工作情況。如果處於非健康狀態,將該節點從負載均衡節點池中將它踢出去。
- Linux 啟動一個應用遠遠比docker 啟動一個容器速度要快。因為物理機或者虛擬機器的Linux作業系統已經啟動,虛擬機器也分配了資源,執行可執行檔案基本上是瞬間啟動。而 docker 啟動容器,要分配資源(分配記憶體和CPU資源,新建檔案系統),相當於建立一個虛擬機器的過程,最後載入約200MB左右的映象,並將映象執行起來,所以啟動所需時間較長,有時不可控,尤其是Java應用更為突出。
- 儲存面臨的問題。傳統 Linux 直接操作本地硬碟,IO效能最大化。私有云還好辦公有云處處受限。自建的 Docker 或 Kubrnetes 可以使用宿主主機資源,公有云只能使用網路檔案系統和分散式系統。這也是我的架構中 KVM,Docker,Kubernetes,物理機混合使用的原因,根據業務場景的需要來選擇哪種方案。
- 物理機上部署 docker 可以分配宿主主機的所有資源,適合做有狀態的服務的儲存持久化的需求。
- 私有云Kubernetes 適合做 CPU密集型運算服務,雖然通過local 卷和 hostPath 可以繫結,但是管理起來不如 Docker 更方便。
- NFS 基本是做實驗用的,不能用在生產環境。我職業生涯遇到過很多奇葩,例如 NFS 卡頓,NFS 用一段時間後訪問不了,或者可以訪問,檔案內容是舊的等等。無論是NFS是更先進的分散式檔案系統,如果不是 10G乙太網,基本都不能用在生產環境。多年前我用4電口1G網路卡做埠聚合勉強可以用於生產環境,不過當年的網際網路生態跟當今不同,那時還是以圖文為主,確切的說是文字為主,配圖還很少。
- 內部域名DNS。由於在叢集環境中容器名稱是隨機,IP地址是不固定的,甚至埠也是動態的。為了定位到容器的節點,通常叢集中帶有DNS功能,為每個節點分配一個域名,在其他容器中使用域名即可訪問到需要的容器。
- 看似沒有問題,我的職業生涯中就遇到過DNS的問題,bind,dnsmseq 我都用過,都出現過事故。解析卡頓,ping www.domain.com 後遲遲解析不出IP。最長一次用了幾分鐘才解析到IP地址。
- 所以後面就非常謹慎,配置檔案中我們仍然使用域名,因為修改配置檔案可能需要 reload 應用,或者重新部署等等。域名寫入配置,方便IP地址變更。例如 db.host=www.domain.com 同時我們會在 /etc/hosts 中增加 xxx.xxx.xxx.xxx www.domain.com。這樣主要使用 /etc/hosts 做解析,一旦漏掉 /etc/hosts 配置 DNS 還能工作。
- 故障分析。DNS 使用 UDP 協議 53 埠,UDP 在網路中傳輸不會返回狀態,有無數種可能導致 DNS 解析失敗。例如內部的交換機繁忙,背板頻寬不夠(使用者儲存轉發資料包,你可以理解就是交換機的記憶體),路由的問題等等……
- 容器中的網路環境。
- 相比傳統網路,容器中的網路環境是十分複雜的。傳統網路中一個資料包僅僅經過路由器,交換機,達到伺服器,最多在服務前在增加一些防火牆,負載均衡等裝置。
- 容器網路部分實現方式SDN(軟體定義網路)相比物理機(路由器、交換機、無服務)實現相對複雜。容器裡面使用了IP轉發,埠轉發,軟路由,lvs,7層負載均衡等等技術…… 除錯起來非常複雜。docker 的 iptables 規則很頭痛。
- 例如一個TCP/IP 請求,需要經過多層虛擬網路裝置(docker0,bridge0,tun0……)層層轉發,再經過4層和7層的各種應用拆包,封包,最終到達容器內部。有興趣你可以測試一下對比硬體裝置,容器的網路延遲和吞吐量。
- 容器的管理。
- 傳統服務可以通過鍵盤和顯示器本地管理,OpenSSH 遠端管理,通過配置還能使用串列埠。容器的管理讓你抓狂 docker exec 和 kubectl exec 進入後與傳統Linux差異非常大,這是映象製作者造成的。
- 有些映象沒有初始化 shell 只有一個 $ 符號,沒有彩色顯示,可能不支援 UTF-8,中文亂碼,可能不是標準 ANSI/XTerm 終端,鍵盤定義五花八門,可能不是美式104鍵盤,國家和時區並不是東八區,HOME 目錄也是不是 /root······
- 想檢視埠情況,發現 netstat 和 ss 命令沒有。想檢視IP地址,發現 ifconfig, ip 命令沒有。想測試IP地址是否暢通,發現 ping, traceroute 沒有。想測試URL,發現 curl , wget 沒有。
- 有些映象 dnf,yum,apk,apt 可以使用,有些映象把包管理也給閹割了,你想安裝上述工具都安裝不了。然後就自己用 Dockerfile 編譯,整出200MB的映象,臥槽這麼大。
- 容器的安全。
- 很多容器的映象中是不包含 iptables 的,所以無法做顆粒度很細的容器內部網路安全設定。即使你製作的映象帶有iptables ,多數容器的策略,IP地址和埠是隨機變化的。繫結IP地址又帶了容器的複雜性。一旦攻入一個容器,進入容器後,容器與容器間基本是暢通無阻。
- 在容器中藏一個後門比物理機更容易,如上文所說很多容器中沒有除錯相關命令,限制了你排查後門的難度。所以Dockerfile 製作映象,最好使用官方映象衍生出你的映象。
- 容器與CI/CD
- 在DevOps場景中,使用 docker 或 kubernetes 做 CI/CD 是很扯淡的。當 git 產生提交後,gitlab/jenkins 啟動容器,下載程式碼,編譯,打包,測試,產生構建物,編譯 Dockerfile ,上傳 docker 映象到 registry,最後部署到容器執行。臥槽!!!速度能急死你。
- 於是乎,我們做了 Cache。 不用每次都 pull 映象,快取 Maven 的 .m2 庫,不再清理程式碼(mvn clean)提速不少,測試環境湊合用吧。 注意不mvn clean 有時會編譯出錯。至於生產環境,我就不說了,有多少人真用CD部署生產環境。
使用物理機,虛擬機器,學習成本,試錯成本,部署成本遠遠低於容器技術。Google 官方也曾經說過,未來 kubernetes 重點可能會轉向虛擬機器。不過Docker的理念和思想還是值得學習的。任何技術都會有平替,只是各種成本的妥協,在你想好怎麼處理一些問題的時候,謹慎引入Docker等各類新技術到你的生產環境中。還有一些Docker+微服務遇到的坑和想法,等有空了再更新。