Service是什么

假如有以下这种场景:
image.png
ginx Pod作为客户端访问tomcat Pod中的应用,当tomcat Pod中的应用因工作节点故障或者被手动删除后重启,它的IP会发生变化,这样会导致nginx通过原先IP访问tomcat中的应用会出错。另外,当Pod应用进行规模缩减时,一部分Pod会下线,如果客户端继续访问也会出错。而当Pod应用进行规模扩容时,新增的Pod将无法被有效访问。
除此以外,Pod对象的IP地址都仅在集群内部可达,无法接入外部请求的流量。
Service资源就是用于解决以上问题的。
image.png

原因 实现方案
由于POD的动态随机调度,所以没法通过POD的IP地址对其访问,需要更上一层的结构。 Service通过tag selector实现pod的筛选。Service主要包含两个属性:ports和tag selector
屏幕快照 2020-03-08 下午7.19.21.png
屏幕快照 2020-03-08 下午6.28.16.png

Service类型

ClusterIP(默认),分配一个集群内部可以访问的虚拟IP
NodePort在每个Node上分配一个端口作为外部访问入口LoadBalancer工作在特定的Cloud Provider上,例如Google
Cloud,AWS,OpenStack
ExternalName表示把集群外部的服务引入到集群内部中来,即实现了
集群内部pod和集群外部的服务进行通

Service特性

问题 解决方案
使用endpoint解耦 service没有和pod直接关联,而是在两者中间加了一层endpoint。这样的好处在于endpoint既可以关联集群内Pod;又可以绑定到集群外地址。
当pod地址变化时,会动态更新etcd内的pod地址。
集群内访问service 在集群内部通过环境变量或者DNS地址可以直接获取pod的IP和端口,该IP是clusterIP、端口是serice端口。实现对service的访问。Service转发流量有轮询或者将同一个源IP路由到同一个Pod的策略。
屏幕快照 2020-03-08 下午8.57.32.png
使用nodePort模式从集群外访问Service 通过在每个node上开一个端口,进入该端口的流量被转发到对应的Service。
屏幕快照 2020-03-08 下午9.00.19.png
使用loadBalancer模式从集群外访问Service 借助云服务商的虚拟ip服务来实现
屏幕快照 2020-03-08 下午9.09.21.png

代理模型

1 userspace 代理模型(用户空间模型)

userspace 是Linux操作系统的用户空间,这种模型中,kube-proxy 负责跟踪API server 上的endpoints对象的变动,并根据其进行相关的调整策略。 对于每个service对象,其都会随机打开一个本地端口,任何到达此端口的请求都会被代理到当前service资源的后端各个POD对象上,其默认使用RR调度策略。 其代理的过程是: 请求到达service后,其被转发到内核,经由套接字送往用户空间的kube-proxy,而后经由kube-proxy送回内核空间,并调度至后端POD,其传输方式效率太低。在1.1 版本之前,其是默认的转发策略。

2 iptables代理模型

kube-proxy 负责跟踪API server上 service和 endpoints对象的变动,并据此作出service资源定义的变动,对于每个service,都会创建iptabls规则直接捕获到达clusterIP 和PORT 的流量,并将其重定向到当前的service后端,默认算法是随机调度算法,POD 直接请求service IP 地址通过其直接访问对应的POD服务,在1.2开始成为默认类型,其使用的是iptables的目标地址转换至后端的POD对象,相对而言,其不用在内核和用户空间之间切换,因此更加高效,但其不能再被挑中的POD资源无响应时进行重定向,但用户空间(userspace)模型可以。

3 ipvs模型

此模型跟踪API service上的service和endpoints对象的变动,据此来调用netlink接口创建IPVS规则,并确保API server中的变动保持同步,其流量调度策略在IPVS中实现,其余的在iptables中实现。 ipvs 支持众多调度算法,如rr、lc、dh、sh、sed和nq 等。

路由转发

iptable
🌲K8s Service - 图8

ipvs
**🌲K8s Service - 图9

Service 是 k8s 的核心概念,通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。


Service 的定义

Service YAML格式的定义文件如下:

  1. apiVersion: v1 // Required
  2. kind: Service // Required
  3. metadata: // Required
  4. name: string // Required
  5. namespace: string // Required
  6. labels:
  7. - name: string
  8. annotations:
  9. - name: string
  10. spec: // Required
  11. selectors: [] // Required
  12. type: string // Required
  13. clusterIP: string
  14. sessionAffinity: string
  15. ports:
  16. - name: string
  17. protocol: string
  18. port: int
  19. targetPort: int
  20. nodePort: int
  21. status:
  22. loadBalancer:
  23. ingress:
  24. ip: string
  25. hostname: string

各属性的说明:

🌲K8s Service - 图10
🌲K8s Service - 图11
🌲K8s Service - 图12
🌲K8s Service - 图13

Service参数

port 访问service使用的端口targetPort Pod中容器端口NodePort 通过Node实现外网用户访问k8s集群内service
(30000-32767)

Service的基本用法

一般k8s的Pod都会以RC或者Deployment对外进行发布,并使用TCP/IP+Port的方式使得外部可以访问内部得服务。例如一个提供Web服务RC,由两个tomcat容器组成,每个容器都通过containerPort设置服务得端口号为8080。

  1. # webapp-rc.yaml
  2. apiVersion: v1
  3. kind: ReplicationController
  4. metadata:
  5. name: webapp
  6. spec:
  7. replicas: 2
  8. template:
  9. metadata:
  10. name: webapp
  11. labels:
  12. app: webapp
  13. spec:
  14. containers:
  15. - name: webapp
  16. image: tomcat
  17. ports:
  18. - containerPort: 8080 ## containerPort 要和 镜像暴露得端口要一直。

创建该RC:

  1. kubectl apply -f webapp-rc.yaml

创建成功之后:

  1. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
  2. webapp-nfrzq 1/1 Running 0 96s 10.244.1.44 k8s-node1 <none> <none>
  3. webapp-sxlmk 1/1 Running 0 96s 10.244.0.73 k8s-master <none> <none>

这样就可以使用Pod得IP+Port得方式访问Tomcat服务了:

  1. # curl 10.244.1.44:8080
  2. # curl 10.244.0.73:8080

但是以这种方式访问服务是有问题得,首先我们需要知道每个Pod得地址,如果该Pod故障,就需要切换使用另一个Pod得IP。所以k8s中Service组件就是用于解决这些问题的。

Service的简单使用

创建Service有两种方法,使用命令kubectl export或者根据定义文件创建。

先来使用命令创建Service,例如为之前的RC提供一个Service,使用命令如下:

  1. [root@k8s-master service]# kubectl expose rc webap
  2. [root@k8s-master service]# kubectl get svc webapp
  3. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  4. webapp ClusterIP 10.107.38.14 <none> 8080/TCP 27s

创建Service后就可以直接使用Service的IP:Port的格式访问服务,请求会被自动负载分发到后端的Pod上。

我们还可以使用yaml定义文件创建Service,定义文件如下:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: webapp
  5. spec:
  6. ports:
  7. - port: 8081
  8. targetPort: 8080
  9. selector:
  10. app: webapp

创建该Service:

  1. [root@k8s-master service]# kubectl create -f webapp-svc.yaml
  2. service/webapp created
  3. [root@k8s-master service]# kubectl get svc webapp
  4. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  5. webapp ClusterIP 10.109.63.40 <none> 8081/TCP 7s

目前k8s提供了两种负载分发策略:

  • RoundRobin:轮询模式,即轮询将请求转发到后端的各个Pod上。
  • SessionAffinity:基于客户端IP地址进行会话保持的模式,即第1次将某个客户端发起的请求转发到后端的某个Pod上,之后从相同的客户端发起的请求都将被转发到后端相同的Pod上。

默认情况下,k8s采用RoundRobin模式,通过设置参数service.spec.sessionAffinity=ClientIP来启用SessionAffinity策略。

多端口Service

Service 支持设置多个端口对应到多个应用服务,配置文件如下:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: webapp
  5. spec:
  6. ports:
  7. - port: 8080
  8. targetPort: 8080
  9. name: web
  10. - port: 8005
  11. targetPort: 8005
  12. name: management
  13. selector:
  14. app: webapp

也可以指定协议:

  1. ...
  2. spec:
  3. ports:
  4. - port: 53
  5. procotol: UDP
  6. name: dns
  7. - port: 53
  8. procotol: TCP
  9. name: dns-tcp
  10. ...

外部服务service

外部服务Service可以将k8s集群外的应用纳入到集群服务,可以通过创建一个无Label Selector的Service实现:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: my-service
  5. spec:
  6. ports:
  7. - port: 8080
  8. procotol: TCP
  9. targetPort: 80

同时还需要创建一个和该Service同名的Endpoint资源,用于指向实际的后端访问地址,配置文件如下:

  1. apiVersion: v1
  2. kind: Endpoint
  3. metadata:
  4. name: my-service
  5. subsets:
  6. - address:
  7. - IP: 1.2.3.4
  8. ports:
  9. - port: 80

Headless Service

k8s支持用户自定义负载均衡的策略,k8s提供了Headless Service来实现这种功能,即不为Service设置ClusterIP(入口IP地址),仅通过Label Selector将后端的Pod列表返回给调用的客户端。例如:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: nginx
  5. spec:
  6. ports:
  7. - port: 80
  8. clusterIP: None
  9. selector:
  10. app: nginx

这样,Service就不再具有一个特定的clusterIP,对其访问将获得包含Label “app=nginx”的全部Pod列表,然后客户端程序自行决定如何处理这个Pod列表。StatefulSet就是使用Headless Service为客户端返回多个服务地址的,而且Headless Service十分适用于“去中心化”类的应用集群。

从集群外部访问Pod或者Service

由于Pod和Service都是k8s集群范围内的虚拟概念,所以集群外的客户端无法通过Pod的IP地址或者Service的虚拟IP地址访问。因此可以将Pod或者Service的端口号映射到宿主机,让容器外部的客户端也可以访问。

将容器应用的端口号映射到物理机

1.设置容器级别的hostPort

  1. # pod-hostport.yaml
  2. apiVersion: v1
  3. kind: Pod
  4. metadata:
  5. name: webapp
  6. labels:
  7. app: webapp
  8. spec:
  9. containers:
  10. - name: webapp
  11. image: tomcat
  12. ports:
  13. - containerPort: 8080
  14. hostPort: 9000

创建Pod

  1. kubectl create -f pod-hostport.yaml

创建完成后就可以访问k8s node的IP:Port。

2.设置Pod级别的hostNetwork=true

该Pod中所有容器的端口号都将被直接映射到物理机上。在设置hostNetwork=true时需要注意,在容器的ports定义部分如果不指定hostPort,则默认hostPort等于containerPort,如果指定了hostPort,则hostPort必须等于containerPort的值:

  1. ## pod-hostnetwork.yaml
  2. apiVersion: v1
  3. kind: Pod
  4. metadata:
  5. name: webapp
  6. labels:
  7. app: webapp
  8. spec:
  9. hostNetwork: true
  10. containers:
  11. - name: webapp
  12. image: tomcat
  13. imagePullPolicy: Never
  14. ports:
  15. - containerPort: 8080

将Service的端口号映射到物理机

1.设置nodePort映射到物理机

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: webapp
  5. spec:
  6. type: NodePort
  7. ports:
  8. - port: 8080
  9. targetPort: 8080
  10. nodePort: 31080 # 默认有效值 30000 - 32767
  11. selector:
  12. app: webapp

对该Service的访问也将被负载分发到后端的多个Pod上。

2.设置LoadBalancer映射到云平台

这种用法仅用于在公有云服务提供商的云平台上设置Service的场景。 在下面的例子中, status.loadBalancer.ingress.ip设置的146.148.47.155为云服务商提供的负载均衡器的IP地址。 对该Service的访问请求将会通过LoadBalancer转发到后端Pod上, 负载分发的实现方式则依赖于云服务商提供的LoadBalancer的实现机制:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: my-service
  5. spec:
  6. selector:
  7. app: MyApp
  8. ports:
  9. - protocol: TCP
  10. port: 80
  11. targetPort: 9376
  12. nodePort: 30061
  13. clusterIP: 10.0.171.239
  14. loadBalancerIP: 78.11.24.19
  15. type: LoadBalancer
  16. status:
  17. loadBalancer:
  18. ingress:
  19. - ip: 146.148.47.155