酷播亮新聞
最棒的知識補給站

[譯] 谷歌團隊的容器運維最佳實踐

文章摘要: 旨在使應用程式更容易在 Kubernetes Engine 和一般的容器中執行你為應用程式的邊車容器新增一個日誌代理(在同一 pod 中

作者 | Google Cloud

譯者 | 覃璐

谷歌大神們帶你進行容器運維最佳實踐

本文介紹了一組使容器更易於運維的最佳實踐。這些實踐涉及安全性、監控和日誌記錄等廣泛的主題,旨在使應用程式更容易在 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://cloud.google.com/solutions/best-practices-for-operating-containers

活動推薦

2018 年,有哪些值得關注的運維技術熱點?第四屆 CNUTCon 全球運維技術大會正式啟動啦,包括智慧化運維、Serverless、DevOps 等熱門話題,數十位大牛聯合出品,揭祕最前沿運維技術,推薦學習!點選「閱讀原文」瞭解更多大會精彩。

如有侵權請來信告知:酷播亮新聞 » [譯] 谷歌團隊的容器運維最佳實踐