从安全到镜像流水线,Docker 最佳实践与反模式一览
作者 |?Timothy Mugayi
譯者 |?彎月,責編 | 夕顏
封圖 | CSDN付費下載自視覺中國
出品 | CSDN(ID:CSDNnews)
在使用Docker的大部分時間里,我們并不關心其內部的工作原理。僅憑啟動一個Docker容器并且讓應用程序運行良好,并不能說明你已經實現了一個良好的解決方案。有時,由于時間限制,我們只能復制粘貼Docker鏡像,卻沒能真正理解實現細節以及如何構建Docker鏡像的細微差別。
?
在本文中,我們將探討Docker的最佳實踐和反模式。反模式是人們對于反復出現的問題的一般解決方案,這些方案沒有效率,甚至會完全抵消Docker技術棧帶來的好處。
?
下面我們來看看我們的哪些做法不可取。
?
我們需要的標簽
?
標簽是必不可少的,我們需要通過標簽傳達有關Docker鏡像的信息。你可以將標簽視為Docker鏡像ID的別稱。Git標簽負責標記特定的提交,而Docker標簽與之類似,可以給不同時間點上的Docker鏡像添加版本。忘記打標簽是小事,但會帶來一些弊端,具體來講,如果未指定標記,則默認鏡像將被標記為latest。
?
FROM your_image_name:latest?
如果你頻繁執行該操作,那么很有可能鏡像不是最新的,可能指向的是舊版本。因此,請使用適當的標簽并遵守某個版本控制標準,例如語義版本控制。這樣,Docker鏡像使用者才能確保Docker鏡像的兼容性并時刻保持最新,還可以有計劃地使用正確的版本。
?
還有一個情況應該避免。你可以利用最新的默認標簽(如FROM python3:latest),從Docker鏡像倉庫中提取最新的鏡像。乍一看,這種做法似乎是個好主意,但卻有一些意想不到的副作用:每個最新的請求可能都會派生出與以前的構建完全不同的Docker鏡像。弄明白Docker鏡像損壞的原因將會變得很困難,因為鏡像本應該是不可變的。因此,我強力建議使用特定的標簽來標記鏡像(例如:python3:1.0.1)。這種方法可以確保你的Dockerfile保持不變。
?
在同一個容器中運行多個服務
?
雖然你可以在同一個容器中運行多個服務,但我并不建議你這么做,原因有兩個。在使用Docker服務時,我們應該努力維持責任單一性。最佳做法是,組成應用程序的每個服務都應在各自的容器中運行,請務必將每項獨立的功能都打包到單獨的獨立容器鏡像中。
?
將多個服務添加到一個Docker鏡像的做法似乎很誘人,但是你不應該將容器鏡像視為虛擬機。一個容器包含多個服務,可能會導致你的應用程序很難水平擴展。Docker容器核心概念是,它們都是瞬態的,專為分發而設計,這對于現代Web應用程序來說很理想,因為它的瞬態特性、擴展和并發會非常容易。添加多個服務會增加管理分發的難度。
?
另外,單個容器上的多個服務還會加大管理安全性的難度。龐大的鏡像可能會降低CI/CD的速度,你需要小心。
?
使用LABEL對鏡像進行分類
?
這并不能說是反模式,但我認為值得一提。我在處理各種Docker鏡像時注意到了一件事:有時這些鏡像的創建者沒有使用LABEL maintainer標簽。這個標簽可在事件中設置鏡像的Author字段,當出現問題或需要澄清時,這個標簽可以方便大家了解該與何人內部聯系;如果鏡像是公開共享的,也可以知道該與哪個外部的人聯系。
?
這絕不是唯一可以使用的標簽。你可以根據需要定義各種標簽,來對鏡像進行分類,定義許可信息,也可以定義標簽來幫助自動化。
?
除了maintainer,還可以使用多行標簽:
# Set one or more individual labels LABEL com.example.version="0.0.1-beta" LABEL vendor1="RBTSB Incorporated" LABEL vendor2=TIPTAPCODE\ Incorporated LABEL com.example.release-date="202-04-02" LABEL com.example.version.production="0.0.1"Docker 1.10之前的單行標簽會創建新的docker層,如果你使用的是最新版的Docker,則不必擔心創建額外的層。
LABEL vendor=ACME\ Incorporated \ com.example.is-beta= \ com.example.is-production="" \ com.example.version="0.0.1-beta" \ com.example.release-date="2015-02-12"我們應該將盡可能多的元數據添加到不可變的Docker鏡像,以方便追蹤,提高可見性和可維護性。
?
避免構建依賴特定環境的鏡像
?
在構建Docker鏡像時,我們應始終牢記不變性。最好不要使用帶有dev、test、staging和production的鏡像,因為這會破壞單一來源的原則。另一個問題是,如果在不同環境上驗證或調試,則這種做法無法保證鏡像的相似。
?
為什么要使用非Root容器?
?
在默認情況下,Docker容器以root身份運行。以root用戶身份運行的Docker容器可以完全控制主機系統。然而,出于安全考慮,我并不推薦這種做法。使用非root運行的Docker容器鏡像可以多一層保護,在生產環境中通常建議使用非root容器。但是,由于這些容器由非root用戶運行,因此無法執行需要特殊權限的任務。如果需要利用USER指令指定非root用戶(如以下示例所示),則需要進行一些上下文切換。
?
FROM python:3.6-slim-buster LABEL maintainer="Timothy Mugayi <timothy.mugayi@gmail.com>"RUN apt-get update && apt-get install -y --no-install-recommends \ wget && rm -rf /var/lib/apt/lists/*# Dumb init RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 RUN chmod +x /usr/local/bin/dumb-initRUN pip install --upgrade pipWORKDIR /usr/src/appCOPY requirements.txt .RUN pip install -r requirements.txtCOPY helloworld.py .USER 1001ENTRYPOINT ["/usr/local/bin/dumb-init", "python3", "-u", "./helloworld.py"]?如果你使用的基礎鏡像不是由root生成的,但需要切換回root,則可以執行以下操作:
FROM <namespace>/<image>:<tag_version>USER root不要在單個容器內運行太多進程
?
容器的優點以及容器相對于虛擬機的優勢在于,通過多個相互交互的容器組成一個完整的應用程序。我們無需在單個容器中運行完整的應用程序。相反,我們應盡可能將應用程序分解為多個服務,并將服務分布到多個容器上。這樣可以最大程度地提高靈活性和可靠性。
?
不要在容器內安裝操作系統
?
使用一段時間Docker后,你就有可能遇到這種情況。雖然你可以在容器內安裝和運行完整的Linux操作系統。但是你應該這樣做嗎?
?
這可能不是一個好主意。Docker鏡像是使用層的概念構建的,因此添加的東西越多,鏡像就會膨脹得越大。一個完整的操作系統并不是Docker的理想使用情況。在理想情況下,你的容器內部只應該加載必要的組件。
?
不要在容器內運行不必要的服務
?
為了充分利用容器的優勢,你應該盡可能保持容器精簡。這樣可以最大程度地提高性能,并最大程度地降低安全風險。因此,請避免運行并非絕對必要的服務。例如,若非必要請不要在容器內運行SSH服務,你可以選用其他方式(例如Docker exec)登錄到容器。
?
不要在容器內加載不必要的程序
?
你必須知道這種反模式。在使用Docker時,很多人傾向于在鏡像中加載sonar之類的工具,來確保代碼覆蓋率等。
?
使用從Docker CE 17.05+開始支持的多階段構建(Multistage builds)模式,你可以在Dockerfile中使用多個FROM stage。臨時構建的階段容器將被丟棄,因此最終的運行時容器鏡像都很精簡。舉個例子,當你需要從源文件編譯一些二進制文件,然后在第二個階段中將二進制文件復制到最終鏡像中。
?
優點:
?
構建速度更快,精簡的鏡像可以讓CI/CD過程更快,鏡像通過網絡進行傳輸時花費的時間也更少。
需要的存儲空間更少。
冷啟動(拉取鏡像)更快。
潛在的受攻擊面更少。
?
缺點:
?
容器內的工具較少,但這是保證容器的精簡所需付出的一點小小的代價。
在容器內加入供觀察的工具
?
即便沒有監視解決方案,你也可以照常運行容器,但需要牢記,從本質上來說,你很難知道容器內部發生了什么,特別是隨著容器數量的增加。
?
Docker本身自帶了許多指標,能夠公開每個運行容器的CPU、內存、網絡和I/O使用情況,可通過Docker遠程API的/stats端點訪問。App dynamics 和 Newrelic 是兩個現成的程序,可以與Docker鏡像一起打包,幫助你從應用程序級別了解應用程序和容器的運行狀況。
?
基礎鏡像的愛恨交織
?
“你知道是誰構建了這個景象,里面都添加了什么嗎?”
?
這里說的都是可追溯性。牢記安全性是所有軟件開發人員都應努力的方向。你需要了解如何跟蹤Docker鏡像的源,并了解里面有什么。
?
你需要時刻牢記以下幾點:
?
鏡像是怎樣創建的。
驗證鏡像在創建后未經更改。
驗證鏡像的內容。
掃描鏡像是否有安全漏洞。
?
我們可以通過一些工具對容器進行靜態分析。這些工具涉及方方面面,已超出了本文的范圍,但是我建議你花一些時間來學習。
?
Clair是一個有趣的工具,可為你的Docker應用程序提供自動容器漏洞和安全掃描。掃描基于常見的漏洞和公開(CVE)的數據庫。如果你本地運行Docker,則可以下載Postgres,然后將Clair連接到Postgres上。以下是啟動和運行Clair所需的最低配置:
$ mkdir $PWD/clair_config $ curl -L https://raw.githubusercontent.com/coreos/clair/master/config.yaml.sample -o $ PWD/clair_config/config.yaml $ docker run -d -e POSTGRES_PASSWORD="" -p 5432:5432 postgres:9.6 $ docker run --net=host -d -p 6060-6061:6060-6061 -v PWD/clair_config:/config quay.io/coreos/clair:latest -config=/config/config.yaml如果你想更改Postgres的端口,請確保同時修改config.yaml文件。如果你的系統上已經運行了另一個Postgres,請注意不要將docker端口更改為5432以外的端口。
?
clair: database: # Database driver type: pgsql options: # PostgreSQL Connection string # https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING source: host=localhost port=5432 user=postgres password=123456 sslmode=disable statement_timeout=60000在鏡像啟動并執行后,你可以運行Docker ps來確認容器已啟動且正常運行:
你需要注意Clair沒有Web UI或CLI。你只能通過REST API或第三方CLI工具來使用。
?
Bayan Collector是一款輕巧的靜態分析應用程序,可以從鏡像倉庫啟動容器以進行靜態分析,可以運行任意腳本并收集有用的信息(例如已安裝的軟件包),強制執行策略,以及對鏡像進行校驗。
?
Docker Bench for Security是由Docker團隊創建的工具,它會在Docker的宿主上運行一個安全最佳做法清單,并標記發現的任何問題。
?
不要在容器鏡像中存儲敏感數據
?
請一定避免這個錯誤。如果你的鏡像對外公開,或者開發人員無意中將鏡像推送到公共的Docker鏡像倉庫中,那么就會導致隱私和敏感信息的泄露。切記永遠不要在Dockerfile中對敏感信息使用COPY或`。
?
為了避免這種情況,請將敏感數據存儲在安全文件系統上,并讓容器進行連接。一般情況下,這個文件系統應該在容器所在的宿主上,或者可通過AWS Elastic Block Storage(EBS)等塊存儲或S3等對象存儲服務來使用。
?
此外,你應避免在Docker鏡像中存儲安全憑證。作為開發人員,有時我們會采用偷懶的做法,在代碼中硬編碼密碼和私鑰。你需要習慣使用-e參數在運行時為Docker容器指定環境變量。
?
你也可以通過env-file,從文件中讀取環境變量。通過CMD或`從第三方來源獲取憑據的自定義腳本也可用于獲取Docker容器所需的相關憑據。
?
不要在容器內存儲數據或日志
?
容器化改變了日志的性質。容器是瞬態、無狀態應用程序的理想選擇。本質上,存儲在運行容器中的所有數據都應該是短暫的,你可能已經注意到,當容器關閉時,數據將丟失。因此,將數據存儲在Docker容器之外的做法更值得推崇。有一些工具可以幫助你提取Docker日志,并將其放在更永久的數據存儲中。
?
在處理Docker日志時需要牢記一點:Docker至少擁有三個級別的日志記錄,即Docker容器、Docker服務和宿主操作系統,你選擇的日志記錄方法應該能夠提取所有級別的日志。
?
不要寫入容器的文件系統
?
每次將內容寫入容器的文件系統都會激活“寫時復制”策略。這會使用存儲驅動程序(deviermapper、overlayfs或其他驅動)創建新的存儲層。在實際應用中,這會給存儲驅動帶來巨大的壓力,特別是使用Devicemapper或BTRFS的情況下。
?
確保容器只向卷中寫入數據。對于小型的臨時文件可以寫入tmpfs,因為tmpfs是僅存在于內存或交換分區中的臨時文件系統。
?
不要運行PID 1
?
這是許多人都不知道的常見問題。
?
Docker中的進程運行時沒有init進程負責清理子進程,所以容器可能會出現僵尸進程,導致意料之外的錯誤。
?
使用tini或dumb-init
?
PID 1在Unix中很特殊,因此在init系統中忽略它通常會導致進程和信號處理錯誤。這會導致類似于容器無法優雅地停止,或本應摧毀的容器出現泄露等問題。
?
僵尸進程指的是運行已經停止,但依然在進程表中占據位置的進程,因為它們的父進程沒有調用wait系統調用進行回收。理論上,每個結束的進程都會非常短暫地呈現僵尸狀態,但有些進程的僵尸狀態會持續很久。
?
如果某個進程會生成新進程,但信號處理的實現不好,無法捕獲子進程的信號并將其終止,那么可以使用Tini或dumb-init。例如,bash腳本就無法正確處理或釋放信號。
?
下面是運行dumb init的示例,其中prepare.sh可以是shell腳本,也可以是用于執行應用程序的命令:
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64RUN chmod +x /usr/local/bin/dumb-initENTRYPOINT ["/usr/local/bin/dumb-init", "/usr/bin/prepare.sh"]或者如果你選擇tini,可以參考下面使用Python anaconda conda的示例:
?
RUN conda install --yes -c conda-forge tiniENTRYPOINT ["tini", "-g", "--", "/usr/bin/prepare.sh"]?
最后,這里是一個更一般的示例,不依賴于任何編程語言:
?
FROM node:13.12.0-slimMAINTAINER?Timothy?Mugayi?<timothy.mugayi@gmail.com>ENV TINI_VERSION='v0.13.0' # Add tini init, see https://github.com/krallin/tini ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tiniRUN chmod +x /tini # Set tini as entrypoint ENTRYPOINT ["/tini", "--"]在CI/CD中使用Dockerfile格式檢查
?
跟編程語言一樣,Docker也有格式檢查工具。
?
采用格式檢查工具有許多好處,因為它會強制你使用最佳實踐。在CI/CD過程中加入格式檢查,可以幫助團隊避免常見的錯誤,并在構建產品Docker鏡像時建立最佳實踐。可以從hadolint開始,這是一個用Haskell編寫的Dockerfile格式檢查工具,能夠解析Dockerfile成AST,并在AST上執行規則檢查。
?
我們來運行一個示例。要對Docker進行格式檢查,可以執行以下命令:
$ docker run --rm -i hadolint/hadolint < Dockerfile?
格式檢查完成后會顯示以下結果:
?
上圖中的錯誤碼DL3008“Pin”來自hadolint的錯誤碼描述狀態。固定版本號可以強制構建時安裝特定版本的包,不論緩存中是什么。這個技巧可以減少依賴包意料之外的改變導致的構建錯誤。
?
最后的一點想法
?
在本文中,我們談了很多內容,覆蓋了從安全到Docker鏡像流水線的方方面面。
?
有許多技巧可以讓Docker鏡像更好、更安全。希望這篇文章可以給你帶來一些啟發,幫助你理解應該做什么、不應該做什么,并為你提供一些在構建內部或外部Docker容器鏡像時可以應用的方案。
??
原文鏈接:
https://medium.com/better-programming/docker-best-practices-and-anti-patterns-e7cbccba4f19
本文為CSDN翻譯文章,轉載請注明出處。?
推薦閱讀
大促下的智能運維挑戰:阿里如何抗住“雙11貓晚”?
20萬個法人、百萬條銀行賬戶信息,正在暗網兜售
當莎士比亞遇見Google Flax:教你用字符級語言模型和歸遞神經網絡寫“莎士比亞”式句子
Hyperledger Fabric 和企業級以太坊,誰才是企業首選?
面試時遇到「看門狗」脖子上掛著「時間輪」,我就問你怕不怕?
同期兩篇 Nature:運行溫度高于 1K 的量子計算平臺問世!
GitHub 標星 10,000+,Apache 頂級項目 ShardingSphere 的開源之路
真香,朕在看了!
總結
以上是生活随笔為你收集整理的从安全到镜像流水线,Docker 最佳实践与反模式一览的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么程序员总是打扮成这样一幅鬼样子
- 下一篇: 【快讯】阿里云张建锋:数据成为经济发展的