k8s进阶
Pod
A/AAAA 记录
一般而言,Pod 会对应如下 DNS 名字解析:
pod-ip-address.my-namespace.pod.cluster-domain.example
例如,对于一个位于 default
名字空间,IP 地址为 172.17.0.3 的 Pod, 如果集群的域名为 cluster.local
,则 Pod 会对应 DNS 名称:
172-17-0-3.default.pod.cluster.local
通过 Service 暴露出来的所有 Pod 都会有如下 DNS 解析名称可用:
pod-ip-address.service-name.my-namespace.svc.cluster-domain.example
普通svc会生成svcname.namespace.svc.cluster.local,在pod之间调用可以简写svcname.namespace,如果在同一个ns下,直接可以简写成svcname
无头服务同样可以用podname.svcname.nsmespace.svc.cluster.local访问到具体的某一个pod
k8s iptables 链: svc映射到pod需要进行一次dnat转换。 最主要的链是kube-services,kube-svc-*,kube-SEP-* 1、kube-services链是访问集群内部服务的数据包入口点,它会根据匹配到目标ip:port 将数据包分发到相应的kube-svc-*链 2、kube-svc-* 链相当于一个负载均衡器,它会将数据包平均分发到kube-sep-* 链。每一个kube-svc-* 链后面的kube-sep-*链都和service的后端pod数量一样 3、kube-sep-*链通过DNAT 将连接的目的地址和端口从service的ip:port替换为后端pod的ip:port,从而将流量转发到相应的pod kube-proxy IPVS模式 --procy-mode参数:除了现在有的userspace和iptables模式,ipvs模式通过--proxy-mode=ipvs进行配置 --proxy-scheduler:用来指定ipvs负载均衡算法,如果不配置则默认使用round-robin(rr)模式 ipvs支持的负载均衡算法有: rr--轮询 lc--最小链接 dh--目的地址哈希 sh--源地址哈希 sed--最短时延 一旦创建一个service和一个endpoints,ipvs的kube-proxy进行做三件事: 1、确保一块dummy网卡(kube-ipvs0)存在,为什么要创建这块网卡?因为ipvs的netfiler钩子挂载input链,我们需要把service的访问ip都绑定在dummy网卡上让内核觉得虚拟ip就是本机ip,从而进入input链。 2、把service的ip绑定在dummy网卡上。 3、通过socket调用,创建ipvsde virtual server和real server,分别对应k8s的service 和 endpoint。
flannel 详解:
falnnel在架构上分为管理面和数据面,管理面主要是etcd,用于协调各个节点上容器分配的网段,数据面就是在每一个节点上运行一个flanned进程。
与其他CNI网络相比,flannel采用no server 架构,不存在所谓的控制节点,更易于运维。k8s集群内所有fannel节点共享一个大的容器地址段,flannel一启动便会
观察etcd,从etcd中得知其他节点上的容器已占用的网段信息,然后像etcd申请该节点可用的ip地址段。并把该网段和主机ip地址等信息都记录在etcd中。
flannel通过etcd分配了每个节点可用的ip地址段后,修改了docker的启动参数,例如--bip=172.17.18.1/24限制了所在节点容器获得的ip范围。以确保每个节点上的docker会使用不同的ip地址,
需要注意的是这个ip范围是由flannel自动分配的,由flannel通过保存在etcd服务中的记录确保他们不会重复,无需用户手动干预。
flannel的底层实现实质上是一种overlay网络(host-gw)除外,把某一端的协议的数据通过封装在另外一种网络协议中进行路由转发。
flannel目前支持的底层实现有:
UDP、Vxlan、alloc、host-gatway、AWS-VPC、GCE路由。
fannel通过以下命令往etcd中写入信息:
etcdctl set /coreos.com/network/config{"network":"172.17.0.0/16"}
随后在各个节点启动flanneld&
需要注意的是flanneld启动早于docker启动。
flanned进程做了以下几件事:
1、从etcd中获取network的配置信息
2、划分subnet(子网),并在etcd中进行注册
3、将子网信息记录到flanned维护的 /run/flannel/subnet.env中
4、将subnet.env转写成一个docker的环境变量文件 /run/flannel/docker
K8s如何优雅得关闭pod:
加preStop钩子函数去优雅的关闭,另外还有postStart钩子函数。
修复ingress EXTERNAL-IP为空的问题。(手工指定外部ip)https://ceshiren.com/t/topic/16525
k8s查看pod崩溃前的日志和原理
单容器pod:
kubectl logs podname --previous
多容器pod:
kubectl logs podname --previous -c container-name
原理:kubelet 会保持pod的前几个失败的容器,这个是查看的前提条件。kubelet实现previous的原理:
将pod日志存放在/var/log/pods/podname,并且是链接文件,链接到docker的容器日志文件,同时kubelet还会保留上一个容器,
同时有一个链接文件链接到pod上一个崩溃的容器的日志文件,使用previous就是在查看这个文件。
如何根据pid查看容器对应的pod名称
生产机器上我们经常能遇到k8s宿主机load过高,用top看到的pid往往无法确认是哪个pod。
1、top查看进程pid
2、根据pid查uid
cat/proc/pid/mountinfo| grep "etc-hosts" | awk -F / {'print $4'}
3、根据uid查podname
需要先安装jq如果没安装用yum install jq -y 安装
crictl -ps -o json | jq '.[][].labels | select (.["io.kubernetes.pod.uid"] == "第2步查出来的uid值")| .["io.kubernetes.pod.name"]' | uniq'
nsenter进去容器中抓包,nsenter还可以进入mnt,ipc,uts,pid,user等命令空间,以及指定根目录和工作目录
首先看下 nsenter 命令的语法:
nsenter [options] [program [arguments]]
options:
-t, --target pid:指定被进入命名空间的目标进程的pid
-m, --mount[=file]:进入mount命令空间。如果指定了file,则进入file的命令空间
-u, --uts[=file]:进入uts命令空间。如果指定了file,则进入file的命令空间
-i, --ipc[=file]:进入ipc命令空间。如果指定了file,则进入file的命令空间
-n, --net[=file]:进入net命令空间。如果指定了file,则进入file的命令空间
-p, --pid[=file]:进入pid命令空间。如果指定了file,则进入file的命令空间
-U, --user[=file]:进入user命令空间。如果指定了file,则进入file的命令空间
-G, --setgid gid:设置运行程序的gid
-S, --setuid uid:设置运行程序的uid
-r, --root[=directory]:设置根目录
-w, --wd[=directory]:设置工作目录
如果没有给出program,则默认执行$SHELL。
示例:
运行一个 nginx 容器,查看该容器的 pid:
[root@staight ~]# docker inspect -f {{.State.Pid}} nginx
5645
然后,使用 nsenter 命令进入该容器的网络命令空间:
[root@staight ~]# nsenter -n -t5645
[root@staight ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
在 Kubernetes 中,在得到容器 pid 之前还需获取容器的 ID,可以使用如下命令获取:
[root@node1 test]# kubectl get pod test -oyaml|grep containerID
- containerID: docker://cf0873782d587dbca6aa32f49605229da3748600a9926e85b36916141597ec85
或者更为精确地获取 containerID :
[root@node1 test]# kubectl get pod test -o template --template='{{range .status.containerStatuses}}{{.containerID}}{{end}}'
docker://cf0873782d587dbca6aa32f49605229da3748600a9926e85b36916141597ec85
Linux在不断的添加命名空间,目前有:
-
mount:挂载命名空间,使进程有一个独立的挂载文件系统,始于Linux 2.4.19
-
ipc:ipc命名空间,使进程有一个独立的ipc,包括消息队列,共享内存和信号量,始于Linux 2.6.19
-
uts:uts命名空间,使进程有一个独立的hostname和domainname,始于Linux 2.6.19
-
net:network命令空间,使进程有一个独立的网络栈,始于Linux 2.6.24
-
pid:pid命名空间,使进程有一个独立的pid空间,始于Linux 2.6.24
-
user:user命名空间,是进程有一个独立的user空间,始于Linux 2.6.23,结束于Linux 3.8
-
cgroup:cgroup命名空间,使进程有一个独立的cgroup控制组,始于Linux 4.6
Linux 的每个进程都具有命名空间,可以在 /proc/PID/ns 目录中看到命名空间的文件描述符。
nsenter 相当于在setns的示例程序之上做了一层封装,使我们无需指定命名空间的文件描述符,而是指定进程号即可。
指定进程号PID以及需要进入的命名空间后,nsenter会帮我们找到对应的命名空间文件描述符/proc/PID/ns/FD,然后使用该命名空间运行新的程序。
查看端口号被哪个进程调用: ll /proc/pid号/cwd
查看docker container内进程信息和宿主机上进程信息的映射关系
1、docker ps 找到容器id
2、docker top 容器id
其中pid是容器内进程在宿主机上的pid,ppid是容器内进程在宿主机上的父进程pid
k8s长连接服务负载不均衡(视频,会议,直播等业务场景会用到长链接):
1、滚动更新负载不均衡,越先启动的pod所收到的连接越多,造成负载均衡。
2、ipvs的rr策略负载不均衡。节点通过ipvsadm -Ln -t CLUSTER-IP:PORT查看某个service的转发情况。
将kube-proxy设置为lc(least-Connection),既倾向于转发给连接数较少的pod。可能会有所缓解,但也不一定。因为ipvs
的负载均衡状态是分散在各个节点上的,并不是收敛到一个地方,也就无法再全局层面感知到哪个pod上的连接数少。并不能真正的做到lc。
可以尝试设置为sh(source Hasning),能够再全局层面尽量保持负载均衡。
解决方法:
1、业务层面自动重连,避免连接固化到某个后端pod上。比如周期性定时重连,或者一个连接中的请求数到达阈值后自动重连。
2、不直接请求后端,通过七层代理访问比如gRPC,可以用nginx ingress或istio转发gRPC,经过七层代理后自动拆分请求,在请求级别负载均衡。
3、kube-proxy的ipvs转发策略设置为sh(ipvs-scheduler=sh)。
CorsDNS性能优化:(转自https://imroc.cc/kubernetes/best-practices/dns/optimize-coredns-performance.html#%E5%90%AF%E7%94%A8-autopath)
1、合理保持coreDNS副本数,根据集群规模来预估。
2、禁用IPV6,如果k8s没有禁用ipv6的话,容器内进程请求coreDNS时候的默认行为是同时发起ipv4和ipv6请求,当容器请求域名时,coredns可能会解析不到ipv6,就会forward去upstream去解析。
如果到upstream需要经过较长时间比如跨公网或者专线,业务层民会感知dns解析慢。
CoreDNS 有一个 template 的插件,可以用它来禁用 IPV6 的解析,只需要给 CoreDNS 加上如下的配置:
template ANY AAAA {
rcode NXDOMAIN
}
这个配置的含义是:给所有 IPV6 的解析请求都响应空记录,即无此域名的 IPV6 记录。
优化 ndots
默认情况下,Kubernetes 集群中的域名解析往往需要经过多次请求才能解析到。查看 pod 内 的
/etc/resolv.conf
可以知道ndots
选项默认为 5:
意思是: 如果域名中
.
的数量小于 5,就依次遍历search
中的后缀并拼接上进行 DNS 查询。举个例子,在 debug 命名空间查询
kubernetes.default.svc.cluster.local
这个 service:
- 域名中有 4 个
.
,小于 5,尝试拼接上第一个 search 进行查询,即kubernetes.default.svc.cluster.local.debug.svc.cluster.local
,查不到该域名。- 继续尝试
kubernetes.default.svc.cluster.local.svc.cluster.local
,查不到该域名。- 继续尝试
kubernetes.default.svc.cluster.local.cluster.local
,仍然查不到该域名。- 尝试不加后缀,即
kubernetes.default.svc.cluster.local
,查询成功,返回响应的 ClusterIP。可以看到一个简单的 service 域名解析需要经过 4 轮解析才能成功,集群中充斥着大量无用的 DNS 请求。
怎么办呢?我们可以设置较小的 ndots,在 Pod 的 dnsConfig 中可以设置:
然后业务发请求时尽量将 service 域名拼完整,这样就不会经过 search 拼接造成大量多余的 DNS 请求。
不过这样会比较麻烦,有没有更好的办法呢?有的!请看下面的 autopath 方式。
启用 autopath
启用 CoreDNS 的 autopath 插件可以避免每次域名解析经过多次请求才能解析到,原理是 CoreDNS 智能识别拼接过 search 的 DNS 解析,直接响应 CNAME 并附上相应的 ClusterIP,一步到位,可以极大减少集群内 DNS 请求数量。
启用方法是修改 CoreDNS 配置:
kubectl -n kube-system edit configmap coredns
修改红框中圈出来的配置:
- 加上
autopath @kubernetes
。- 默认的
pods insecure
改成pods verified
。需要注意的是,启用 autopath 后,由于 coredns 需要 watch 所有的 pod,会增加 coredns 的内存消耗,根据情况适当调节 coredns 的 memory request 和 limit。
calico和flannel的插件网络方案对比:https://www.cnblogs.com/v-fan/p/14452770.html
Qos和limitrange and resourcequota详解:http://www.mydlq.club/article/36/
QoS 等级分类
Kubernetes 中如果一个 Node 节点上的 Pod 占用资源过多并且不断飙升导致 Node 节点资源不足,可能会导致为了保证节点可用,将容器被杀掉。在遇见这种情况时候,我们希望先杀掉那些不太重要的容器,确保核心容器不会首先被杀掉。为了衡量先杀掉哪个程序,所以推出了优先级机制 QoS (Quality of Service)来做判断,Kubernetes 将容器划分为三种 QoS 等级:
- Guaranteed: 完全可靠的。
- Burstable: 较可靠的。
- BestEffort: 不太可靠的。
2、Kubernetes Pod QoS 特点
在 Kubernetes 中资源不足时,根据 QoS 等级杀死 Pod 会有以下特点:
- BestEffort Pod: 优先级最低,在 Kubernetes 资源不足时候会将此 Pod 先杀掉。
- Burstable Pod: 优先级居中,如果整个系统内存资源不足,且已经杀完全部 BestEffort 等级的 Pod 时可能被杀掉。
- Guaranteed Pod: 优先级最高,一般情况下不会被杀掉,除非资源不足且系统 BestEffort 和 Burstable 的 Pod 都不存在的情况下,才可能被杀掉。
3、Pod 是如何分 QoS 等级
Kubernetes 中 Qos 等级是根据 Limits 和 Requests 这两个参数设置的值息息相关,Kubernetes 会根据这两个值的不同而给 Pod 设置不同的 QoS 等级。
(1)、Guaranteed (等级-最高)
如果 Pod 中所有容器都定义了 Limits 和 Requests,并且全部容器的 Limits 值 = Requests 值(值不能为0),那么该 Pod 的 QoS 级别就是 Guaranteed。
注意:这种情况下容器可以只设置 Limits 值即可,引入在只设置 Limits 不设置 Requests 情况下,Requests 值默认等于 Limits 的值。
(2)、BestEffort(等级-最低)
如果 Pod 中所有容器都未定义 Requests 和 Limits 值,该 Pod 的 Qos 即为 BestEffort。
(3)、Burstable(等级-中等)
当一个 Pod 既不是 Guaranteed 级别,也不说 BestEffort 级别时候,该 Pod 的 QoS 级别就是 Burstable。例如,Pod 中全部或者部分容器 Requests 和 Limits 都定义,且 Requests 小于 Limits 值,或者 Pod 中一部分容器未定义 Requests 和 Limits 资源时。
(1)、Guaranteed (等级-最高)
如果 Pod 中所有容器都定义了 Limits 和 Requests,并且全部容器的 Limits 值 = Requests 值(值不能为0),那么该 Pod 的 QoS 级别就是 Guaranteed。
注意:这种情况下容器可以只设置 Limits 值即可,引入在只设置 Limits 不设置 Requests 情况下,Requests 值默认等于 Limits 的值。
(2)、BestEffort(等级-最低)
如果 Pod 中所有容器都未定义 Requests 和 Limits 值,该 Pod 的 Qos 即为 BestEffort。
(3)、Burstable(等级-中等)
当一个 Pod 既不是 Guaranteed 级别,也不说 BestEffort 级别时候,该 Pod 的 QoS 级别就是 Burstable。例如,Pod 中全部或者部分容器 Requests 和 Limits 都定义,且 Requests 小于 Limits 值,或者 Pod 中一部分容器未定义 Requests 和 Limits 资源时
LIMITRANGE
四、资源范围管理对象 LimitRange
默认情况下如果创建一个 Pod 没有设置
Limits
和Requests
对其加以限制,那么这个 Pod 可能能够使用 Kubernetes 集群中全部资源, 但是每创建 Pod 资源时都加上这个动作是繁琐的,考虑到这点 Kubernetes 提供了LimitRange
对象,它能够对一个 Namespace 下的全部 Pod 使用资源设置默认值、并且设置上限大小和下限大小等操作。这里演示将使用 LimitRange 来限制某个 namespace 下的资源的测试用例。创建一个 LimitRange 对象限制 Namespace 下的资源使用,其中 limit 的类型有两种,是通过type字段来区分的,一个是pod一个是container:
- 对 Container 使用资源进行限制,在 Pod 中的每一个容器都受此限制。
- 对 Pod 进行限制,即 Pod 中全部 Container 使用资源总和来进行限制。
- 可以通过limitrange绑定namespace来关联namespace:
[root@k8s-master limitrange]# cat limitrange.yaml apiVersion: v1 kind: LimitRange metadata: name: mem-limit-range-test namespace: limitrange-test-ns #指定命名空间 spec: limits: - default: memory: 256Mi defaultRequest: memory: 128Mi type: Container4、对 LimitRange 对象参数介绍
Container 参数:
- max: Pod 中所有容器的 Limits 值上限。
- min: Pod 中所有容器的 Requests 值下限。
- default: Pod 中容器未指定 Limits 时,将此值设置为默认值。
- defaultRequest: Pod 中容器未指定 Requests 是,将此值设置为默认值。
- maxLimitRequestRatio: Pod 中的容器设置 Limits 与 Requests 的比例的值不能超过 maxLimitRequestRatio 参数设置的值,即 Limits/Requests ≤ maxLimitRequestRatio。
Pod 参数:
- max: Pod 中所有容器资源总和值上限。
- min: Pod 中所有容器资源总和值下限。
- maxLimitRequestRatio: Pod 中全部容器设置 Limits 总和与 Requests 总和的比例的值不能超过 maxLimitRequestRatio 参数设置的值,即 (All Container Limits)/(All Container Requests) ≤ maxLimitRequestRatio。
quota是namespace整体,limitrange是对namespace下面的具体的resource的限制 应该说每个namespace有自己独立的quota设计 (3)、ResourcesQuota 支持限制的对象资源: Configmaps: 允许存在的 ConfigMap 的数量。 Pods: 允许存在的非终止状态的 Pod 数量,如果 Pod 的 status.phase 为 Failed 或 Succeeded , 那么其处于终止状态。 Replicationcontrollers: 允许存在的 Replication Controllers 的数量。 Resourcequotas: 允许存在的 Resource Quotas 的数量。 Services: 允许存在的 Service 的数量。 services.loadbalancers: 允许存在的 LoadBalancer 类型的 Service 的数量。 services.nodeports: 允许存在的 NodePort 类型的 Service 的数量。 Secrets: 允许存在的 Secret 的数量。