Kubernetes网络管理
Kubernetes的网络通信
外网访问服务
1.Nodeport方式
通过配置service资源把端口改成NodePort的形式,在集群每个节点(Node)的IP上都开一个端口(范围 30000–32767)。
- 访问方式:然后可通过
node ip(节点IP)+node port(节点端口)的形式进行访问。 - 优点:配置简单,不需要额外组件,不依赖云厂商,在裸机环境也能用。
- 缺点:端口范围有限(30000–32767),每个服务要占用一个端口,只能做到端口级别的访问,无法根据域名或路径做智能转发。如果Node的IP变动,需要重新找IP。管理起来很麻烦。
2.Ingress服务
在集群部署一个类似Nginx的代理网关,该服务会暴露对应自身的80和443端口到节点上以Nodeport方式开出对应端口。
- 访问方式:可以通过服务的配置路由规则,根据不同域名以及不同路径访问内部不同的服务。
- 优点:只需要暴露 一个入口(IP/域名),就能访问所有服务,而且支持基于域名/路径的智能路由,还支持HTTPS、证书管理、限流、重定向等高级功能。是应用中最常见、最推荐的方式。
- 缺点:需要额外部署Ingress Controller(如 Nginx、Traefik、HAProxy),默认只支持 HTTP/HTTPS 流量(TCP/UDP 要用额外配置)。配置相对复杂,需要一定学习成本。
3.LoadBalancer
Kubernetes会请求云厂商(如 AWS ELB、阿里云 SLB、GCP LB)创建一个 外部负载均衡器,把流量转发到集群内部的某个Service。但是我们往往都是将流量转发到我们的Ingress上,有的也会用厂商自研的网关服务,模式跟Ingress相识。
- 访问方式:跟Ingress服务相似。
- 优点:云原生支持:直接获得云厂商的公网IP,负载均衡由云平台托管,可靠性高。
- 缺点:每个服务都要占用一个公网 IP,成本高,功能有限,只做四层转发(TCP/UDP),不支持基于路径/域名的路由。而且依赖云厂商,裸机环境(物理机/KVM)不好用。
内部互相访问
1.通过Pod IP
在K8S中每部署一个服务pod就会给每个服务创建一个IP,集群内部之间的服务的IP网络互通。
- 访问方式:
pod ip(pod容器的ip)+targetport(容器端口)的方式访问服务。 - 优点:路径最短,性能好(少了一层代理),适合调试。
- 缺点:Pod是动态的,重启/调度后IP会变,不稳定,而且只能访问单个Pod,没有负载均衡。
2.通过Service
随着每次pod的重启,容器的IP地址又会重新分配修改,于是有了service服务,而且还能负载到控制器下的所有Pod,创建service也会给service创建一个IP地址,集群内部之间也互通该IP网络。
- 访问方式:
cluster ip(service的ip)+port(service的端口)进行访问。 - 优点:Service有固定的IP,Pod变化不影响访问,内置负载均衡,请求自动分发到多个Pod。
- 缺点:只能通过IP,不好记忆,遇到修改service或者删除重建IP地址还是会变,服务的访问配置还是要重新配置。
3.通过Coredns域名
Service只能通过IP,不好记忆,而且不小心删了重新创建IP地址还是会变。所以K8S集群中提供了一个全新的服务,Coredns。使用内置域名解析到service上,即使serviceip地址发生变化,也不影响域名的解析,而且方便记忆。
- 访问方式:通过给service分配的内置域名加端口,比如
服务名.default.svc.cluster.local:端口。 - 优点:可读性好,用域名代替IP,更直观,支持跨命名空间访问,Kubernetes内部访问推荐的最佳实践。
- 缺点:依赖DNS服务,CoreDNS出问题会影响解析,解析过程比直接用IP慢一点点(但基本可以忽略)。
三种IP和三种端口的区分
1.在k8s中的三种ip分别是:node ip、pod ip、 cluster ip
node ip:节点的ip,也就是物理机的网卡ip地址pod ip:pod的ip地址,docker0网桥分配的地址cluster ip:service的ip地址,用于群集内部访问分配给service的虚拟ip地址
2.在k8s中三种端口分别是:port、targetport、nodeport
port: service的端口,用于k8s集群内部服务之间访问的端口targetport: 容器的端口,与制作镜像时expose的端口一致nodeport: 节点的端口,用于外部访问群集内部服务的端口
service
K8S中出现的问题,如果pod挂掉,重新启动一个pod,那么pod的ip地址就会发生变化,那么如果服务之间的访问地址是写死的,那么就会出现问题。而且我们使用deployment控制器的时候会有多个pod,那么该服务的pod地址也会有多个。这个时候service就能发挥到作用。
什么是Service?
Service 是一种抽象的对象,它定义了一组 Pod 的逻辑集合和一个用于访问它们的策略,其实这个概念和微服务非常类似。一个 Serivce 下面包含的 Pod 集合是由 Label Selector 来决定的。
注意:在学习Service之前可以先去了解一下三种IP地址和三种端口的区分,笔记在下面有写
Service的服务类型
- ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的服务类型。
- NodePort:通过每个 Node 节点上的 IP 和静态端口(NodePort)暴露服务。NodePort服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求"NodeIp:NodePort",可以从集群的外部访问一个 NodePort 服务。
- LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务,这个需要结合具体的云厂商进行操作。
- ExternalName:通过返回CNAME和它的值,可以将服务映射到externalName字段的内容(例如, foo.bar.example.com)。
如何编写Service,资源清单编写如下:
apiVersion: v1
kind: Service
metadata:
name: my-nginx
spec:
selector:
app: nginx-1.23.1
type: NodePort
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30080
name: mynginx-http
不写type类型,默认是ClusterIP。以上我们使用了NodePort的类型,如果不指定端口好,会自动从给定的配置范围内(默认:30000-32767)分配端口。
Endpoint
Endpoint 是一种 API 对象,它用于表示集群内某个 Service 的具体网络地址,也就是记录了 Service 中负载均衡pod的 IP 和 Port,它连接到一组由 Service 选择的 Pod,从而使它们能够提供服务。每个 Endpoint 对象都与相应的 Service 对象具有相同的名称,并属于相同的命名空间。
Endpoint的主要特点和用途:
- 服务发现:Endpoint 可以用于将服务的网络地址与后端容器或节点上的实际服务进行关联,从而实现服务的发现和访问。
- 负载均衡:Endpoint 可以用于将流量负载均衡到后端 Pod 上,从而实现服务的高可用性和水平扩展。
- 动态更新:Endpoint 是根据 Service 和 Pod 的标签选择器自动生成的,因此当 Service 或 Pod 的标签发生变化时,Endpoint 会自动更新,从而实现动态的服务发现和负载均衡。
- 可用性和性能:Endpoint 可以帮助 Kubernetes 控制平面自动管理服务的网络终结点,从而提高服务的可用性和性能。
可以通过get命令查看详情:
kubectl get endPoints
CoreDNS
环境变量
Kubernetes 采用了环境变量的方法,每个 Pod 启动的时候,会通过环境变量设置所有服务的 IP 和 port 信息,这样 Pod 中的应用可以通过读取环境变量来获取依赖服务的地址信息。
比如我们进入到之前部署的nginx容器里面使用env命令查看环境变量,如下:
root@nginx:/var/www/html# env
NGINX_PORT_80_TCP_ADDR=10.97.171.41
NGINX_SERVICE_PORT=80
NGINX_PORT_80_TCP_PORT=80
NGINX_SERVICE_HOST=10.97.171.41
NGINX_PORT=tcp://10.97.171.41:80
NGINX_PORT_80_TCP_PROTO=tcp
NGINX_PORT_80_TCP=tcp://10.97.171.41:80
在上面我们可以看到有HOST、PORT、PROTO、ADDR等信息的记录,也包括其他已经存在的Service的环境变量
可能会出现的问题:
但是如果这个 Pod 启动起来的时候 nginx-service 服务还没启动起来,在环境变量中我们是无法获取到这些信息的,当然我们可以通过 initContainer 之类的方法来确保 nginx-service 启动后再启动 Pod,但是这种方法毕竟增加了 Pod 启动的复杂性,所以这不是最优的方法,局限性太多了。
依赖的服务必须在 Pod 启动之前就存在,不然是不会被注入到环境变量中的。
DNS
为了解决上面问题的局限性,我们的方案就是DNS,可以直接使用Service的名称,因为Service的名称不会变化,我们就不用关心ClusterIP地址的分配。
DNS服务不是一个独立的系统服务,而是作为一种addon插件而存在,现在比较推荐的两个插件:kube-dns和CoreDNS,实际上在比较新点的版本中已经默认是 CoreDNS 了,因为kube-dns默认一个Pod中需要3个容器配合使用,CoreDNS只需要一个容器即可。
我们可以进行查看,如下:
[root@master ~]# kubectl get pods -n kube-system -l k8s-app=kube-dns
coredns-7f74f766bc-5htzm 1/1 Running 0 4d14h
coredns-7f74f766bc-mf6g8 1/1 Running 0 4d14h
CoreDNS的Service地址一般情况下是固定的,类似于kubernetes这个Service地址一般就是第一个IP地址 10.96.0.1,CoreDNS 的 Service 地址就是 10.96.0.10,该 IP 被分配后,kubelet 会将使用 --cluster-dns=<dns-service-ip>参数配置的 DNS 传递给每个容器。DNS 名称也需要域名,本地域可以使用参数–cluster-domain = <default-local-domain>在 kubelet 中配置:
[root@node1 ~]# cat /var/lib/kubelet/config.yaml
......
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
域名解析规则
- 普通Service:会生成servicename.namespace.svc.cluster.local的域名,会解析到 Service对应的ClusterIP上,在 Pod 之间的调用可以简写成"servicename.namespace",如果处于同一个命名空间下面,甚至可以只写成"servicename"即可访问。
- Headless Service:无头服务,就是把clusterIP设置为 None 的,会被解析为指定 Pod 的 IP 列表,同样还可以通过podname.servicename.namespace.svc.cluster.local访问到具体的某一个 Pod。
我们进入到之前部署的nginx容器里面Service 的域名访问,如下:
root@nginx:/var/www/html# cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5 single-request-reopen timeout:2
我们进入到 Pod 中,查看/etc/resolv.conf中的内容,可以看到nameserver的地址10.96.0.10,该 IP 地址即是在安装 CoreDNS 插件的时候集群分配的一个固定的静态 IP 地址。
也就是说我们这个Pod现在默认的nameserver就是kube-dns的地址,我们可以通过以下地址来访问我们的nginx服务:nginx-service.default.svc.cluster.local
Pod的DNS策略以及配置
Kubernetes支持以下特定Pod的DNS策略。这些策略可以在Pod规范中的dnsPolicy字段设置:
- Default:kubelet默认的方式,目前kubelet默认的方式就是使用宿主机的/etc/resolv.conf。
- ClusterFirst:使用k8s中的kubedns或者coredns进行解析,不成功则使用宿主机的/etc/resolv.conf。
- ClusterFirstWithHostNet:在pod使用HostNetwork模式启动情况下,这个pod的所有容器都要使用宿主机的/etc/resolv.conf。
- None:表示空的DNS设置,一般和dnsconfig配合一起使用
如何配置策略,资源清单编写如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-1.23.1
spec:
selector:
matchLabels:
app: nginx-1.23.1
replicas: 2
template:
metadata:
labels:
app: nginx-1.23.1
spec:
containers:
- name: nginx
image: nginx:1.23.1
ports:
- containerPort: 80
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
如何配置dnsconfig,资源清单编写如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-1.23.1
spec:
selector:
matchLabels:
app: nginx-1.23.1
replicas: 2
template:
metadata:
labels:
app: nginx-1.23.1
spec:
containers:
- name: nginx
image: nginx:1.23.1
ports:
- containerPort: 80
dnsPolicy: None
dnsConfig:
nameservers:
- 1.2.3.4
searches:
- ns1.svc.cluster-domain.example
- my.dns.search.suffix
options:
- name: ndots
value: '2'
- name: edns0
创建上面的 Pod 后,容器nginx-1.23.1会在其/etc/resolv.conf文件中获取以下内容:
root@nginx:/var/www/html# cat /etc/resolv.conf
nameserver 1.2.3.4
search ns1.svc.cluster-domain.example my.dns.search.suffix
options ndots:2 edns0
LocalDNS
NodeLocal DNSCache
通过在集群节点上运行一个 DaemonSet 来提高集群DNS性能和可靠性,通过在每个集群节点上运行DNS缓存,NodeLocal DNSCache可以缩短DNS查找的延迟时间、使DNS查找时间更加一致,以及减少发送到kube-dns的DNS查询次数。
安装NodeLocal DNSCache:
wget https://github.com/kubernetes/kubernetes/raw/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml
该清单nodelocaldns.yaml的重要参数值
__PILLAR__DNS__SERVER__:表示kube-dns这个 Service的ClusterIP
__PILLAR__LOCAL__DNS__:表示 DNSCache 本地的 IP,默认为169.254.20.10
__PILLAR__DNS__DOMAIN__:表示集群域,默认就是cluster.local
使用apply部署,查看部署情况
[root@master ~]# kubectl get pods -n kube-system -l k8s-app=node-local-dns
NAME READY STATUS RESTARTS AGE
node-local-dns-4wclp 1/1 Running 0 5m43s
node-local-dns-gxq57 1/1 Running 0 5m43s
node-local-dns-v7gtz 1/1 Running 0 5m43s
注意:使用DaemonSet部署node-local-dns使用了hostNetwork=true,会占用宿主机的8080端口
修改kubelet的–cluster-dns参数。
sed -i 's/10.96.0.10/169.254.20.10/g' /var/lib/kubelet/config.yaml
systemctl daemon-reload && systemctl restart kubelet
我们再进行查看pod中的dns配置文件,nameserver 已经变成169.254.20.10了
root@nginx:/var/www/html# cat /etc/resolv.conf
nameserver 169.254.20.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5