五.k8s四层负载均衡-Service
5.1-什么是Service
5.1.1-Service作用
在 kubernetes 中,Pod 是有生命周期的,如果 Pod 重启它的 IP 很有可能会发生变化。如果我们的服 务都是将 Pod 的 IP 地址写死,Pod 挂掉或者重启,和刚才重启的 pod 相关联的其他服务将会找不到它所 关联的 Pod,为了解决这个问题,在 kubernetes 中定义了 service 资源对象,Service 定义了一个服务 访问的入口,客户端通过这个入口即可访问服务背后的应用集群实例,service 是一组 Pod 的逻辑集合, 这一组 Pod 能够被 Service 访问到,通常是通过 Label Selector 实现的。
5.1.2-Service概念
service 是一个固定接入层,客户端可以通过访问 service 的 ip 和端口访问到 service 关联的后端pod,这个 service 工作依赖于在 kubernetes 集群之上部署的一个附件,就是kubernetes 的 dns 服务(不同 kubernetes 版本的 dns 默认使用的也是不一样的,1.11 之前的版本使用的是 kubeDNs,较新的版本使用的是 coredns),service 的名称解析是依赖于 dns 附件的,因此在部署完 k8s 之后需要再部署 dns附件,kubernetes 要想给客户端提供网络功能,需要依赖第三方的网络插件(flannel,calico 等)。每个 K8s 节点上都有一个组件叫做 kube-proxy,kube-proxy 这个组件将始终监视着 apiserver 中有关service 资源的变动信息,需要跟 master 之上的 apiserver 交互,随时连接到 apiserver 上获取任何一个与 service 资源相关的资源变动状态,这种是通过 kubernetes 中固有的一种请求方法 watch(监视)来实现的,一旦有 service 资源的内容发生变动(如创建,删除),kube-proxy 都会将它转化成当前节点之上的能够实现 service 资源调度,把我们请求调度到后端特定的 pod 资源之上的规则,这个规则可能是 iptables,也可能是 ipvs,取决于 service 的实现方式。
5.1.3-Service工作原理
k8s 在创建 Service 时,会根据标签选择器 selector(lable selector)来查找 Pod,据此创建与 Service 同名的 endpoint 对象,当 Pod 地址发生变化时,endpoint 也会随之发生变化,service 接收前 端 client 请求的时候,就会通过 endpoint,找到转发到哪个 Pod 进行访问的地址。(至于转发到哪个节 点的 Pod,由负载均衡 kube-proxy 决定)
k8s中有三类网络地址
1.Node network :宿主机网络
2.Pod network :pod网络,创建的pod具有的IP
#node和pod网络是物理存在的,节点网络地址配置在节点接口至上,pod网络地址配置在pod资源至上。/硬件,软件模拟
3.Cluster Network:集群地址,虚拟的地址,没有接口,只是在service规则中
service 只要创建完成,我们就可以直接解析它的服务名,每一个服务创建完成后都会在集群 dns 中 动态添加一个资源记录,添加完成后我们就可以解析了,资源记录格式是:
SVC_NAME.NS_NAME.DOMAIN.LTD.
服务名.命名空间.域名后缀
集群默认的域名后缀是 svc.cluster.local.
如:在web 命名空间创建一个nginx-web服务的解析为
nginx-web.web.svc.cluster.local
5.2-Service 代理:kube-proxy 组件详解
5.2.1-kube-proxy 组件介绍
service 只是把应用对外提供服务的方式做了抽象,真正的应用跑在 Pod 中的container 里,我们的请求转到 nodes 对应的 nodePort 上,就是通过 kube-proxy 实现的
kube-proxy 部署在 k8s 的每一个 Node 节点上,是 Kubernetes 的核心组件,我们创建一个 service 的时候,kube-proxy 会在 iptables 中追加一些规则,为我们实现路由与负载均衡的功能。
在 k8s1.8 之前,kube-proxy 默认使用的是 iptables 模式,通过各个 node 节点上的iptables 规则来实现 service 的负载均衡,但是随着 service 数量的增大,iptables 模由于线性查找匹配、全量更新等特点,其性能会显著下降。
从 k8s 的 1.8 版本开始,kube-proxy 引入了 IPVS 模式,IPVS 模式与 iptables 同样基于Netfilter,但是采用的 hash表,因此当 service 数量达到一定规模时,hash 查表的速度优势就会显现出来,从而提高 service 的服务性能。
service 是一组 pod 的服务抽象,相当于一组 pod 的 LB,负责将请求分发给对应的 pod。
service 会 为这个 LB 提供一个 IP,一般称为 cluster IP。kube-proxy 的作用主要是负责 service 的实现,具体来 说,就是实现了内部从 pod 到 service 和外部的从 node port 向 service 的访问。
1、kube-proxy 其实就是管理 service 的访问入口,包括集群内 Pod 到 Service 的访问和集群外访问
service。
2、kube-proxy 管理 sevice 的 Endpoints,该 service 对外暴露一个 Virtual IP,也可以称为是Cluster IP, 集群内通过访问这个 Cluster IP:Port 就能访问到集群内对应的 serivce 下的 Pod。
5.2.2-Kube-proxy三种工作模式
1.Userspace方式:
Client Pod 要访问 Server Pod 时,它先将请求发给内核空间中的 service iptables 规则,由它再 将请求转给监听在指定套接字上的 kube-proxy 的端口,kube-proxy 处理完请求,并分发请求到指定Server Pod 后,再将请求转发给内核空间中的 service ip,由 service iptables 将请求转给各个节点中 的 Server Pod。
该模式的问题:客户端请求先进入内核空间的,又进去用户空间访问 kube-proxy,由 kube-proxy 封装完成后再进去内核空间的 iptables,再根据 iptables 的规则分发给各节点的用户空间 的 pod。由于其需要来回在用户空间和内核空间交互通信,因此效率很差。在 Kubernetes 1.1 版本之 前,userspace 是默认的代理模型。
2.iptables方式:
客户端 IP 请求时,直接请求本地内核 service ip,根据 iptables 的规则直接将请求转发到到各 pod 上,因为使用 iptable NAT 来完成转发,也存在不可忽视的性能损耗。另外,如果集群中存上万的 Service/Endpoint,那么 Node 上的 iptables rules 将会非常庞大,性能还会再打折。1.2为默认模式
3.ipvs方式:
客户端请求时到达内核空间时,根据 ipvs 的规则直接分发到各 pod 上。kube-proxy 会监视 Kubernetes Service 对象和 Endpoints,调用 netlink 接口以相应地创建 ipvs 规则并定期与 Kubernetes Service 对 象和 Endpoints 对象同步 ipvs 规则,以确保 ipvs 状态与期望一致。访问服务时,流量将被重定向到其 中一个后端 Pod。与 iptables 类似,ipvs 基于 netfilter 的 hook 功能,但使用哈希表作为底层数据结 构并在内核空间中工作。这意味着 ipvs 可以更快地重定向流量,并且在同步代理规则时具有更好的性能.1.11默认
ipvs 为负载均衡算法提供了更多选项:rr:轮询,lc:最小链接数,dh:目标哈希,sh:源哈希,sed:最短期望延迟,nq:不排队调度.
如果某个服务后端 pod 发生变化,标签选择器适应的 pod 又多一个,适应的信息会立即反映到apiserver 上,而 kube-proxy 一定可以 watch 到 etc 中的信息变化,而将它立即转为 ipvs 或者 iptables中的规则,这一切都是动态和实时的,删除一个 pod 也是同样的原理。如图:
以上不论哪种,kube-proxy 都通过 watch 的方式监控着 apiserver 写入 etcd 中关于 Pod 的最新状 态信息,它一旦检查到一个 Pod 资源被删除了或新建了,它将立即将这些变化,反应再 iptables 或 ipvs 规则中,以便 iptables 和 ipvs 在调度 Clinet Pod 请求到 Server Pod 时,不会出现 Server Pod 不存在 的情况.k8s1.11 以后,service 默认使用 ipvs 规则,若 ipvs 没有被激活,则降级使用 iptables 规 则.
5.2.3-kube-proxy 生成的 iptables 规则分析
在 k8s 创建的 service,虽然有 ip 地址,但是 service 的 ip 是虚拟的,不存在物理机上的,是在 iptables 或者 ipvs 规则里的。
1.service的type是clusterIP类型
[root@master2 sc]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
aliyun-mysql ClusterIP 10.104.231.127 <none> 3306/TCP 31m
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5d20h
[root@master2 sc]# iptables -t nat -L |grep 10.104.231.127
KUBE-SVC-3Z5SCIS6QZ4IIO7R tcp -- anywhere 10.104.231.127 /* default/aliyun-mysql cluster IP */ tcp dpt:mysql
KUBE-MARK-MASQ tcp -- !192.168.0.0/16 10.104.231.127 /* default/aliyun-mysql cluster IP */ tcp dpt:mysql
创建的 service,会通过 kube-proxy 在 iptables 中生成一个规则,来实现 流量路由,一系列目标为 KUBE-SVC-xxx 链的规则,每条规则都会匹配某个目标 ip 与端口。也就是说访问某个 ip:port 的请求会由 KUBE-SVC-xxx 链来处理。这个目标 IP 其实就是 service ip。
2.sercice的type是nodePort类型
[root@master2 sc]# kubectl get svc -n kubernetes-dashboard
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dashboard-metrics-scraper ClusterIP 10.98.37.51 <none> 8000/TCP 3d23h
kubernetes-dashboard NodePort 10.101.159.76 <none> 443:30443/TCP 3d23h
[root@master2 sc]# iptables -t nat -L | grep 10.101.159.76
KUBE-SVC-CEZPIJSAUFW5MYPQ tcp -- anywhere 10.101.159.76 /* kubernetes-dashboard/kubernetes-dashboard cluster IP */ tcp dpt:https
KUBE-MARK-MASQ tcp -- !192.168.0.0/16 10.101.159.76 /* kubernetes-dashboard/kubernetes-dashboard cluster IP */ tcp dpt:https
[root@master2 sc]# iptables -t nat -S | grep 30443
-A KUBE-NODEPORTS -p tcp -m comment --comment "kubernetes-dashboard/kubernetes-dashboard" -m tcp --dport 30443 -j KUBE-EXT-CEZPIJSAUFW5MYPQ
5.3-Service服务发现:coredns
5.3.1-什么是coredns
CoreDNS 其实就是一个 DNS 服务,而 DNS 作为一种常见的服务发现手段,所以很多开源项目以及 工程师都会使用 CoreDNS 为集群提供服务发现的功能,Kubernetes 就在集群中使用 CoreDNS 解决服务 发现的问题。 作为一个加入 CNCF(Cloud Native Computing Foundation)的服务, CoreDNS 的实现 非常简单。
在线下载配置文件地址是:https://docs.tigera.io/calico/3.25/getting-started/kubernetes/quickstart
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.25.0/manifests/tigera-operator.yaml
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.25.0/manifests/custom-resources.yaml
[root@master2 sc]# kubectl get svc |grep kubernetes
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5d20h
5.4-Service的应用
5.4.1-Service的资源清单
apiVersion: v1
kind: Service
metada:
name:
labels:
key: value
spec:
clusterIP: #动态分配地址,创建指定无法修改
ports: #定义 service 端口,用来和后端 pod 建立联系
selector <map[string]string> #通过标签选择器选择关联的 pod 有哪些
sessionAffinity <string>
sessionAffinityConfig <Object>
#service 在实现负载均衡的时候还支持 sessionAffinity,sessionAffinity 什么意思?会话联系,默认是 none,随机调度的(基于 iptables 规则调度的);如果我们定义sessionAffinity 的 client ip,那就表示把来自同一客户端的 IP 请求调度到同一个 pod上
type <string> #定义 service 的类型
#四种类型:
#1.ExternalName: 类似于仅主机模式,容器访问外部,无selctor无端口无endpoint
#示例
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
#将 prod 名称空间中的 my-service 服务映射到 my.database.example.com,当查询主机 my-service.prod.svc.cluster.local 时,群集 DNS 将返回值为my.database.example.com 的 CNAME 记录。
#2.ClusterIP:通过集群内部IP暴露服务,只能内部访问,默认的
#3.Nodeport: 通过node的IP和容器的端口映射,实现访问
#Client----->NodeIP:NodePort----->Service Ip:ServicePort----->PodIP:ContainerPort。
#4.LoadBalancer: 使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和ClusterIP 服务。
5.4.2-Service的端口
#service.spec.ports
spec:
ports:
- name:
nodePort: #宿主机上的映射端口
port: #service的端口,集群内部
protocol: #协议
targetPort: #pod上的端口从 port 和 nodePort 上来的流量,经过 kube-proxy 流入到后端 pod的 targetPort 上,最后进入容器。
5.4.3-ClusterIP 类型
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-web
spec:
replicas: 2
selector:
matchLabels:
demo: web
version: v1
template:
metadata:
labels:
demo: web
version: v1
spec:
containers:
- name: demo-web-deploy
image: docker.io/library/nginx:1.23.3
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: demo-web-sc
labels:
demo: web
spec:
type: ClusterIP
ports:
- port: 80 #集群内部中暴露的端口
protocol: TCP
targetProt: 80 #容器端口
selector:
demo: web
[root@master2 sc]# kubectl get svc -l demo=web
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demo-web-sc ClusterIP 10.105.73.16 <none> 80/TCP 69s
#在 k8s 控制节点访问 service 的 ip:端口就可以把请求代理到后端 pod
[root@master2 sc]# kubectl describe svc demo-web-sc|grep End
Endpoints: 192.168.137.84:80,192.168.180.41:80
#访问192.168.137.84和service IP是一样的;
#service 可以对外提供统一固定的 ip 地址,并将请求重定向至集群中的 pod。其中“将请求重定向至集群中的 pod”就是通过 endpoint 与 selector 协同工作实现。selector 是用于选择 pod,由selector 选择出来的 pod 的 ip 地址和端口号,将会被记录在 endpoint 中。endpoint 便记录了所有 pod的 ip 地址和端口号。当一个请求访问到 service 的 ip 地址时,就会从 endpoint 中选择出一个 ip 地址和端口号,然后将请求重定向至 pod 中。具体把请求代理到哪个 pod,需要的就是 kube-proxy 的轮询实现的。service 不会直接到 pod,service 是直接到 endpoint 资源,就是地址加端口,再由 endpoint 再关联到 pod。
#client→service→selector选择pod的ip和端口,记录到endpoint→kube-proxy轮询→service→client
5.4.4-NodePort类型
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-web
spec:
replicas: 2
selector:
matchLabels:
demo: web
version: v1
template:
metadata:
labels:
demo: web
version: v1
spec:
containers:
- name: demo-web-deploy
image: docker.io/library/nginx:1.23.3
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: demo-web-sc
labels:
demo: web
spec:
type: NodePort
ports:
- port: 80 #集群内部中暴露的端口
protocol: TCP
targetPort: 80 #容器端口
nodePort: 30080
selector:
demo: web
#更新启动验证结果
[root@master2 deploy]# kubectl apply -f nginx.yaml
deployment.apps/demo-web unchanged
service/demo-web-sc configured
[root@master2 deploy]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demo-nginx ExternalName <none> kubernetes.default.svc.nginx.com 80/TCP 5d17h
demo-web-sc NodePort 10.105.73.16 <none> 80:30080/TCP 17h
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5d19h
[root@master2 deploy]# curl localhost:30080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
服务走向:
Client-→node ip:30080->service ip:80-→pod ip:container port
5.4.5-ExternalName类型
ExternalName类型类似于仅主机模式,容器访问外部,应用场景:跨名称空间访问
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-busybox
spec:
replicas: 1
selector:
matchLabels:
demo: busybox
version: v1
template:
metadata:
labels:
demo: busybox
version: v1
spec:
containers:
- name: demo-busybox
image: docker.io/library/busybox:latest
command: ["/bin/sh","-c","sleep 36000"]
---
apiVersion: v1
kind: Service
metadata:
name: demo-busybox-svc
labels:
demo: busybox
spec:
type: ExternalName
externalName: demo-web-sc.default.svc.cluster.local
ports:
- port: 80 #集群内部中暴露的端口
protocol: TCP
targetPort: 80 #容器端口
selector:
demo: busybox
[root@master2 sc]# kubectl exec -it demo-busybox-bc9fd6585-wvwn2 -- sh
/ # wget -q -o - demo-web-sc.default.svc.cluster.local
/ # wget -q -o - demo-web-sc.default.svc.cluster.local
wget: can't open 'index.html': File exists
5.4.6-K8s最佳实践:映射外部MySQL服务
1.在集群外部署一个mysql服务。以下为docker-compse部署
[root@my cfsql]# cat docker-compose.yaml
version: "3.3"
services:
cfdb:
image: mysql:5.7
volumes:
- ./db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: #自定义
ports:
- "3306:3306"
[root@my cfsql]# docker-compose ps
Name Command State Ports
--------------------------------------------------------------------------------------
cfsql_cfdb_1 docker-entrypoint.sh mysqld Up 0.0.0.0:3306->3306/tcp, 33060/tcp
2.k8s集群编写SVC的服务
apiVersion: v1
kind: Service
metadata:
name: aliyun-mysql
labels:
app: online
spec:
type: ClusterIP
ports:
- port: 3306
---
apiVersion: v1
kind: Endpoints
metadata:
name: aliyun-mysql
subsets:
- addresses:
- ip: 47.109.97.22 #集群外部服务IP地址
ports:
- port: 3306 #放开的端口
[root@master2 sc]# kubectl get svc |grep ali
aliyun-mysql ClusterIP 10.104.231.127 <none> 3306/TCP 17s
[root@master2 sc]# mysql -u root -h 10.104.231.127 -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 39056
Server version: 5.7.40 MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]>
现在即可用集群内部访问clusterIP+端口进行访问部署在阿里云的mysql;在生产环境中,如果用的公有云数据库,可用此方案。