生產(chǎn)環(huán)境k8s集群優(yōu)化技巧
生產(chǎn)環(huán)境k8s集群優(yōu)化技巧
1、內(nèi)核參數(shù)調(diào)化
fs.file-max=1000000
# max-file 表示系統(tǒng)級別的能夠打開的文件句柄的數(shù)量, 一般如果遇到文件句柄達到上限時,會碰到
# "Too many open files"或者Socket/File: Can’t open so many files等錯誤。
# 配置arp cache 大小
net.ipv4.neigh.default.gc_thresh1=1024
# 存在于ARP高速緩存中的最少層數(shù),如果少于這個數(shù),垃圾收集器將不會運行。缺省值是128。
net.ipv4.neigh.default.gc_thresh2=4096
# 保存在 ARP 高速緩存中的最多的記錄軟限制。垃圾收集器在開始收集前,允許記錄數(shù)超過這個數(shù)字 5 秒。缺省值是 512。
net.ipv4.neigh.default.gc_thresh3=8192
# 保存在 ARP 高速緩存中的最多記錄的硬限制,一旦高速緩存中的數(shù)目高于此,垃圾收集器將馬上運行。缺省值是1024。
# 以上三個參數(shù),當內(nèi)核維護的arp表過于龐大時候,可以考慮優(yōu)化
net.netfilter.nf_conntrack_max=10485760
# 允許的最大跟蹤連接條目,是在內(nèi)核內(nèi)存中netfilter可以同時處理的“任務”(連接跟蹤條目)
net.netfilter.nf_conntrack_tcp_timeout_established=300
net.netfilter.nf_conntrack_buckets=655360
# 哈希表大?。ㄖ蛔x)(64位系統(tǒng)、8G內(nèi)存默認 65536,16G翻倍,如此類推)
net.core.netdev_max_backlog=10000
# 每個網(wǎng)絡(luò)接口接收數(shù)據(jù)包的速率比內(nèi)核處理這些包的速率快時,允許送到隊列的數(shù)據(jù)包的最大數(shù)目。
fs.inotify.max_user_instances=524288
# 默認值: 128 指定了每一個real user ID可創(chuàng)建的inotify instatnces的數(shù)量上限
fs.inotify.max_user_watches=524288
# 默認值: 8192 指定了每個inotify instance相關(guān)聯(lián)的watches的上限
2、etcd性能優(yōu)化
2.1、Etcd對磁盤寫入延遲非常敏感,因此對于負載較重的集群,etcd一定要使用local SSD或者高性能云盤??梢允褂胒io測量磁盤實際順序 IOPS。
fio -filename=/dev/sda1 -direct=1 -iodepth 1 -thread -rw=write -ioengine=psync -bs=4k -size=60G -numjobs=64 -runtime=10 -group_reporting -name=file
2.2、由于etcd必須將數(shù)據(jù)持久保存到磁盤日志文件中,因此來自其他進程的磁盤活動可能會導致增加寫入時間,結(jié)果導致etcd請求超時和臨時leader丟失。因此可以給etcd進程更高的磁盤優(yōu)先級,使etcd服務可以穩(wěn)定地與這些進程一起運行。
| ionice -c2 -n0 -p $(pgrep etcd) | header |
| ------------------------------- | ------ |
| | |
2.3、默認etcd空間配額大小為 2G,超過 2G 將不再寫入數(shù)據(jù)。通過給etcd配置 --quota-backend-bytes 參數(shù)增大空間配額,最大支持 8G。
| --quota-backend-bytes 8589934592 | header |
| -------------------------------- | ------ |
| | |
2.4、如果etcd leader處理大量并發(fā)客戶端請求,可能由于網(wǎng)絡(luò)擁塞而延遲處理follower對等請求。在follower 節(jié)點上可能會產(chǎn)生如下的發(fā)送緩沖區(qū)錯誤的消息:
dropped MsgProp to 247ae21ff9436b2d since streamMsg's sending buffer is full
dropped MsgAppResp to 247ae21ff9436b2d since streamMsg's sending buffer is full
可以通過提高etcd對于對等網(wǎng)絡(luò)流量優(yōu)先級來解決這些錯誤。在 Linux 上,可以使用 tc 對對等流量進行優(yōu)先級排序:
tc qdisc add dev eth0 root handle 1: prio bands 3
tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip sport 2380 0xffff flowid 1:1
tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip dport 2380 0xffff flowid 1:1
tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip sport 2379 0xffff flowid 1:1
tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip dport 2379 0xffff flowid 1:1
2.5、為了在大規(guī)模集群下提高性能,可以將events存儲在單獨的 ETCD 實例中,可以配置kube-apiserver參數(shù):
--etcd-servers="http://etcd1:2379,http://etcd2:2379,http://etcd3:2379"
--etcd-servers-overrides="/events#http://etcd4:2379,http://etcd5:2379,http://etcd6:2379"
3、docker優(yōu)化
3.1、配置docker daemon并行拉取鏡像,以提高鏡像拉取效率,在/etc/docker/daemon.json中添加以下配置:
"max-concurrent-downloads": 10
3.2、可以使用local SSD或者高性能云盤作為docker容器的持久數(shù)據(jù)目錄,在/etc/docker/daemon.json中添加以下配置:
"data-root": "/ssd_mount_dir"
3.3、啟動pod時都會拉取pause鏡像,為了減小拉取pause鏡像網(wǎng)絡(luò)帶寬,可以每個node預加載pause鏡像,在每個node節(jié)點上執(zhí)行以下命令:
docker load -i /tmp/preloaded_pause_image.tar
4、kubelet優(yōu)化
4.1、設(shè)置 --serialize-image-pulls=false, 該選項配置串行拉取鏡像,默認值時true,配置為false可以增加并發(fā)度。但是如果docker daemon 版本小于 1.9,且使用 aufs 存儲則不能改動該選項。
4.2、設(shè)置--image-pull-progress-deadline=30, 配置鏡像拉取超時。默認值時1分,對于大鏡像拉取需要適量增大超時時間。
4.3、kubelet 單節(jié)點允許運行的最大 Pod 數(shù):--max-pods=110(默認是 110,可以根據(jù)實際需要設(shè)置)
5、kube-apiserver優(yōu)化
5.1、設(shè)置 --apiserver-count 和 --endpoint-reconciler-type,可使得多個 kube-apiserver 實例加入到 Kubernetes Service 的 endpoints 中,從而實現(xiàn)高可用。
]5.2、設(shè)置 --max-requests-inflight 和 --max-mutating-requests-inflight,默認是 200 和 400。 節(jié)點數(shù)量在 1000 - 3000 之間時,推薦:
--max-requests-inflight=1500
--max-mutating-requests-inflight=500
節(jié)點數(shù)量大于 3000 時,推薦:
--max-requests-inflight=3000
--max-mutating-requests-inflight=1000
5.3、使用--target-ram-mb配置kube-apiserver的內(nèi)存,按以下公式得到一個合理的值:
--target-ram-mb=node_nums * 60
6、kube-controller-manager優(yōu)化
6.1、kube-controller-manager可以通過 leader election 實現(xiàn)高可用,添加以下命令行參數(shù):
--leader-elect=true
--leader-elect-lease-duration=15s
--leader-elect-renew-deadline=10s
--leader-elect-resource-lock=endpoints
--leader-elect-retry-period=2s
6.2、限制與kube-apiserver通信的qps,添加以下命令行參數(shù):
--kube-api-qps=100
--kube-api-burst=150
7、kube-scheduler優(yōu)化
7.1、kube-scheduler可以通過 leader election 實現(xiàn)高可用,添加以下命令行參數(shù):
--leader-elect=true
--leader-elect-lease-duration=15s
--leader-elect-renew-deadline=10s
--leader-elect-resource-lock=endpoints
--leader-elect-retry-period=2s
7.2、限制與kube-apiserver通信的qps,添加以下命令行參數(shù):
--kube-api-qps=100
--kube-api-burst=150
8、pod優(yōu)化
8.1、為容器設(shè)置資源請求和限制,尤其是一些基礎(chǔ)插件服務
spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory
spec.containers[].resources.limits.ephemeral-storage
spec.containers[].resources.requests.ephemeral-storage
在k8s中,會根據(jù)pod的limit 和 requests的配置將pod劃分為不同的qos類別:
* Guaranteed
* Burstable
* BestEffort
當機器可用資源不夠時,kubelet會根據(jù)qos級別劃分遷移驅(qū)逐pod。被驅(qū)逐的優(yōu)先級:BestEffort > Burstable > Guaranteed。
8.2、對關(guān)鍵應用使用 nodeAffinity、podAffinity 和 podAntiAffinity 等保護,使其調(diào)度分散到不同的node上。比如kube-dns配置
8.3、盡量使用控制器來管理容器(如 Deployment、StatefulSet、DaemonSet、Job 等)
9、kubernetes集群數(shù)據(jù)備份與還原
9.1、etcd數(shù)據(jù)備份(備份數(shù)據(jù)前先找到etcd集群當前的leader)
ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key --cacert=/etc/kubernetes/pki/etcd/ca.crt endpoint --cluster status | grep -v 'false' | awk -F '[/ :]' '{print $4}'
然后登錄到leader節(jié)點,備份snapshot db文件:
rsync -avp /var/lib/etcd/member/snap/db /tmp/etcd_db.bak
還原 將備份的snaphost db文件上傳到各個etcd節(jié)點,使用以下命令還原數(shù)據(jù):
ETCDCTL_API=3 etcdctl snapshot restore
/tmp/etcd_db.bak
--endpoints=192.168.0.11:2379
--name=192.168.0.11
--cert=/etc/kubernetes/pki/etcd/server.crt
--key=/etc/kubernetes/pki/etcd/server.key
--cacert=/etc/kubernetes/pki/etcd/ca.crt
--initial-advertise-peer-urls=https://192.168.0.11:2380
--initial-cluster-token=etcd-cluster-0
--initial-cluster=192.168.0.11=https://192.168.0.11:2380,192.168.0.12=https://192.168.0.12:2380,192.168.0.13=https://192.168.0.13:2380
--data-dir=/var/lib/etcd/
--skip-hash-check=true
10、harbor
如果使用harbor作為鏡像倉庫與chart倉庫,可使用腳本將harbor中所有的鏡像和chart導入導出。
10.1、備份
#!/bin/bash
harborUsername='admin'
harborPassword='Harbor12345'
harborRegistry='registry.test.com'
harborBasicAuthToken=$(echo -n "${harborUsername}:${harborPassword}" | base64)
docker login --username ${harborUsername} --password ${harborPassword} ${harborRegistry}
rm -f dist/images.list
rm -f dist/charts.list
# list projects
projs=`curl -s -k -H "Authorization: Basic ${harborBasicAuthToken}" "https://${harborRegistry}"'/api/projects?page=1&page_size=1000' | jq -r '.[] | "(.project_id)=(.name)"'`
for proj in ${projs[*]}; do
projId=`echo $proj|cut -d '=' -f 1`
projName=`echo $proj|cut -d '=' -f 2`
# list repos in one project
repos=`curl -s -k -H "Authorization: Basic ${harborBasicAuthToken}" "https://${harborRegistry}"'/api/repositories?page=1&page_size=1000&project_id='"${projId}" | jq -r '.[] | "(.id)=(.name)"'`
for repo in ${repos[*]}; do
repoId=`echo $repo|cut -d '=' -f 1`
repoName=`echo $repo|cut -d '=' -f 2`
# list tags in one repo
tags=`curl -s -k -H "Authorization: Basic ${harborBasicAuthToken}" "https://${harborRegistry}"'/api/repositories/'"${repoName}"'/tags?detail=1' | jq -r '.[].name'`
for tag in ${tags[*]}; do
#echo ${tag};
# pull image
docker pull ${harborRegistry}/${repoName}:${tag}
# tag image
docker tag ${harborRegistry}/${repoName}:${tag} ${repoName}:${tag}
# save image
mkdir -p $(dirname dist/${repoName})
docker save -o dist/${repoName}:${tag}.tar ${repoName}:${tag}
# record image to list file
echo "${repoName}:${tag}" >> dist/images.list
done
done
# list charts in one project
charts=`curl -s -k -H "Authorization: Basic ${harborBasicAuthToken}" "https://${harborRegistry}"'/api/chartrepo/'"${projName}"'/charts' | jq -r '.[].name'`
for chart in ${charts[*]}; do
#echo ${chart}
# list download urls in one chart
durls=`curl -s -k -H "Authorization: Basic ${harborBasicAuthToken}" "https://${harborRegistry}"'/api/chartrepo/'"${projName}"'/charts/'"${chart}" | jq -r '.[].urls[0]'`
#echo ${durl[*]}
for durl in ${durls[*]}; do
#echo ${durl};
# download chart
mkdir -p $(dirname dist/${projName}/${durl})
curl -s -k -H "Authorization: Basic ${harborBasicAuthToken}" -o dist/${projName}/${durl} "https://${harborRegistry}/chartrepo/${projName}/${durl}"
# record chart to list file
echo "${projName}/${durl}" >> dist/charts.list
done
done
done
10.2、還原
#!/bin/bash
harborUsername='admin'
harborPassword='Harbor12345'
harborRegistry='registry.test.com'
harborBasicAuthToken=$(echo -n "${harborUsername}:${harborPassword}" | base64)
docker login --username ${harborUsername} --password ${harborPassword} ${harborRegistry}
while IFS="" read -r image || [ -n "$image" ]
do
projName=${image%%/*}
# echo ${projName}
# create harbor project
curl -k -X POST -H "Authorization: Basic ${harborBasicAuthToken}" "https://${harborRegistry}/api/projects" -H "accept: application/json" -H "Content-Type: application/json" -d '{ "project_name": "'"$projName"'", "metadata": { "public": "true" }}'
# load image
docker load -i dist/${image}.tar
# tag image
docker tag ${image} ${harborRegistry}/${image}
# push image
docker push ${harborRegistry}/${image}
done < dist/images.list
while IFS="" read -r chart || [ -n "$chart" ]
do
projName=${chart%%/*}
# echo ${projName}
# create harbor project
curl -k -X POST -H "Authorization: Basic ${harborBasicAuthToken}" "https://${harborRegistry}/api/projects" -H "accept: application/json" -H "Content-Type: application/json" -d '{ "project_name": "'"$projName"'", "metadata": { "public": "true" }}'
# upload chart
curl -s -k -H "Authorization: Basic ${harborBasicAuthToken}" -X POST "https://${harborRegistry}/api/chartrepo/${projName}/charts" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "chart=@dist/${chart};type=application/gzip"
done < dist/charts.list
11、pvc對應的存儲卷
集群中其它應用數(shù)據(jù)一般是保存在pvc對應的存儲卷中的。
11.1、備份
首先根據(jù)pvc找到對應的pv:
kubectl -n test get pvc demo-pvc -o jsonpath='{.spec.volumeName}'
找到pv的掛載目錄:
mount | grep pvc-xxxxxxxxxxxxxxxxxxx
使用rsync命令備份數(shù)據(jù):
rsync -avp --delete /var/lib/kubelet/pods/xxxxxx/volumes/xxxxxxx/ /tmp/pvc-data-bak/test/demo-pvc/
11.2、還原
首先根據(jù)pvc找到對應的pv:
kubectl -n test get pvc demo-pvc -o jsonpath='{.spec.volumeName}'
找到pv的掛載目錄:
mount | grep pvc-xxxxxxxxxxxxxxxxxxx
使用rsync命令備份數(shù)據(jù):
rsync -avp --delete /tmp/pvc-data-bak/test/demo-pvc/ /var/lib/kubelet/pods/xxxxxx/volumes/xxxxxxx/
12、備份數(shù)據(jù)管理
所有備份出的數(shù)據(jù)可以存放在一個目錄下,并使用restic工具保存到多個后端存儲系統(tǒng)上
# 初始化備份倉庫
restic --repo sftp:user@host:/srv/restic-repo init
# 將目錄備份到備份倉庫
restic --repo sftp:user@host:/srv/restic-repo backup /data/k8s-all-data