前言
在实际业务中,因业务压力问题,经常会有多个后端服务副本,它们共同承担请求.我们使用云服务的时候,可以购买阿里云的slb或者aws的elb/alb等负载均衡器向这些后端副本分发流量. 并通过这些负载均衡向公/内网发布后端程序。
k8s设计了一个service对象来实现这一目的.
k8sIP
在提Service对象之前,需要先知道k8s中存在的三种IP.即 NodeIP, ClusterIP, PodIP
NodeIP 就是物理节点ip, 这个没得说, 玩家自己定义
ClusterIP 是k8s的一个虚拟ip, 本身没有任何实体, 也就是VIP
PodIP 是容器共享的一个网络命名空间对应的ip, 一个pod里的容器共用
与kube-proxy
kube-proxy 是实现 svc 的重要组件,kube-proxy 通过代理方式转发流量到Pod。其代理方式分三种:namespace 方式,iptables 方式,ipvs方式。基本用的都是 ipvs 方式。
ipvs方式的前置要求是 ipvs 组件,如果 kube-proxy 没有检测到 ipvs 组件则会回退到 iptables 方式,如果依然不合适,则继续回退到 namespace。
类型
https://kubernetes.io/zh/docs/concepts/services-networking/service/
https://kubernetes.io/docs/reference/kubernetes-api/service-resources/service-v1/
Service对象通过spec.type来设定类型, 共计4个类型: ClusterIP, NodePort, LoadBalancer, ExternalName. 这四个类型可以分为两类
ClusterIP
ClusterIP(默认类型): 反向代理集群内的pod. 供集群内其它服务访问. 流量过程是: 集群内部其它服务=>svc_name=>ClusterIP:端口=>Pod
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
selector:
app: myapp
type: ClusterIP
ports:
- name: myapp-http
protocol: TCP
port: 80
targetPort: 8080
- name: myapp-https
protocol: TCP
port: 443
targetPort: 8081
spec.selector:选择器,选择目标pod的标签
spec.ports:
name 是端口名,由小写字母、数字、
-
组成port 是Service暴露端口
targetPort是pod暴露端口. 默认情况下, targetPort将等于port
ℹ️若spec.type没有定义,则默认是ClusterIP
ExternalName
ExternalName: 构建一个CNAME解析(service对象-CNAME-其它域名). 我能想到的主要是让集群内部服务可以访问到集群外部服务.
例如:
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
集群内部访问my-service.prod的时候, 将通过k8s的dns服务返回my.database.example.com
NodePort
NodePort: 反向代理集群内的pod(使用NAT在每一个集群node上的相同端口上公开Service, 是【ClusterIP类型的超集】). 供集群外服务访问. 流量过程是: 集群外=>任意节点ip:端口=>svc_name=>ClusterIP:端口=>Pod
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
selector:
app: myapp
type: NodePort
ports:
- protocol: TCP
nodePort: 31000
port: 80
targetPort: 80
name: myapp-http
集群外部此时可以访问任意物理节点ip:31000, 此时可以访问到集群内部app=myapp的pod.
这里nodePort是物理节点暴露端口, port是Service暴露端口, targetPort是pod暴露端口.
nodePort 可以不定义, 默认会自动从30000-32767随机分配。如果你想修改,则需要指定 apiserver 组件的
--service-node-port-range
。
LoadBalancer
LoadBalancer: 对接云商的负载均衡服务, 给Service分配一个固定IP. 方便云服务商的LB服务绑定. 【是NodePort类型的超集。 流量过程是: 集群外=>云服务LB=>任意物理节点ip:端口=>svc_name=>ClusterIP:端口=>Pod
这种类型建议直接参考官方文档: https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer
Headless Service
正常情况下,dns服务会将svc的域名解析为clusterIP(vip),然后通过代理方式将到达vip的流量转发到endpoints列表。
而Headless类型下,svc没有vip,也没有转发规则,而是通过A记录解析将svc_name.ns_name
直接解析为endpoints
列表,即直达pod。
不同点:
- 配置上,设置
spec.clusterIP: None
。 - 解析上,A记录方式解析为endpoints列表的所有ip,而不是ClusterIP。
常见于分布式应用部署场景,例如StatefulSet 控制器使用 Headless 结合Pod标识构建 Pod 唯一子域名,具体如何使用参考 StatefulSet,这里暂不考虑。
粘性会话
有些时候,我们需要会话黏性,你可以通过service.spec.sessionAffinity=ClientIP来设置.并同时可以通过service.spec.sessionAffinityConfig.clientIP.timeoutSeconds来设置会话保持时间,它默认是3小时.
服务发现
Pod 可以通过两种方式发现svc。
DNS
https://kubernetes.io/zh/docs/concepts/services-networking/service/#dns
Pod可以发现集群任何位置的svc
不同ns下,你可以通过svc_name.ns_name
访问 svc
相同ns下,可只通过svc_name
访问 svc
环境变量
Pod仅可以发现相同ns下的svc
https://kubernetes.io/docs/concepts/services-networking/service/#environment-variables
当service创建的时候,kubelet会生成一批环境变量。例如,svc 名称是 myservice,ClusterIP是10.0.0.11,暴漏的端口是6379,则生成以下环境变量:
MYSERVICE_SERVICE_HOST=10.0.0.11
MYSERVICE_SERVICE_PORT=6379
MYSERVICE_PORT=tcp://10.0.0.11:6379
MYSERVICE_PORT_6379_TCP=tcp://10.0.0.11:6379
MYSERVICE_PORT_6379_TCP_PROTO=tcp
MYSERVICE_PORT_6379_TCP_PORT=6379
MYSERVICE_PORT_6379_TCP_ADDR=10.0.0.11
ℹ️ 则不管你使用哪种方式,都应该提前通过init
容器来校验svc对象已成功创建,例如通过until死循环来判断解析是否成功。
其它暴露方式
除了 svc 暴露服务,还可以通过 ingress 暴露服务,它是充当集群入口,可以在单ip下暴露多服务。这里暂不讨论。