服务发现与负载均衡。
一、背景
在微服务架构中,这里以开发环境「Dev」为基础来描述,在K8S集群中通常会开放:路由网关、注册中心、配置中心等相关服务,可以被集群外部访问;
对于测试「Tes」环境或者生产「Pro」环境,出于安全或者环境隔离性来考虑,在正常情况下只会开放网关服务,而「注册、配置」中心并不会对外暴露;
对于架构中的其它业务服务一般不会对外开放,在K8S集群内部服务间是可以正常通信的,对于「Dev」环境来说,研发会使用「注册、配置」中心,网关是系统的访问入口;
在K8S集群中,通过Service组件,可以快速简单的实现服务发现和负载均衡;
二、Service组件
1、简介
在K8S集群中是通过Pod组件来部署应用服务,Deployment组件实现Pod编排管理,Service组件实现应用的访问;
【Pod】自身的特点是临时的,使用过后直接抛弃的实体,这样在Pod创建和销毁的状态中,会导致IP地址发生变化,即无法使用固定的IP进行应用访问;
【Deployment】控制器通过管理ReplicaSet间接实现Pod管理,比如发布方式,更新和回滚策略,维持Pod副本数量,对应用进行快速的编排,但是并没有涉及应用的访问;
【Service】是将运行在一个或一组Pod上的网络应用程序公开为网络服务的方法,可以在不修改现有应用程序的情况下,使用服务发现机制访问到该应用;
基于Pod、Deployment、Service三个组件的协作,同一个应用的部署脚本可以在开发、测试、生产不同环境中复用;
2、基础语法
这里提供一个简单的【Service】语法做参考;
需要注意的是:在该脚本中没有指定服务类型即ServiceType
,默认采用的是ClusterIP
,通过集群的内部IP暴露服务,选择该值时服务只能够在集群内部访问;
三、内部服务发现
1、Pod创建
基于【Deployment】组件,创建「auto-serve」应用;
apiVersion: apps/v1kind: Deploymentmetadata:name: serve-deploymentlabels:app: auto-servespec:replicas: 1selector:matchLabels:app: auto-servetemplate:metadata:labels:app: auto-servespec:containers:- name: auto-serveimage: auto-serve:latestimagePullPolicy: Neverports:- containerPort: 8082name: auto-serve-portapiVersion: apps/v1 kind: Deployment metadata: name: serve-deployment labels: app: auto-serve spec: replicas: 1 selector: matchLabels: app: auto-serve template: metadata: labels: app: auto-serve spec: containers: - name: auto-serve image: auto-serve:latest imagePullPolicy: Never ports: - containerPort: 8082 name: auto-serve-portapiVersion: apps/v1 kind: Deployment metadata: name: serve-deployment labels: app: auto-serve spec: replicas: 1 selector: matchLabels: app: auto-serve template: metadata: labels: app: auto-serve spec: containers: - name: auto-serve image: auto-serve:latest imagePullPolicy: Never ports: - containerPort: 8082 name: auto-serve-port
执行创建命令
kubectl apply -f serve-deployment.yamlkubectl apply -f serve-deployment.yamlkubectl apply -f serve-deployment.yaml
2、Service创建
简单的脚本文件:app-service.yaml
;
apiVersion: v1kind: Servicemetadata:name: app-servicespec:selector:app: auto-serveports:- name: app-service-portprotocol: TCPport: 8082targetPort: auto-serve-portapiVersion: v1 kind: Service metadata: name: app-service spec: selector: app: auto-serve ports: - name: app-service-port protocol: TCP port: 8082 targetPort: auto-serve-portapiVersion: v1 kind: Service metadata: name: app-service spec: selector: app: auto-serve ports: - name: app-service-port protocol: TCP port: 8082 targetPort: auto-serve-port
创建【Service】
kubectl apply -f app-service.yamlkubectl apply -f app-service.yamlkubectl apply -f app-service.yaml
查看【Service】,可以使用命令行或者界面;
kubectl describe svc app-servicekubectl describe svc app-servicekubectl describe svc app-service
删除【Service】
kubectl delete -f app-service.yamlkubectl delete -f app-service.yamlkubectl delete -f app-service.yaml
3、内部访问
在上面已经说明,当Type
不指定时采用的是ClusterIP
,只能在集群内部访问,集群外部的网络是无法访问的;
在【auto-client】服务中提供一段访问【auto-serve】接口的代码,并制作镜像【auto-client:3.3.3】,完成部署后查看日志打印;
@Componentpublic class HttpServiceJob {private static final Logger LOG = LoggerFactory.getLogger(HttpServiceJob.class.getName()) ;private static final String SERVER_NAME = "http://app-service:8082/serve";private static final String SERVER_IP = "http://10.103.252.94:8082/serve";/*** 每30秒执行一次*/@Scheduled(fixedDelay = 30000)public void systemDate () {SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();factory.setReadTimeout(3000);factory.setConnectTimeout(6000);RestTemplate restTemplate = new RestTemplate(factory);try {Map<String, String> paramMap = new HashMap<>();String result = restTemplate.getForObject(SERVER_NAME, String.class, paramMap);LOG.info("service-name-resp::::" + result);} catch (Exception e) {e.printStackTrace();}try {Map<String, String> paramMap = new HashMap<>();String result = restTemplate.getForObject(SERVER_IP, String.class, paramMap);LOG.info("service-ip-resp::::" + result);} catch (Exception e) {e.printStackTrace();}}}@Component public class HttpServiceJob { private static final Logger LOG = LoggerFactory.getLogger(HttpServiceJob.class.getName()) ; private static final String SERVER_NAME = "http://app-service:8082/serve"; private static final String SERVER_IP = "http://10.103.252.94:8082/serve"; /** * 每30秒执行一次 */ @Scheduled(fixedDelay = 30000) public void systemDate () { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setReadTimeout(3000); factory.setConnectTimeout(6000); RestTemplate restTemplate = new RestTemplate(factory); try { Map<String, String> paramMap = new HashMap<>(); String result = restTemplate.getForObject(SERVER_NAME, String.class, paramMap); LOG.info("service-name-resp::::" + result); } catch (Exception e) { e.printStackTrace(); } try { Map<String, String> paramMap = new HashMap<>(); String result = restTemplate.getForObject(SERVER_IP, String.class, paramMap); LOG.info("service-ip-resp::::" + result); } catch (Exception e) { e.printStackTrace(); } } }@Component public class HttpServiceJob { private static final Logger LOG = LoggerFactory.getLogger(HttpServiceJob.class.getName()) ; private static final String SERVER_NAME = "http://app-service:8082/serve"; private static final String SERVER_IP = "http://10.103.252.94:8082/serve"; /** * 每30秒执行一次 */ @Scheduled(fixedDelay = 30000) public void systemDate () { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setReadTimeout(3000); factory.setConnectTimeout(6000); RestTemplate restTemplate = new RestTemplate(factory); try { Map<String, String> paramMap = new HashMap<>(); String result = restTemplate.getForObject(SERVER_NAME, String.class, paramMap); LOG.info("service-name-resp::::" + result); } catch (Exception e) { e.printStackTrace(); } try { Map<String, String> paramMap = new HashMap<>(); String result = restTemplate.getForObject(SERVER_IP, String.class, paramMap); LOG.info("service-ip-resp::::" + result); } catch (Exception e) { e.printStackTrace(); } } }
在代码中通过服务名:端口
和IP:端口
都可以正常访问,在Pod中查看两个应用的日志,请求和响应都正常;
四、外部服务发现
1、NodePort类型
指定类型为NodePort
的脚本:app-np-service.yaml
;
apiVersion: v1kind: Servicemetadata:name: app-np-servicespec:type: NodePortselector:app: auto-serveports:- protocol: TCPport: 8082targetPort: 8082nodePort: 30010apiVersion: v1 kind: Service metadata: name: app-np-service spec: type: NodePort selector: app: auto-serve ports: - protocol: TCP port: 8082 targetPort: 8082 nodePort: 30010apiVersion: v1 kind: Service metadata: name: app-np-service spec: type: NodePort selector: app: auto-serve ports: - protocol: TCP port: 8082 targetPort: 8082 nodePort: 30010
创建【Service】
kubectl apply -f app-np-service.yamlkubectl apply -f app-np-service.yamlkubectl apply -f app-np-service.yaml
使用NodePort
类型,K8S控制平面会在指定的范围内分配端口,如果需要特定的端口号可以指定nodePort
字段中的值,但是该类型需要自己设置负载均衡解决方案;
2、LoadBalancer类型
指定类型为LoadBalancer
的脚本:app-lb-service.yaml
;
apiVersion: v1kind: Servicemetadata:name: app-lb-servicespec:type: LoadBalancerselector:app: auto-serveports:- protocol: TCPport: 8082targetPort: 8082apiVersion: v1 kind: Service metadata: name: app-lb-service spec: type: LoadBalancer selector: app: auto-serve ports: - protocol: TCP port: 8082 targetPort: 8082apiVersion: v1 kind: Service metadata: name: app-lb-service spec: type: LoadBalancer selector: app: auto-serve ports: - protocol: TCP port: 8082 targetPort: 8082
创建【Service】
kubectl apply -f app-lb-service.yamlkubectl apply -f app-lb-service.yamlkubectl apply -f app-lb-service.yaml
查看【Service】
在查看「app-lb-service」时,值得注意一下Endpoints
的字段属性,这里就是Pod选择器选中的Pod
;
kubectl get svc app-lb-service -o wideNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTORapp-lb-service LoadBalancer 10.111.65.220 localhost 8082:30636/TCP 6m49s app=auto-servekubectl describe svc app-lb-serviceName: app-lb-serviceNamespace: defaultLabels: <none>Annotations: <none>Selector: app=auto-serveType: LoadBalancerIP Family Policy: SingleStackIP Families: IPv4IP: 10.111.65.220IPs: 10.111.65.220LoadBalancer Ingress: localhostPort: <unset> 8082/TCPTargetPort: 8082/TCPNodePort: <unset> 30636/TCPEndpoints: 10.1.0.160:8082,10.1.0.161:8082,10.1.0.162:8082Session Affinity: NoneExternal Traffic Policy: ClusterEvents: <none>kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODEserve-deployment-f6f6c5bbd-9qvgr 1/1 Running 0 39m 10.1.0.162 docker-desktopserve-deployment-f6f6c5bbd-w7nj2 1/1 Running 0 39m 10.1.0.161 docker-desktopserve-deployment-f6f6c5bbd-x7v4d 1/1 Running 0 39m 10.1.0.160 docker-desktopkubectl get svc app-lb-service -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR app-lb-service LoadBalancer 10.111.65.220 localhost 8082:30636/TCP 6m49s app=auto-serve kubectl describe svc app-lb-service Name: app-lb-service Namespace: default Labels: <none> Annotations: <none> Selector: app=auto-serve Type: LoadBalancer IP Family Policy: SingleStack IP Families: IPv4 IP: 10.111.65.220 IPs: 10.111.65.220 LoadBalancer Ingress: localhost Port: <unset> 8082/TCP TargetPort: 8082/TCP NodePort: <unset> 30636/TCP Endpoints: 10.1.0.160:8082,10.1.0.161:8082,10.1.0.162:8082 Session Affinity: None External Traffic Policy: Cluster Events: <none> kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE serve-deployment-f6f6c5bbd-9qvgr 1/1 Running 0 39m 10.1.0.162 docker-desktop serve-deployment-f6f6c5bbd-w7nj2 1/1 Running 0 39m 10.1.0.161 docker-desktop serve-deployment-f6f6c5bbd-x7v4d 1/1 Running 0 39m 10.1.0.160 docker-desktopkubectl get svc app-lb-service -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR app-lb-service LoadBalancer 10.111.65.220 localhost 8082:30636/TCP 6m49s app=auto-serve kubectl describe svc app-lb-service Name: app-lb-service Namespace: default Labels: <none> Annotations: <none> Selector: app=auto-serve Type: LoadBalancer IP Family Policy: SingleStack IP Families: IPv4 IP: 10.111.65.220 IPs: 10.111.65.220 LoadBalancer Ingress: localhost Port: <unset> 8082/TCP TargetPort: 8082/TCP NodePort: <unset> 30636/TCP Endpoints: 10.1.0.160:8082,10.1.0.161:8082,10.1.0.162:8082 Session Affinity: None External Traffic Policy: Cluster Events: <none> kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE serve-deployment-f6f6c5bbd-9qvgr 1/1 Running 0 39m 10.1.0.162 docker-desktop serve-deployment-f6f6c5bbd-w7nj2 1/1 Running 0 39m 10.1.0.161 docker-desktop serve-deployment-f6f6c5bbd-x7v4d 1/1 Running 0 39m 10.1.0.160 docker-desktop
五、参考源码
文档仓库:https://gitee.com/cicadasmile/butte-java-note脚本仓库:https://gitee.com/cicadasmile/butte-auto-parent文档仓库: https://gitee.com/cicadasmile/butte-java-note 脚本仓库: https://gitee.com/cicadasmile/butte-auto-parent文档仓库: https://gitee.com/cicadasmile/butte-java-note 脚本仓库: https://gitee.com/cicadasmile/butte-auto-parent