一、概述
1.Service介绍
Service主要用于提供网络服务,通过Service的定义,能够为客户端应用提供稳定的访问地址(域名或IP地址)和负载均衡功能,以及屏蔽后端Endpoint的变化,是kubernetes实现微服务的核心资源。
Service通过命名空间、标签选择器、后端pod端口绑定指定pod。pod中的容器经常在不停地销毁和重建,因此pod的IP会不停的改变,这时候客户端就没法访问到pod了,现在有了service作为客户端和pod的中间层,它在这里抽象出一个虚拟IP,然后集群内部都可以通过这个虚拟IP访问到具体的pod。
2.Service类型
k8s提供了多种机制将Service暴露出去,供集群外部的客户端访问。这可以通过service资源对象的类型字段“type”进行设置。
目前service的类型如下:
- ClusterIP:kubernetes默认会自动设置Service的VIP地址,仅可被集群内部的客户端应用访问。用户也可手动指定一个ClusterIP地址,不过需要确保该IP在kubernetes集群设置的ClusterIP地址范围内(通过kube-apiserver服务的启动参数--service-cluster-ip-range设置),并且没有被其他service使用。
- NodePort:将Service的端口号映射到每个Node的一个端口号上,这样集群中的任意Node都可以作为Service的访问入口地址,即NodeIP:NodePort
- LoadBalancer:将service映射到一个已存在的负载均衡器的IP地址上,通常在公有云环境中使用。
- ExternalName:将service映射为一个外部域名地址,通过externalName字段进行设置。
3.kube-proxy介绍
3.1概念
在k8s中,service的集群IP能够实现数据报文请求的转发,需要在node节点上部署的一个组件kube-proxy,具体来说kube-proxy实现的主要有几点:
- 实时监控API,获取service和pod的信息,来保持pod和虚拟IP的映射关系
- 维护本地Netfilter、iptables、IPVS等内核组件,实现数据报文的转发规则
- 实现每个node节点上虚拟IP的发布和路由维护
- 构建路由信息,通过转发规则转发报文到虚拟IP对应的pod
3.2kube-proxy代理模式
userspace:用户空间模式,由kube-proxy完成代理的实现,效率最低,不在推荐使用。
iptables:kube-proxy通过设置Linux Kernel的iptables规则,实现从Service到后端Endpoint列表的负载分发规则,效率很高。iptables是kube-proxy默认的代理模式。
ipvs:在kubernetes1.11版本中达到Stable阶段,kube-proxy通过设置Linux Kernel的netlink接口设置IPVS规则,转发效率和支持的吞吐率都是最高的。ipvs模式要求Linux Kernel启用IPVS模块,如果未启用ipvs内核模块,kube-proxy会自动切换成iptables。同时,ipvs模式支持更多的负载均衡策略,如下:
- rr:round-robin,轮询
- lc:least connection,最小连接数
- dh:destination hashing,目的地址哈希
- sh:source hashing,源地址哈希。
- sed:shortest expected delay,最短期望延时
- nq:never queue,永不排队
kernelspace:windows server上的代理模式
ipvs sample:
[root@k8s-master ~]# lsmod | grep vs
ip_vs_sh 12688 0
ip_vs_wrr 12697 0
ip_vs_rr 12600 45
ip_vs 145458 51 ip_vs_rr,ip_vs_sh,ip_vs_wrr
nf_conntrack 143360 9 ip_vs,nf_nat,nf_nat_ipv4,nf_nat_ipv6,xt_conntrack,nf_nat_masquerade_ipv4,nf_conntrack_netlink,nf_conntrack_ipv4,nf_conntrack_ipv6
libcrc32c 12644 4 xfs,ip_vs,nf_nat,nf_conntrack
[root@k8s-master ~]# kubectl edit cm kube-proxy -n kube-system
。。。
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
strictARP: false
syncPeriod: 0s
tcpFinTimeout: 0s
tcpTimeout: 0s
udpTimeout: 0s
kind: KubeProxyConfiguration
metricsBindAddress: ""
mode: "ipvs" // kube-proxy默认是iptables模式,这里为空,修改为ipvs
nodePortAddresses: null
oomScoreAdj: null
portRange: ""
showHiddenMetricsForVersion: ""
udpIdleTimeout: 0s
。。。
[root@k8s-master ~]# kubectl delete pod -l k8s-app=kube-proxy -n kube-system
[root@k8s-master ~]# ipvsadm -Ln // 如果服务器没有ipvsadm命令,那么需要安装
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 172.17.0.1:30718 rr
-> 10.244.58.200:80 Masq 1 0 0
-> 10.244.58.201:80 Masq 1 0 0
TCP 172.17.0.1:31427 rr
-> 10.244.58.200:443 Masq 1 0 0
-> 10.244.58.201:443 Masq 1 0 0
TCP 10.5.0.211:30718 rr
-> 10.244.58.200:80 Masq 1 0 0
-> 10.244.58.201:80 Masq 1 0 0
二、Service yaml 定义
apiVersion: v1 // required
kind: Service // required
metadata: // required
name: string // required
namespace: string // required
labels:
- name: string
annotations: // 注解
- name: string
spec: // required
selector: [] // required 标签选择器
type: string // required 类型
clusterIP: string // clusterIP地址,如果不指定会自动生成
sessionAffinity: string // 会话保持,可选值:ClientIP,默认为None
ports: // 端口列表
- name: string // 端口名称
protocol: TCP // 协议,支持TCP和UDP,默认值为TCP
port: int // service的端口
targetPort: int // Pod的端口
nodePort: int // 当type为NodePort时,映射到宿主机的端口
status: // 当type为loadbalance时,外部负载均衡器的地址,用于公有云
loadBalancer:
ingress:
ip: string // 外部负载均衡器IP
hostname: string // 外部负载均衡器主机名
三、演示(base on ipvs mode)
1.无service演示
[root@k8s-master test]# cat deploy-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app
namespace: test
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx-app
spec:
containers:
- name: nginx
image: core.harbor.domain/test/nginx-1.16.1:v4
ports:
- containerPort: 80
imagePullSecrets:
- name: harbor-secret
[root@k8s-master test]# kubectl apply -f deploy-nginx.yaml
deployment.apps/nginx-app created
[root@k8s-master nginx-image]# kubectl get pod -n test -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES <none>
nginx-app-64c47d8f97-nzw9w 1/1 Running 0 5m48s 10.244.58.223 k8s-node02 <none> <none>
nginx-app-64c47d8f97-qs4gd 1/1 Running 0 5m46s 10.244.85.205 k8s-node01 <none> <none>
[root@k8s-master nginx-image]# kubectl delete pod nginx-app-64c47d8f97-qs4gd -n test
pod "nginx-app-64c47d8f97-qs4gd" deleted
[root@k8s-master nginx-image]# kubectl get pod -n test -owide // Pod IP变了
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-app-64c47d8f97-992xm 1/1 Running 0 44s 10.244.58.224 k8s-node02 <none> <none>
nginx-app-64c47d8f97-nzw9w 1/1 Running 0 9m51s 10.244.58.223 k8s-node02 <none> <none>
[root@k8s-master nginx-image]# curl 10.244.58.224
10.244.58.224
[root@k8s-master nginx-image]# curl 10.244.58.223
10.244.58.223
需要访问每个pod,且pod重启之后,ip就会变化,客户端就会丢失ip,无法再获取请求内容
2.ClusterIP类型
创建svc
[root@k8s-master test]# cat deploy-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app
namespace: test
spec:
replicas: 2
selector:
matchLabels:
app: nginx-app
template:
metadata:
labels:
app: nginx-app
spec:
containers:
- name: nginx
image: core.harbor.domain/test/nginx-1.16.1:v4
ports:
- containerPort: 80
imagePullSecrets:
- name: harbor-secret
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: test
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-app
type: ClusterIP
[root@k8s-master test]# kubectl apply -f deploy-nginx.yaml
deployment.apps/nginx-app created
service/nginx-svc created
查看Pod和svc
[root@k8s-master test]# kubectl get pod -n test
NAME READY STATUS RESTARTS AGE
nginx-app-68c44578b7-2w8j7 1/1 Running 0 33s
nginx-app-68c44578b7-4cxwr 1/1 Running 0 33s
nginx-app-68c44578b7-5zv57 1/1 Running 0 33s
[root@k8s-master test]# kubectl get svc -n test -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
nginx-svc ClusterIP 10.102.127.32 <none> 80/TCP 20d app=nginx-app
查看ipvs规则
[root@k8s-master nginx-image]# ipvsadm -Ln | grep -A 2 10.102.127.32
TCP 10.102.127.32:80 rr
-> 10.244.58.223:80 Masq 1 0 0
-> 10.244.58.224:80 Masq 1 0 0
ipvsadm 是 ipvs规则管理命令
10.102.127.32:80 rr SvcIP:Port rr 负载均衡策略:轮询
10.244.58.223:80 PodIP:Port
10.244.58.224:80 PodIP:Port
访问测试
[root@k8s-master nginx-image]# curl 10.102.127.32
10.244.58.224
[root@k8s-master nginx-image]# curl 10.102.127.32
10.244.58.223
[root@k8s-master nginx-image]# curl 10.102.127.32
10.244.58.224
[root@k8s-master nginx-image]# curl 10.102.127.32
10.244.58.223
[root@k8s-master nginx-image]# curl 10.102.127.32
10.244.58.224
[root@k8s-master nginx-image]# curl 10.102.127.32
10.244.58.223
[root@k8s-master nginx-image]# curl 10.102.127.32
10.244.58.224
[root@k8s-master nginx-image]# curl 10.102.127.32
10.244.58.223
// 请求由svc均匀分发给后端pod
无法通过nodeIP:port访问
[root@k8s-master nginx-image]# curl k8s-node02
curl: (7) Failed connect to k8s-node02:80; Connection refused
3.NodePort类型
修改svc类型为nodeport
[root@k8s-master test]# vim deploy-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app
namespace: test
spec:
replicas: 2
selector:
matchLabels:
app: nginx-app
template:
metadata:
labels:
app: nginx-app
spec:
containers:
- name: nginx
image: core.harbor.domain/test/nginx-1.16.1:v2
ports:
- containerPort: 80
imagePullSecrets:
- name: harbor-secret
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: test
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-app
type: NodePort // 修改为nodeport
[root@k8s-master test]# kubectl apply -f deploy-nginx.yaml
deployment.apps/nginx-app unchanged
service/nginx-svc configured
查看svc信息
[root@k8s-master test]# kubectl get svc -n test -o wide // 将80端口映射到主机32752端口
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
nginx-svc NodePort 10.102.127.32 <none> 80:32752/TCP 20d app=nginx-app
使用nodeIP:Port访问
[root@k8s-master test]# curl k8s-node02:32752 // 访问node02
10.244.85.214
[root@k8s-master test]# curl k8s-node02:32752
10.244.58.229
[root@k8s-master test]# curl k8s-node02:32752
10.244.85.214
[root@k8s-master test]# curl k8s-node01:32752 // 访问node01
10.244.58.229
[root@k8s-master test]# curl k8s-node01:32752
10.244.85.214
[root@k8s-master test]# curl k8s-node01:32752
10.244.58.229
Note:nodeport类型会在每一个node上面都做映射,所以生产环境必须规划nodeport的使用
通过公网访问(需要做策略)
[root@k8s-master test]# curl http://106.14.27.13:32752/
10.244.58.229
[root@k8s-master test]# curl http://106.14.27.13:32752/
10.244.85.214
[root@k8s-master test]# curl http://106.14.27.13:32752/
10.244.58.229
[root@k8s-master test]# curl http://106.14.27.13:32752/
10.244.85.214
[root@k8s-master test]# curl http://106.14.27.13:32752/
4.Loadbalance类型
没有公有云环境,放一张图解释一下loadbalance
nodeport和loadbalance是用一种方式,但loadbalance多了LB这一层,在服务创建成功之后,云服务商会在Service的定义中补充LoadBalance的IP地址:
status:
loadBanlancer:
ingress:
- ip: 192.0.2.127
5.externalName
创建externalName svc
[root@k8s-master test]# vim svc-external.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-external
namespace: test
spec:
type: ExternalName
externalName: nginx-svc.test.svc.cluster.local
[root@k8s-master test]# kubectl apply -f svc-external.yaml
service/nginx-external created
查看svc
[root@k8s-master ~]# kubectl get svc -n test
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-external ExternalName <none> nginx-svc.test.svc.cluster.local <none> 63m
nginx-svc NodePort 10.102.127.32 <none> 80:32752/TCP 20d
启动测试pod
[root@k8s-master ~]# kubectl run -i --tty --image core.harbor.domain/library/busybox dns-test --restart=Never --rm -n test
If you don't see a command prompt, try pressing enter.
/ #
/ #
/ # nslookup nginx-svc.test.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: nginx-svc.test.svc.cluster.local
Address: 10.102.127.32
/ # nslookup nginx-external.test.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
nginx-external.test.svc.cluster.local canonical name = nginx-svc.test.svc.cluster.local
Name: nginx-svc.test.svc.cluster.local
Address: 10.102.127.32
/ #
scenario sample
应用A和应用B通过nginx-external service 访问 nginx-svc service,当nginx-svc修改为nginx-svc1,那么只需要修改nginx-external service的externalName字段,减少了因为外部域名变动而引起的变更操作
四、高级用法
1.将外部服务定义为service
普通的Service通过Label Selector对后端Endpoint列表进行一次抽象,如果后端Endpoint不是Pod副本提供的,则Service还可以抽象定义任意其它服务,将一个Kubernetes集群外部的已知服务定义为Kubernetes内的一个Service,提供集群内的其他应用访问,常见的应用常见包括:
- 已部署的集群外服务,例如数据库服务、缓存服务等
- 其他Kubernetes集群的某个服务
- 迁移过程中对某个服务进行Kubernetes内的服务名访问机制的验证
对与这种场景,用户创建Service资源对象时不设置Label Selector(后端Pod也不存在),同时再定义一个Service关联的Endpoint资源对象,在Endpoint中设置外部服务的IP地址和端口号,例如:
############
apiVersion: v1
kind: Service
metadata:
name: myservice
sepc:
ports:
- protocol: TCP
port: 80
targetPort: 80
################
apiVersion: v1
kind: Endpoints
metadata:
name: myservice
subsets:
- addresses:
- IP: 1.2.3.4
ports:
- port: 80
2.Headless service
在某些应用场景中,客户端应用不需要通过k8s内置service实现的负载均衡功能,或者需要自行完成对服务后端个实例的服务发现机制,或者需要自行实现负载均衡功能,此时可以通过创建一种特殊的名为“headless”的服务来实现。
Headless Service的概念是这种服务没有入口访问地址(无ClusterIP地址),kube-proxy不会为其创建负载转发规则,而服务名(DNS域名)的解析机制取决于该Headless Service是否设置了label selector。
[root@k8s-master test]# vim headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: headless-svc
namespace: test
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-app
type: ClusterIP
clusterIP: None // 这里为None,表示不需要clusterIP
[root@k8s-master test]# kubectl apply -f headless-svc.yaml
service/headless-svc created
[root@k8s-master test]# kubectl get svc -n test
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
headless-svc ClusterIP None <none> 80/TCP 19s
nginx-external ExternalName <none> nginx-svc.test.svc.cluster.local <none> 123m
nginx-svc NodePort 10.102.127.32 <none> 80:32752/TCP 20d
查看详情
[root@k8s-master test]# kubectl describe svc headless-svc -n test
Name: headless-svc
Namespace: test
Labels: <none>
Annotations: <none>
Selector: app=nginx-app
Type: ClusterIP
IP Families: <none>
IP: None
IPs: None
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.58.229:80,10.244.85.214:80 // 后端pod的IP和端口
Session Affinity: None
Events: <none>
nslookup测试
[root@k8s-master test]# kubectl run -i --tty --image core.harbor.domain/library/busybox dns-test --restart=Never --rm -n test
If you don't see a command prompt, try pressing enter.
/ #
/ #
/ # nslookup headless-svc.test.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: headless-svc.test.svc.cluster.local
Address: 10.244.58.229
Name: headless-svc.test.svc.cluster.local
Address: 10.244.85.214
当客户端通过DNS服务名“headless-svc”和服务端口号访问该Headless服务时,将得到Service后端Endpoint列表“10.244.58.229,10.244.85.214”,然后由客户端程序自行决定如何操作,例如通过轮询机制访问各个Endpoint