在k8s环境下容器网络一般会涉及到CNI 、iptables/ipvs,network policy等,有没好的办法能在出现网络问题时快速定位到问题点?
k8s环境下网络不通的问题可能会涉及 Pod 网络、CNI、Service、Iptables、IPVS、Ingress、DNS、NetworkPolicy、Loadbalancer 等等多个组件。
K8s的网络中pod的通信:
因为pause容器提供pod内网络共享,所以容器直接可以使用localhost(lo)访问其他容器
1)两个pod在一台主机上面: 通过docker默认的docker网桥互连容器(docker0), ifconfig 查了docker0
2)两个pod分布在不同主机之上: 通过CNI插件实现,eg: flannel,calico,canal
Service分配的ip叫cluster ip是一个虚拟ip(相对固定,除非删除service),这个ip只能在k8s集群内部使用,如果service需要对外提供,只能使用Nodeport方式映射到主机上,使用主机的ip和端口对外提供服务。
节点上面有个kube-proxy进程,这个进程从master apiserver获取信息,感知service和endpoint的创建,然后做两个事:
1.为每个service 在集群中每个节点上面创建一个随机端口,任何该端口上面的连接会代理到相应的pod
2.集群中每个节点安装iptables/ipvs规则,用于clusterip + port路由到上一步定义的随机端口上面, 所以集群中每个node上面都有service的转发规则:iptables -L -n -t filter
排查网络问题基本上从这几种情况出发,定位出具体的网络异常点,进而寻找解决方法。
网络异常可能的原因比较多,常见的有
忘记开启 IP 转发等
Flannel 网络插件非常容易部署,只要一条命令即可
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
然而,部署完成后,Flannel Pod 有可能会碰到初始化失败的错误
查看日志会发现
$ kubectl -n kube-system logs kube-flannel-ds-jpp96 -c install-cni
cp: can't create '/etc/cni/net.d/10-flannel.conflist': Permission denied
这一般是由于 SELinux 开启导致的,关闭 SELinux 既可解决。有两种方法:
如果 Node 上安装的 Docker 版本大于 1.12,那么 Docker 会把默认的 iptables FORWARD 策略改为 DROP。这会引发 Pod 网络访问的问题。解决方法则在每个 Node 上面运行 iptables -P FORWARD ACCEPT,比如
echo "ExecStartPost=/sbin/iptables -P FORWARD ACCEPT" >> /etc/systemd/system/docker.service.d/exec_start.conf
systemctl daemon-reload
systemctl restart docker
如果使用了 flannel/weave 网络插件,更新为最新版本也可以解决这个问题。
DNS 无法解析也有可能是 kube-dns 服务异常导致的,可以通过下面的命令来检查 kube-dns 是否处于正常运行状态
$ kubectl get pods --namespace=kube-system -l k8s-app=kube-dns
NAME READY STATUS RESTARTS AGE
...
kube-dns-v19-ezo1y 3/3 Running 0 1h
...
如果 kube-dns 处于 CrashLoopBackOff 状态,那么可以参考 Kube-dns/Dashboard CrashLoopBackOff 排错 来查看具体排错方法。
如果 kube-dns Pod 处于正常 Running 状态,则需要进一步检查是否正确配置了 kube-dns 服务:
$ kubectl get svc kube-dns --namespace=kube-system
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns 10.0.0.10 53/UDP,53/TCP 1h
$ kubectl get ep kube-dns --namespace=kube-system
NAME ENDPOINTS AGE
kube-dns 10.180.3.17:53,10.180.3.17:53 1h
如果 kube-dns service 不存在,或者 endpoints 列表为空,则说明 kube-dns service 配置错误,可以重新创建 kube-dns service,比如
apiVersion: v1
kind: Service
metadata:
name: kube-dns
namespace: kube-system
labels:
k8s-app: kube-dns
kubernetes.io/cluster-service: "true"
kubernetes.io/name: "KubeDNS"
spec:
selector:
k8s-app: kube-dns
clusterIP: 10.0.0.10
ports:
- name: dns
port: 53
protocol: UDP
- name: dns-tcp
port: 53
protocol: TCP
访问 Service ClusterIP 失败时,可以首先确认是否有对应的 Endpoints
kubectl get endpoints
如果该列表为空,则有可能是该 Service 的 LabelSelector 配置错误,可以用下面的方法确认一下
# 查询 Service 的 LabelSelector
kubectl get svc -o jsonpath='{.spec.selector}'
# 查询匹配 LabelSelector 的 Pod
kubectl get pods -l key1=value1,key2=value2
如果 Endpoints 正常,可以进一步检查
kube-proxy 服务有可能未启动或者未正确配置相应的 iptables 规则,比如正常情况下名为 hostnames的服务会配置 iptables 规则
$ iptables-save | grep hostnames
这通常是 hairpin 配置错误导致的,可以通过 Kubelet 的 --hairpin-mode 选项配置,可选参数包括 "promiscuous-bridge"、"hairpin-veth" 和 "none"(默认为"promiscuous-bridge")。
对于 hairpin-veth 模式,可以通过以下命令来确认是否生效
$ for intf in /sys/devices/virtual/net/cbr0/brif/*; do cat $intf/hairpin_mode; done
而对于 promiscuous-bridge 模式,可以通过以下命令来确认是否生效
$ ifconfig cbr0 |grep PROMISC
UP BROADCAST RUNNING PROMISC MULTICAST MTU:1460 Metric:1
很多扩展服务需要访问 Kubernetes API 查询需要的数据(比如 kube-dns、Operator 等)。通常在 Kubernetes API 无法访问时,可以首先通过下面的命令验证 Kubernetes API 是正常的:
$ kubectl run curl --image=appropriate/curl -i -t --restart=Never --command -- sh
如果出现超时错误,则需要进一步确认名为 kubernetes 的服务以及 endpoints 列表是正常的:
$ kubectl get service kubernetes
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 443/TCP 25m
$ kubectl get endpoints kubernetes
NAME ENDPOINTS AGE
kubernetes 172.17.0.62:6443 25m
然后可以直接访问 endpoints 查看 kube-apiserver 是否可以正常访问。无法访问时通常说明 kube-apiserver 未正常启动,或者有防火墙规则阻止了访问。
但如果出现了 403 - Forbidden 错误,则说明 Kubernetes 集群开启了访问授权控制(如 RBAC),此时就需要给 Pod 所用的 ServiceAccount 创建角色和角色绑定授权访问所需要的资源。比如 CoreDNS 就需要创建以下 ServiceAccount 以及角色绑定:
# 1. service account
apiVersion: v1
kind: ServiceAccount
metadata:
name: coredns
namespace: kube-system
labels:
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
---
# 2. cluster role
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
kubernetes.io/bootstrapping: rbac-defaults
addonmanager.kubernetes.io/mode: Reconcile
name: system:coredns
rules:
- apiGroups:
- ""
resources:
- endpoints
- services
- pods
- namespaces
verbs:
- list
- watch
---
# 3. cluster role binding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
addonmanager.kubernetes.io/mode: EnsureExists
name: system:coredns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:coredns
subjects:
- kind: ServiceAccount
name: coredns
namespace: kube-system
---
# 4. use created service account
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: coredns
namespace: kube-system
labels:
k8s-app: coredns
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
kubernetes.io/name: "CoreDNS"
spec:
replicas: 2
selector:
matchLabels:
k8s-app: coredns
template:
metadata:
labels:
k8s-app: coredns
spec:
serviceAccountName: coredns
...
附:IPVS模式下的Kubernetes容器网络排障
CNI插件采用了Calico,而kube-proxy则是开启了IPVS模式。在容器网络的建造中,CNI和kube-proxy都有很多参与。
$ route -n
首先CNI很好排查,只需要记录各个分区上容器的IP(每个副本记一个IP可)然后在各个代表上Ping这些IP,如果可以Ping通,那CNI插件的工作就是完全正常的。
kubectl get pods -o wide -n kube-system|grep 10.244|awk '{print $6}'|xargs nmap -sP|grep Host
运行的结果是排除了CNI的问题。
随后,由于第一次使用nmap扫描对端端口时,使用的是群集IP,也即是Service的IP,而群集IP到Pod IP之间只夹着一条LVS负载均衡规则(或者如果kube-proxy使用替代查询现有的IPVS规则,就发现了只有虚拟服务器终结点而没有真实服务器的IPVS规则:既然,配置,则是iptables规则),既然Pod IP的连通性没有问题,那大概率是IPVS规则出现了问题。
$ ipvsadm -Ln
$ kubectl get service --all-namespaces|grep 10.108.62.55
$ kubectl get pods -n kube-system|grep calico
至此可以确定是由于由于IPVS规则来将到达服务的流量负载均衡到Pod上导致了这个问题。而IPVS规则是由kube-proxy维护的,打开了kube-proxy的日志,发现了报错
kubectl logs -n kube-system kube-proxy-cdsgl |tail
收起