什么是service
pod不是永恒存在的,pod会随时被创建,同时pod也会被随时销毁(运行出错,超过资源限制等原因)。pod不可能被复活,因此pod被销毁和重建之后的IP地址是不同的。
其他依赖该pod的服务不可能去维护这些pod的IP地址信息。这里我们引入的service,service保存了我们如何访问一组pod的方式。这一组pod通过pod selector来确定。无论这些pod怎样创建和销毁,他们的label不变。service可以动态维护这些pod的真实IP(通过endpoint对象)。所以说,service解耦了pod和依赖这些pod的应用。
定义一个service
创建service的描述文件:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
这里我们定义了一个service的描述文件。service的名字为my-service。service指向的pod需要具有app: MyApp标签。可以使用80端口访问每个pod的9376端口。这个service拥有一个cluster IP。这个IP只能在kubernetes集群内部访问。
具有selector的service在创建的时候会自动创建一个同名的endpoint。endpoint维护了具有这些标签的一组pod的真正的IP。如果这些pod的IP发生变化,endpoint中的IP也会随着变化。
没有pod selector的service
如下情况我们需要使用没有pod selector的service:
- 使用集群外部的数据库
- 要创建的service指向位于另一个namespace,或者是另一个kubernetes集群的service
编写描述文件:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
只有这样是不够的,service需要有一个对应的endpoint对象。创建没有selector的service并不会随之创建一个endpoint。这时候我们需要手工创建endpoint。endpoint的描述文件如下:
apiVersion: v1
kind: Endpoints
metadata:
name: my-service
subsets:
- addresses:
- ip: 192.0.2.42
ports:
- port: 9376
service proxy
service使用kube-proxy实现
不使用轮询DNS方式的原因
- 不少DNS的实现不支持TTL(Time To Live)。无法在pod IP发生变化的时候及时改变缓存。
- 一部分应用在DNS查询之后便永久保存,再也不会更新。
- 即便是支持DNS再次解析,我们需要降低DNS的TTL。越低的TTL会导致DNS解析服务的频繁运行,增加系统的负载。
user space proxy 模式
kube-proxy 负责监视service和endpoint的创建和删除。对于每个service,在本地节点上随机开启一个端口(代理端口)。任何连接到该端口的连接会被代理到service后端一组pod中的一个。kube-proxy使用SessionAffinity
配置项来决定使用哪一个后端pod。
user space模式使用iptables规则,把目标为service的cluster IP和端口的请求发送到代理端口。
iptables proxy 模式
iptables使用规则配置,将访问service的cluster IP和port的连接重定向到某一个后端pod。默认来说,kube-proxy随机选择后端pod提供服务。
iptables模式系统开销较小。
如果连接到的第一个被选定的pod没有响应,此连接会失败。这个和userspace模式不同。userspace模式中,如果连接到第一个pod失败,系统会自动重试连接另一个后端pod。
可以使用readness probe
来确保pod是否正常工作。如果某个podreadness probe
失败,这个pod会从endpoint中移除,网络请求不会再转发到这个pod中,直到readness probe
恢复到success状态。
IPVS proxy 模式
ipvs 模式kube-proxy使用netlink
接口创建IPVS规则。IPVS基于钩子函数,使用内核空间的hash表作为底层数据结构。因此IPVS相比iptables具有更低的延迟,吞吐量更高。同时同步代理规则的时候也更快。
IPVS重定向网络通信具有如下策略:
- rr: round-robin 轮询
- lc: least connection (smallest number of open connections) 优先连接数最少
- dh: destination hashing 目标hash
- sh: source hashing 源hash
- sed: shortest expected delay 延迟最小
- nq: never queue 不排队
如果需要相同客户端的请求被重定向到相同的pod,需要配置service.spec.sessionAffinity
为ClientIP
(默认为None
)。我们还可以设置pod和client的最大粘性时间service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
。默认为10800,即3小时。如果3小时内client没有访问pod,那么该client下次访问service的时候将重新指定新的pod,原先的session会失效。
多端口服务
service可以暴露多个port。如果使用多个port,必须为每一个port设置name。描述文件如下所示:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
显式指定service的IP
可以设定spec.clusterIP
配置项。但是IP必须位于API server的service-cluster-ip-range
CIDR范围中。
service发现
环境变量方式
Kubernetes自动为每一个运行的pod中加入各个service环境变量。这些service的环境变量的格式为
- {SVCNAME}_SERVICE_HOST
- {SVCNAME}_SERVICE_PORT
以redis-master服务为例,对应的环境变量为:
REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
注意:如果pod需要使用环境变量的方式访问service,一定要在创建pod之前创建service。否则pod中将没有这个service的环境变量。如果使用DNS方式的话不需要担心这个创建顺序问题。
DNS方式
集群的DNS服务,例如CoreDNS会监听service,创建service对应的DNS入口。
如果在命名空间my-ns定义了一个服务my-service,自动创建的DNS入口为my-service.my-ns。集群内任何pod可以通过此DNS访问这个服务。如果是和该服务位于同一个命名空间的pod,可以省略DNS中的命名空间部分,使用my-service这个DNS入口访问my-service服务。
DNS支持命名端口的方式。如果一个端口命名为http,使用的是TCP协议,可以通过_http._tcp.my-service.my-ns
这条DNS解析到它的IP和端口号。
Headless Service
特点:
- 禁用掉了service的负载均衡功能。
- 没有指定service的clusterIP。
- service不使用kube-proxy代理。
适用场景:
- 需要不经过代理直接访问pod。
- 使用服务发现功能,例如部署zookeeper等。
配置方式:
设置spec.clusterIP
为None
DNS自动配置的方式和是否配置了pod selector有关。
- 配置了pod selector:endpoints会被创建,DNS直接指向后台pod的IP
- 没有配置pod selector:endpoints不会被创建。DNS服务会查找: 1. CNAME记录(ExternalName-type Services)2. 和service同名的endpoints
公开service(允许service在集群外访问)
通过设置服务类型(ServiceType)来实现。
service type有如下类型:
- clusterIP:一个只有在集群内部可访问的虚拟的IP。这个是Service Type的默认值。
- NodePort:使用每个节点的一个静态端口来访问service。集群外可使用<NodeIP>:<NodePort>形式访问。
- LoadBalancer:负载均衡器,使用云提供商的负载均衡器访问服务。
- ExternalName:映射为一个自定义DNS名称,适用于访问外部服务。
除此之外还可以使用Ingress方式,但是Ingress不是ServiceType。
NodePort
NodePort范围从--service-node-port-range
flag中随机选择(默认为30000-32767)。分配的端口号在.spec.ports[*].nodePort
字段中。
可以使用kube-proxy的--nodeport-addresses
配置指定nodePort IP的范围。
可以通过指定nodePort字段的内容,来使得NodePort使用一个固定的IP。
NodePort的使用例子:
apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
nodePort: 30123
selector:
app: kubia
LoadBalancer
自己部署的集群中无LoadBalancer,此部分省略。
ExternalName
映射service到一个DNS名称。
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
External IPs
使用外部IP访问服务。要求可以通过外部IP访问到集群节点。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
externalIPs:
- 80.11.12.10
Service 使用Session Affinity
session affinity,同一个client发出的请求会发送给同一个pod处理。配置方法如下:
apiVersion: v1
kind: Service
spec:
sessionAffinity: ClientIP
...
使用命名端口
可以在Pod中为端口命令,在service中使用。
步骤如下所示:
- 在pod中给端口命名
kind: Pod
spec:
containers:
- name: kubia
ports:
- name: http
containerPort: 8080
- name: https
containerPort: 8443
- 创建service时使用命名端口(targetPort中使用)
apiVersion: v1
kind: Service
spec:
ports:
- name: http
port: 80
targetPort: http
- name: https
port: 443
targetPort: https
Service关联k8s集群外部的服务
把集群外部的某一服务的入口做成k8s的endpoint。创建service的时候如果不指定label selector,endpoints就不会自动创建。
下面举一个例子。我们需要在k8s集群中访问集群外部11.11.11.11和22.22.22.22的80端口这个服务。
这里我们需要手工创建service和endpoint:
apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
ports:
- port: 80
apiVersion: v1
kind: Endpoints
metadata:
name: external-service
subsets:
- addresses:
- ip: 11.11.11.11
- ip: 22.22.22.22
ports:
- port: 80
完成之后,集群内部可以通过访问external-service来实现访问集群外部11.11.11.11
和22.22.22.22
。
减少网络连接的hop数量
可以通过配置service,只把连接重定向到接收这个连接的node上运行的pod。
方法如下:
spec:
externalTrafficPolicy: Local
...
缺点:如果这个node上没有运行该service对应的pod,连接会挂起。