k8s技術(shù)圈一周精選[第7期]

1. HPA 控制器算法
HPA 控制器與聚合 API 獲取到 Pod 性能指標(biāo)數(shù)據(jù)之后,基于下面的算法計(jì)算出目標(biāo) Pod 副本數(shù)量,與當(dāng)前運(yùn)行的 Pod 副本數(shù)量進(jìn)行對(duì)比,決定是否需要進(jìn)行擴(kuò)縮容操作:
desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]即當(dāng)前副本數(shù) * (當(dāng)前指標(biāo)值/期望的指標(biāo)值),將結(jié)果向上取整
以 CPU 請(qǐng)求數(shù)量為例,如果用戶設(shè)置的期望指標(biāo)值為 100m,當(dāng)前實(shí)際使用的指標(biāo)值為 200,則結(jié)果得到期望的 Pod 副本數(shù)量應(yīng)為兩個(gè)(200/100=2)。如果設(shè)置的期望指標(biāo)值為 50m,計(jì)算結(jié)果為 0.5,則向上取整為 1,得到目標(biāo) Pod 副本數(shù)量應(yīng)為 1 個(gè)。當(dāng)結(jié)果計(jì)算與 1 非常接近時(shí),可以設(shè)置一個(gè)容忍度讓系統(tǒng)不做擴(kuò)縮容操作。容忍度通過 kube-controller-manager 服務(wù)的啟動(dòng)參數(shù) --horizontal-pod-autoscaler-tolerance 進(jìn)行設(shè)置,默認(rèn)值為 0.1(即 10%),表示基于上述算法得到的結(jié)果在 [-10% + 10% ] 區(qū)間內(nèi),即 [ 0.9 - 1.1],控制器都不會(huì)進(jìn)行擴(kuò)縮容操作。
也可以將期望指標(biāo)值設(shè)置為指標(biāo)的平均值類型,例如 targetAverageValue 或 targetAverageUtilization,此時(shí)當(dāng)前指標(biāo)值的算法為所有 Pod 副本當(dāng)前指標(biāo)值的總和除以 Pod 副本數(shù)量得到的平均值。此外,存在幾種 Pod 異常的情況,如下所述。
Pod 正在被刪除:將不會(huì)計(jì)入目標(biāo) Pod 副本數(shù)量。
Pod 的當(dāng)前指標(biāo)值無法獲得:本次探測不會(huì)將這個(gè) Pod 納入目標(biāo) Pod 副本數(shù)量,后續(xù)的探測會(huì)被重新納入計(jì)算范圍。
如果指標(biāo)類型是 CPU 使用率,則對(duì)于正在啟動(dòng)但是還未達(dá)到 Ready 狀態(tài)的 Pod,也暫時(shí)不會(huì)納入目標(biāo)副本數(shù)量范圍??梢酝ㄟ^ kube-controller-manager 服務(wù)的啟動(dòng)參數(shù)
--horizontal-pod-autoscaler-initial-readiness-delay設(shè)置首次探測 Pod 是否 Ready 的延時(shí)時(shí)間,默認(rèn)值為 30s。另一個(gè)啟動(dòng)參數(shù)--horizontal-pod-autoscaler-cpu-initialization-period用于標(biāo)記剛啟動(dòng)一定時(shí)間內(nèi)的 Pod 為 ignoredPod,實(shí)時(shí)獲取不到信息的 Pod 被標(biāo)記為 missingPod,默認(rèn)為5min。
在計(jì)算 "當(dāng)前指標(biāo)值/期望的指標(biāo)值" 時(shí)將不會(huì)包括上述這些異常 Pod。當(dāng)存在缺失指標(biāo)的 Pod 時(shí),系統(tǒng)將更保守地重新計(jì)算平均值。系統(tǒng)會(huì)假設(shè)這些 Pod 在需要縮容時(shí)消耗了期望指標(biāo)值的 100%,在需要擴(kuò)容時(shí)消耗了期望指標(biāo)值的 0%,這樣可以抑制潛在額擴(kuò)縮容操作。此外,如果存在未達(dá)到 Ready 狀態(tài)的 Pod,并且系統(tǒng)原來會(huì)在不考慮缺失指標(biāo)或 NotReady 的 Pod 情況下進(jìn)行擴(kuò)展,則系統(tǒng)仍會(huì)保守地假設(shè)這些 Pod 消耗期望指標(biāo)值的 0%,從而進(jìn)一步抑制擴(kuò)容操作。如果在 HPA 中設(shè)置了多個(gè)指標(biāo),系統(tǒng)就會(huì)對(duì)每個(gè)指標(biāo)都執(zhí)行上面的算法,在全部結(jié)果中以期望副本數(shù)的最大值為最終結(jié)果。如果這些指標(biāo)中的任意一個(gè)都無法轉(zhuǎn)換為期望的副本數(shù)(例如無法獲取指標(biāo)的值),系統(tǒng)就會(huì)跳過擴(kuò)縮容操作。最后,在 HPA 控制器執(zhí)行擴(kuò)縮容操作之前,系統(tǒng)會(huì)記錄擴(kuò)縮容建議信息。控制器會(huì)在操作時(shí)間窗口中考慮所有的建議信息,并從中選擇得分最高的建議。這個(gè)值可通過 kube-controller-manager 服務(wù)的啟動(dòng)參數(shù) --horizontal-pod-autoscaler-downscale-stabilization-window 進(jìn)行配置,默認(rèn)值為 5min。這個(gè)配置可以讓系統(tǒng)更為平滑地進(jìn)行縮容操作,從而消除短時(shí)間內(nèi)指標(biāo)值快速波動(dòng)產(chǎn)生的影響。
2. CGroup Driver 區(qū)別
我們?cè)诎惭b Kubernetes 的時(shí)候知道有一個(gè)強(qiáng)制要求就是 Docker 和 kubelet 的 cgroup driver 必須保持一致,要么都使用 systemd,要都使用 cgroupfs,官方是推薦使用 systemd 的,但是一直搞不清楚這兩個(gè)驅(qū)動(dòng)到底有什么區(qū)別。首先我們要弄明白 cgroup 的概念,cgroup 是 control group 的縮寫,是 linux 內(nèi)核提供的一種可以限制、記錄、隔離進(jìn)程組所使用的物理資源的機(jī)制,其中物理資源包含 cpu/memory/io 等等。cgroup 是將任意進(jìn)程進(jìn)行分組化管理的 linux 內(nèi)核功能,cgroup 本身是提供將進(jìn)程進(jìn)行分組化管理的功能和接口的基礎(chǔ)結(jié)構(gòu),I/O 或內(nèi)存的分配控制等具體的資源管理功能是通過這個(gè)功能來實(shí)現(xiàn)的。這些具體的資源管理功能稱為 cgroup 子系統(tǒng)或控制器。cgroup 子系統(tǒng)有控制內(nèi)存的 Memory 控制器、控制進(jìn)程調(diào)度的 CPU 控制器等。那么 systemd 和 cgroupfs 這兩種驅(qū)動(dòng)有什么區(qū)別呢?
systemd cgroup driver 是 systemd 本身提供了一個(gè) cgroup 的管理方式,使用systemd 做 cgroup 驅(qū)動(dòng)的話,所有的 cgroup 操作都必須通過 systemd 的接口來完成,不能手動(dòng)更改 cgroup 的文件。
cgroupfs 驅(qū)動(dòng)就比較直接,比如說要限制內(nèi)存是多少、要用 CPU share 為多少?直接把 pid 寫入對(duì)應(yīng)的一個(gè) cgroup 文件,然后把對(duì)應(yīng)需要限制的資源也寫入相應(yīng)的 memory cgroup 文件和 CPU 的 cgroup 文件就可以了
所以 systemd 更加安全,因?yàn)椴荒苁謩?dòng)去更改 cgroup 文件,當(dāng)然我們也推薦使用 systemd 驅(qū)動(dòng)來管理 cgroup。
3. LeastRequestedPriority 調(diào)度算法
在研究 k8s 調(diào)度器里面的優(yōu)選算法的時(shí)候有一個(gè) LeastRequestedPriority 算法:通過計(jì)算 CPU 和內(nèi)存的使用率來決定權(quán)重,使用率越低權(quán)重越高,當(dāng)然正常肯定也是資源是使用率越低權(quán)重越高,能給別的 Pod 運(yùn)行的可能性就越大。計(jì)算的算法也很簡單:
(cpu((capacity-sum(requested))*10/capacity) + memory((capacity-sum(requested))*10/capacity))/2就是查看節(jié)點(diǎn)上剩下的 CPU 和 內(nèi)存資源(百分比)來計(jì)算權(quán)重,但是需要注意的是在計(jì)算節(jié)點(diǎn)上 requested 的值的時(shí)候是當(dāng)前節(jié)點(diǎn)上已經(jīng) requested 的值加上當(dāng)前待調(diào)度的 Pod 的 requested 的值的:(nodeInfo.NonZeroRequest().MilliCPU + podRequest)不過在計(jì)算 podRequest 的值的時(shí)候也需要注意,如果你待調(diào)度的 Pod 沒有設(shè)置 requests 資源,則會(huì)使用默認(rèn)值(CPU:100m,內(nèi)存:200M),如果設(shè)置了 requests 值即使是聲明的0,那么也是使用設(shè)置的值進(jìn)行計(jì)算。

4. go-template 獲取 ca.crt
我們可以使用 go-template 獲取 YAML 文件中的某個(gè)字段的數(shù)據(jù),比如我們要獲取某個(gè) Secret 對(duì)象的 token 或者 ca.crt 的值:
$ kubectl get secret -n kubernetes-dashboard |grep adminadmin-token-scj2m kubernetes.io/service-account-token 3 215distio.admin istio.io/key-and-cert 3 158d
比如要獲取 admin-token-scj2m 這個(gè)對(duì)象的數(shù)據(jù),首先我們要查看該對(duì)象的結(jié)構(gòu):
kubectl get secret -n kubernetes-dashboard admin-token-scj2m -o yamlapiVersion: v1data:ca.crt:namespace: a3ViZXJuZXRlcy1kYXNoYm9hcmQ=token:kind: Secret......
要獲取 token 的值比較簡單,直接使用下面的命令即可:
$ kubectl get secret -n kubernetes-dashboard admin-token-scj2m -o go-template='{{.data.token}}'但是要獲取 ca.crt 的值就要稍微麻煩點(diǎn),因?yàn)閷?duì)應(yīng)的 key 是 ca.crt,如果我們改成 .data.ca\.crt 去獲取是識(shí)別不了的,這個(gè)時(shí)候我們可以使用 go-template 里面的 index 函數(shù)來獲取這個(gè)字段的值:
$ echo $(kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get secret | grep admin | awk 'NR==1{print $1}') -o go-template='{{index .data "ca.crt"}}')5. CoreDNS 做 CNAME 解析
如何在 K8S 中做 cname,這個(gè)其實(shí) coredns 中就可以直接支持,如下圖所示

6. 強(qiáng)制刪除 namespace
有時(shí)候在 K8S 中刪除一個(gè) namespace 會(huì)卡住,強(qiáng)制刪除也沒用,前面我們介紹了可以去 etcd 里面去刪除對(duì)應(yīng)的數(shù)據(jù),這種方式比較暴力,除此之外我們也可以通過 API 去刪除。首先執(zhí)行如下命令開啟 API 代理:
$ kubectl proxy然后在另外一個(gè)終端中執(zhí)行如下所示的命令:(將 monitoring 替換成你要?jiǎng)h除的 namespace 即可)
$ kubectl get namespace monitoring -o json | jq 'del(.spec.finalizers[] | select("kubernetes"))' | curl -s -k -H "Content-Type: application/json" -X PUT -o /dev/null --data-binary @- http://localhost:8001/api/v1/namespaces/monitoring/finalize7. 調(diào)度器調(diào)優(yōu)
微信群有群友咨詢 kube-scheduler 預(yù)選輸入的是所有節(jié)點(diǎn)嗎,如果節(jié)點(diǎn)非常多豈不是性能比較差?這個(gè)其實(shí)和版本有關(guān)系,在 Kubernetes 1.12 版本之前,kube-scheduler 會(huì)檢查集群中所有節(jié)點(diǎn)的可調(diào)度性,并且給可調(diào)度節(jié)點(diǎn)打分。Kubernetes 1.12 版本添加了一個(gè)新的功能,允許調(diào)度器在找到一定數(shù)量的可調(diào)度節(jié)點(diǎn)之后就停止繼續(xù)尋找可調(diào)度節(jié)點(diǎn)。該功能能提高調(diào)度器在大規(guī)模集群下的調(diào)度性能,這個(gè)數(shù)值是集群規(guī)模的百分比,這個(gè)百分比通過 percentageOfNodesToScore 參數(shù)來進(jìn)行配置,其值的范圍在 1 到 100 之間,最大值就是 100%,如果設(shè)置為 0 就代表沒有提供這個(gè)參數(shù)配置。Kubernetes 1.14 版本又加入了一個(gè)特性,在該參數(shù)沒有被用戶配置的情況下,調(diào)度器會(huì)根據(jù)集群的規(guī)模自動(dòng)設(shè)置一個(gè)集群比例,然后通過這個(gè)比例篩選一定數(shù)量的可調(diào)度節(jié)點(diǎn)進(jìn)入打分階段。該特性使用線性公式計(jì)算出集群比例,比如100個(gè)節(jié)點(diǎn)的集群下會(huì)取 50%,在 5000節(jié)點(diǎn)的集群下取 10%,這個(gè)自動(dòng)設(shè)置的參數(shù)的最低值是 5%,換句話說,調(diào)度器至少會(huì)對(duì)集群中 5% 的節(jié)點(diǎn)進(jìn)行打分,除非用戶將該參數(shù)設(shè)置的低于 5。
8. DNS 5s 解析問題
由于 Linux 內(nèi)核中的缺陷,在 Kubernetes 集群中你很可能會(huì)碰到惱人的 DNS 間歇性 5 秒延遲問題。原因是鏡像底層庫 DNS 解析行為默認(rèn)使用 UDP 在同一個(gè) socket 并發(fā) A 和 AAAA 記錄請(qǐng)求,由于 UDP 無狀態(tài),兩個(gè)請(qǐng)求可能會(huì)并發(fā)創(chuàng)建 conntrack 表項(xiàng),如果最終 DNAT 成同一個(gè)集群 DNS 的 Pod IP 就會(huì)導(dǎo)致 conntrack 沖突,由于 conntrack 的創(chuàng)建和插入是不加鎖的,最終后面插入的 conntrack 表項(xiàng)就會(huì)被丟棄,從而請(qǐng)求超時(shí),默認(rèn) 5s 后重試,造成現(xiàn)象就是 DNS 5 秒延時(shí)。要避免 DNS 延遲的問題,有下面幾種方法:
禁止并發(fā) DNS 查詢,比如在 Pod 配置中開啟 single-request-reopen 選項(xiàng)強(qiáng)制 A 查詢和 AAAA 查詢使用相同的 socket:
dnsConfig:options:- name: single-request-reopen
禁用 IPv6 從而避免 AAAA 查詢,比如可以給 Grub 配置
ipv6.disable=1來禁止 ipv6(需要重啟節(jié)點(diǎn)才可以生效)。
使用 TCP 協(xié)議,比如在 Pod 配置中開啟 use-vc 選項(xiàng)強(qiáng)制 DNS 查詢使用 TCP 協(xié)議:
dnsConfig:options:- name: single-request-reopen- name: ndotsvalue: "5"- name: use-vc
使用 Nodelocal DNS Cache,所有 Pod 的 DNS 查詢都通過本地的 DNS 緩存查詢,避免了 DNAT,從而也繞開了內(nèi)核中的競爭問題。相關(guān)鏈接:https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/dns/nodelocaldns
9. 往期精選內(nèi)容
K8S進(jìn)階訓(xùn)練營,點(diǎn)擊下方圖片了解詳情

