k8s 在生产中需要注意的各种事项和便捷方法
上下文切换
https://github.com/ahmetb/kubectx
https://github.com/junegunn/fzf
https://github.com/sbstp/kubie
kubectx是一个在 kubectl 上更快地在上下文(集群)之间切换的工具。
kubens是一个可以轻松在 Kubernetes 命名空间之间切换(并为 kubectl 配置它们)的工具。
合并多集群 kubeconfig
安装
使用
pod 亲和性
podAffinity
亲和性
podAntiAffinity
反亲和性
差异:
-
匹配过程相同
-
最终处理调度结果时取反
即
podAffinity 中可调度节点,在 podAntiAffinity 中为不可调度
podAffini 中高分节点,在 podAntiAffinity 中为低分
requiredDuringSchedulingIgnoredDuringExecution
: #硬策略
preferredDuringSchedulingIgnoredDuringExecution
: #软策略
- In:label 的值在某个列表中
- NotIn:label 的值不在某个列表中
- Gt:label 的值大于某个值
- Lt:label 的值小于某个值
- Exists:某个 label 存在
- DoesNotExist:某个 label 不存在
ingress
apisix
开启 gzip
nginx-ingress
开启 gzip
配置 gzip 的时候,若更改好 ingress controler 的 configmap, 默认情况下所有域名都开了 gzip,若需要关闭指定的域名,那么直接在 annotations 中设置 nginx.ingress.kubernetes.io/server-snippet: gzip off;
上传文件大小:
cm: nginx-configuration
超时时间
出于某种原因,nginx 进程不断重新加载 - 每 30 秒 . 每次重新加载时,所有连接都被删除 .
解决方案是设置:
在 nginx-ingress config-map
中 .
配置了 nginx-ingress 超时:
proxy_connect_timeout :后端服务器连接的超时时间_发起握手等候响应超时时间(默认 60 秒)
proxy_read_timeout:连接成功后_等候后端服务器响应时间_其实已经进入后端的排队之中等候处理(也可以说是后端服务器处理请求的时间)
proxy_send_timeout :后端服务器数据回传时间_就是在规定时间之内后端服务器必须传完所有的数据
代理 k8s api-server
负载不均问题
对于长连接的服务,可能会存在负载不均的问题,下面介绍两种场景。
滚动更新负载不均
滚动更新时,旧 Pod 上的连接逐渐断掉,重连到新启动的 Pod 上,越先启动的 Pod 在连接数比较固定或波动不大的情况下,所接收到的连接数越多,造成负载不均
rr 策略负载不均
假如长连接服务的不同连接的保持时长差异很大,而 ipvs 转发时默认是 r 策略转发,如果某些后端 Pod”运气较差”,它们上面的连接保持时间比较较长,而由于是 r 转发,它们身上累计的连接数就可能较多,节点上通过 pvsadm -Ln -t CLUSTER-IP:PORT 查看某个 service 的转发情况
扩容失效问题
在连接数比较固定或波动不大的情况下,工作负载在 HPA 自动扩容时,由于是场链接,连接数又比较固定,所有连接都“固化”在之前的 Pod 上,新扩出的 Pod 几乎没有连接,造成之前的 Pod 高负载,而扩出来的 Pd 又无法分担压力,导致扩容失丝
最佳实践
- 业务层面自动重连,避免连接“固化”到某个后端 Po 上。比如周期性定时重连,或者一个连接中处理的请求数达到阙值后自动重连
- 不直接请求后端,通过七层代理访问。比如 gRPC 协议,可以使用 nginxingress 转发 gRPC,也可以使用 istio 转发 gRPC,这样对于 gRPC 这样多个请求复用同一个长连接的场景,经过七层代理后,可以自动拆分请求,在请求级别负载均衡。
- 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。
限制 CPU 和内存资源
kubectl patch
启用 RBAC
人数少可使用permission-manager
插件,多了就直接用 rancher
配置网络策略
网络策略只不过是一个对象,它使你能够明确地声明和决定哪些流量是允许的,哪些是不允许的。这样,Kubernetes 将能够阻止所有其他不想要的和不符合规则的流量。在我们的集群中定义和限制网络流量是强烈推荐的基本且必要的安全措施之一。
日志记录
标准输出只输出 warn,后端程序直接推送至 logstore,或写文件后抓取
磁盘(日志)清理
监控
spring-cloud-prometheus 插件,钉钉告警
无状态,不可变
镜像里只有运行代码
自动扩缩容
水平 pod 自动伸缩(HPA)、垂直 pod 自动伸缩(VPA)和集群自动伸缩。
控制镜像拉取来源
控制在集群中运行所有容器的镜像源。如果您允许您的 Pod 从公共资源中拉取镜像,您就不知道其中真正运行的是什么。
如果从受信任的注册表中提取它们,则可以在注册表上应用策略以提取安全和经过认证的镜像。
持续学习
不断评估应用程序的状态和设置,以学习和改进。例如,回顾容器的历史内存使用情况可以得出这样的结论:我们可以分配更少的内存,在长期内节省成本。
零停机时间
通过在 HA 中运行所有服务,支持集群和服务的零停机升级。这也将保证您的客户获得更高的可用性。
使用 pod 反亲和性来确保在不同的节点上调度一个 pod 的多个副本,从而通过计划中的和计划外的集群节点停机来确保服务可用性。
使用 pod Disruptions 策略,不惜一切代价确保您有最低的 Pod 副本数量!
为容器配置 Security Context
大部分情况下容器不需要太多的权限,我们可以通过 Security Context
限定容器的权限和访问控制,只需加上 SecurityContext 字段:
禁用 allowPrivilegeEscalation
allowPrivilegeEscalation=true
表示容器的任何子进程都可以获得比父进程更多的权限。最好将其设置为 false,以确保 RunAsUser
命令不能绕过其现有的权限集。
不要使用 root 用户
为了防止来自容器内的提权攻击,最好不要使用 root 用户运行容器内的应用。UID 设置大一点,尽量大于 3000
。
不必挂载 Service Account Token
ServiceAccount 为 Pod 中运行的进程提供身份标识,怎么标识呢?当然是通过 Token 啦,有了 Token,就防止假冒伪劣进程。如果你的应用不需要这个身份标识,可以不必挂载:
确保 seccomp 设置正确
对于 Linux 来说,用户层一切资源相关操作都需要通过系统调用来完成,那么只要对系统调用进行某种操作,用户层的程序就翻不起什么风浪,即使是恶意程序也就只能在自己进程内存空间那一分田地晃悠,进程一终止它也如风消散了。seccomp(secure computing mode)就是一种限制系统调用的安全机制,可以可以指定允许那些系统调用。
对于 Kubernetes 来说,大多数容器运行时都提供一组允许或不允许的默认系统调用。通过使用 runtime/default
注释或将 Pod 或容器的安全上下文中的 seccomp 类型设置为 RuntimeDefault
,可以轻松地在 Kubernetes 中应用默认值。
默认的 seccomp 配置文件应该为大多数工作负载提供足够的权限。如果你有更多的需求,可以自定义配置文件。
限制容器的 capabilities
容器依赖于传统的 Unix 安全模型,通过控制资源所属用户和组的权限,来达到对资源的权限控制。以 root 身份运行的容器拥有的权限远远超过其工作负载的要求,一旦发生泄露,攻击者可以利用这些权限进一步对网络进行攻击。
默认情况下,使用 Docker 作为容器运行时,会启用 NET_RAW
capability,这可能会被恶意攻击者进行滥用。因此,建议至少定义一个PodSecurityPolicy
(PSP),以防止具有 NET_RAW 功能的容器启动。
通过限制容器的 capabilities,可以确保受攻击的容器无法为攻击者提供横向攻击的有效路径,从而缩小攻击范围。
如果你对 Linux capabilities 这个词一脸懵逼,建议去看看脑残入门系列:
只读
如果容器不需要对根文件系统进行写入操作,最好以只读方式加载容器的根文件系统,可以进一步限制攻击者的手脚。
node 污点
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
做系统预留。
node 驱逐阈值
驱逐监控时间间隔
kubelet
根据其配置的整理时间间隔计算驱逐阈值。
housekeeping-interval
是容器管理时间间隔。
增大内核选项配置
/etc/sysctl.conf
:
/etc/security/limits.conf
SLB 实例压测请求超时
- 登录后端服务器,排查发现 Nginx 日志没有异常,但是 messages 日志存在“nf_conntrack: table full, dropping packet”错误。该信息是因为 Linux 系统为每个经过内核网络栈的数据包,都生成一个新的连接记录项,当服务器处理的连接过多时,连接跟踪表无法记录新的连接记录项,服务器会丢弃新建连接的数据包。所以导致 SLB 和后端服务器 TCP 三次握手失败,出现 504 状态码。
解决方案
-
建议调整 nf_conntrack 参数,调整命令如下所示,参数值请以实际情况为准。
说明:该方法会临时修改参数,重启实例后配置会不生效。
在 init-container 做信号量限制放开
Pod 时区同步(PodPreset)
往往都会遇到修改 Pod 时区的需求,更多的需求是要求所有的 Pod 都在同一个时区,比如我们所在的东 8 区,一般我们可以通过环境变量或者挂载主机的时区文件到 Pod 中来实现同步,但是这样需要我们对每一个 Pod 手动做这样的操作,一个更好的方式就是利用 PodPreset 来预设。
首先启用 PodPreset:在 kube-apiserver 启动参数 -runtime-config 增加 settings.k8s.io/v1alpha1=true
;
然后在 –admission-control 增加 PodPreset 启用。最后重启 kube-apiserver 即表示启用成功。可以通过如下命令查看是否启用成功:
然后创建一个 PodPresents 资源对象:
这里需要注意的地方是,一定需要写 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 的可写层存储空间:
为 emptyDir 请求 64MB 的存储空间:
lxcfs 让 pod 正确的意识到给自己分配的资源
不支持 alpine 镜像
DEBUG
如何摘下某个 Pod 进行 Debug
使用 label 机制,对 Pod 进行标记。在 Service 定义中,我们添加 status: serving 字段。当需要摘下某个 Pod 做 Debug,而又不影响整个服务,可以:
此时 kubelet 就会把这个 Pod 从 Service 的后端列表中删掉。等到 Debug 完,想恢复?再改回去就好了:
Pod 重启了,如何看重启之前的日志?
下面的命令只能看到当前 Pod 的日志:
通过 --previous
参数可以看之前 Pod 的日志
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
客户端安装
服务端安装
debug-agent 两种运行方式:
daemon-set 模式,agent pod 预先部署在所有 node 上,会始终占用资源,对于排错调试频率不高的环境造成资源浪费;
agentless 模式,kubectl-debug 执行命令后,才创建 agent pod 和排错工具容器,并在退出后删除工具容器和 agent pod。由于每次执行都要重新拉起 agent,启动会比 daemon-set 模式稍慢。
使用-a, –agentless 开启 agentless 模式:
需要配置文件
建议使用 daemon-set 模式
kanyixia zhege