一、Service(svc)
1、理论知识
- 因为Pod是有生命周期的,所以为了能够给对应的客户端,提供一个固定的访问端点,需要在客户端与服务端(服务Pod资源)之间加一个固定的中间层(service)。service的真正工作还要严重依赖于,在kubermetes集群上部署的一个附件(kubernetes的dns服务),不同版本的kubernetes上的 附件不同,新版本中叫做coredns,kubernetes的1.11版本之前,叫做kube-dns。
- service的名称解析是强依赖于dns附件的,所以,部署完kubernetes集群之后,需要部署一个coredns或者kube-dns。
kubernetes集群要想,能够向客户端提供网络功能,需要依赖于第三方的方案(这种第三方方案可通过cni——container network interface容器网络插件标准的接口,接入任何遵循这种插件标准的第三方方案。)第三方方案有flannel、calico等等。 - kubernetes集群中有三类网络:【1】、node网络;【2】、pod网络(node网络和pod网络是真实的网络,是实实在在配置在某个设备上的,这个设备可能是硬件的,也可能是软件模拟的。);【3】、集群地址(cluster network),也称为service network(这种网络是虚拟的网络,Virtual IP,因为这些IP没有实实在在配置在哪个设备上,它仅是出现在service的规则当中)。
- 在一定程度上讲,组件kube-proxy始终监视着在Apiserver组件中的有关service的变动信息。一旦有service资源的内容发生变动,包括创建,kube-proxy都要把它转换为当前节点上的,能够实现service资源调度的(包括将用户请求调度到特定pod资源)之上的规则,这种规则可能是iptables,也可能是ipvs,取决于service的实现方式。
- 在kubernetes上,service的实现方式有三种模式,【1】、UserSapce(用户空间,这种方式效率很低);【2】、iptables;【3】、ipvs。kubernetes1.1之前的版本用的是userspace,1.1至1.10的版本用的是iptables;1.11后的版本用的是ipvs。如果ipvs没有被激活,则自动降级为iptables。
UserSpace模式
iptables工作模式
ipvs工作模式
2、实践
(1)有关apiserver的IP地址
[root@master manifests]# kubectl get svc #可以看到安装好集群之后,集群内有一个kubernetes服务,集群中的组件就是通过这个服务与apiserver组件进行通信的。既可以认为i这个IP地址是apiserver的Ip地址,用于集群内部的通信
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11d
[root@master manifests]# kubectl get pods -o wide -n kube-system #下面这个apiserver的Ip地址是用于集群外部通信的
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-546565776c-448jk 1/1 Running 16 11d 10.244.0.36 master <none> <none>
coredns-546565776c-k7whb 1/1 Running 16 11d 10.244.0.35 master <none> <none>
etcd-master 1/1 Running 18 11d 10.0.2.2 master <none> <none>
kube-apiserver-master 1/1 Running 18 11d 10.0.2.2 master <none> <none>
kube-controller-manager-master 1/1 Running 33 11d 10.0.2.2 master <none> <none>
kube-flannel-ds-amd64-2zd7f 1/1 Running 9 10d 10.0.2.4 node02 <none> <none>
kube-flannel-ds-amd64-6b8pq 1/1 Running 7 10d 10.0.2.3 node01 <none> <none>
kube-flannel-ds-amd64-hs2ck 1/1 Running 25 11d 10.0.2.2 master <none> <none>
kube-proxy-n7cmq 1/1 Running 18 11d 10.0.2.2 master <none> <none>
kube-proxy-qfc2v 1/1 Running 8 10d 10.0.2.3 node01 <none> <none>
kube-proxy-vpflj 1/1 Running 9 10d 10.0.2.4 node02 <none> <none>
kube-scheduler-master 1/1 Running 35 11d 10.0.2.2 master <none> <none>
(2)service的四种类型
[root@master manifests]# kubectl explain svc.spec
clusterIP <string>
ports <[]Object>
selector <map[string]string>
type <string> 默认为ClusterIP
#type有四种模式:ExternalName, ClusterIP, NodePort, LoadBalancer。
【1】、ExternalName:把集群外部的服务引入到集群内部。
【2】、ClusterIP:仅用于集群内部进行访问的Ip地址
【3】、NodePort:可以接入集群外部的流量
【4】、LoadBalancer:把Kubernetes集群部署在虚拟机上,而虚拟机工作在云环境中,而云环境支持LBAAS(负载均衡及服务的一键调用)。
(3)ClusterIP类型的service
[root@master manifests]# vim deploy-redis.yaml #用deployment启动一个提供redis服务的Pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: redis
role: logstor
template:
metadata:
labels:
app: redis
role: logstor
spec:
containers:
- name: redis
image: redis:4.0-alpine
ports:
- name: redis
containerPort: 6379
[root@master manifests]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
redis-588694bf8c-5mdhr 1/1 Running 0 6s 10.244.1.69 node01 <none> <none>
[root@master manifests]# vim redis-svc.yaml #为提供redis服务的Pod创建一个service
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: default
spec:
clusterIP: 10.97.97.97
type: ClusterIP
ports:
- port: 6379 #service端口
targetPort: 6379 #pod端口
selector: #要注意这里的标签必须是提供service的pod的标签
app: redis
role: logstor
[root@master manifests]# kubectl apply -f redis-svc.yaml
[root@master manifests]# kubectl get svc -o wide #可以看到IP和端口都是自己指定的
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11d <none>
redis ClusterIP 10.97.97.97 <none> 6379/TCP 9s app=redis,role=logstor
[root@master manifests]# kubectl describe svc redis #可以看到这个service匹配到的endpoints为10.244.1.69:6379(其实endpoint就是Pod的IP地址+Pod端口)。
Name: redis
Namespace: default
Labels: <none>
Annotations: Selector: app=redis,role=logstor
Type: ClusterIP
IP: 10.97.97.97
Port: <unset> 6379/TCP
TargetPort: 6379/TCP
Endpoints: 10.244.1.69:6379
Session Affinity: None
Events: <none>
- service创建完之后,都会在coredns/kube-dns中自动动态添加记录(不止一个记录。资源记录的格式:服务名.命名空间的名字.资源域名后缀,即SVC_NAME.NS_NAME.DOMAIN.LTD.。service的资源域名后缀默认为:svc.cluster.local.。eg:redis这个服务的域名为:redis.default.svc.cluster.local.),所以,只要Kubernetes集群上的coredns/kube-dns是存在的,就可以直接解析service的服务名。
[root@master manifests]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 11d
[root@master manifests]# dig -t A myapp.default.svc.cluster.local. @10.96.0.10
; <<>> DiG 9.11.4-P2-RedHat-9.11.4-16.P2.el7_8.6 <<>> -t A myapp.default.svc.cluster.local. @10.96.0.10
;; 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: 25886
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;myapp.default.svc.cluster.local. IN A
;; ANSWER SECTION:
myapp.default.svc.cluster.local. 30 IN A 10.99.99.99 #可以看到:解析到的正是myapp这个Service的IP地址
;; Query time: 6 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Fri Sep 11 07:52:55 EDT 2020
;; MSG SIZE rcvd: 107
[root@master manifests]# kubectl get svc myapp -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
myapp NodePort 10.99.99.99 <none> 80:30080/TCP 116m app=myapp,release=canary
(4)service是如何到达Pod的?
service——EndPoint(Pod的IP地址+Pod的端口)——Pod
(5)NodePort类型的service
[root@master manifests]# cat deploy-demo.yaml #通过创建deployment来创建pod,以此来提供myapp服务
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: canary
template:
metadata:
labels:
app: myapp
release: canary
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v3
ports:
- name: http
containerPort: 80
[root@master manifests]# kubectl apply -f deploy-demo.yaml
[root@master manifests]# vim myapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
spec:
clusterIP: 10.99.99.99
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080 #nodePort可以不指定,会自动分配(30000-32767)。即使类型是NodePort。如果指定了nodePort,则要确保Node节点(Node01和Node02都要确保)的该节点没有被占用。
selector:
app: myapp
release: canary
[root@master manifests]# kubectl apply -f myapp-svc.yaml
[root@master manifests]# kubectl get svc -o wide #可以看到nginx服务中的80(service的端口)映射到了node节点上的30080(node的端口)
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11d <none>
myapp NodePort 10.99.99.99 <none> 80:30080/TCP 2m38s app=myapp,release=canary
redis ClusterIP 10.97.97.97 <none> 6379/TCP 128m app=redis,role=logstor
root@master manifests]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-deploy-6b9865d969-4rhd8 1/1 Running 0 3m21s 10.244.1.71 node01 <none> <none>
myapp-deploy-6b9865d969-77vwb 1/1 Running 0 3m21s 10.244.2.79 node02 <none> <none>
myapp-deploy-6b9865d969-dr9js 1/1 Running 0 3m21s 10.244.2.78 node02 <none> <none>
redis-588694bf8c-5mdhr 1/1 Running 1 124m 10.244.1.70 node01 <none> <none>
- 这样,就可以在集群外部的主机上通过命令:"curl http://node01的IP地址或是node02的Ip地址:30080"进行访问Pod了。其过程是:node01的IP地址或node02的IP地址:30080——service的IP地址:80(这里的80为service的端口)——service所管理的Pod的IP地址:80(这里的80为Pod的端口)。下面进行演示:可以在master、node01、node02中的任意一台主机进行测试(因为master、node01、node02相当于集群外部)
[root@master manifests]# while true; do curl http://node01:30080/hostname.html; sleep 1; done
myapp-deploy-6b9865d969-4rhd8
myapp-deploy-6b9865d969-77vwb
myapp-deploy-6b9865d969-4rhd8
myapp-deploy-6b9865d969-77vwb
myapp-deploy-6b9865d969-77vwb
myapp-deploy-6b9865d969-dr9js
#从上面的结果可以看到:自动实现了多个pod之间的轮询。
(6)NodePort类型的service——设置轮询的方式
[root@master manifests]# kubectl explain svc.spec
sessionAffinity <string> #有两种方法:ClientIP(来自同一个客户端IP的请求
调度到同一个Endpoint)和None(不做后端映射,随机调取,基于iptables的规则进行调度)。默认是None
[root@master manifests]# kubectl patch svc myapp -p '{"spec":{"sessionAffinity":"ClientIP"}}' #修改service的调度方式
service/myapp patched
[root@master manifests]# kubectl describe svc myapp
Name: myapp
Namespace: default
Labels: <none>
Annotations: Selector: app=myapp,release=canary
Type: NodePort
IP: 10.99.99.99
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 30080/TCP
Endpoints: 10.244.1.71:80,10.244.2.78:80,10.244.2.79:80
Session Affinity: ClientIP #可以看到这里的调度方式为ClientIP。
External Traffic Policy: Cluster
Events: <none>
- 测试:
[root@master manifests]# while true; do curl http://node01:30080/hostname.html; sleep 1; done #可以看到来自同一客户端的请求,访问到的Pod是同一个
myapp-deploy-6b9865d969-4rhd8
myapp-deploy-6b9865d969-4rhd8
myapp-deploy-6b9865d969-4rhd8
myapp-deploy-6b9865d969-4rhd8
myapp-deploy-6b9865d969-4rhd8
(7)headless service(无头service)——在StatefulSet中可以用到
[root@master manifests]# vim myapp-svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
namespace: default
spec:
clusterIP: None #指定clusterIP为None即可构造headless service
ports:
- port: 80
targetPort: 80
selector:
app: myapp
release: canary
[root@master manifests]# kubectl apply -f myapp-svc-headless.yaml
[root@master manifests]# kubectl get svc -o wide #可以看到myapp-svc这个服务的ClusterIP是None
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11d <none>
myapp NodePort 10.99.99.99 <none> 80:30080/TCP 86m app=myapp,release=canary
myapp-svc ClusterIP None <none> 80/TCP 7s app=myapp,release=canary
redis ClusterIP 10.97.97.97 <none> 6379/TCP 3h32m app=redis,role=logstor
[root@master manifests]# kubectl get svc -n kube-system #可以看到kube-system这个命名空间中有kube-dns这个dns服务。
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 11d
[root@master manifests]# dig -t A myapp-svc.default.svc.cluster.local. @10.96.0.10 #使用10.96.0.10这个Ip地址对应的kube-dns来解析myapp-svc.default.svc.cluster.local.这个域名
; <<>> DiG 9.11.4-P2-RedHat-9.11.4-16.P2.el7_8.6 <<>> -t A myapp-svc.default.svc.cluster.local. @10.96.0.10
;; 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: 61246
;; flags: qr aa 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-svc.default.svc.cluster.local. IN A
;; ANSWER SECTION:
myapp-svc.default.svc.cluster.local. 30 IN A 10.244.1.71 #可以看到解析到的IP地址正是Service所管理的后端Pod的IP地址
myapp-svc.default.svc.cluster.local. 30 IN A 10.244.2.78
myapp-svc.default.svc.cluster.local. 30 IN A 10.244.2.79
;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Fri Sep 11 07:47:12 EDT 2020
;; MSG SIZE rcvd: 217
[root@master manifests]# kubectl get pods -o wide --show-labels -l app=myapp
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
myapp-deploy-6b9865d969-4rhd8 1/1 Running 0 113m 10.244.1.71 node01 <none> <none> app=myapp,pod-template-hash=6b9865d969,release=canary
myapp-deploy-6b9865d969-77vwb 1/1 Running 0 113m 10.244.2.79 node02 <none> <none> app=myapp,pod-template-hash=6b9865d969,release=canary
myapp-deploy-6b9865d969-dr9js 1/1 Running 0 113m 10.244.2.78 node02 <none> <none> app=myapp,pod-template-hash=6b9865d969,release=canary
(8)客户端Pod是如何访问Service所管理的Pod的
情况1:Service是有ClusterIP的
- 【1】、客户端Pod访问Service的域名
- 【2】、coredns将Service的域名解析为ClusterIP(ClusterIP只有1个)
- 【3】、Service将请求调度到多个Endpoints
情况2:Service没有ClusterIP(这种Service称为headless Service)
- 【1】、客户端Pod访问Service的域名
- 【2】、coredns将Service的域名解析为多个Endpoints的IP地址(即多个后端Pod的地址)