在微服務和容器化方面,工程師傾向於避免使用 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 生命週期管理(例如升級或加強)很是繁瑣,尤爲是若是已經在 裸金屬或虛擬機 上構建了本身的集羣。對於升級,咱們已經意識到,最簡單的方法是使用最新版本構建新集羣,並將工做負載從舊版本過渡到新版本。節點原地升級所作的努力和計劃是不值得的。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,並設計一種方法將它們組合在一塊兒。
通過幾回迭代,咱們決定採用如下設計:
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 公開服務會對內核的鏈接跟蹤機制形成巨大代價。除非進行完整的計劃,不然它很輕易就破壞了擴展性。
咱們的集羣運行在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
限定容器的權限和訪問控制,只需加上 SecurityContext 字段:
apiVersion: v1 kind: Pod metadata: name: <Pod name> spec: containers: - name: <container name> image: <image> securityContext:
allowPrivilegeEscalation=true 表示容器的任何子進程均可以得到比父進程更多的權限。最好將其設置爲 false,以確保 RunAsUser 命令不能繞過其現有的權限集。
apiVersion: v1 kind: Pod metadata: name: <Pod name> spec: containers: - name: <container name> image: <image> securityContext: allowPrivilegeEscalation: false
爲了防止來自容器內的提權攻擊,最好不要使用 root 用戶運行容器內的應用。UID 設置大一點,儘可能大於 3000。
apiVersion: v1 kind: Pod metadata: name: <name> spec: securityContext: runAsUser: <UID higher than 1000> runAsGroup: <UID higher than 3000>
requests和limits都加上
ServiceAccount 爲 Pod 中運行的進程提供身份標識,怎麼標識呢?固然是經過 Token 啦,有了 Token,就防止假冒僞劣進程。若是你的應用不須要這個身份標識,能夠沒必要掛載:
apiVersion: v1 kind: Pod metadata: name: <name> spec: automountServiceAccountToken: false
對於 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 配置文件應該爲大多數工做負載提供足夠的權限,若是你有更多的需求,能夠自定義配置文件.
容器依賴於傳統的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 做爲一種「服務」使用,它能夠減輕平臺維護帶來的大部分開銷,例如「如何擴展內部網絡 CIDR?」或「如何升級個人 Kubernetes 版本?」
今天,咱們意識到,你須要問本身的第一個問題是「你是否必定須要 Kubernetes?」。這能夠幫助你評估所遇到的問題以及 Kubernetes 解決該問題的重要性。
Kubernetes 轉型並不便宜,爲此支付的價格必須確實證實「你的」用例的必要性及其如何利用該平臺。若是能夠,那麼 Kubernetes 能夠極大地提升你的生產力。
記住,爲了技術而技術是沒有意義的。