如何Docker化任意一個應用

weixin_33763244發表於2018-07-28

網上有很多關於如何將應用Docker化的教程,為什麼我還要再寫一個呢?

\\

我見過的大部分教程都是限定在某種特定技術(例如Java或者Python),可能無法滿足讀者的需求。同時,這些教程也沒有說清楚關於Dev和Ops團隊之間建立明確約定所涉及到的所有相關方面(這正是容器化的精髓所在)。

\\

我根據最近的經驗總結了以下一些步驟。它是一份細節清單,包含了其他指南中忽略的內容。

\\

宣告:這不是一份新手指南。我建議讀者先掌握一些如何設定和使用docker的基礎知識,並且建立和執行一些容器之後,再來閱讀。

\\

讓我們開始吧。

\\

一、選擇基礎映象

\\

每種對應技術幾乎都有自己的基礎映象,例如:

\\

如果不能直接使用這些映象,我們就需要從基礎作業系統映象開始安裝所有的依賴。

\\

外面有很多教程使用的都是Ubuntu(例如 ubuntu:16.04)作為基礎映象,這不能算有問題,但是我建議優先考慮Alpine映象:

\\

https://hub.docker.com/_/alpine/

\\

它是一個非常小的基礎映象(大約只有5MB)。

\\

注意:在基於Alpine的映象中無法使用“apt-get”命令,Alpine系統有自己的軟體包倉庫和包管理工具。詳細請參考:

\\

https://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management

\\

https://pkgs.alpinelinux.org/packages

\\

二、安裝必要軟體包

\\

這個步驟通常比較瑣碎,有一些容易忽略的細節:

\\
  • apt-get update和apt-get install命令應該寫在一行(如果使用Alpine則對應的是apk命令)。這不是常見的做法,但是在Dockerfile中應該要這麼做,否則“apt-get update”命令產出的臨時層可能會被快取,導致構建時沒有更新包資訊(參見 https://forums.docker.com/t/dockerfile-run-apt-get-install-all-packages-at-once-or-one-by-one/17191 這個討論)。\\t
  • 確認是否只安裝了實際需要的軟體(尤其是這個容器會在生產環境中執行)。我看見過有人在他們的映象中安裝了vim和其他開發工具。\

如果有必要,針對構建、除錯和開發環境建立不同的Dockerfile。這不僅僅關係到映象大小,還涉及到安全性、可維護性等等。

\\

三、新增自定義檔案

\\

一些優化Dockerfile的小提示:

\\

四、定義容器執行時的使用者許可權

\\

現在可以休息一下,閱讀下這篇不錯的的文章:Understanding how uid and gid work in Docker containers

\\

讀完這篇文章,我們會了解:

\\
  • 僅當應用程式需要訪問使用者或組資料(/etc/passwd或/etc/group)時,才需要在容器啟動時指定固定的使用者ID。\\t
  • 儘可能避免容器以root許可權執行。\

不幸的是,不少熱門應用程式映象需要用特定的使用者id來執行(例如Elastic Search需要uid:gid = 1000:1000)。儘量不要在寫出這樣的映象……

\\

五、定義暴露的埠

\\

這也是一個微不足道的小操作,但是不要為了暴露特權埠(例如80)而將容器以root許可權執行。如果有這樣的需求,可以讓容器暴露一個非特權埠(例如8080),然後在啟動時進行埠對映。

\\

關於特權埠和非特權埠的不同:https://www.w3.org/Daemon/User/Installation/PrivilegedPorts.html

\\

六、定義入口點(entrypoint)

\\

普通方式:直接執行可執行檔案。

\\

更好的方式:建立一個“docker-entrypoint.sh”指令碼,可以用來通過環境變數來配置容器的入口點(具體請參照下一節)。

\\

這是一個非常普遍的做法,這裡有一些例子:

\\

https://github.com/elastic/elasticsearch-docker/tree/master/build/elasticsearch/bin

\\

https://github.com/docker-library/postgres/tree/de8ba87d50de466a1e05e111927d2bc30c2db36d/10

\\

七、定義一種配置方式

\\

基本上每個應用程式都需要引數化。基本上有兩條路可以遵循:

\\
  1. 使用應用程式特定的配置檔案:該方式需要通過文件說明配置檔案的格式、欄位、放置位置等等(當執行環境比較複雜,例如應用程式跨越不同的技術,則不太合適)。\\t
  2. 使用(作業系統)環境變數:簡單而有效。\

如果讀者認為這種方式不夠現代,記住這也是12-factors推薦的方式:

\\

https://12factor.net/zh_cn/config

\\

這並不意味著我們可以拋開所有的配置檔案,並對應用程式進行重構,去除配置檔案機制。只需要通過envsubst命令來替換配置檔案模板(這個流程需要在docker-entrypoint.sh檔案中完成,因為這需要在執行時完成)。

\\

例如:

\\

https://docs.docker.com/samples/library/nginx/#using-environment-variables-in-nginx-configuration

\\

這種方式可以將應用程式的配置檔案封裝在容器內部,無須讓使用者瞭解這些細節。

\\

八、外部化資料

\\

關於資料儲存有一條黃金法則:絕對不要將任何持久化資料儲存到容器內。

\\

容器的檔案系統被設計成臨時和短暫的。因此任何由應用程式生成的內容、資料檔案和處理結果都應該儲存到掛載的卷或者作業系統繫結掛載點上(既將宿主機作業系統的目錄掛載到容器中)。

\\

對於掛載卷我不太有經驗,因此我個人更傾向於將資料儲存到繫結掛載點(bind mounts)。這些掛載點一般通過類似Salt Stack這樣的配置管理工具仔細的在宿主機上建立。

\\

這裡說的“仔細建立”,主要包括下面幾個步驟:

\\
  1. 在宿主機作業系統上建立非特權使用者(和組)。\\t
  2. 所有需要繫結目錄的所有者都是該使用者。\\t
  3. 根據使用場景給授權(僅針對這個特定的使用者和組,其他使用者無權訪問)。\\t
  4. 容器也以該使用者執行。\\t
  5. 此時容器就可以完全控制這些目錄。\

九、確保處理好日誌

\\

前面關於“永續性資料”沒有一個明確的定義,日誌在這裡就是灰色地帶。我們該如何處理它們呢?

\\

如果這是一個新的應用程式,並且希望它能夠堅持docker約定,就不應該將日誌寫入檔案。應用程式應該使用標準輸出和標準錯誤輸出日誌。和之前推薦使用環境變數一樣,這也是12-factors之一:

\\

https://12factor.net/zh_cn/logs

\\

Docker會自動捕捉應用程式的標準輸出,並可以通過“docker logs”命令檢視:

\\

https://docs.docker.com/engine/reference/commandline/logs/

\\

當然還有一些實際場景下會遇到問題。例如執行一個簡單的nginx容器,至少會有兩種不同的日誌檔案:

\\
  • HTTP訪問日誌(Access Logs)\\t
  • 錯誤日誌(Error Logs)\

對於這種日誌按照特定結構輸出的應用,可能不太適合將它們的日誌輸出到標準輸出。這個例子中,只需要按照前面一節中說的處理好持久化問題,並確保正確配置檔案的輪轉。

\\

十、輪轉日誌和其他僅追加檔案

\\

如果應用程式將日誌寫到檔案,或者會無限追加內容到檔案,就需要關注這些檔案的輪轉(rotation),這對於防止伺服器空間耗盡非常有用(尤其是GDPR和其他資料安全條例出來之後)。

\\

如果使用繫結掛載,我們可以依靠宿主機的一些工具來實現檔案輪轉功能,例如logrotate(文件參見https://linux.die.net/man/8/logrotate)。

\\

最近我找到的一個簡單且完整的例子:

\\

https://www.aerospike.com/docs/operations/configure/log/logrotate.html

\\

另外一個例子:

\\

https://www.digitalocean.com/community/tutorials/how-to-manage-logfiles-with-logrotate-on-ubuntu-16-04

\\

檢視英文原文:How to dockerize any application

\\

感謝張嬋對本文的審校。

相關文章