k8s生产实践

k8s在生产中需要注意的各种事项和便捷方法

上下文切换

https://github.com/ahmetb/kubectx

https://github.com/junegunn/fzf

https://github.com/sbstp/kubie

kubectx是一个在 kubectl 上更快地在上下文(集群)之间切换的工具。
kubens是一个可以轻松在 Kubernetes 命名空间之间切换(并为 kubectl 配置它们)的工具。

合并多集群kubeconfig

1
2
3
4
5
6
7
8
9
10
export KUBECONFIG=$(find $PWD -maxdepth 1 -type f | grep -v all-in-one | tr '\n' ':' | sed 's/.$//')
echo $KUBECONFIG
# 将所有 kubeconfig 文件合并为一个
kubectl config view --flatten > all-in-one.yaml
# 显示在 kubeconfig 中定义的所有集群:
kubectl config get-contexts --kubeconfig=all-in-one.yaml


# 替换默认kubeconf

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# win
choco install kubens kubectx
choco install fzf

# linux
sudo git clone https://github.com/ahmetb/kubectx /opt/kubectx
sudo ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx
sudo ln -s /opt/kubectx/kubens /usr/local/bin/kubens

apt install fzf

# 补全
Completion scripts for bash
git clone https://github.com/ahmetb/kubectx.git ~/.kubectx
COMPDIR=$(pkg-config --variable=completionsdir bash-completion)
ln -sf ~/.kubectx/completion/kubens.bash $COMPDIR/kubens
ln -sf ~/.kubectx/completion/kubectx.bash $COMPDIR/kubectx

source <(kubectl completion bash)
cat << EOF >> ~/.bashrc

#kubectx and kubens
export PATH=~/.kubectx:\$PATH
EOF

如果您已fzf安装,但想选择不使用此功能,请设置环境变量KUBECTX_IGNORE_FZF=1

使用

1
2
kubectx 直接切换集群
kubens 切换命名空间

pod亲和性

podAffinity 亲和性

podAntiAffinity 反亲和性

差异:

  • 匹配过程相同

  • 最终处理调度结果时取反

    podAffinity中可调度节点,在 podAntiAffinity中为不可调度
    podAffini中高分节点,在 podAntiAffinity中为低分

requiredDuringSchedulingIgnoredDuringExecution: #硬策略

preferredDuringSchedulingIgnoredDuringExecution: #软策略

  • In:label 的值在某个列表中
  • NotIn:label 的值不在某个列表中
  • Gt:label 的值大于某个值
  • Lt:label 的值小于某个值
  • Exists:某个 label 存在
  • DoesNotExist:某个 label 不存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
name: pod-anti-affinity
labels:
anti-affinity-demo: anti-affinity-demo-yahaha
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: #硬策略
- labelSelector:
matchExpressions:
- key: label_key_135
operator: In
values:
- label_value_135
topologyKey: kubernetes.io/hostname
containers:
- name: pod-anti-affinity
image: nginx
imagePullPolicy: IfNotPresent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: v1
kind: Pod
metadata:
name: pod-anti-affinity
labels:
anti-affinity-demo: anti-affinity-demo-yahaha
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: "kubernetes.io/hostname"
labelSelector:
matchExpressions:
- key: "label_key_135"
operator: In
values:
- "label_value_135"
containers:
- name: pod-anti-affinity
image: nginx
imagePullPolicy: IfNotPresent

ingress

apisix

开启gzip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"comp_level": 9,
"disable": false,
"types": [
"text/plain",
"text/css",
"application/json",
"application/javascript",
"text/xml",
"application/xml",
"application/xml+rss",
"text/javascript"
]
}

nginx-ingress

开启gzip

1
2
use-gzip: 'true'
gzip-level: '5'

配置gzip的时候,若更改好ingress controler 的configmap, 默认情况下所有域名都开了gzip,若需要关闭指定的域名,那么直接在 annotations 中设置 nginx.ingress.kubernetes.io/server-snippet: gzip off;

上传文件大小:

cm: nginx-configuration

1
2
proxy-body-size: 500m
client-body-buffer-size: 100m

超时时间

出于某种原因,nginx进程不断重新加载 - 每30秒 . 每次重新加载时,所有连接都被删除 .

解决方案是设置:

在nginx-ingress config-map 中 .

1
worker-shutdown-timeout: "900s"

配置了nginx-ingress超时:

1
2
3
4
5
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-body-size: "1024m"
proxy-next-upstream-timeout

proxy_connect_timeout :后端服务器连接的超时时间_发起握手等候响应超时时间(默认60秒)

proxy_read_timeout:连接成功后_等候后端服务器响应时间_其实已经进入后端的排队之中等候处理(也可以说是后端服务器处理请求的时间)

proxy_send_timeout :后端服务器数据回传时间_就是在规定时间之内后端服务器必须传完所有的数据

代理k8s api-server

1
2
3
4
#先使用frp或ssh将端口代理至可达网络主机
尝试连接如果报x509 ip 认证失败没查证书可信域名
https://www.jianshu.com/p/35ac252b5045
后添加hosts即可连接

负载不均问题

对于长连接的服务,可能会存在负载不均的问题,下面介绍两种场景。

滚动更新负载不均

滚动更新时,旧 Pod 上的连接逐渐断掉,重连到新启动的 Pod 上,越先启动的 Pod在连接数比较固定或波动不大的情况下,所接收到的连接数越多,造成负载不均

rr 策略负载不均

假如长连接服务的不同连接的保持时长差异很大,而 ipvs 转发时默认是r 策略转发,如果某些后端 Pod”运气较差”,它们上面的连接保持时间比较较长,而由于是r 转发,它们身上累计的连接数就可能较多,节点上通过 pvsadm -Ln -t CLUSTER-IP:PORT 查看某个 service 的转发情况

扩容失效问题

在连接数比较固定或波动不大的情况下,工作负载在 HPA 自动扩容时,由于是场链接,连接数又比较固定,所有连接都“固化”在之前的 Pod 上,新扩出的 Pod 几乎没有连接,造成之前的 Pod 高负载,而扩出来的 Pd 又无法分担压力,导致扩容失丝

最佳实践

  1. 业务层面自动重连,避免连接“固化”到某个后端 Po上。比如周期性定时重连,或者一个连接中处理的请求数达到阙值后自动重连
  2. 不直接请求后端,通过七层代理访问。比如 gRPC 协议,可以使用 nginxingress 转发 gRPC,也可以使用istio 转发gRPC,这样对于 gRPC 这样多个请求复用同一个长连接的场景,经过七层代理后,可以自动拆分请求,在请求级别负载均衡。
  3. kube-proxy 的ipvs 转发策略设置为 sh(–ipvs-scheduler=sh)。如果用的腾讯云 EKS 弹性集群,没有节点,看不到kube-proxy,可以通过 eks.tke.cloud.tencent.com/ipvs-scheduler:sh 这样的注解来设置,另外还支持将端口号也加入到 hash 的 key,更利于负载均衡,需再设置下eks.tke.cloud.tencent.com/ipvs-sh-port: “true’

容器3个探针

Readiness-就绪探针

就绪探针的目的是让Kubernetes知道该应用是否已经准备好为流量服务。Kubernetes将始终确保准备就绪探针通过之后开始分配服务,将流量发送到Pod。

Liveness-存活探针

你怎么知道你的应用程序是活的还是死的?存活探针可以让你做到这一点。如果你的应用死了,Kubernetes会移除旧的Pod并用新Pod替换它。

startup-启动探针

指示容器中的应用是否已经启动。如果提供了启动探针(startup probe),则禁用所有其他探针,直到它成功为止。如果启动探针失败,kubelet 将杀死容器,容器服从其重启策略进行重启。如果容器没有提供启动探针,则默认状态为成功Success。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
readinessProbe:
httpGet:
port: 20011
path: /actuator/health
scheme: HTTP
httpHeaders:
- name: Content-Type
value: application/json
initialDelaySeconds: 95
periodSeconds: 2
timeoutSeconds: 15
livenessProbe:
httpGet:
port: 20011
path: /actuator/health
scheme: HTTP
httpHeaders:
- name: Content-Type
value: application/json
initialDelaySeconds: 95
periodSeconds: 10
timeoutSeconds: 15

限制 CPU 和内存资源

1
2
3
4
5
6
7
resources:
requests:
cpu: "0.5"
memory: 2Gi
limits:
cpu: "2"
memory: 2Gi

kubectl patch

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
APP_LIST=`kubectl get deployments.apps -n suosi-prod | egrep -v 'NAME|nacos|redis|artemis|rocket' | grep 'suosi-'|awk '{print $1}'`
for deploy in $APP_LIST
do
echo $deploy
kubectl patch deploy -n suosi-prod $deploy -p '{"spec":{"template":{"spec":{"containers":[{"name":"'${deploy}'","env":[{"name":"aliyun_logs_suosi-app-logs","value":"stdout"},{"name":"aliyun_logs_logtag_tags","value":"app='${deploy}'"},{"name":"TZ","value":"Asia/Shanghai"}]}]}}}}'
sleep 20
done
{"spec":{"template":{"spec":{"containers":[{"name":"'${deploy}'","env":[{"name":"TZ","value":"Asia/Shanghai"}]}]}}}}

kubectl set env -n suosi-dev deployment/fe-manager-container -c fe-manager-container TZ=Asia/Shanghai

启用RBAC

人数少可使用permission-manager插件,多了就直接用rancher

配置网络策略

网络策略只不过是一个对象,它使你能够明确地声明和决定哪些流量是允许的,哪些是不允许的。这样,Kubernetes将能够阻止所有其他不想要的和不符合规则的流量。在我们的集群中定义和限制网络流量是强烈推荐的基本且必要的安全措施之一。

日志记录

标准输出只输出warn,后端程序直接推送至logstore,或写文件后抓取

磁盘(日志)清理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
docker image prune -a -f
docker container prune -f
find /var/lib/docker/overlay2/ -name "info-log.log" -exec cp /dev/null {} \;
find /var/lib/docker/overlay2/ -name "ons.log.*" -exec rm -rf {} \;
find /var/lib/docker/overlay2/ -name "ons.log" -exec cp /dev/null {} \;
find /var/lib/docker/containers/ -name "*-json.log.*" -exec rm -rf {} \;
find /var/lib/docker/overlay2/*/diff/data/applogs/xxl-job/jobhandler/ -name "*.log" -mtime +1 -exec rm -rf {} \;


vim /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
crictl 要下载k8s对应版本
#wget https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.20.0/crictl-v1.20.0-linux-amd64.tar.gz
https://git.xfj0.cn/https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.20.0/crictl-v1.20.0-linux-amd64.tar.gz
wget https://kgithub.com/kubernetes-sigs/cri-tools/releases/download/v1.20.0/crictl-v1.20.0-linux-amd64.tar.gz
tar xf crictl-v1.20.0-linux-amd64.tar.gz
mv crictl /usr/local/bin/
crictl rmi --prune
find /var/lib/container/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/ -name "info-log.log" -exec cp /dev/null {} \;
1
2
forfiles -p "C:\comchatlog" -s -m *.log -d -5 -c "cmd /c del @path"
forfiles -p "C:\comchatlog" -s -m *.log -d -5 -c "cmd /c echo @path"

监控

spring-cloud-prometheus插件,钉钉告警

无状态,不可变

镜像里只有运行代码

自动扩缩容

水平pod自动伸缩(HPA)、垂直pod自动伸缩(VPA)和集群自动伸缩。

控制镜像拉取来源

控制在集群中运行所有容器的镜像源。如果您允许您的Pod从公共资源中拉取镜像,您就不知道其中真正运行的是什么。

如果从受信任的注册表中提取它们,则可以在注册表上应用策略以提取安全和经过认证的镜像。

持续学习

不断评估应用程序的状态和设置,以学习和改进。例如,回顾容器的历史内存使用情况可以得出这样的结论:我们可以分配更少的内存,在长期内节省成本。

零停机时间

通过在HA中运行所有服务,支持集群和服务的零停机升级。这也将保证您的客户获得更高的可用性。

使用pod反亲和性来确保在不同的节点上调度一个pod的多个副本,从而通过计划中的和计划外的集群节点停机来确保服务可用性。

使用pod Disruptions策略,不惜一切代价确保您有最低的Pod副本数量!

1
2
3
4
5
6
7
8
lifecycle:
preStop:
exec:
command:
- "sh"
- "-c"
- |
curl -X "PUT" http://eureka:20001/eureka/apps/<APP-NAME>/${HOSTNAME}/status?value=DOWN;sleep 90

为容器配置 Security Context

大部分情况下容器不需要太多的权限,我们可以通过 Security Context 限定容器的权限和访问控制,只需加上 SecurityContext 字段:

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: <Pod name>
spec:
containers:
-name: <container name>
image: <image>
+ securityContext:

禁用 allowPrivilegeEscalation

allowPrivilegeEscalation=true 表示容器的任何子进程都可以获得比父进程更多的权限。最好将其设置为 false,以确保 RunAsUser 命令不能绕过其现有的权限集。

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: <Pod name>
spec:
containers:
-name: <container name>
image: <image>
securityContext:
+ allowPrivilegeEscalation: false

不要使用 root 用户

为了防止来自容器内的提权攻击,最好不要使用 root 用户运行容器内的应用。UID 设置大一点,尽量大于 3000

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Pod
metadata:
name: <name>
spec:
securityContext:
+ runAsUser: <UID higher than 1000>
+ runAsGroup: <UID higher than 3000>

不必挂载 Service Account Token

ServiceAccount 为 Pod 中运行的进程提供身份标识,怎么标识呢?当然是通过 Token 啦,有了 Token,就防止假冒伪劣进程。如果你的应用不需要这个身份标识,可以不必挂载:

1
2
3
4
5
6
apiVersion: v1
kind: Pod
metadata:
name: <name>
spec:
+ automountServiceAccountToken: false

确保 seccomp 设置正确

对于 Linux 来说,用户层一切资源相关操作都需要通过系统调用来完成,那么只要对系统调用进行某种操作,用户层的程序就翻不起什么风浪,即使是恶意程序也就只能在自己进程内存空间那一分田地晃悠,进程一终止它也如风消散了。seccomp(secure computing mode)就是一种限制系统调用的安全机制,可以可以指定允许那些系统调用。

对于 Kubernetes 来说,大多数容器运行时都提供一组允许或不允许的默认系统调用。通过使用 runtime/default 注释或将 Pod 或容器的安全上下文中的 seccomp 类型设置为 RuntimeDefault,可以轻松地在 Kubernetes 中应用默认值。

1
2
3
4
5
6
apiVersion: v1
kind: Pod
metadata:
name: <name>
annotations:
+ seccomp.security.alpha.kubernetes.io/pod: "runtime/default"

默认的 seccomp 配置文件应该为大多数工作负载提供足够的权限。如果你有更多的需求,可以自定义配置文件。

限制容器的 capabilities

容器依赖于传统的Unix安全模型,通过控制资源所属用户和组的权限,来达到对资源的权限控制。以 root 身份运行的容器拥有的权限远远超过其工作负载的要求,一旦发生泄露,攻击者可以利用这些权限进一步对网络进行攻击。

默认情况下,使用 Docker 作为容器运行时,会启用 NET_RAW capability,这可能会被恶意攻击者进行滥用。因此,建议至少定义一个PodSecurityPolicy(PSP),以防止具有 NET_RAW 功能的容器启动。

通过限制容器的 capabilities,可以确保受攻击的容器无法为攻击者提供横向攻击的有效路径,从而缩小攻击范围。

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: <name>
spec:
securityContext:
+ runAsNonRoot: true
+ runAsUser: <specific user>
capabilities:
drop:
+ -NET_RAW
+ -ALL

如果你对 Linux capabilities 这个词一脸懵逼,建议去看看脑残入门系列:

只读

如果容器不需要对根文件系统进行写入操作,最好以只读方式加载容器的根文件系统,可以进一步限制攻击者的手脚。

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: <Pod name>
spec:
containers:
-name: <container name>
image: <image>
securityContext:
+ readOnlyRootFilesystem: true

node污点

1
kubectl taint node cn-hangzhou.10.10.198.18 schedule_type=true:PreferNoSchedule

NoSchedule:只有拥有和这个 taint 相匹配的 toleration 的 pod 才能够被分配到这个节点。

PreferNoSchedule:系统会尽量避免将 pod 调度到存在其不能容忍 taint 的节点上,但这不是强制的。

NoExecute :任何不能忍受这个 taint 的 pod 都会马上被驱逐,任何可以忍受这个 taint 的 pod 都不会被驱逐。

node预留资源

https://kubernetes.io/zh/docs/tasks/administer-cluster/out-of-resource/#

K8S 把计算节点资源分为 4 个部分:

  • Kube Reserved:预留给 K8S 管理进程的资源,如 Kubelet,Docker Daemon 等

  • System Reserved:预留给系统资源,如 kernel,sshd,udev 等

  • Eviction Thresholds:驱逐(Eviction)的阈值,只支持 memory 和 storage。

  • Allocatable(available for pods):pods 可以使用的资源

为了简化管理,建议不对 kube-reserved/system-reserved 做区分,直接使用 --system-reserved做系统预留。

1
--system-reserved=memory=4Gi,storage=5Gi \ # 保留4G内存 5G磁盘

node驱逐阈值

1
2
3
4
5
6
7
# 软驱逐阈值
--eviction-soft=memory.available<4Gi,nodefs.available<15Gi,imagefs.available<20Gi \ # 驱逐阈值的集合
--eviction-soft-grace-period=memory.available=2m,nodefs.available=2m,imagefs.available=2m \ # 驱逐宽限期的集合
--eviction-max-pod-grace-period=30 \ # 当满足软驱逐阈值并终止 pod 时允许的最大宽限期值
# 硬驱逐阈值
--eviction-hard=memory.available<1Gi,nodefs.available<10Gi,imagefs.available<15Gi \ # 如果满足条件将触发 Pod 驱逐。
--eviction-minimum-reclaim=memory.available=500Mi,nodefs.available=500Mi,imagefs.available=2Gi \ # 最小驱逐回收

驱逐监控时间间隔

kubelet 根据其配置的整理时间间隔计算驱逐阈值。

  • housekeeping-interval 是容器管理时间间隔。

增大内核选项配置

/etc/sysctl.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 表示系统级别的能够打开的文件句柄的数量, 一般如果遇到文件句柄达到上限时,会碰到"Too many open files"或者Socket/File: Can’t open so many files等错误。
fs.file-max=52706963
fs.nr_open = 52706963
#-----------------------
# 最大文件句柄
vm.max_map_count = 262144
#-----------------------
# 最大跟踪连接数,默认 nf_conntrack_buckets * 4
net.nf_conntrack_max = 10485760
# 允许的最大跟踪连接条目,是在内核内存中netfilter可以同时处理的“任务”(连接跟踪条目)
net.netfilter.nf_conntrack_max=10485760
#-----------------------


#-----------------------
vm.panic_on_oom=0 # 等于0时,表示当内存耗尽时,内核会触发OOM killer杀掉最耗内存的进程
vm.overcommit_memory=1 # 不检查物理内存是否够用
# 0:表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
# 1:表示内核允许分配所有的物理内存,而不管当前的内存状态如何
# 2:表示内核允许分配超过所有物理内存和交换空间总和的内存
#-----------------------
# 配置arp cache 大小
# 存在于ARP高速缓存中的最少层数,如果少于这个数,垃圾收集器将不会运行。缺省值是128。
net.ipv4.neigh.default.gc_thresh1=1024
# 保存在 ARP 高速缓存中的最多的记录软限制。垃圾收集器在开始收集前,允许记录数超过这个数字 5 秒。缺省值是 512。
net.ipv4.neigh.default.gc_thresh2=4096
# 保存在 ARP 高速缓存中的最多记录的硬限制,一旦高速缓存中的数目高于此,垃圾收集器将马上运行。缺省值是1024。
net.ipv4.neigh.default.gc_thresh3=8192
# 以上三个参数,当内核维护的arp表过于庞大时候,可以考虑优化
#-----------------------
# 该参数用于设定系统中最多允许存在多少tcp套接字不被关联到任何一个用户文件句柄上
net.ipv4.tcp_max_orphans = 32768
#-----------------------
# 在 TIME_WAIT 数量等于 tcp_max_tw_buckets 时,不会有新的 TIME_WAIT 产生
net.ipv4.tcp_max_tw_buckets = 32768
#-----------------------
# tcp_max_syn_backlog是指定所能接受SYN同步包的最大客户端数量,即半连接上限,默认值是128,即SYN_REVD状态的连接数
net.ipv4.tcp_max_syn_backlog = 8096
#-----------------------
# 每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。
net.netfilter.nf_conntrack_tcp_timeout_established=300
#-----------------------
# 哈希表大小(只读)(64位系统、8G内存默认 65536,16G翻倍,如此类推)
net.netfilter.nf_conntrack_buckets = 65536
#-----------------------
# 关于conntrack的详细说明:https://testerhome.com/topics/7509
#-----------------------
# 默认值: 128 指定了每一个real user ID可创建的inotify instatnces的数量上限
fs.inotify.max_user_instances=524288
#-----------------------
# 默认值: 8192 指定了每个inotify instance相关联的watches的上限
fs.inotify.max_user_watches=524288
#-----------------------
# 每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目
net.core.netdev_max_backlog = 10000
# 表示socket监听(listen)的backlog上限,也就是就是socket的监听队列(accept queue),当一个tcp连接尚未被处理或建立时(半连接状态),会保存在这个监听队列,默认为 128,在高并发场景下偏小,优化到 32768。参考 https://imroc.io/posts/kubernetes-overflow-and-drop/
net.core.somaxconn = 32768
# PID 与线程限制
kernel.pid_max=655350
kernel.threads-max=655350


#net.netfilter.nf_conntrack_max = 262144
#net.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_max=10485760
net.nf_conntrack_max = 10485760


#net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120
#net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
#net.netfilter.nf_conntrack_tcp_timeout_close_wait = 3600
#net.netfilter.nf_conntrack_tcp_timeout_established = 86400

net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 360
net.netfilter.nf_conntrack_tcp_timeout_established = 8640

/etc/security/limits.conf

1
2
3
4
root soft nofile 655350
root hard nofile 655350
* soft nofile 655350
* hard nofile 655350

SLB实例压测请求超时

  1. 登录后端服务器,排查发现Nginx日志没有异常,但是messages日志存在“nf_conntrack: table full, dropping packet”错误。该信息是因为Linux系统为每个经过内核网络栈的数据包,都生成一个新的连接记录项,当服务器处理的连接过多时,连接跟踪表无法记录新的连接记录项,服务器会丢弃新建连接的数据包。所以导致SLB和后端服务器TCP三次握手失败,出现504状态码。

解决方案

  1. 建议调整nf_conntrack参数,调整命令如下所示,参数值请以实际情况为准。

    说明:该方法会临时修改参数,重启实例后配置会不生效。

    1
    2
    3
    sysctl -w net.netfilter.nf_conntrack_max=1048576
    sysctl -w net.netfilter.nf_conntrack_buckets=262144
    sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=3600

在init-container做信号量限制放开

1
sysctl -w kernel.sem="1034 32000 100 1000";

Pod 时区同步(PodPreset)

往往都会遇到修改 Pod 时区的需求,更多的需求是要求所有的 Pod 都在同一个时区,比如我们所在的东8区,一般我们可以通过环境变量或者挂载主机的时区文件到 Pod 中来实现同步,但是这样需要我们对每一个 Pod 手动做这样的操作,一个更好的方式就是利用 PodPreset 来预设。

首先启用 PodPreset:在 kube-apiserver 启动参数 -runtime-config 增加 settings.k8s.io/v1alpha1=true;

然后在 –admission-control 增加 PodPreset 启用。最后重启 kube-apiserver 即表示启用成功。可以通过如下命令查看是否启用成功:

1
$ kubectl get podpresets

然后创建一个 PodPresents 资源对象:

1
2
3
4
5
6
7
8
9
10
apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:
name: tz-env
spec:
selector:
matchLabels:
env:
- name: TZ
values: Asia/Shanghai

这里需要注意的地方是,一定需要写 selector...matchLabels,但是 matchLabels 为空,表示应用于所有容器,这个就是我们想要的,创建上面这个资源对象,然后我们去创建一个普通的 Pod 可以查看下是否注入了上面的 TZ 这个环境变量。需要注意的是,PodPreset 是 namespace 级别的对象,其作用范围只能是同一个命名空间下的容器。

限制日志大小和emptydir

v1.7 + 支持对基于本地存储(如 hostPath, emptyDir, gitRepo 等)的容量进行调度限额。为了支持这个特性,Kubernetes 将本地存储分为两类:

  • storage.kubernetes.io/overlay,即 /var/lib/docker 的大小
  • storage.kubernetes.io/scratch,即 /var/lib/kubelet 的大小

Kubernetes 根据 storage.kubernetes.io/scratch 的大小来调度本地存储空间,而根据 storage.kubernetes.io/overlay 来调度容器的存储。比如为容器请求 64MB 的可写层存储空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: ls1
spec:
restartPolicy: Never
containers:
- name: hello
image: busybox
command: ["df"]
resources:
requests:
storage.kubernetes.io/overlay: 64Mi

为 emptyDir 请求 64MB 的存储空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: ls1
spec:
restartPolicy: Never
containers:
- name: hello
image: busybox
command: ["df"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir:
sizeLimit: 64Mi

lxcfs让pod正确的意识到给自己分配的资源

1
2
3
4
$ git clone https://gitclone.com/github.com/denverdino/lxcfs-admission-webhook.git
$ kubectl apply -f lxcfs-admission-webhook/deployment/lxcfs-daemonset.yaml
$ cd lxcfs-admission-webhook && deployment/install.sh
$ kubectl label namespace default lxcfs-admission-webhook=enabled

不支持alpine镜像

DEBUG

如何摘下某个Pod进行Debug

使用label机制,对Pod进行标记。在Service定义中,我们添加 status: serving字段。当需要摘下某个Pod做Debug,而又不影响整个服务,可以:

1
2
$ kubectl get pods --selector="status=serving"
$ kubectl label pods webserver-rc-lxag2 --overwrite status=debuging

此时kubelet就会把这个Pod从Service的后端列表中删掉。等到Debug完,想恢复?再改回去就好了:

1
$ kubectl label pods webserver-rc-lxag2 --overwrite status=serving

Pod重启了,如何看重启之前的日志?

下面的命令只能看到当前Pod的日志:

1
$ kubectl logs zookeeper-1

通过 --previous参数可以看之前Pod的日志

1
$ kubectl logs zookeeper-1 --previous

kubectl debug

https://github.com/aylei/kubectl-debug/blob/master/docs/zh-cn.md

kubectl-debug 包含两部分:
kubectl-debug:命令行工具;
debug-agent:部署在K8s的node上,用于启动关联排错工具容器

1.20版本之前的k8s都需要安装插件后kubectl就能自动识别插件(1.15之前都需要使用kubect-debug二进制命令)

containerd 支持要安装pre最新分支

kubectl-debug –agentless=false -n suosi-prod xxl-job-78dfcbdcff-58nql # 最新分支无法使用配置文件

https://github.com/aylei/kubectl-debug/issues/140

客户端安装

1
2
3
4
curl -Lo debug_0.1.1_linux_amd64.tar.gz https://ghproxy.com/https://github.com/aylei/kubectl-debug/releases/download/v0.1.1/kubectl-debug_0.1.1_linux_amd64.tar.gz
tar xf debug_0.1.1_linux_amd64.tar.gz
mv kubectl-debug /usr/local/bin/
kubectl debug -h

服务端安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
kubectl apply -f https://raw.githubusercontent.com/aylei/kubectl-debug/master/scripts/agent_daemonset.yml
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app: debug-agent
name: debug-agent
spec:
selector:
matchLabels:
app: debug-agent
template:
metadata:
labels:
app: debug-agent
spec:
hostPID: true
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: debug-agent
image: aylei/debug-agent:v0.1.1
imagePullPolicy: Always
securityContext:
privileged: true
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10027
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
ports:
- containerPort: 10027
hostPort: 10027
name: http
protocol: TCP
volumeMounts:
- name: cgroup
mountPath: /sys/fs/cgroup
- name: lxcfs
mountPath: /var/lib/lxc
mountPropagation: Bidirectional
- name: docker
mountPath: "/var/run/docker.sock"
- name: runcontainerd
mountPath: "/run/containerd"
- name: runrunc
mountPath: "/run/runc"
- name: vardata
mountPath: "/var/data"
hostNetwork: true #要开启
volumes:
- name: cgroup
hostPath:
path: /sys/fs/cgroup
- name: lxcfs
hostPath:
path: /var/lib/lxc
type: DirectoryOrCreate
- name: docker
hostPath:
path: /var/run/docker.sock
# containerd client will need to access /var/data, /run/containerd and /run/runc
- name: vardata
hostPath:
path: /var/data
- name: runcontainerd
hostPath:
path: /run/containerd
- name: runrunc
hostPath:
path: /run/runc
updateStrategy:
rollingUpdate:
maxUnavailable: 5
type: RollingUpdate

debug-agent 两种运行方式:
daemon-set模式,agent pod预先部署在所有node上,会始终占用资源,对于排错调试频率不高的环境造成资源浪费;
agentless模式,kubectl-debug执行命令后,才创建agent pod和排错工具容器,并在退出后删除工具容器和agent pod。由于每次执行都要重新拉起agent,启动会比daemon-set模式稍慢。
使用-a, –agentless开启agentless模式:

需要配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
cat .kube/debug-config
# debug-agent 映射到宿主机的端口
# 默认 10027
#agentPort: 10027

# 是否开启ageless模式
# 默认 true
agentless: false
# agentPod 的 namespace, agentless模式可用
# 默认 default
#agentPodNamespace: default
# agentPod 的名称前缀,后缀是目的主机名, agentless模式可用
# 默认 debug-agent-pod
#agentPodNamePrefix: debug-agent-pod
# agentPod 的镜像, agentless模式可用
# 默认 aylei/debug-agent:latest
#agentImage: aylei/debug-agent:latest

# debug-agent DaemonSet 的名字, port-forward 模式时会用到
# 默认 'debug-agent'
#debugAgentDaemonset: debug-agent
# debug-agent DaemonSet 的 namespace, port-forward 模式会用到
# 默认 'default'
#debugAgentNamespace: devops
# 是否开启 port-forward 模式
# 默认 true
portForward: false
# image of the debug container
# default as showed
image: registry-vpc.cn-hangzhou.aliyuncs.com/nbugs-share/share:debug_imagev1
# start command of the debug container
# default ['bash']
#command:
#- '/bin/bash'
#- '-l'
# private docker registry auth kuberntes secret, default is kubectl-debug-registry-secret
# 使用私有仓库镜像,并设置私有仓库使用的kubernetes secret
# secret data原文请设置为 {Username: <username>, Password: <password>}
# 默认RegistrySecretName为kubectl-debug-registry-secret,默认RegistrySecretNamespace为default
#RegistrySecretName: qh-docker-registry
#RegistrySecretNamespace: default
# 在默认的agentless模式下可以设置agent pod的resource资源限制
# 若不设置,默认为空
#agentCpuRequests: ""
#agentCpuLimits: ""
#agentMemoryRequests: ""
#agentMemoryLimits: ""
# 当使用fork mode时,如果需要复制出来的pod保留原pod的labels,可以设置需要保留的labels列表
# 格式为[]string
# 默认为空(既不保留任何原POD的labels,新fork出pod的labels)
#forkPodRetainLabels: []

建议使用daemon-set模式

1
2
3
进阶使用:
排错init-container:
kubectl debug demo-pod --container=init-pod
1
2
排错crash pod:
kubectl debug POD_NAME --fork