Service
我们前面学习了Pod 的基本用法,也了解到 Pod 的生命周期管理。后面学习到的RC 和Deployment 可以用来动态的创建和销毁Pod 。尽管每个Pod 都有自己的IP 地址,但是如果 Pod 重新启动了的话那么他的IP 很有可能也就变化了。这就会带来一个问题:比如我们有一些后端的Pod 的集合为集群中的其他前端的Pod 集合提供 API 服务,如果我们在前端的Pod 中把所有的这些后端的Pod 的地址都写死,然后去某种方式去访问其中一个Pod 的服务,这样看上去是可以工作的,但是如果这个Pod 挂掉了,然后重新启动起来了,是不是IP 地址非常有可能就变了,这个时候前端就极大可能访问不到后端的服务了。
遇到这样的问题该怎么解决呢?在没有使用Kubernetes 之前,我相信可能很多同学都遇到过这样的问题,不一定是IP 变化的问题,比如我们在部署一个WEB 服务的时候,前端一般部署一个Nginx作为服务的入口,然后Nginx后面肯定就是挂载的这个服务的大量后端,很早以前我们可能是去手动更改Nginx配置中的 upstream选项,来动态改变提供服务的数量,到后面出现了一些服务发现 的工具,比如Consul 、ZooKeeper 还有etcd等工具,我们就可以只需要把我们的服务注册到这些服务发现中心去就可以,然后让这些工具动态的去更新Nginx 的配置就可以了,完全不用去手工的操作了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jBiXNx7t-1628220810982)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210303121143046.png)]
同样的,要解决我们上面遇到的问题是不是实现一个服务发现的工具也可以解决啊?当我们Pod 被销毁或者新建过后,我们可以把这个Pod 的地址注册到这个服务发现中心去就可以,但是这样的话我们的前端的Pod 集合就不能直接去连接后台的Pod 集合了是吧,应该连接到一个能够做服务发现的中间件上面,对吧?
而Kubernetes 集群就为我们提供了这样的一个对象 - Service,Service是一种抽象的对象,它定义了一组Pod 的逻辑集合和一个用于访问它们的策略,其实这个概念和微服务非常类似。一个Serivce下面包含的Pod 集合一般是由Label Selector来决定的。
比如我们上面的例子,假如我们后端运行了3个副本,这些副本都是可以替代的,因为前端并不关心它们使用的是哪一个后端服务。尽管由于各种原因后端的Pod 集合会发生变化,但是前端却不需要知道这些变化,也不需要自己用一个列表来记录这些后端的服务,Service的这种抽象就可以帮我们达到这种解耦的目的。
三种IP
在继续往下学习Service之前,我们需要先弄明白Kubernetes 系统中的三种IP这个问题
- Node IP:Node节点的IP 地址
- Pod IP: Pod 的IP地址
- Cluster IP: Service的 IP 地址
首先,node IP是kubernetes集群中节点的物理网卡IP地址(一般内网),所有属于这个网络的服务器之前都可以通信,所以kubernetes集群外想要访问kubernetes集群内部的某个节点或者服务,肯定要通过node IP进行通信,(这个时候一般是外网ip)
Pod IP是每个pod的IP地址,他是Docker Engine根据docker0网桥的IP地址段进行分配(我们这里使用的是flannel这种网络插件保证所有节点的Pod IP 不会冲突)
最后Cluster IP是一个虚拟的IP,仅仅作用于Kubernetes Service这个对象,由Kubernetes 自己进行管理和分配地址,当然我们也无法ping这个地址,他没有一个真正的实体对象来响应,他只能结合Service Port来组成一个可以通信的服务。
定义Service
定义Service的方式和我们前面定义的各种资源对象的方式类型,例如,假定我们有一组Pod 服务,它们对外暴露了 8080 端口,同时都被打上了app=myapp 这样的标签,那么我们就可以像下面这样来定义一个Service对象:
[root@k8s-master ~]# vim myservice.yaml
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
selector:
app: myapp
ports:
- protocol: TCP
port: 80 #service暴露的端口
targetPort: 8080 #容器内暴露的端口
name: myapp-http
然后通过的使用
[root@k8s-master ~]# kubectl create -f myservice.yaml
service/myservice created
[root@k8s-master ~]# kubectl get svc | grep my
myservice ClusterIP 10.111.217.109 <none> 80/TCP 68s
就可以创建一个名为myservice 的Service对象,它会将请求代理到使用 TCP 端口为8080,具有标签app=myapp 的Pod 上,这个Service会被系统分配一个我们上面说的Cluster IP ,该Service还会持续的监听selector下面的Pod ,会把这些Pod 信息更新到一个名为myservice 的 Endpoints 对象上去,这个对象就类似于我们上面说的Pod 集合。
需要注意的是,Service能够将一个接收端口映射到任意的targetPort 。默认情况下,targetPort 将被设置为与port字段相同的值。targetPort可以是一个字符串,引用了 backend Pod 的一个端口的名称。 因实际指派给该端口名称的端口号,在每个 backend Pod 中可能并不相同,所以对于部署和设计 Service ,这种方式会提供更大的灵活性。
另外Service能够支持 TCP 和 UDP 协议,默认是 TCP 协议。
kube-proxy
前面我们讲到过,在Kubernetes 集群中,每个Node会运行一个kube- proxy进程, 负责为Service实现一种 VIP(虚拟 IP,就是我们上面说的clusterIP )的代理形式,现在的Kubernetes 中默认是使用的 iptables这种模式来代理。这种模式,kube-proxy会监视 Kubernetes master 对Service 对象Endpoints 对象的添加和移除。 对每个 Service,它会添加上 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某一个上面。对于每个 Endpoints 对象,它也会安装 iptables 规则,这个规则会选择一个 backend Pod。
默认的策略是,随机选择一个 backend。 我们也可以实现基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity 的值设置为"ClientIP" (默认值为 “None”)。
配置服务上的会话亲和性
如果多次执行同样的命令,每次调用执行应该在不同的pod上。因为服务代理通常将每个连接随机指向选中的后端pod中的一个,即使连接来自于同一个客户端
sessionAffinity属性:如果希望特定客户端产生的所有请求每次都指向同一个pod,可以设置服务的sessionAffinity属性为ClientIP(默认值为None),如下面所示:![]()
这种方式将会使服务代理将来自同一个client IP的所有请求转发至同一个pod上
该选项仅支持两种配置:
None和ClientIP
你或许惊讶竟然不支持基于cookie的会话亲和性的选项,但是你要了解Kubernetes服务不是在HTTP层面上工作。服务处理TCP和UDP包,并不关心其中的载荷内容。因为cookie是HTTP协议中的一部分,服务并不知道它们,这就解释了为什么会话亲和性不能基于cookie
另外需要了解的是如果最开始选择的 Pod 没有响应,iptables 代理能够自动地重试另一个 Pod,所以它需要依赖 readiness probes。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BY3Qp2Bo-1628220810984)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210303142533398.png)]
我们在定义Service的时候可以指定一个自己需要的类型的Service,如果不指定的话默认是ClusterIP 类型。
我们可以使用的服务类型如下:
- ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的ServiceType。
- NodePort:通过每个 Node 节点上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 :,可以从集群的外部访问一个 NodePort 服务。
- LoadBalancer:使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务,这个需要结合具体的云厂商进行操作。
- ExternalName:通过返回 CNAME 和它的值,可以将服务映射到externalName 字段的内容(例如, foo.bar.example.com)。没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns才支持。
NodePort 类型
如果设置 type 的值为 “NodePort”,Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每个 Node 将从该端口(每个Node 上的同一端口)代理到 Service。该端口将通过 Service 的spec.ports[*].nodePort 字段被指定,如果不指定的话会自动生成一个端口。
需要注意的是,Service 将能够通过 :spec.ports[].nodePort 和spec.clusterIp:spec.ports[].port 而对外可见。
接下来我们创建一个NodePort的服务来访问前面的Nginx服务:(保存为service-demo.yaml)
apiVersion: v1
kind: Service
metadata:
name: myservice1
spec:
selector:
app: myapp
type: NodePort
ports:
- protocol: TCP
port: 80
targetPort: 80
name: myapp-http
创建该Service :
[root@k8s-master ~]# kubectl create -f service-demo.yaml
service/myservice1 created
然后我们可以查看Service对象信息:
[root@k8s-master ~]# kubectl get svc | grep myservice1
myservice1 NodePort 10.101.23.188 <none> 80:31508/TCP 82s
#查看监听端口
[root@k8s-master ~]# ss -tnl | grep 31508
LISTEN 0 128 *:31508 *:*
我们可以看到myservice 的 TYPE 类型已经变成了NodePort,后面的PORT(S)部分也多了一个 32560 的映射端口
端口也已经开始监听(会在所有节点监听这个端口)
也可以通过kubectl进行创建service
语法:kubectl create service [clusterip|externalname|loadbalancer|nodeport] NAME [--tcp=<port>:<targetPort>] [--dry-run=server|client|none] [options] -n namespace
如创建:一个关连deployment中nginx类型为nodePort的service
当前的deployment信息
[root@k8s-master ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deploy 10/10 10 10 23h
kubectl create service nodeport nginx --tcp=80:80 -n default
#service/nginx created #此时已经创建成功
[root@k8s-master ~]# kubectl get svc
#进行查看
nginx NodePort 10.110.113.249 <none> 80:32710/TCP 65s
访问测试
[root@k8s-master ~]# curl 172.31.0.171:32710
teST!!!!!!!
#此时已经可以正常访问
如果使用yaml进行创建,则可以使用进入edit,然后进行复制内容进行修改
[root@k8s-master ~]# kubectl edit service nginx
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2021-08-05T09:06:43Z"
labels:
app: nginx
name: nginx
namespace: default
resourceVersion: "1051783"
selfLink: /api/v1/namespaces/default/services/nginx
uid: dff37258-5c82-4aef-b7a7-7853fb79a40c
spec:
clusterIP: 10.110.113.249
externalTrafficPolicy: Cluster
ports:
- name: 80-80
nodePort: 32710
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
sessionAffinity: None
type: NodePort
status:
loadBalancer: {}
复制内容,进行退出,
删除service
[root@k8s-master ~]# kubectl delete service nginx
service "nginx" deleted
#修改刚刚复制的yaml文件
[root@k8s-master 06-Deployment的使用]#vim nginx-service.yaml
#可以根据需求进行更改
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2021-08-05T09:06:43Z" #删除
labels:
app: nginx
name: nginx
namespace: default
resourceVersion: "1051783" #删除
selfLink: /api/v1/namespaces/default/services/nginx #删除
uid: dff37258-5c82-4aef-b7a7-7853fb79a40c #删除
spec:
clusterIP: 10.110.113.249 #删除
externalTrafficPolicy: Cluster
ports:
- name: 80-80
nodePort: 32710
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
sessionAffinity: None #删除
type: NodePort
status: #删除
loadBalancer: {} #删除
[root@k8s-master 06-Deployment的使用]# kubectl apply -f nginx-service.yaml
service/nginx created
[root@k8s-master 06-Deployment的使用]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx NodePort 10.102.59.50 <none> 80:32710/TCP 24s
访问测试
[root@k8s-master 06-Deployment的使用]# curl 172.31.0.171:32710
teST!!!!!!!
此时以访问正常
#其他类型的资源也可以使用这种方法进行创建
无头服务headless方式
一、Headless Services介绍
service:一组pod访问策略,提供cluster-ip群集之间的通信,还提供负载均衡和服务发现
headless service :无头服务,不需要cluster-ip,直接绑定具体的pod的ip
查看当前用于测试deployment
[root@k8s-master 07-service]# kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deploy 2/2 2 2 18h
[root@k8s-master 07-service]# vim null-heald-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
clusterIP: None # handless service
ports:
- name: foo
port: 81
targetPort: 80
#chuang
进行创建
[root@k8s-master 07-service]# kubectl apply -f test-no-heald-service.yaml
service/nginx-service created
[root@k8s-master 07-service]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP None <none> 81/TCP 40s
测试无头服务
#创建一个测试应用
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: test
name: test
namespace: default
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: test
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: test
spec:
containers:
- command:
- /bin/sh
- -c
- sleep 50000
image: busybox:latest
imagePullPolicy: Always
name: busybox
[root@k8s-master 06-Deployment的使用]# kubectl apply -f nslookup-test.yaml
deployment.apps/test created
[root@k8s-master 06-Deployment的使用]# kubectl get po -o wide |awk '{print $1,$6}'
NAME IP
nginx-deploy-7bffc778db-qd9v4 172.17.0.11
nginx-deploy-7bffc778db-x5ss8 172.17.0.9
test-7f9b8b45c7-mn49h 172.17.0.8
[root@k8s-master 06-Deployment的使用]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP None <none> 81/TCP 82m
#进入测试容器进行解析
[root@k8s-master 06-Deployment的使用]# kubectl exec -it test-7f9b8b45c7-mn49h /bin/sh
/ # nslookup nginx-service.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: nginx-service.default.svc.cluster.local
Address: 172.17.0.9
Name: nginx-service.default.svc.cluster.local
Address: 172.17.0.11
#此时已经可以正常解析到nginx服务了,
ExternalName
ExternalName 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。 对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。
用于集群内部需要访问外部服务:
[root@k8s-master ~]# vim ExternalName.yaml
kind: Service
apiVersion: v1
metadata:
name: my-service2
spec:
type: ExternalName
externalName: my.database.example.com #指定cname
[root@k8s-master ~]# kubectl create -f ExternalName.yaml
service/my-service2 created
[root@k8s-master ~]# kubectl get svc | grep my-service2
my-service2 ExternalName <none> my.database.example.com <none> 43s
当查询主机 my-service.prod.svc.cluster.local (后面服务发现的时候我们会再深入讲解)时,集群的 DNS 服务将返回一个值为my.database.example.com 的 CNAME 记录。 访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发。 如果后续决定要将数据库迁移到 Kubernetes 集群中,可以启动对应的 Pod,增加合适的 Selector 或 Endpoint,修改 Service 的 type,完全不需要修改调用的代码,这样就完全解耦了。