Docker遇到的一些問題和感想

有痣青年發表於2022-03-03

Docker 是“不可變”架構。

  當你希望改變一個服務的時候(比如更新版本、修改配置、開放埠),不允許直接登入到伺服器上改變某個檔案,而是應該把這個服務整個刪掉,然後替換成新的版本。你不能改變它,只能替換它,這就是 Docker 的優點。

  在服務規模大的時候,這種維護方式能夠保持每個服務版本、配置的一致性。Docker 禁止對容器內部做任何修改,所以只要檢視映象版本和排程引數,就能判斷服務的一致性。系統執行在軟體定義的基礎架構上,這樣就可以使用版本管理工具(比如 Git)管理基礎架構的變化,像管理軟體版本一樣管理整個環境。這是他的優勢。

Docker “還不夠好”。

  1. 不少同事或者朋友,吐槽了Docker的很多麻煩事兒,簡單說就是拋棄了傳統的作業系統環境,很多原來的東西都要用新的容器工具鏈。Docker的隔離性也沒有虛擬機器級別的好。這些都是客觀存在的。Docker是一套新的承載環境,相對於傳統的虛擬機器需要非常多的新的工具鏈,但遠沒有成熟。帶來的好處,在傳統的模式下也不是沒有方案。所以Docker仍然缺少決定性的優勢。並不能說服大家大規模的遷移和適應。
  2. 目前docker 映象,沒有統一標準,體現在一下幾個方面。在使用過程中會遇到過各種本班的 OS。包括 alpine, debian, ubuntu, centos, oraclelinux, redhat 等等。即使是映象採用 CentOS 母版,很多映象製作者會給作業系統減肥。經過優化後,已經不是官方版本,在使用過程中你會遇到各種麻煩。例如除錯的時候需要 curl,wget,telnet,nslookup 等工具在映象中沒有。甚至 ps, top, free, find, netstat, ifconfig 命令都沒有。很多容器都不帶 iptables 所以,即使帶有iptables 在容器中修改規則也很麻煩。
  3. 傳統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

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

使用物理機,虛擬機器,學習成本,試錯成本,部署成本遠遠低於容器技術。Google 官方也曾經說過,未來 kubernetes 重點可能會轉向虛擬機器。不過Docker的理念和思想還是值得學習的。任何技術都會有平替,只是各種成本的妥協,在你想好怎麼處理一些問題的時候,謹慎引入Docker等各類新技術到你的生產環境中。還有一些Docker+微服務遇到的坑和想法,等有空了再更新。

相關文章