[譯] 谷歌團隊的容器運維最佳實踐
谷歌大神們帶你進行容器運維最佳實踐
本文介紹了一組使容器更易于運維的最佳實踐。這些實踐涉及安全性、監控和日志記錄等廣泛的主題,旨在使應用程序更容易在 Kubernetes Engine 和一般的容器中運行。這里討論的許多實踐都受到 12 因素方法的啟發 ,12 因素方法是一個構建云原生應用程序的優質資源。
使用容器的原生日志記錄機制
重要性:高
作為應用程序管理的一部分,日志中包含寶貴的信息,可讓人了解應用程序中發生的事件。Docker 和 Kubernetes 致力于簡化日志管理。
在傳統服務器上,你可能需要將日志寫入特定文件并處理日志輪換以避免填滿磁盤。如果有高級日志系統,則可以將這些日志轉發到遠程服務器來集中它們。
通過容器可以將日志寫入 stdout 和 stderr,因而容器提供了一種簡單且標準化的方式來處理日志。Docker 捕獲這些日志行,并允許你使用 docker logs 命令訪問它們。作為應用程序開發人員,你不需要實現高級日志記錄機制,試試用原生的日志記錄機制吧。
平臺運營商必須提供一個系統來集中日志并進行搜索,你可以使用 Kubernetes Engine 提供的 fluentd 和 Stackdriver Logging。其他常見方法包括使用 EFK (Elasticsearch,Fluentd,Kibana)棧。
圖 1. Kubernetes 中典型的日志管理系統圖
JSON 日志
大多數日志管理系統實際上是時序數據庫,用于存儲時間索引文檔。這些文檔通常以 JSON 格式提供。在 Stackdriver Logging 和 EFK 中,單個日志行和一些元數據(容器組、容器、節點等相關信息)一起被存儲為一個文檔。
你可以直接通過將不同字段以 JSON 格式進行日志記錄。然后,可以根據這些字段更有效地搜索日志。
例如,考慮將以下日志轉換為 JSON 格式:
[2018-01-01 01:01:01] foo - WARNING - foo.bar - There is something wrong.
這是轉換后的日志:
{ "date": "2018-01-01 01:01:01", "component": "foo", "subcomponent": "foo.bar", "level": "WARNING", "message": "There is something wrong." }
通過這種轉換,你可以在日志中輕松搜索所有 WARNING 級別日志或 foo.bar 組件中的所有日志。
如果你決定記錄 JSON 格式的日志,請注意必須在每一行上加入事件才能正確解析。在實際中,它看起來是下面這樣:
{"date":"2018-01-01 01:01:01","component":"foo","subcomponent":"foo.bar","level": "WARNING","message": "There is something wrong."}
如你所見,結果遠不如正常的日志可讀。如果決定使用此方法,請確保你的團隊不會嚴重依賴手動日志檢查。
邊車模式的記錄聚合器
某些應用程序(如 Tomcat)無法通過簡單配置來生成日志。這些應用程序在磁盤上寫入不同的日志文件,所以在 Kubernetes 中處理它們的最佳方法是使用邊車模式進行日志記錄。邊車是一個小容器,與應用程序在同一個 pod 中運行。有關邊車的更詳細信息,請參閱 Kubernetes 官方文檔 (https://kubernetes.io/docs/concepts/cluster-administration/logging/#sidecar-container-with-a-logging-agent)。
在這種解決方案中,你為應用程序的邊車容器添加一個日志代理(在同一 pod 中,)并在兩個容器之間共享 emptyDir 卷,可參考 GitHub 上的這個 YAML 示例:https://github.com/kubernetes/contrib/blob/0.7.0/logging/fluentd-sidecar-gcp/logging-sidecar-example.yaml。然后,配置應用程序將日志寫入共享卷,接著配置日志代理進行讀取,并轉發到需要的地方。
在此模式中,因為沒有使用 Docker 和 Kubernetes 原生的日志記錄機制,所以必須處理日志輪換。如果你的日志代理程序不處理日志輪換,則同一 pod 中的另一個邊車容器會處理。
圖 2. 日志管理的邊車模式
確保容器是無狀態且不可變的
重要性:高
如果你是第一次嘗試容器,請不要將它們視為傳統服務器。比如,你可能想要在正在運行的容器內更新應用程序,或者在出現漏洞時給正在運行的容器打補丁。從根本上說,容器不是以這種方式工作的。它們被設計成了無狀態且不可改變。
無狀態
無狀態意味著任何狀態(任何類型的持久數據)都存儲在容器之外。這個外部存儲可以采取多種形式,具體取決于你的需求:
-
要存儲文件,我們建議使用 Cloud Storage 等對象存儲。
-
要存儲用戶會話等信息,我們建議使用外部的低延遲鍵值存儲,例如 Redis 或 Memcached。
-
如果需要塊級存儲(例如數據庫),則可以使用連接到容器的外部磁盤。對于 Kubernetes Engine,我們建議使用 持久化磁盤。
通過以上方法將數據從容器本身中移出,這意味著可以隨時干凈地關閉和銷毀容器,而不必擔心數據丟失。如果創建了一個新容器來替換舊容器,則只需將新容器連接到同一數據存儲區或將其綁定到同一磁盤即可。
不變性
不可變意味著容器在其生命周期內不會被修改:沒有更新,沒有補丁,沒有配置更改。如果必須更新應用程序代碼或打補丁,則需要構建新鏡像并重新部署。不變性使部署更安全,更可重復。如果需要回滾,只需重新部署舊鏡像即可。此方法允許你在每個環境中部署相同的容器鏡像,使它們盡可能一致。
為了在不同環境中使用相同的容器鏡像,我們建議你外部化容器配置(偵聽端口,運行時選項等)。容器通常配置有環境變量或掛載到特定路徑上的配置文件。在 Kubernetes 中,你可以使用 Secrets 和 ConfigMaps 作為環境變量或文件將配置注入到容器中。
如果需要更新配置,請使用更新的配置部署新容器(基于相同的鏡像)。
圖 3. 如何使用掛載到 pod 配置文件中的 ConfigMaps 更新部署中的配置
無狀態和不變性的結合是基于容器的基礎設施的賣點之一。這種組合允許你自動化部署并提高其頻率和可靠性。
避免使用特權容器
重要性:高
在虛擬機或裸機服務器中,你會避免使用 root 用戶運行應用程序,原因很簡單:如果應用程序受到攻擊,攻擊者就可以完全訪問服務器。出于同樣的原因,請避免使用特權容器。特權容器是一個容器,可以訪問主機的所有設備,繞過容器的幾乎所有安全功能。
如果你認為需要使用特權容器,請考慮以下備選方案:
-
通過 Kubernetes 的 securityContext 選項或 Docker 的 --cap-add 標志為容器提供特定功能 。該 Docker 文檔(https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities) 同時列出了默認啟用和必須明確啟用的功能。
-
如果你的應用程序必須修改主機設置才能運行,請在邊車容器或初始化容器中修改這些設置 。與你的應用程序不同,這些容器不需要暴露于內部或外部流量,更加獨立。
-
如果需要在 Kubernetes 中修改 sysctls,請使用 專用注解。
在 Kubernetes 中,特權容器可以被特定的 Pod 安全策略禁止 。Pod 安全策略是集群管理員配置和管理的 Kubernetes 對象,它強制執行對 pod 的特定要求。在 Kubernetes 集群中,你無法創建違反這些要求的 pod。
使應用程序易于監控
重要性:高
與日志一樣,監控是應用程序管理的一個組成部分。在許多方面,監控容器化應用的原則與非容器化應用的監控相同。但是,由于容器化的基礎架構往往是高度動態的,伴隨著頻繁創建或刪除的容器,你無法每次都去重新配置監控系統。
你可以區分兩種主要的監控類型:黑盒監控和白盒監控。黑盒監控是指從外部檢查應用程序,你就是最終用戶。如果你想要最終提供的服務可用且有效,則黑盒監控非常有用。由于它位于基礎設施外部,因此黑盒監控在傳統基礎設施和容器化基礎設施之間沒有區別。
白盒監控是指使用某種特權訪問檢查應用程序,并收集最終用戶無法查看的度量指標。由于白盒監控必須檢查基礎架構的最深層,因此傳統基礎架構和容器化基礎架構的差異很大。
Prometheus 是 Kubernetes 社區中用于白盒監控的一個流行選擇,可以自動發現必須監控的容器。Prometheus 以期望的特定格式獲取容器組的指標。Stackdriver 能夠監控 Kubernetes 集群,應用也可以運行自己的的 Prometheus。
以下是一個 Stackdriver Kubernetes Monitoring 的演示實例:
圖 4. Stackdriver Kubernetes Monitoring 中的儀表板
要從 Prometheus 或 Stackdriver Kubernetes Monitoring 中受益,應用程序需要按照 Prometheus 的格式公開指標。你可以按照以下兩種方法來做。
HTTP 端點度量
HTTP 端點度量的工作方式與下文提到的公開應用程序的運行狀況的端點類似 。它通常在 /metrics URI 上公開應用程序的內部指標。響應如下:
http_requests_total{method="post",code="200"} 1027 http_requests_total{method="post",code="400"} 3 http_requests_total{method="get",code="200"} 10892 http_requests_total{method="get",code="400"} 97
在這個例子中,http_requests_total 是度量,method 和 code 是標簽,最右邊的數字是該指標對于這些標簽的值。上圖中所示,自啟動以來,該應用程序已使用 400 錯誤碼響應了 97 次 HTTP 的 GET 請求。
通過已有的多種語言的 Prometheus 客戶端庫,可以輕松生成此 HTTP 端點 。 OpenCensus 還可以使用此格式(以及許多其他功能)導出指標。不要將此端點暴露給公共網絡。
Prometheus 官方文檔 (https://prometheus.io/docs/introduction/overview/)詳細介紹了該主題。你還可以閱讀站點可靠性工程的第 6 章 ,以了解有關白盒(和黑盒)監控的更多信息。
監控中使用邊車模式
并非所有應用程序都可以使用 /metrics HTTP 端點進行檢測。為了保持標準化監控,我們建議使用邊車模式以正確的格式導出指標。
日志聚合邊車模式 部分介紹如何使用邊車容器來管理應用程序日志。你可以使用相同的模式進行監控:邊車容器托管監控代理程序,該代理程序將應用程序公開的度量標準轉換為全局監控系統可以理解的格式和協議。
考慮一個具體示例:Java 應用程序和 Java Management Extensions(JMX)。許多 Java 應用程序使用 JMX 公開指標。利用 jmx_exporter,你可以不必重寫應用程序就公開 Prometheus 格式的指標。jmx_exporter 通過 JMX 從應用程序收集指標,并通過 Prometheus 可以讀取的 /metrics 端點公開它們。這種方法還具有限制 JMX 端點暴露的優點,因為它可以用來修改應用程序設置。
圖 5. 用于監控的邊車模式
暴露應用程序的健康狀況
重要性:中等
為了便于在生產中進行管理,應用程序必須將其狀態傳達給整個系統:應用程序是否正在運行?它健康嗎?它準備好接收流量嗎?它是如何表現的?
Kubernetes 有兩種類型的健康檢查:活性探針(liveness probes )和就緒探針(readiness probes)。如下文所述,每個都有特定的用途。你可以通過多種方式實現這兩種方式(包括在容器內運行命令或檢查 TCP 端口),但首選方法是使用此最佳實踐中描述的 HTTP 端點。
注意:本節中給出的路徑只是一種約定。HTTP 端點的實際路徑可能因應用程序而異。
活性探針
實現活性探針的推薦方法是讓應用程序公開 /health HTTP 端點。在此端點上收到請求后,如果認為健康,應用程序應發送“200 OK”響應。在 Kubernetes 中,健康意味著容器不需要被殺死或重新啟動。影響健康的因素因應用程序而異,但通常意味著以下內容:
-
應用程序正在運行。
-
它的主要依賴性得到滿足(例如,它可以訪問其數據庫)。
就緒探針
實現就緒探針的推薦方法是讓應用程序公開 /ready HTTP 端點。在此端點上收到請求后,如果應用程序已準備好接收流量,則應發送“200 OK”響應。準備接收流量意味著以下內容:
-
該應用程序是健康的。
-
完成任何潛在的初始化步驟。
-
發送到應用程序的任何有效請求都不會導致錯誤。
Kubernetes 使用就緒探針來編排應用程序的部署。如果更新部署,Kubernetes 將對屬于該部署的 pod 進行滾動更新。默認更新策略是一次更新一個 pod:Kubernetes 在更新下一個 pod 之前等待新 pod 準備就緒(如就緒探針所示)。
注意:在許多應用程序中,/health 和 /ready 端點合并為一個 /health 端點,因為它們的健康狀態和就緒狀態之間沒有真正的區別。
避免以 root 身份運行
重要性:中等
容器提供隔離:使用默認設置,Docker 容器內的進程無法訪問來自主機或其他并置容器的信息。但是,由于容器共享主機的內核,因此隔離不像虛擬機那樣完整。攻擊者可以找到未知的漏洞(在 Docker 或 Linux 內核本身中),這些漏洞將允許攻擊者從容器中逃脫。如果攻擊者確實發現了漏洞并且你的進程在容器內以 root 身份運行,則他們將獲得對主機的 root 訪問權限。
圖 6. 左側,虛擬機使用虛擬化硬件。右側,容器中的應用程序使用主機內核。
為避免這種可能性,最佳做法是不在容器內以 root 身份運行進程。你可以使用 PodSecurityPolicy 在 Kubernetes 中強制執行此行為 。在 Kubernetes 中創建 pod 時,使用 runAsUser 選項 指定正在運行該進程的 Linux 用戶。這種方法會覆蓋 Dockerfile 中的 USER 指令。
實際上,存在挑戰。許多軟件包都以 root 身份運行其主進程。如果要避免以 root 用戶身份運行,設計你的容器使用未知的非特權用戶運行。這種做法通常意味著你必須調整各種文件夾的權限。在容器中,如果按照一個容器一個應用的最佳實踐,并且一個應用一個用戶(最好不是 root 用戶),則授予所有用戶對文件夾和文件的讀寫權限不是問題 。
檢查容器是否符合此最佳實踐的一種簡單方法是在本地使用隨機用戶運行容器并測試是否正常工作。替換 [YOUR_CONTAINER] 為你的容器名稱。
docker run --user $((RANDOM + 1))[YOUR_CONTAINER]
如果容器需要外部卷,則可以配置 fsGroup Kubernetes 選項 以將此卷的所有權授予給特定的 Linux 組。此配置解決了外部文件所有權的問題。
如果你的進程由非特權用戶運行,則它將無法綁定到 1024 以下的端口。這不是什么大問題,因為你可以配置 Kubernetes 服務將流量從一個端口路由到另一個端口。例如,你可以配置 HTTP 服務器綁定到 8080 端口,并通過 Kubernetes 服務從 80 端口將流量重定向回來。
仔細選擇鏡像版本
重要性:中等
當你使用 Docker 鏡像時,無論是作為 Dockerfile 中的基礎鏡像,還是作為 Kubernetes 中部署的鏡像,你都必須選擇正在使用的鏡像的標簽。
大多數公共和私有鏡像都遵循構建容器最佳實踐(https://cloud.google.com/solutions/best-practices-for-building-containers#properly_tag_your_images)中所述的標簽系統 。如果鏡像使用語義版本控制的系統 ,則必須考慮一些標簽細節。
最重要的是,“latest”標簽可以在鏡像之間頻繁移動。結果是你無法依賴此標簽進行可預測或可重現的構建。例如,采用以下 Dockerfile:
FROM debian:latest RUN apt-get -y update && \ apt-get -y install nginx
如果你在不同的時間使用這個 Dockerfile 構建兩次鏡像,你最終會得到兩個不同版本的 Debian 和 NGINX。相反,考慮這個修訂版:
FROM debian:9.4 RUN apt-get -y update && \ apt-get -y install nginx
通過使用更精確的標簽,你可以確保生成的鏡像始終基于 Debian 的特定子版本。因為特定的 Debian 版本還附帶了特定的 NGINX 版本,所以你可以更好地控制正在構建的鏡像。
這個結果不僅適用于構建時,也適用于運行時。如果你在 Kubernetes 清單中引用“latest”標簽,則無法保證 Kubernetes 將使用的版本。集群的不同節點可能會在不同時刻拉取相同的“latest”標簽。如果標簽已經在拉動之間的某個點更新,則最終可能會在不同的節點運行不同的鏡像(這是因為同時打上了“latest”標簽)。
理想情況下,你應始終在 FROM 行中使用不可變標簽。此標簽允許你重現構建。但是,存在一些安全性權衡:你固定使用的版本越多,安全補丁在鏡像中的自動化程度就越低。如果你使用的鏡像使用正確的語義版本控制,則補丁版本(即“X.Y.Z”中的“Z”)不應具有向后不兼容的更改:你可以使用“X.Y”標簽并自動修復錯誤。
注意:標簽在 Docker 中不是真正不變的。只要鏡像的所有者決定更改標簽。但是,“X.Y.Z”標簽實際上幾乎總是不變的。
設想一個名為“SuperSoft”的軟件。假設 SuperSoft 的安全過程是通過新的補丁版本來修復漏洞。你想自定義 SuperSoft,并編寫了以下 Dockerfile:
FROM supersoft:1.2.3 RUN a-command
一段時間后,供應商發現了一個漏洞,并發布了 SuperSoft 的 1.2.4 版本來解決這個問題。在這種情況下,你可以隨時了解 SuperSoft 的補丁并相應地更新 Dockerfile。如果你在 Dockerfile 中使用 FROM supersoft:1.2 進行替換,則會自動拉取新版本。
最后,你必須仔細檢查正在使用的每個外部鏡像的標簽系統,判定你對構建這些鏡像的人員的信任程度,并確定要使用的標簽。
來自:https://mp.weixin.qq.com/s/FEqVWBQ1LkQAf6sdX7MPng