目录
假如说,我们有下面的服务需要部署进入我们的k8s集群中,
方法:先创建我们的deployment,通过deployment部署我们的NGINX,它的副本数为1,然后去部署php-fpm,副本数为3,再部署MySQL,通过我们的StatefulSet,对于有状态服务,我们一般通过StatefulSet。
意外情况:假如说,有一天我们的php-fpm中有一个突然挂掉了,那么此时我们的副本数目不满足3个了,此时就会再次创建出一个pod,那么新创建出来的这个pod,它的ip就会变化,此时NGINX里面还是原来的那个ip,新的ip它不认识,此时就会报错。。。这样就引入出我们的SVC概念,根据标签匹配至对应的pod,然后负责去监测pod的信息,会同步它所监测的pod信息,我们的NGINX再去反向代理SVC的话,SVC会自动更新,不需要我们的NGINX做任何的修改。
Service 的概念
Kubernetes Service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。 这一组 Pod 能够被 Service 访问到,通常是通过 Label Selector(如下图的红框)。
分析:我们的Frontend Deployment创建了三个pod,那么个pod里面有三组不同的标签,上面又定义一个Frontend Service,它所对应的pod标签是当app=webapp,role=frontend的时候,就会匹配上我们的这个SVC,注意,匹配的话,主要满足就行,pod里面那个version=1.0.0不用管。。一旦匹配成功之后,这个pod的信息就会被写入到SVC中,以后我们去访问这个SVC,相当于访问下属的pod,通过轮询的方式访问pod。。并且当我们的pod有死亡的时候,我们新创建出来的pod的信息也会被写入到SVC中去。
Service能够提供负载均衡的能力,但是在使用上有以下限制:
只提供 4 层负载均衡能力(也就是只能基于我们的ip地址和端口进行转发,实现负载均衡),而没有 7 层功能,也就是不能通过我们的主机名和域名的方案去负载均衡(这个可以通过ingress实现七层负载均衡),但有时我们可能需要更多的匹配规则来转发请求,这点上 4 层负载均衡是不支持的。
Service 的类型
四种类型
Service 在 K8s 中有以下四种类型:
ClusterIp:默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP。
NodePort:在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 : NodePort 来访问该服务。
LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到: NodePort。
ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7 或更高版本的 kube-dns 才支持。
分析:SVC服务发现需要上图几个组件的配合,首先由我们的apiserver去监听我们的服务和端口,通过我们的kube-proxy,kube-proxy负责去监听对应的pod,把对应pod的信息写入到iptables规则里去,当我们的客户端想要访问SVC的时候,其实就是访问我们的iptables规则。。这里其实几个概念,客户端访问到我们的节点是通过iptables去实现的,iptables的规则是通过我们的kube-proxy去实现的,apiserver通过kube-proxy去实现监听我们服务的端口和发现。kube-proxy会通过pod的标签去判断这个端点信息是否写入到我们的SVC中的端点信息中去。
VIP 和 Service 代理
在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy 进程。 kube-proxy 负责为 Service 实现了一种VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。 在 Kubernetes v1.0 版本,代理完全在 userspace。在Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。 从 Kubernetes v1.2 起,默认就是iptables 代理。 在 Kubernetes v1.8.0-beta.0 中,添加了 ipvs 代理。
在 Kubernetes 1.14 版本开始默认使用 ipvs 代理
在 Kubernetes v1.0 版本, Service 是 “4层”(TCP/UDP over IP)概念。 在 Kubernetes v1.1 版本,新增了Ingress API(beta 版),用来表示 “7层”(HTTP)服务。。
问:为何不使用 round-robin DNS?
回答:DNS会在很多的客户端中进行缓存。。。我们很多服务用DNS进行域名解析之后,不会对DNS进行清除缓存的操作,也就是我们一旦有它的地址信息之后,不管访问几次,还是原来的地址信息。
代理模式的分类
Ⅰ、userspace 代理模式
分析:我们的客户端访问server的方式是通过iptables(防火墙),再到kube-proxy,然后到server,包括我们的kube-apiserver也会通过kube-proxy去监听所谓的服务的更新以及端点的维护。这样我们的kube-proxy的负载压力很大。
Ⅱ、iptables 代理模式
分析:我们发现,所有的访问直接由我们的iptables(防火墙)去完成,不需要通过kube-proxy去调度一个,这样我们的访问速度就会增加,以及kube-proxy的稳定性会提高和压力会减小。这个的话,除了防火墙的性能没那么高,,其他没什么影响。
Ⅲ、ipvs 代理模式
这种模式,kube-proxy 会监视 Kubernetes Service 对象和 Endpoints ,调用 netlink 接口以相应地创建ipvs 规则并定期与 Kubernetes Service 对象和 Endpoints 对象同步 ipvs 规则,以确保 ipvs 状态与期望一致。访问服务时,流量将被重定向到其中一个后端 Pod。。
与 iptables 类似,ipvs 于netfilter 的 hook 功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着 ipvs 可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs 为负载均衡算法提供了更多选项,例如:
rr :轮询调度
lc :最小连接数
dh :目标哈希
sh :源哈希
sed :最短期望延迟
nq : 不排队调度
注意如下说明:
ipvs模式启动成功的前提是宿主机上安装了ipvs相关模块,如果未安装,则会退回到iptables代理模式
分析:这个模式已经成为标准了,先安装yum install ipvsadm,使用命令:ipvsadm -Ln,这个也是检测我们集群当中有没有开启ipvs模式
测试用例
ClusterIP
clusterIP 主要在每个 node 节点使用 iptables/ipvs,将发向 clusterIP 对应端口的数据,转发到 kube-proxy 中。然后 kube-proxy 自己内部实现有负载均衡的方法,并可以查询到这个 service 下对应 pod 的地址和端口,进而把数据转发给对应的 pod 的地址和端口。。。
分析:Nginx相当于访问者,WebApp-1/2/3相当于被访问者
为了实现图上的功能,主要需要以下几个组件的协同工作:
1. apiserver 用户通过kubectl命令向apiserver发送创建service的命令,apiserver接收到请求后将数据存储到etcd中。
2. kube-proxy kubernetes的每个节点中都有一个叫做kube-porxy的进程,这个进程负责感知service,pod的变化,并将变化的信息写入本地的iptables规则中
3. iptables 使用NAT等技术将virtualIP的流量转至endpoint中
为了演示的效果,我们需要先创建一个deployment,然后去绑定我们的SVC。。。
创建 depl-demo.yaml文件
[root@master1 svc]# ls
depl-demo.yaml depl-svc.yaml
[root@master1 svc]# cat depl-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: stabel
template:
metadata:
labels:
app: myapp
release: stabel
env: test
spec:
containers:
- name: myapp
image: nginx:alpine
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8
[root@master1 svc]#
然后给这个deployment绑定了一个SVC
创建 Service 信息
[root@master1 svc]# cat depl-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
spec:
type: ClusterIP
selector:
app: myapp
release: stabel
ports:
- name: http
port: 80
targetPort: 80 #后端的pod的真实端口
[root@master1 svc]#
注意:我们创建的SVC跟上面先创建的deployment实现匹配的上,是根据labels,也就是deployment下面的matchLabels的键值对和SVC中的selector对应的上即可绑定成功。。
然后下面就是显示的效果:访问SVC的10.1.96.245的80端口,就相当于去访问后端的那三个ip
rr:表示轮询
注意:我们在用xxx.yaml文件创建pod的时候,如果不想要这个pod了,可以使用kubectl delete -f xxx.yaml命令删除我们这个yaml文件创建的pod。
在 Cluster 内部中,除了可以通过 Cluster IP 访问 Service,Kubernetes 还提供了更为方便的 DNS 访问,在kubeadm 部署时会默认安装 kube-dns 组件。
coredns 是一个 DNS 服务器。每当有新的 Service 被创建,coredns 会添加该 Service 的 DNS 记录。Cluster 中的 Pod 可以通过 <SERVICE_NAME>.<NAMESPACE_NAME> 访问 Service。
比如可以用 myapp.default 访问 Service myapp。
[root@master1 controller]# kubectl run nginx --image=nginx:alpine
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx created
[root@master1 controller]#
[root@master1 controller]#
[root@master1 controller]#
[root@master1 controller]# kubectl get pod
NAME READY STATUS RESTARTS AGE
myapp-deploy-65ff67dcb6-2q92v 1/1 Running 0 8m24s
myapp-deploy-65ff67dcb6-8pbcg 1/1 Running 0 8m24s
myapp-deploy-65ff67dcb6-x88m4 1/1 Running 0 8m24s
nginx-74d5899f46-ggggl 1/1 Running 0 4s
[root@master1 controller]# kubectl exec -it nginx-74d5899f46-ggggl sh
/ # curl myapp.default
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
/ #
上面的就是通过我们重新创建一个pod(nginx-74d5899f46-ggggl),然后验证集群内部通过访问<SERVICE_NAME>.<NAMESPACE_NAME> 访问 Service ,可以看到访问成功。
Headless Service(无头服务)
有时不需要或不想要负载均衡,以及单独的 Service IP 。遇到这种情况,可以通过指定 Cluster
IP(spec.clusterIP) 的值为 “None” 来创建 Headless Service 。这类 Service 并不会分配 Cluster IP, kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。。。
(这类服务的作用主要就是,通过这种方式去解决我们的所谓的hostname和我们的podname的变化问题,即通过它去绑定)
[root@master1 svc]# cat svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-headless
namespace: default
spec:
selector:
app: myapp
clusterIP: "None"
ports:
- port: 80
targetPort: 80
[root@master1 svc]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 63m
myapp ClusterIP 10.1.96.245 <none> 80/TCP 20m
myapp-headless ClusterIP None <none> 80/TCP 47s
[root@master1 svc]# kubectl get pod -o wide -n kube-system
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-645bfc575f-mpmf8 1/1 Running 0 64m 10.244.0.69 master1 <none> <none>
coredns-645bfc575f-vlktl 1/1 Running 0 64m 10.244.0.68 master1 <none> <none>
etcd-master1 1/1 Running 0 63m 192.168.64.150 master1 <none> <none>
kube-apiserver-master1 1/1 Running 0 63m 192.168.64.150 master1 <none> <none>
kube-controller-manager-master1 1/1 Running 0 63m 192.168.64.150 master1 <none> <none>
kube-flannel-ds-amd64-wn26n 1/1 Running 0 64m 192.168.64.150 master1 <none> <none>
kube-proxy-8vhd7 1/1 Running 0 46m 192.168.64.150 master1 <none> <none>
kube-scheduler-master1 1/1 Running 0 63m 192.168.64.150 master1 <none> <none>
[root@master1 svc]#
分析:我们如果访问这个无头服务,会有什么情况发生呢?
我们知道,对应SVC一旦创建成功,就会有一个主机名,它就会写入到我们的coredns中去,它的写入格式是:svc的名称+名字空间的名称+集群的域名(默认为svc.cluster.local)
下面的是通过dig命令去解析一下
最后会得到如下:
[root@master1 svc]# dig -t A myapp-headless.default.svc.cluster.local. @10.244.0.69
; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.5 <<>> -t A myapp-headless.default.svc.cluster.local. @10.244.0.69
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13719
;; flags: qr rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;myapp-headless.default.svc.cluster.local. IN A
;; ANSWER SECTION:
myapp-headless.default.svc.cluster.local. 22 IN A 10.244.0.73
myapp-headless.default.svc.cluster.local. 22 IN A 10.244.0.71
myapp-headless.default.svc.cluster.local. 22 IN A 10.244.0.72
;; Query time: 0 msec
;; SERVER: 10.244.0.69#53(10.244.0.69)
;; WHEN: 六 8月 28 15:58:36 CST 2021
;; MSG SIZE rcvd: 237
[root@master1 svc]#
可以和我的pod对应起来,也就意味着在我们的无头服务中,虽然没有自己的SVC了,但是可以通过访问域名的方案去访问这几个不同的pod上去。
[root@master1 svc]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-deploy-65ff67dcb6-27vmt 1/1 Running 0 24m 10.244.0.73 master1 <none> <none>
myapp-deploy-65ff67dcb6-lgtg8 1/1 Running 0 24m 10.244.0.71 master1 <none> <none>
myapp-deploy-65ff67dcb6-mcttp 1/1 Running 0 24m 10.244.0.72 master1 <none> <none>
[root@master1 svc]#
[root@master1 svc]# kubectl exec -it myapp-deploy-65ff67dcb6-27vmt sh
/ # ping myapp-headless.default.svc.cluster.local -c 3
PING myapp-headless.default.svc.cluster.local (10.244.0.71): 56 data bytes
64 bytes from 10.244.0.71: seq=0 ttl=64 time=0.070 ms
64 bytes from 10.244.0.71: seq=1 ttl=64 time=0.160 ms
64 bytes from 10.244.0.71: seq=2 ttl=64 time=0.193 ms
--- myapp-headless.default.svc.cluster.local ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.070/0.141/0.193 ms
/ #
关于无头服务更加详细的介绍:https://cloud.tencent.com/developer/article/1820094
NodePort
它的概念就是可以在我们的物理机上暴露一个端口,我们就可以通过物理机的ip+端口的方式去访问至集群内部的服务
nodePort 的原理在于在 node 上开了一个端口,将向该端口的流量导入到 kube-proxy,然后由 kube-proxy 进一步到给对应的 pod。
[root@master1 svc]# cat svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
spec:
type: NodePort
selector:
app: myapp
release: stabel
ports:
- name: http
port: 80
targetPort: 80
而且我们发现上面的是端口是kube-proxy去监听的
[root@master1 svc]# lsof -i :32435
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
kube-prox 27288 root 11u IPv6 243499 0t0 TCP *:32435 (LISTEN)
LoadBalancer(了解即可)
loadBalancer 和 nodePort 其实是同一种方式。区别在于 loadBalancer 比 nodePort 多了一步,就是可以调用cloud provider (云供应商)去创建 LB 来向节点导流。。。
ExternalName
这种类型的 Service 通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容( 例如:hub.atguigu.com )。ExternalName Service 是 Service 的特例,它没有 selector(也就是没有标签选择),也没有定义任何的端口和Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。
[root@master1 svc]# cat externalname.yaml
kind: Service
apiVersion: v1
metadata:
name: my-service-1
namespace: default
spec:
type: ExternalName
externalName: hub.atguigu.com
[root@master1 svc]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 93m
my-service-1 ExternalName <none> hub.atguigu.com <none> 98s
myapp NodePort 10.1.52.170 <none> 80:32435/TCP 14m
myapp-headless ClusterIP None <none> 80/TCP 30m
当查询主机 my-service-1.defalut.svc.cluster.local (SVC_NAME.NAMESPACE.svc.cluster.local )时,集群的DNS 服务将返回一个值hub.atguigu.com 的 CNAME(别名) 记录。访问这个服务的工作方式和其他的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发 。