在Kubernetes上部署應用時咱們常忽略的幾件事

根據個人經驗,大多數人(使用Helm或手動yaml)將應用程序部署到Kubernetes上,而後認爲他們就能夠一直穩定運行。
然而並不是如此,實際使用過程仍是遇到了一些「陷阱」,我但願在此處列出這些「陷阱」,以幫助您瞭解在Kubernetes上啓動應用程序以前須要注意的一些問題。html

Kubernetes調度簡介

調度器經過 kubernetes 的 watch 機制來發現集羣中新建立且還沒有被調度到 Node 上的 Pod。調度器會將發現的每個未調度的 Pod 調度到一個合適的 Node 上來運行。kube-scheduler做爲集羣的默認調度器,對每個新建立的 Pod 或者是未被調度的 Pod,kube-scheduler會選擇一個最優的 Node 去運行這個 Pod。然而,Pod 內的每個容器對資源都有不一樣的需求,並且 Pod 自己也有不一樣的資源需求。所以,Pod 在被調度到 Node 上以前,根據這些特定的資源調度需求,須要對集羣中的 Node 進行一次過濾。nginx

在一個集羣中,知足一個 Pod 調度請求的全部 Node 稱之爲 可調度節點。若是沒有任何一個 Node 能知足 Pod 的資源請求,那麼這個 Pod 將一直停留在未調度狀態直到調度器可以找到合適的 Node。redis

在作調度決定時須要考慮的因素包括:單獨和總體的資源請求、硬件/軟件/策略限制、親和以及反親和要求、數據局域性、負載間的干擾等等。關於調度更多信息請官網自行查閱shell

Pod Requests and Limits

來看個簡單例子,這裏只截取yaml部分信息數據庫

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx-demo
    image: nginx
    resources:
      limits:
        memory: "100Mi"
        cpu: 100m
      requests:
        memory: "1000Mi"
        cpu: 100m

默認狀況下,我們建立服務部署文件,若是不寫resources字段,Kubernetes集羣會使用默認策略,不對Pod作任何資源限制,這就意味着Pod能夠隨意使用Node節點的內存和CPU資源。可是這樣就會引起一個問題:資源爭搶。
例如:一個Node節點有8G內存,有兩個Pod在其上運行。
剛開始運行,兩個Pod都只須要2G內存就足夠運行,這時候都沒有問題,可是若是其中一個Pod由於內存泄漏或者流程忽然增長到致使內存用到了7G,這時候Node的8G內存顯然就不夠用了。這就會致使服務服務極慢或者不可用。
因此,通常狀況下,咱們再部署服務時,須要對pod的資源進行限制,以免發生相似的問題。api

如示例文件所示,須要加上resources;安全

requests: 表示運行服務所須要的最少資源,本例爲須要內存100Mi,CPU 100m
limits: 表示服務能使用的最大資源,本例最大資源限制在內存1000Mi,CPU 100m

什麼意思呢?一圖勝千言吧。bash

在Kubernetes上部署應用時咱們常忽略的幾件事

Liveness and Readiness Probes

Kubernetes社區中常常討論的另外一個熱點話題。 掌握Liveness和Readiness探針很是重要,由於它們提供了一種運行容錯軟件並最大程度減小停機時間的機制。 可是,若是配置不正確,它們可能會對您的應用程序形成嚴重的性能影響。 如下是這兩個探測的概要以及如何推理它們:網絡

Liveness Probe:探測容器是否正在運行。 若是活動性探針失敗,則kubelet將殺死Container,而且Container將接受其從新啓動策略。 若是「容器」不提供活動性探針,則默認狀態爲「成功」。tcp

由於Liveness探針運行頻率比較高,設置儘量簡單,好比:將其設置爲每秒運行一次,那麼每秒將增長1個請求的額外流量,所以須要考慮該請求所需的額外資源。一般,咱們會爲Liveness提供一個健康檢查接口,該接口返回響應代碼200代表您的進程已啓動而且能夠處理請求。

Readiness Probe:探測容器是否準備好處理請求。 若是準備就緒探針失敗,則Endpoint將從與Pod匹配的全部服務的端點中刪除Pod的IP地址。

Readiness探針的檢查要求比較高,由於它代表整個應用程序正在運行並準備好接收請求。對於某些應用程序,只有從數據庫返回記錄後,纔會接受請求。 經過使用通過深思熟慮的準備狀況探針,咱們可以實現更高水平的可用性以及零停機部署。

Liveness and Readiness Probes檢測方法一致,有三種

  1. 定義存活命令:
    若是命令執行成功,返回值爲零,Kubernetes 則認爲本次探測成功;若是命令返回值非零,本次 Liveness 探測失敗。
  2. 定義一個存活態 HTTP 請求接口;
    發送一個HTTP請求,返回任何大於或等於 200 而且小於 400 的返回代碼表示成功,其它返回代碼都標示失敗。
  3. 定義 TCP 的存活探測
    向執行端口發送一個tcpSocket請求,若是可以鏈接表示成功,不然失敗。

來看個例子,這裏以經常使用的 TCP存活探測爲例

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx-demo
    image: nginx
    livenessProbe:
      tcpSocket:
        port: 80
      initialDelaySeconds: 10
      periodSeconds: 10
    readinessProbe:
      tcpSocket:
        port: 80
      initialDelaySeconds: 10
      periodSeconds: 10
livenessProbe 部分定義如何執行 Liveness 探測:
1. 探測的方法是:經過tcpSocket鏈接nginx的80端口。若是執行成功,返回值爲零,Kubernetes 則認爲本次 Liveness 探測成功;若是命令返回值非零,本次 Liveness 探測失敗。

2. initialDelaySeconds: 10 指定容器啓動 10 以後開始執行 Liveness 探測,通常會根據應用啓動的準備時間來設置。好比應用正常啓動要花 30 秒,那麼 initialDelaySeconds 的值就應該大於 30。

3. periodSeconds: 10 指定每 10 秒執行一次 Liveness 探測。Kubernetes 若是連續執行 3 次 Liveness 探測均失敗,則會殺掉並重啓容器。

readinessProbe 探測同樣,可是 readiness 的 READY 狀態會經歷了以下變化:
1. 剛被建立時,READY 狀態爲不可用。
2. 20 秒後(initialDelaySeconds + periodSeconds),第一次進行 Readiness 探測併成功返回,設置 READY 爲可用。
3. 若是Kubernetes連續 3 次 Readiness 探測均失敗後,READY 被設置爲不可用。

爲Pod設置默認的網絡策略

Kubernetes使用一種「扁平」的網絡拓撲,默認狀況下,全部Pod均可以直接相互通訊。 可是在某些狀況下咱們不但願這樣,甚至是沒必要要的。 會存在一些潛在的安全隱患,例如一個易受的應用程序被利用,則能夠爲者提供徹底訪問權限,以將流量發送到網絡上的全部pod。 像在許多安全領域中同樣,最小訪問策略也適用於此,理想狀況下,將建立網絡策略以明確指定容許哪些容器到容器的鏈接。

舉例,如下是一個簡單的策略,該策略將拒絕特定名稱空間的全部入口流量

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-ingress-flow
spec:
  podSelector: {}
  policyTypes:
    - Ingress

此配置的示意圖
在Kubernetes上部署應用時咱們常忽略的幾件事

經過Hooks和init容器的自定義行爲

咱們使用Kubernetes系統的主要目標之一就是嘗試爲現成的開發人員提供儘量零停機的部署。 因爲應用程序關閉自身和清理已利用資源的方式多種多樣,所以這很困難。 咱們遇到特別困難的一個應用是Nginx。 咱們注意到,當咱們啓動這些Pod的滾動部署時,活動鏈接在成功終止以前被丟棄。 通過普遍的在線研究,事實證實,Kubernetes並無等待Nginx在終止Pod以前耗盡其鏈接。 使用中止前掛鉤,咱們可以注入此功能,並經過此更改實現了零停機時間。

一般狀況下,好比咱們要對Nginx進行滾動升級,可是Kubernetes在中止Pod以前並不會等待Nginx終止鏈接。這就會致使被停掉的nginx並無正確關閉全部鏈接,這樣是不合理的。因此咱們須要在中止錢使用鉤子,以解決這樣的問題。

咱們能夠在部署文件添加lifecycle

lifecycle:
  preStop:
    exec:
      command: ["/usr/local/bin/nginx-killer.sh"]

nginx-killer.sh

#!/bin/bash
sleep 3
PID=$(cat /run/nginx.pid)
nginx -s quit
while [ -d /proc/$PID ]; do
    echo "Waiting while shutting down nginx..."
    sleep 10
done

這樣,Kubernetes在關閉Pod以前,會執行nginx-killer.sh腳本,以咱們定義的方式關閉nginx

另一種狀況就是使用init容器
Init Container就是用來作初始化工做的容器,能夠是一個或者多個,若是有多個的話,這些容器會按定義的順序依次執行,只有全部的Init Container執行完後,主容器纔會被啓動
例如:

initContainers:
        - name: init
          image: busybox
          command: ["chmod","777","-R","/var/www/html"]
          imagePullPolicy: Always
          volumeMounts:
          - name: volume
            mountPath: /var/www/html
      containers:
      - name: nginx-demo
        image: nginx
        ports:
        - containerPort: 80
          name: port
        volumeMounts:
        - name: volume
          mountPath: /var/www/html

咱們給nginx的/var/www/html掛載了一塊數據盤,在主容器運行前,咱們把/var/www/html權限改爲777,以便主容器使用時不會存在權限問題。
固然這裏只是一個小栗子,Init Container更多強大的功能,好比初始化配置等。。。

Kernel Tuning(內核參數優化)

最後,將更先進的技術留給最後,哈哈
Kubernetes是一個很是靈活的平臺,旨在讓你以本身認爲合適的方式運行服務。一般若是咱們有高性能的服務,對資源要求比較嚴苛,好比常見的redis,啓動之後會有以下提示

WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

這就須要咱們修改系統的內核參數了。好在Kubernetes容許咱們運行一個特權容器,該容器能夠修改僅適用於特定運行Pod的內核參數。 如下是咱們用來修改/proc/sys/net/core/somaxconn參數的示例。

initContainers:
   - name: sysctl
      image: alpine:3.10
      securityContext:
          privileged: true
       command: ['sh', '-c', "echo 511 > /proc/sys/net/core/somaxconn"]

總結

儘管Kubernetes提供了一種開箱即用的解決方案,可是也須要你採起一些關鍵的步驟來確保程序的穩定運行。在程序上線前,務必進行屢次測試,觀察關鍵指標,並實時進行調整。
在咱們將服務部署到Kubernetes集羣前,咱們能夠問本身幾個問題:

  • 咱們的程序須要多少資源,例如內存,CPU等?
  • 服務的平均流量是多少,高峯流量是多少?
  • 咱們但願服務多長時間進行擴張,須要多長時間新的Pod能夠接受流量?
  • 咱們的Pod是正常的中止了嗎?怎麼作不影響線上服務?
  • 怎麼保證咱們的服務出問題不會影響其餘服務,不會形成大規模的服務宕機?
  • 咱們的權限是否過大?安全嗎?

終於寫完了,嗚嗚嗚~真滴好難呀~
在Kubernetes上部署應用時咱們常忽略的幾件事