Kubernetes上生產環境後的常見問題以及解決方法

隨着微服務的不斷推進,使用 k8s 集羣越來越多,越來越深入,隨之而來會遇到一系列的問題,本文向大家介紹實際使用 k8s 遇到的一些問題以及解決方法。

1問題一:修復 K8S 內存泄露問題
問題描述
當 k8s 集羣運行日久以後,有的 node 無法再新建 pod,並且出現如下錯誤,當重啓服務器之後,纔可以恢復正常使用。查看 pod 狀態的時候會出現以下報錯。

applying cgroup … caused: mkdirno space left on device

或者在 describe pod 的時候出現 cannot allocate memory。

這時候你的 k8s 集羣可能就存在內存泄露的問題了,當創建的 pod 越多的時候內存會泄露的越多,越快。

具體查看是否存在內存泄露

$ cat /sys/fs/cgroup/memory/kubepods/memory.kmem.slabinfo

當出現 cat: /sys/fs/cgroup/memory/kubepods/memory.kmem.slabinfo: Input/output error 則說明不存在內存泄露的情況 如果存在內存泄露會出現

slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>

解決方案
解決方法思路:關閉 runc 和 kubelet 的 kmem,因爲升級內核的方案改動較大,此處不採用。

kmem 導致內存泄露的原因:

內核對於每個 cgroup 子系統的的條目數是有限制的,限制的大小定義在 kernel/cgroup.c #L139,當正常在 cgroup 創建一個 group 的目錄時,條目數就加 1。我們遇到的情況就是因爲開啓了 kmem accounting 功能,雖然 cgroup 的目錄刪除了,但是條目沒有回收。這樣後面就無法創建 65535 個 cgroup 了。也就是說,在當前內核版本下,開啓了 kmem accounting 功能,會導致 memory cgroup 的條目泄漏無法回收。

2.1 編譯 runc
配置 go 語言環境

$ wget https://dl.google.com/go/go1.12.9.linux-amd64.tar.gz
$ tar xf go1.12.9.linux-amd64.tar.gz -C /usr/local/

# 寫入bashrc
$ vim ~/.bashrc
$ export GOPATH="/data/Documents"
$ export GOROOT="/usr/local/go"
$ export PATH="$GOROOT/bin:$GOPATH/bin:$PATH"
$ export GO111MODULE=off

# 驗證
$ source ~/.bashrc
$ go env

下載 runc 源碼

$ mkdir -p /data/Documents/src/github.com/opencontainers/
$ cd /data/Documents/src/github.com/opencontainers/
$ git clone https://github.com/opencontainers/runc
$ cd runc/
$ git checkout v1.0.0-rc9  # 切到v1.0.0-rc9 tag

編譯

# 安裝編譯組件
$ sudo yum install libseccomp-devel
$ make BUILDTAGS='seccomp nokmem'
# 編譯完成之後會在當前目錄下看到一個runc的可執行文件,等kubelet編譯完成之後會將其替換

2.2 編譯 kubelet
下載 kubernetes 源碼

$ mkdir -p /root/k8s/
$ cd /root/k8s/
$ git clone https://github.com/kubernetes/kubernetes
$ cd kubernetes/
$ git checkout v1.15.3

製作編譯環境的鏡像(Dockerfile 如下)

FROM centos:centos7.3.1611

ENV GOROOT /usr/local/go
ENV GOPATH /usr/local/gopath
ENV PATH /usr/local/go/bin:$PATH

RUN yum install rpm-build which where rsync gcc gcc-c++ automake autoconf libtool make -y \
    && curl -L https://studygolang.com/dl/golang/go1.12.9.linux-amd64.tar.gz | tar zxvf - -C /usr/local

在製作好的 go 環境鏡像中來進行編譯 kubelet

$ docker run  -it --rm   -v /root/k8s/kubernetes:/usr/local/gopath/src/k8s.io/kubernetes   build-k8s:centos-7.3-go-1.12.9-k8s-1.15.3   bash
$ cd /usr/local/gopath/src/k8s.io/kubernetes
#編譯
$ GO111MODULE=off KUBE_GIT_TREE_STATE=clean KUBE_GIT_VERSION=v1.15.3 make kubelet GOFLAGS="-tags=nokmem"

替換原有的 runc 和 kubelet
將原有 runc 和 kubelet 備份

$ mv /usr/bin/kubelet /home/kubelet
$ mv /usr/bin/docker-runc /home/docker-runc

停止 docker 和 kubelet

$ systemctl stop docker
$ systemctl stop kubelet

將編譯好的 runc 和 kubelet 進行替換

$ cp kubelet /usr/bin/kubelet
$ cp kubelet /usr/local/bin/kubelet
$ cp runc /usr/bin/docker-runc

檢查 kmem 是否關閉前需要將此節點的 pod 殺掉重啓或者重啓服務器,當結果爲 0 時成功

$ cat /sys/fs/cgroup/memory/kubepods/burstable/memory.kmem.usage_in_bytes

檢查是否還存在內存泄露的情況

$ cat /sys/fs/cgroup/memory/kubepods/memory.kmem.slabinfo

2問題二:k8s 證書過期問題的兩種處理方法
前情提要
公司測試環境的 k8s 集羣使用已經很長時間了,突然有一天開發聯繫我說 k8s 集羣無法訪問,開始以爲是測試環境的機器磁盤空間不夠了,導致組件異常或者把開發使用的鏡像自動清理掉了,但是當登上機器去查驗的時候發現不是這個原因。當時覺得也很疑惑。因爲開發環境使用人數較少,不應該會出問題,所以就去查驗 log 的相關報錯信息。

問題現象
出現 k8s api 無法調取的現象,使用 kubectl 命令獲取資源均返回如下報錯:

$ Unable to connect to the server: x509: certificate has expired or is not yet valid

經網上搜索之後發現應該是 k8s 集羣的證書過期了,使用命令排查證書的過期時間

$ kubeadm alpha certs check-expiration
發現確實是證書過期了

相關介紹以及問題解決
因爲我們是使用 kubeadm 部署的 k8s 集羣,所以更新起證書也是比較方便的,默認的證書時間有效期是一年,我們集羣的 k8s 版本是 1.15.3 版本是可以使用以下命令來更新證書的,但是一年之後還是會到期,這樣就很麻煩,所以我們需要了解一下 k8s 的證書,然後我們來生成一個時間很長的證書,這樣我們就可以不用去總更新證書了。

$ kubeadm alpha certs renew all --config=kubeadm.yaml
$ systemctl restart kubelet
$ kubeadm init phase kubeconfig all --config kubeadm.yaml
# 然後將生成的配置文件替換,重啓 kube-apiserver、kube-controller、kube-scheduler、etcd 這4個容器即可

另外 kubeadm 會在控制面板升級的時候自動更新所有證書,所以使用 kubeadm 搭建的集羣最佳的做法是經常升級集羣,這樣可以確保你的集羣保持最新狀態並保持合理的安全性。但是對於實際的生產環境我們可能並不會去頻繁的升級集羣,所以這個時候我們就需要去手動更新證書。

下面我們通過調用 k8s 的 api 來實現更新一個 10 年的證書

首先在 /etc/kubernetes/manifests/kube-controller-manager.yaml 文件加入配置

spec:
  containers:
  - command:
    - kube-controller-manager
    # 設置證書有效期爲 10年
    - --experimental-cluster-signing-duration=87600h
    - --client-ca-file=/etc/kubernetes/pki/ca.crt

修改完成後 kube-controller-manager 會自動重啓生效。然後我們需要使用下面的命令爲 Kubernetes 證書 API 創建一個證書籤名請求。如果您設置例如 cert-manager 等外部簽名者,則會自動批准證書籤名請求(CSRs)。否者,您必須使用 kubectl certificate 命令手動批准證書。以下 kubeadm 命令輸出要批准的證書名稱,然後等待批准發生:

# 需要將全部 pending 的證書全部批准
$ kubeadm alpha certs renew all --use-api --config kubeadm.yaml &

我們還不能直接重啓控制面板的幾個組件,這是因爲使用 kubeadm 安裝的集羣對應的 etcd 默認是使用的 /etc/kubernetes/pki/etcd/ca.crt 這個證書進行前面的,而上面我們用命令 kubectl certificate approve 批准過後的證書是使用的默認的 /etc/kubernetes/pki/ca.crt 證書進行簽發的,所以我們需要替換 etcd 中的 ca 機構證書:

# 先拷貝靜態 Pod 資源清單
$ cp -r /etc/kubernetes/manifests/ /etc/kubernetes/manifests.bak
$ vi /etc/kubernetes/manifests/etcd.yaml
......
spec:
  containers:
  - command:
    - etcd
    # 修改爲 CA 文件
    - --peer-trusted-ca-file=/etc/kubernetes/pki/ca.crt
    - --trusted-ca-file=/etc/kubernetes/pki/ca.crt
......
    volumeMounts:
    - mountPath: /var/lib/etcd
      name: etcd-data
    - mountPath: /etc/kubernetes/pki  # 更改證書目錄
      name: etcd-certs
  volumes:
  - hostPath:
      path: /etc/kubernetes/pki  # 將 pki 目錄掛載到 etcd 中去
      type: DirectoryOrCreate
    name: etcd-certs
  - hostPath:
      path: /var/lib/etcd
      type: DirectoryOrCreate
    name: etcd-data
......

由於 kube-apiserver 要連接 etcd 集羣,所以也需要重新修改對應的 etcd ca 文件:

$ vi /etc/kubernetes/manifests/kube-apiserver.yaml
......
spec:
  containers:
  - command:
    - kube-apiserver
    # 將etcd ca文件修改爲默認的ca.crt文件
    - --etcd-cafile=/etc/kubernetes/pki/ca.crt
......

除此之外還需要替換 requestheader-client-ca-file 文件,默認是 /etc/kubernetes/pki/front-proxy-ca.crt 文件,現在也需要替換成默認的 CA 文件,否則使用聚合 API,比如安裝了 metrics-server 後執行 kubectl top 命令就會報錯:

$ cp /etc/kubernetes/pki/ca.crt /etc/kubernetes/pki/front-proxy-ca.crt
$ cp /etc/kubernetes/pki/ca.key /etc/kubernetes/pki/front-proxy-ca.key

這樣我們就得到了一個 10 年證書的 k8s 集羣,還可以通過重新編譯 kubeadm 來實現一個 10 年證書的,這個我沒有嘗試,不過在初始化集羣的時候也是一個方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章