Ⅰ 記一次k8s集群節點鏡像存儲容量報警問題
自從我們的kubernetes集群部署到生產環境後,將流量從原有的伺服器上切過來之後,部分節點出現掛載目錄容量爆滿的情況。
運維的同事報給我們之後,我們首先想到的是節點鏡像過多,於是我們提供一個命令用於清理當前節點上無用的、報錯的、鏡像和docker資源文件
docker system prune 命令可以用於清理磁碟,刪除關閉的容器、無用的數據卷和網路,以及dangling鏡像(即無tag的鏡像)
docker system prune -a 命令清理得更加徹底,可以將沒有容器使用Docker鏡像都刪掉。
待運維執行之後,目錄存儲資源釋放了一些,我們本以為這就告一李畝友段落了。然而,事與願違,沒過多久,再次容量報警。。。
我們開始重視起來,開始檢視節點上工作的容器,發現在日誌爆炸的節點上運行了定時任務,開發人員將定時任務的日誌輸出到控制台,於是我們回到節點docker的工作目錄,通過 -sh * 方式查看每個文件夾大小,發現docker目錄下containers目錄佔用空間巨大,進去看原來是每個運行的容器存放日誌的目錄,我們找出佔用空間最大的日誌目錄,發現容器日誌特別的大
我們可使用如下命令查看各個日誌的文件大小
ls -lh $(find /var/lib/docker/containers/ -name *-json.log)
那我們如何清理日誌呢,如果docker容器正在運行,那麼使哪槐用rm -rf 方式刪除日誌後,通過df -h會發現磁碟空間並沒有釋放
原因:在Linux或者Unix系統中,通過rm或者文件管理器刪除文件將會從文件系統的目錄結構上解除鏈接(unlink).然而如果文件是被打開的(有一個進程正在使用),那麼進程將仍然可以讀取該文件,磁碟空間也一直被佔用
我們通過 cat /dev/null > *-json.log 來清理相應的日誌,然後重啟
systemctl daemon-reload
systemctl restart docker
然而,我思考,不能每次滿的時候找運維清理日誌啊,這多麻煩,難道docker沒有相應的機制應付輸出到控制台的日誌嗎?答案是:當然不會
在新版的docker中我們可以通過設置 vim /etc/docker/daemon.json 來限制docker的日誌量
"log-driver":"json-file","log-opts":{ "max-size" :"200m","max-file":"5"}
顧名思義max-size就是每個日誌文件大小,max-file是最多生成的文件數,如上我設置成功後,每個容器運行的日誌最多有五份每份200M大小,這樣就基本限制了容器的日誌大小。
然後你覺得結束了嗎??並不!!
容器日誌我們是限制完了,本以為高枕無憂,不用擔心出現日誌爆滿的情況了,但是事與願違,過幾天硬碟容量又滿了。。。
我們究其原因,發現在docker的運行目錄下overlay這個文件夾里存放著所有的容器掛載目錄,也就是容器的系統文件在這里放著,在容器中跑著的服務產生日誌很可能並不是輸出到控制台,而是保存到本地,容器內的日誌文件也是會佔用磁碟空間的,這就讓我們犯愁了,這個不好限制開發團隊不存日誌或者規定團隊存放目錄啊,對於一個成熟的容器平台來說,海納百川那是必須的~
於是我們打起了kubelet的主意
在 k8s中文社區中有詳細的限制方法 那具體做法呢,其實就是為節點加上驅逐策略,當cpu或者內存或者硬碟空間不滿足要求時,自動驅逐一些消耗資源大的容器,保證節點穩定性。
裡面主要是有以下幾個關鍵驅逐信號
上面的每個信號都支持整數值或者百分比。百分比的分母部分就是各個信號的總量。kubelet 支持兩種文件系統分區。
nodefs:保存 kubelet 的卷和守護進程日誌等。
imagefs:在容器運行時,用於保存鏡像以及可寫入層。
imagefs 是可選的。Kubelet 能夠利用 cAdvisor 自動發現這些文耐殲件系統。Kubelet 不關注其他的文件系統。所有其他類型的配置,例如保存在獨立文件系統的卷和日誌,都不被支持。
因為磁碟壓力已經被驅逐策略接管,因此未來將會停止對現有 垃圾收集 方式的支持。
具體的內容大家可以詳細去看看社區里的介紹,我這里就不再贅述了,我這邊獻上我的驅逐方案~
執行vim /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
在裡面插入
Environment="KUBELET_OTHER_ARGS=
--eviction-hard=memory.available<2Gi,nodefs.available<5Gi,imagefs.available<5Gi
--eviction-minimum-reclaim=memory.available=500Mi,nodefs.available=5Gi,imagefs.available=5Gi
--node-status-update-frequency=10s
--eviction-pressure-transition-period=30s"
解讀:內存小於2G驅逐,root目錄磁碟空間小於5G驅逐,鏡像目錄磁碟空間小於5G驅逐,節點檢測為每10秒一次,在跳出壓力狀態之前要等待的時間為30秒。
在某些場景下,驅逐 Pod 可能只回收了很少的資源。這就導致了 kubelet 反復觸發驅逐閾值。另外回收資源例如磁碟資源,是需要消耗時間的。
要緩和這種狀況,Kubelet 能夠對每種資源定義 minimum-reclaim。kubelet 一旦發現了資源壓力,就會試著回收至少 minimum-reclaim 的資源,使得資源消耗量回到期望范圍。
也就是說當內存觸發驅逐時,kubelet至少要讓內存有2.5G,當root和鏡像磁碟空間發生驅逐時,kubelet至少要讓磁碟有10G的空間。
那驅逐的規則是什麼呢,對什麼樣的容器做驅逐呢?這個我們下回分解哈。
那總的來說,若要解決節點鏡像存儲報警,我們可以從三個方面入手
1.容器:通過docker限制容器日誌大小
2.k8s:通過kubelet來驅逐過大的容器
3.跟開發人員溝通,精簡容器,不讓內存泄漏,不隨意使用資源(很難啦~~~)
祝各位新春快樂~
Ⅱ k8s-踩坑篇2-伺服器重啟後重啟集群
昨天不知道說明原因,測試環境的物理顫兆機掛了,安裝k8s的3台虛枯洞嘩擬機正好全在這台物理機上面,現在要把他們全部啟動起來,安裝的時候好像沒有相關的步驟,今天研究一下手動重啟。
報錯:The connection to the server 10.100.1.236:6443 was refused
很明顯apiserver沒有起來,但是apiserver安裝的時候是以容器的方式安裝的
顯示一個容器也沒起來,完全不知道咋整,搜索k8s重啟,看了好幾篇文章,有的文章居然是kubeadm init,這txx還有什麼好說的呢。不過民間的高手也是很多的,如下:
靜態pod可以直接被kubelet啟動,那很有可能是kubelet沒有正確啟動,嘗試如下:每台機器沒行上都要操作
然後用 docker ps 查看,可以看到master節點上的很多k8s容器已經啟動起來了,但是worker node上的容器依然沒有啟動,用 kubectl get nodes ,看到node的狀態還是notReady,那就很有可能是防火牆的問題了,直接關閉防火牆,看到worker node上的容器也起來了。
等待所有的calico pod啟動完畢,node狀態就變成ready了。
但是之前啟動的 nignx pod 都不存在了,原因可能是:etcd的啟動方式也是容器化的,重啟後etcd內的數據被初始化了。
---本來懷疑是 systemctl daemon-reload 命令造成的,但是,今天這台伺服器又重啟了,我又試了一遍,不執行 systemctl daemon-reload 命令是無法重啟k8s的。
---但是今天重啟k8s,完成之後,昨天新建的2個pod仍然是存在的,那很有可能是我昨天不熟悉流程參雜了誤操作,但是現在也想不起來了,就暫時告一段落了,後面遇到問題再說吧。
Ⅲ 9. K8s存儲
Kubernetes的Volume是Pod的一部分,Volume不是單獨的對象,不能獨立創建,只能在Pod中定義。
Pod中的所有容器都可以訪問Volume,但必須要掛載,且可以掛載到容器中任何目錄。
實際中使用容器存儲如下圖所示,將容器的內容掛載到Volume中,通過Volume兩個容器間實現了存儲共享。
Volume的生命周期與掛載它的Pod相同,但是Volume裡面的文件可能在Volume消失後仍然存在,這取決於Volume的類型。
Kubernetes的Volume有非常多的類型:
這個Volume掛載後就是一個空目錄,應用程序可以在裡面讀寫文件,emptyDir Volume的生命周期與Pod相同,Pod刪除後Volume的數據也同時刪除掉。
emptyDir的一些用途:
配置示例
emptyDir也可以設置存儲介質為內存
HostPath存儲的內容與節點相關,如果Pod被調度到其他Node,HostPath無法提供跨Node的數據。
配置示例
ConfigMap存儲的是鍵值對,在Volume中應用時鍵值對表示的是 文件名 和 文件內容 ,代表將ConfigMap的每條數據填入Volume。ConfigMap的配置數據在 data 欄位下定義。
ConfigMap文件示例
Volume中引用ConfigMap
與ConfigMap類似,在data欄位中存儲key-value鍵值對形式,不過存儲的value不是明文,是Base64編碼的加密值。
在Volume中引用後,文件中的值是Base64解碼後的值,而非加密值。
Kubernetes抽象了PV(PersistentVolume)和PVC(PersistentVolumeClaim)來解耦網路存儲種類多樣的問題,從而讓使用者不用關心具體的基礎設施,當需要存儲資源的時候,只要像CPU和內存一樣,聲明要多少即可。
PV是集群級別的資源,並不屬於某個命名空間(在 default 命名空間下),而PVC是命名空間級別的資源,PV可以與任何命名空間的PVC資源綁定。
Kubernetes管理員設置好網路存儲的類型,提供對應的PV描述符配置到Kubernetes,使用者需要存儲的時候只需要創建PVC,然後在Pod中使用Volume關聯PVC,即可讓Pod使用到存儲資源,它們之間的關系如下圖所示。
PV 持久卷是用 插件 的形式來實現的,目前支持以下插件:
CSI
Container Storage Interface,容器存儲介面,基於CSI這套介面,可以開發定製出CSI插件,從而支持特定的存儲,達到解耦的目的。
PVC創建示例:
使用StorageClass可以自動創建PV,StorageClass包含 provisioner 、 parameters 和 reclaimPolicy 欄位, 這些欄位會在 StorageClass 需要動態分配 PersistentVolume 時會使用到。在聲明PVC時加上StorageClassName,就可以自動創建PV,並自動創建底層的存儲資源。
provisioner: PV配置器,用來決定使用哪個卷插件制備 PV。 該欄位必須指定。
reclaimPolicy: 回收策略,可以是 Delete 或者 Retain ,默認是 Delete
使用StorageClassName創建PVC示例:
Ⅳ 深入剖析k8s中的存儲
本文是《深入剖析k8s》學習筆記的第四篇,主要對k8s中的存儲數據卷進行分析和學喚臘習。
容器中的存儲是不穩定的,一旦容器重啟或者銷毀,這些存儲就都會丟失。所以真實使用場景下,都會以數據卷掛載的方式將外部存儲應用到容器中,帶來的好處就是:
在k8s中,如果要在容器中使用數據卷,需要像如下一個pod的yaml示例一樣進行聲明定義:
其中pod的定義中,聲明使用了自定義名稱為 my-volume 的數據卷,並且類型為emptyDir;k8s的volume支持多種數據類型,可以通過命令 kubectl explain pod.spec.volumes 來查看所有支持的volume類型,其中常用的類型及意義比較如下:
從工程分工角度上來說,存儲的定義和聲明應該由運維人員完成,開發人員只要使用即可。因此,為了避免將存儲細節過度地暴露給開發人員,k8s才引進了Persistent Volume Claim(PVC)和Persistent Volume(PV)這兩個API對象,同時也降低了開發人員使用存儲卷的門檻。如此,開發人員只需要如下兩步就能解決存儲卷的使用問題:
那麼開發人員聲明的PVC到底使用的是什麼存儲卷呢,這個和廳就由運維人員負責維護就行了,如下是一個PV的定義,開發人員了解即可:
為什麼這個PV可以和上面的PVC綁定呢?只要符合如下的條件,k8s就會自動將它們綁定,綁定後,在PVC的配置文件中就能看到使用的數據卷就是該PV了。
總的來說,PVC和PV的關系就像是介面和實現的關系,PVC是介面定義,聲明要使用什麼,至於怎麼實現,就是PV去完成的事情。如此解耦,使得開發和運維都能高效地搞定存儲。
假設開發人員在創建好帶有PVC的pod之後,且pod已經運行了,才發現運維還沒有來得及創建對應的PVC或者PVC創建有問題,致使pod存儲卷使用有問題該怎麼辦?只要運維及時創建對應的PV,k8s中的volume controller會循環遍歷沒有成功綁定PV的PVC,幫它們尋找喚鏈隱合適的PV進行綁定。
一個k8s集群往往有很多開發團隊在使用,開發會部署很多pod,如果這些pod都需要存儲卷,運維人員就需要天天創建pv來滿足開發人員pvc綁定的需求了,太浪費人力,所以這種重復工作就被k8s中的storageClass取代了。原先手動創建PV的方式被稱為static provisioning,使用storageClass自動創建PV的方式被稱為dynamic provisioning,storageClass其實就是PV的一個模板,其定義大致分為兩個部分:
在開發人員創建PVC的時候,只要指定使用的storageClassName是如上定義和創建的my-storageclass,那麼k8s就會根據該storageClass自動創建一個PV與該PVC綁定。
Ⅳ 解決k8s集群中Redis Cluster故障
k8s集群中的一個node節點故障,將這個node節點下線後上面的pod遷移到其他節點,但是大量pod都產生報錯。經排查,是由於redis集群故障導致。但是查看resdis pod,都是running狀態,如下圖
由於這些pod是組成集群使用,既然pod是正常的,應用又報redis鏈接的錯誤,所以問題肯定出在Redis Cluster上,查看Redis Cluster狀態:
這個示意圖我只畫出三個node,簡單表達一下意思即可。三個node上各運行了一個master和一個slave節點。由於node3節點故障已經移除集群,這個節點上之前運行的其他無狀態pod遷移到其他節點可以正常運行,但是master2和slave2在node3上有持久化數據,雖然在node4上重建了,但是由於缺失數據,原來的集群狀態被破壞了,所以重新部署也無法恢復,由於是master2和slave2的數據都丟失了,集群無法重建。通過開發了解到,redis上都是緩存數據,丟失影響不大,於是刪除本地持久化數據,重新部署redis node,再手動創建集群。
三個節點都添加完成,並且沒有報錯。進入一個master節點查看集群狀態:
集群狀態終於恢復正常。重建後的Redis Cluster集群架構示意圖如下
總結:對於有狀態的應用,redis、mysql等,容器化時一定要考慮周全,避免主從節點運行在一個節點上。對於redis應用,如果讀寫I/O不是特別高,還是建議直接使用主從復制架構,故障恢復簡單且迅速。