使用K8s的一些經驗和體會

使用K8s的一些經驗和體會

Java應用程序的奇怪案例

​ 在微服務和容器化方面,工程師傾向於避免使用 Java,這主要是因爲 Java 臭名昭著的內存管理。可是,如今狀況發生了改變,過去幾年來 Java 的容器兼容性獲得了改善。畢竟,大量的系統(例如Apache Kafka和Elasticsearch)在 Java 上運行。java

​ 回顧 2017-18 年度,咱們有一些應用程序在 Java 8 上運行。這些應用程序一般很難理解像 Docker 這樣的容器環境,並因堆內存問題和異常的垃圾回收趨勢而崩潰。咱們瞭解到,這是因爲 JVM 沒法使用Linuxcgroup和namespace形成的,而它們是容器化技術的核心。python

​ 可是,從那時起,Oracle 一直在不斷提升 Java 在容器領域的兼容性。甚至 Java 8 的後續補丁都引入了實驗性的 JVM標誌來解決這些,XX:+UnlockExperimentalVMOptions和XX:+UseCGroupMemoryLimitForHeap。git

​ 可是,儘管作了全部的這些改進,不能否認的是,Java 在內存佔用方面仍然聲譽不佳,與 Python 或 Go 等同行相比啓動速度慢。這主要是由 JVM 的內存管理和類加載器引發的。docker

​ 如今,若是咱們必須選擇 Java,請確保版本爲 11 或更高。而且 Kubernetes 的內存限制要在 JVM 最大堆內存(-Xmx)的基礎上增長 1GB,以留有餘量。也就是說,若是 JVM 使用 8GB 的堆內存,則咱們對該應用程序的 Kubernetes 資源限制爲 9GB。數據庫

Kubernetes生命週期管理: 升級

​ Kubernetes 生命週期管理(例如升級或加強)很是繁瑣,尤爲是若是已經在 裸金屬或虛擬機 上構建了本身的集羣。對於升級,咱們已經意識到,最簡單的方法是使用最新版本構建新集羣,並將工做負載從舊版本過渡到新版本。節點原地升級所作的努力和計劃是不值得的。api

​ Kubernetes 具備多個活動組件,須要升級保持一致。從 Docker 到 Calico 或 Flannel 之類的 CNI 插件,你須要仔細地將它們組合在一塊兒才能正常工做。雖然像 Kubespray、Kubeone、Kops 和 Kubeaws 這樣的項目使它變得更容易,但它們都有缺點.安全

​ 咱們在 RHEL 虛擬機上使用 Kubespray 構建了本身的集羣。Kubespray 很是棒,它具備用於構建、添加和刪除新節點、升級版本的 playbook,以及咱們在生產環境中操做 Kubernetes 所需的幾乎全部內容。可是,用於升級的 playbook 附帶了免責聲明,以免咱們跳過子版本。所以,必須通過全部中間版本才能到達目標版本。網絡

​ 關鍵是,若是你打算使用 Kubernetes 或已經在使用 Kubernetes,請考慮生命週期活動以及解決這一問題的方案。構建和運行集羣相對容易一些,可是生命週期維護是一個全新的體驗,具備多個活動組件。架構

構建和部署

​ 在準備從新設計整個構建和部署流水線以前, 咱們的構建過程和部署必須經歷 Kubernetes 世界的完整轉型。不只在 Jenkins 流水線中進行了大量的重構,並且還使用了諸如 Helm 之類的新工具,策劃了新的 git 流和構建、標籤化 docker 鏡像,以及版本化 helm 的部署 chart。app

​ 你須要一種策略來維護代碼,以及 Kubernetes 部署文件、Docker 文件、Docker 鏡像、Helm chart,並設計一種方法將它們組合在一塊兒。

通過幾回迭代,咱們決定採用如下設計:

  • 應用程序代碼及其 helm chart 放在各自的 git 存儲庫中。這使咱們能夠分別對它們進行版本控制(語義版本控制)。
  • 而後,咱們將 chart 版本與應用程序版本關聯起來,並使用它來跟蹤發佈。例如,app-1.2.0使用charts-1.1.0進行部署。若是隻更改 Helm 的 values 文件,則只更改 chart 的補丁版本(例如,從1.1.0到1.1.1)。全部這些版本均由每一個存儲庫中的RELEASE.txt中的發行說明規定。
  • 對於咱們未構建或修改代碼的系統應用程序,例如 Apache Kafka 或 Redis ,工做方式有所不一樣。也就是說,咱們沒有兩個 git 存儲庫,由於 Docker 標籤只是 Helm chart 版本控制的一部分。若是咱們更改了 docker 標籤以進行升級,則會升級 chart 標籤的主要版本。

存活和就緒探針(雙刃劍)

​ Kubernetes 的存活探針和就緒探針是自動解決系統問題的出色功能。它們能夠在發生故障時重啓容器,並將流量從不正常的實例進行轉移。可是,在某些故障狀況下,這些探針可能會變成一把雙刃劍,並會影響應用程序的啓動和恢復,尤爲是有狀態的應用程序,例如消息平臺或數據庫。

​ 咱們的 Kafka 系統就是這個受害者。咱們運行了一個3 Broker 3 Zookeeper有狀態副本集,該狀態集的ReplicationFactor爲 3,minInSyncReplica爲 2。當系統意外故障或崩潰致使 Kafka 啓動時,問題發生了。這致使它在啓動期間運行其餘腳原本修復損壞的索引,根據嚴重性,此過程可能須要 10 到 30 分鐘。因爲增長了時間,存活探針將不斷失敗,從而向 Kafka 發出終止信號以從新啓動。這阻止了 Kafka 修復索引並徹底啓動。

​ 惟一的解決方案是在存活探針設置中配置initialDelaySeconds,以在容器啓動後延遲探針評估。可是,問題在於很難對此加以評估。有些恢復甚至須要一個小時,所以咱們須要提供足夠的空間來解決這一問題。可是,initialDelaySeconds越大,彈性的速度就越慢,由於在啓動失敗期間 Kubernetes 須要更長的時間來重啓容器。

​ 所以,折中的方案是評估initialDelaySeconds字段的值,以在 Kubernetes 中的彈性與應用程序在全部故障狀況(磁盤故障、網絡故障、系統崩潰等)下成功啓動所花費的時間之間取得更好的平衡 。

​ 更新:若是你使用最新版本,Kubernetes 引入了第三種探針類型,稱爲「啓動探針」,以解決此問題。從 1.16 版開始提供 alpha 版本,從 1.18 版開始提供 beta 版本。
啓動探針會禁用就緒和存活檢查,直到容器啓動爲止,以確保應用程序的啓動不會中斷。

公開外部IP

​ 咱們瞭解到,使用靜態外部 IP 公開服務會對內核的鏈接跟蹤機制形成巨大代價。除非進行完整的計劃,不然它很輕易就破壞了擴展性。

​ 咱們的集羣運行在Calico for CNI上,在 Kubernetes 內部採用BGP做爲路由協議,並與邊緣路由器對等。對於 Kubeproxy,咱們使用IP Tables模式。咱們在 Kubernetes 中託管着大量的服務,經過外部 IP 公開,天天處理數百萬個鏈接。因爲來自軟件定義網絡的全部 SNAT 和假裝,Kubernetes 須要一種機制來跟蹤全部這些邏輯流。爲此,它使用內核的Conntrack and netfilter工具來管理靜態 IP 的這些外部鏈接,而後將其轉換爲內部服務 IP,而後轉換爲 pod IP。全部這些都是經過conntrack表和 IP 表完成的。

​ 可是conntrack表有其侷限性。一旦達到限制,你的 Kubernetes 集羣(以下所示的 OS 內核)將再也不接受任何新鏈接。在 RHEL 上,能夠經過這種方式進行檢查。

sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012
net.netfilter.nf_conntrack_max = 262144

​ 解決此問題的一些方法是使用邊緣路由器對等多個節點,以使鏈接到靜態 IP 的傳入鏈接遍佈整個集羣。所以,若是你的集羣中有大量的計算機,累積起來,你能夠擁有一個巨大的conntrack表來處理大量的傳入鏈接。
回到 2017 年咱們剛開始的時候,這一切就讓咱們望而卻步,但最近,Calico 在 2019 年對此進行了詳細研究,標題爲「爲何 conntrack 再也不是你的朋友」。

安全方面

​ 對於大部分 Kubernetes 用戶來講,安全是可有可無的,或者說沒那麼緊要,就算考慮到了,也只是敷衍一下,草草了事。實際上 Kubernetes 提供了很是多的選項能夠大大提升應用的安全性,只要用好了這些選項,就能夠將絕大部分的攻擊抵擋在門外。爲了更容易上手,我將它們總結成了幾個最佳實踐配置,你們看完了就能夠開幹了。固然,本文所述的最佳安全實踐僅限於 Pod 層面,也就是容器層面,於容器的生命週期相關,至於容器以外的安全配置(好比操做系統啦、k8s 組件啦),之後有機會再嘮。

爲容器配置Security Context

​ 大部分狀況下容器不須要太多的權限,咱們能夠經過 Security Context 限定容器的權限和訪問控制,只需加上 SecurityContext 字段:

apiVersion: v1
kind: Pod
metadata:
  name: <Pod name>
spec:
  containers:
  - name: <container name>
  image: <image>
    securityContext:

禁用allowPrivilegeEscalation

​ allowPrivilegeEscalation=true 表示容器的任何子進程均可以得到比父進程更多的權限。最好將其設置爲 false,以確保 RunAsUser 命令不能繞過其現有的權限集。

apiVersion: v1
kind: Pod
metadata:
  name: <Pod name>
spec:
  containers:
  - name: <container name>
  image: <image>
    securityContext:
      allowPrivilegeEscalation: false

不要使用root用戶

​ 爲了防止來自容器內的提權攻擊,最好不要使用 root 用戶運行容器內的應用。UID 設置大一點,儘可能大於 3000。

apiVersion: v1
kind: Pod
metadata:
  name: <name>
spec:
  securityContext:
    runAsUser: <UID higher than 1000>
    runAsGroup: <UID higher than 3000>

限制CPU和內存資源

requests和limits都加上

不比掛載Service Account Token

​ ServiceAccount 爲 Pod 中運行的進程提供身份標識,怎麼標識呢?固然是經過 Token 啦,有了 Token,就防止假冒僞劣進程。若是你的應用不須要這個身份標識,能夠沒必要掛載:

apiVersion: v1
kind: Pod
metadata:
  name: <name>
spec:
  automountServiceAccountToken: false

確保seccomp設置正確

​ 對於 Linux 來講,用戶層一切資源相關操做都須要經過系統調用來完成,那麼只要對系統調用進行某種操做,用戶層的程序就翻不起什麼風浪,即便是惡意程序也就只能在本身進程內存空間那一分田地晃悠,進程一終止它也如風消散了。seccomp(secure computing mode)就是一種限制系統調用的安全機制,能夠能夠指定容許那些系統調用。

​ 對於 Kubernetes 來講,大多數容器運行時都提供一組容許或不容許的默認系統調用。經過使用 runtime/default 註釋或將 Pod 或容器的安全上下文中的 seccomp 類型設置爲 RuntimeDefault,能夠輕鬆地在 Kubernetes 中應用默認值。

apiVersion: v1
kind: Pod
metadata:
  name: <name>
  annotations:
    seccomp.security.alpha.kubernetes.io/pod: "runtime/default"

​ 默認的seccomp 配置文件應該爲大多數工做負載提供足夠的權限,若是你有更多的需求,能夠自定義配置文件.

限制容器的capabilities

​ 容器依賴於傳統的Unix安全模型,經過控制資源所屬用戶和組的權限,來達到對資源的權限控制。以 root 身份運行的容器擁有的權限遠遠超過其工做負載的要求,一旦發生泄露,攻擊者能夠利用這些權限進一步對網絡進行攻擊。
​ 默認狀況下,使用 Docker 做爲容器運行時,會啓用 NET_RAW capability,這可能會被惡意攻擊者進行濫用。所以,建議至少定義一個PodSecurityPolicy(PSP),以防止具備 NET_RAW 功能的容器啓動。
經過限制容器的 capabilities,能夠確保受攻擊的容器沒法爲攻擊者提供橫向攻擊的有效路徑,從而縮小攻擊範圍。

apiVersion: v1
kind: Pod
metadata:
  name: <name>
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: <specific user>
  capabilities:
  drop:
    -NET_RAW
    -ALL

是否必定須要Kubernetes?

​ 它是一個複雜的平臺,具備本身的一系列挑戰,尤爲是在構建和維護環境方面的開銷。它將改變你的設計、思惟、架構,並須要提升技能和擴大團隊規模以適應轉型。

​ 可是,若是你在雲上而且可以將 Kubernetes 做爲一種「服務」使用,它能夠減輕平臺維護帶來的大部分開銷,例如「如何擴展內部網絡 CIDR?」或「如何升級個人 Kubernetes 版本?」

​ 今天,咱們意識到,你須要問本身的第一個問題是「你是否必定須要 Kubernetes?」。這能夠幫助你評估所遇到的問題以及 Kubernetes 解決該問題的重要性。

​ Kubernetes 轉型並不便宜,爲此支付的價格必須確實證實「你的」用例的必要性及其如何利用該平臺。若是能夠,那麼 Kubernetes 能夠極大地提升你的生產力。

記住,爲了技術而技術是沒有意義的。

相關文章
相關標籤/搜索