在之前的文章说过,每一个Pod都是独立的IP、HostName、存储,同时Pod是随时可以被动态创建和回收的,那么就有个问题,我们如何知道Pod的IP并进行访问的呢? 其实K8S是使用Service VIP技术的虚拟ip + kube-proxy来解决这个问题,其中service VIP用来转发请求,kube-proxy用来监控pod状态,并且会及时修改pod的ip。
service是K8S的资源对象,service资源对象运行在每一个node节点上,每一个node节点都有一个service进程,service有自己的IP地址(虚拟IP),而service VIP相当于一个网关,所有的请求都要经过service VIP,通过service VIP进行转发,从而实现负载均衡。
service VIP一旦被创建,是不会被修改的,除非删除service后重新创建service;同时由于service信息存储在高可用的etcd中,且service实例运行在多个node节点上,因此Service VIP不存在单点故障的问题;由于service VIP中使用的是虚拟IP,因此Service VIP只能在局域网内部进行访问,不能通过外网进行访问,如果想要进行外网访问,则需要借助物理网卡进行端口映射转发。
在K8S中IP资源的分类如下:
Node IP:Node物理节点IP
Pod IP:物理机内部运行的一个虚拟容器pod的ip
cluster IP:集群IP,也是个虚拟IP,是K8S抽象出来的一个service的IP。此IP只能局域网内部访问,不能通过外网访问,如果要使用外网访问,就必须开辟nodeport类型的IP地址。如下图所示,外网访问物理IP,然后将访问请求映射到service VIP上,service VIP从etcd上获取endpoints中pod的IP,然后使用负载均衡策略选择一个pod进行调用
Pod服务发现借助kube-proxy实现,该组件实现了三件事情:监控pod;pod如果发生了变化,及时修改映射关系;修改映射关系的同时,修改路由规则,以便在负载均衡时可以选择到新的pod。
K8S的负载均衡方案有三种:kube-proxy(userspace)、iptables(防火墙)、ipvs。
使用kube-proxy的负载方案是使用kube-proxy来监控pod的状态,如果pod发生了变化,则需要kube-proxy去修改service和pod的映射关系(endpoints),同时修改路由规则,并且由kube-proxy转发请求,这种方式kube-proxy的压力比较大,性能可能会出现问题。
IPtables是K8S默认采用的负载策略,这种方式中,kube-proxy同样用来监控pod和修改映射关系以及修改路由规则,但是转发请求是采用轮询iptables路由规则的方式进行调用处理的。
这种模式kube-proxy主要做好watching Cluster API即可,路由和请求的转发都交给了iptables,但是kube-proxy在请求无响应时会换一个pod进行重试,而iptables则是一条条的路由规则,不会进行重试。
在iptables中,默认的轮询策略是随机的轮询策略,但是也可以将其设置为轮询。
# 随机:(Random balancing)
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode random --probability 0.33 -j DNAT --to-destination 10.0.0.2:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode random --probability 0.5 -j DNAT --to-destination 10.0.0.3:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -j DNAT --to-destination 10.0.0.4:1234
在iptables命令中,命令的执行和顺序有关,在上述命令中,用–probability 设置了命中几率,第一条设置了纪律是0.33,也就是整个请求的0.33,第二条命中率为0.5,实际是剩余请求的0.5,也就是总请求的0.335,第三条是剩余的流量全部打到该ip上,也就是0.335,基本上就是随机分配了。
#every:每n个包匹配一次规则
#packet:从第p个包开始
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 10.0.0.2:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode nth --every 2 --packet 0 -j DNAT --to-destination 10.0.0.3:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -j DNAT --to-destination 10.0.0.4:1234
ipvs (IP Virtual Server) 实现传输层负载均衡,通常称为第四层LAN交换,是Linux内核的一部分。
ipvs与iptables的区别:
ipvs为大型集群提供了更好的可扩展性和性能
ipvs支持比iptables更复杂的负载均衡算法
ipvs支持服务器的健康检查和连接重试等。
对于上述差异做个说明:
在linux中iptables设计是用于防火墙的,对于比较少的规则,没有太多的性能影响,如果对于一个庞大的K8S集群,会有上千Service服务,Service服务会对应多个pod,每条都是一个iptables规则,那么对于集群来说,每个node上都有大量的规则,简直是噩梦。而IPVS则是使用hash tables来存储网络转发规则的,比iptables更有优势,而且ipvs主要工作在kerbespace,减少了上下文的切换。
IPVS有轮询(rr)、最小连接数(lc)、目的地址hash(dh)、源地址hash(sh)、最短期望延迟(sed)、无须队列等待(nq)等负载均衡算法,在node上通过 “–ipvs-scheduler”参数,指定kube-proxy的启动算法。
kube-proxy和IPVS合作的流程:
(1)kube-proxy仍然是watching Cluster API,获取新建、删除Service或Endpoint pod指令,如果有新的Service建立,kube-proxy回调网络接口,构建IPVS规则。
(2)kube-proxy会定期同步Service和Pod的转发规则,确保失效的转发可以被及时修复
(3)有请求转发到后端的集群时,IPVS的负载均衡直接将其转发到对应的Pod上
1、先介绍以下部署K8S服务时使用到的配置文件:
创建配置文件(ingres.yaml),用来部署一个nginx的deployment和service
# 创建一个service资源对象
# kubectl expose deployment xxName –port=80 –target-port=80 k8s指令模式创建一个service
# k8s部署yaml方式
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
---
# 部署deployment对象,k8s创建3个资源对象:Deployment,ReplicaSet,POD
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.16
ports:
- containerPort: 80
首先看Deployment:
apiVersion标记版本,Kind标记类型,说明要创建一个Deployment,该deployment的名字叫nginx,空间为默认空间。
spec下是rs和pod的配置,首先replicas是副本的数量,然后就是选择器的名称为nginx。
template下是副本的配置,首先副本的标签是nginx,容器的名字是nginx,镜像为nginx1.16,这里可以配置为镜像仓库地址和对应的镜像,最后ports是容器开放的端口。
这里说明几个比较重要的配置,就是选择器的名称和容器的标签一定要一致(绿色连接线),否则容器不会被副本控制器RS所控制。
然后说一下Service:
apiVersion和Kind都和Deployment一样,Kind的值表明了这是一个Service配置。
然后就是这个service的名字,service标签选择器的名字,以及目标容器的端口。
这里说几个比较重要的配置,和Deployment一样,选择器的名字要和容器的名字一致(绿色连接线),目标容器端口要和容器的端口保持一致(紫色连接线)
执行配置文件,生成deployment和service:
kubectl apply -f ingres.yaml
执行后查看deployment、service、rs、pod的情况(注意:这一步一般时间比较久,需要等一会)
从上面图片的输出中可以看到,nginx的type为ClusterIp,这种类型的service只能在集群内部访问,而不能在外部直接访问,那么就需要修改类型为NodePort,修改命令:
kubectl edit svc nginx
更改完毕之后,重新查看service,发现type已经变为NodePort,且port也变更了,前面的80是容器内的端口,后面的31758是对外开放的端口,也就是宿主机的端口。
此时,使用宿主机的ip + 31758访问,即可访问nginx
但是有个问题,就是如果每个服务都要对外开启一个端口,那么就需要开启很多的端口,这样即麻烦又有点浪费,因此就需要Ingres来解决这个问题,Ingres只需要一个NodePort就可以解决上述的问题。因为ingress相当于一个7层的负载均衡器,是k8s对反向代理的一个抽象。大概的工作原理也确实类似于Nginx,可以理解成在 Ingress 里建立一个个映射规则 , ingress Controller 通过监听 Ingress这个api对象里的配置规则并转化成 Nginx 的配置(kubernetes声明式API和控制循环) , 然后对外部提供服务。
Ingres是K8S对于nginx进行云原生模式的封装,使得nginx更适合云原生的结构,使用Ingres可以对Service进行负载均衡,因此Ingres工作在七层,属于七层负载均衡。
Ingres通过http协议的方式实现Service的负载均衡:
对于K8S来说,Ingres就是一个资源控制器,用来控制资源的访问策略,
Ingres的配置文件如下所示:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
rules:
- host: ingress.lcl.com
http:
paths:
- backend:
serviceName: nginx
servicePort: 80
在上面文件中,配置了Ingres的规则,那么对于使用同一个域名访问不同服务的配置,则是在paths下面增加多个path路径,让path指向不同的服务,并且在metadata中新增请求重写配置annotations,去除path的路径,保证访问到指定服务上的路径不带有该path,具体配置如下所示:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
namespace: default
labels:
app: nginx
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # 请求重写
spec:
rules:
- host: ingress.lcl.com
http:
paths:
- path: /nginx # 把path追加到域名后面 ingress.lcl.com/nginx 把/nignx当成服务请求的一部分
backend:
serviceName: nginx
servicePort: 80
- path: /tomcat
backend:
serviceName: tomcat
servicePort: 8080
不同域名访问不同服务主要是在rules下面配置不同的规则即可。
# 使用多个域名,访问不同的服务
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
rules:
- host: ingress.lcl.com
http:
paths:
- path: /
backend:
serviceName: nginx
servicePort: 80
- host: ingress.lcl.com
http:
paths:
- path: /
backend:
serviceName: tomcat
servicePort: 8080
如果想将http请求升级为https,我们就需要制作证书
# 生成私钥
openssl genrsa -out lcl.key 2048
# 自签发证书
openssl req -new -x509 -key lcl.key -out lcl.crt -subj /C=CN/ST=Shanghai/L=Shanghai/O=DevOps/CN=ingres.lcl.com
# 创建K8S使用的证书
kubectl create secret tls lcl-secret --cert=lcl.crt --key=lcl.key
创建ingres,使用证书的Ingres
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-tomcat-tls
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
labels:
app: tomcat
spec:
tls:
- hosts:
- ingress.lcl.com
secretName: lcl
rules:
- host: ingres.lcl.com
http:
paths:
- backend:
serviceName: nginx
servicePort: 8080
查看secret资源
kubectl describe secret lcl
修改原来的ingres配置文件
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
tls:
- hosts:
- ingres.lcl.com
secretName: lcl-secret
rules:
- host: ingress.lcl.com
http:
paths:
- backend:
serviceName: nginx
servicePort: 80