微服务的自动发现

在熟悉了的基本操作后,我们来讨论下如何实现微服务的自动发现。

Service是在Pod基础上做的另一层抽象,通过虚拟IP的方式,提供了统一的代理入口和负载均衡。

Service本身不会创建Pod,而是通过标签的方式与已有Pod产生关联,这与Deployment是类似的。因此,在创建第一个Service前,我们需要先应用之前的lmsia-abc-server-deployment,具体可参考前一节Kubernetes 快速入门

下面来看一下Service描述文件,lmsia-abc-server-service.yaml

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: lmsia-abc-server-service
  5. spec:
  6. selector:
  7. app: lmsia-abc-server
  8. ports:
  9. - name: http
  10. protocol: TCP
  11. port: 8080
  12. - name: rpc
  13. protocol: TCP
  14. port: 3000

与Deployment相比,上述Service的描述文件更简单一些。

  • kind: 类型是Service
  • metadata.name: 定义了Service名字
  • spec.selector.app: 定义了要关联的Pod标签
  • spec.ports: 定义了需要进行负载均衡的端口,这里定义了两套需要负载均衡的端口,http的8080和rpc的3000。

有了描述文件后,我们来应用服务:

  1. kubectl apply -f lmsia-abc-server-service.yaml
  2. service "lmsia-abc-server-service" created

成功创建Service后,可以使用’describe service’来查看:

  1. kubectl describe service lmsia-abc-server-service
  2. Name: lmsia-abc-server-service
  3. Namespace: default
  4. Labels: <none>
  5. Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"lmsia-abc-server-service","namespace":"default"},"spec":{"ports":[{"name":"htt...
  6. Selector: app=lmsia-abc-server
  7. Type: ClusterIP
  8. IP: 10.109.20.138
  9. Port: http 8080/TCP
  10. TargetPort: 8080/TCP
  11. Endpoints: 172.17.0.4:8080,172.17.0.5:8080
  12. Session Affinity: None
  13. Events: <none>

上面返回的结果中,有一些关键信息:

  • Type: 指的是ServiceType,ClusterIP是仅供集群内访问的负载均衡IP。类似的,如果想将虚拟IP暴露给集群外,可以使用NodePort等,具体可以参考官方文档Publising Service Types
  • IP: 服务提同的虚拟IP地址。
  • Port: 微服务进程上的端口,即HTTP的8080和RPC的3000
  • TargetPort: 虚拟IP对外提供负载均衡的端口,由于我们未单独制定,默认是与上述Port保持一致的。
  • Endpoints:我们在Deployment中定义的两个Pod。Service通过虚拟IP将流量分发到这两个后端Pod上。

让我们来验证下负载均衡的配置是否生效,由于rpc接口的数据格式较为复杂,在此我们仅验证http端口。

首先登录到minikube

  1. minikube ssh
  2. curl http://10.109.20.138:8080/lmsia-abc/api/
  3. Hello, REST
  4. curl http://10.109.20.138:8080/lmsia-abc/api/
  5. Hello, REST

我们执行了两次,都成功了,那么这个请求真的被均匀地分发到后端的进程上了么?我们需要验证一下。

首先获取两个容器的ID

  1. # list pod
  2. kubectl get pods -l app=lmsia-abc-server
  3. NAME READY STATUS RESTARTS AGE
  4. lmsia-abc-server-deployment-bd4949ff9-7bgvq 1/1 Running 0 16m
  5. lmsia-abc-server-deployment-bd4949ff9-mlmlq 1/1 Running 0 16m
  6. # get container id for pod1
  7. kubectl describe pod lmsia-abc-server-deployment-bd4949ff9-7bgvq
  8. ...
  9. Name: lmsia-abc-server-deployment-bd4949ff9-7bgvq
  10. ...
  11. Containers:
  12. lmsia-abc-server-ct:
  13. Container ID: docker://a146ee545d11638a331d1696e7e6e3c88cc3231b97f3eb50c63cb9f50724cf2c
  14. ...
  15. # get container id for pod 2
  16. kubectl describe pod
  17. ...
  18. Name: lmsia-abc-server-deployment-bd4949ff9-mlmlq
  19. ...
  20. Containers:
  21. lmsia-abc-server-ct:
  22. Container ID: docker://608decbb198dcbdce5442a4401eeeec1cb316e483ddba2d5c993ea10081a5e6a
  23. ...

登录minikube集群,分别查看两个Container的日志

  1. minikube ssh
  2. # check pod 1 access log
  3. $ docker exec -i -t a146ee545d11638a331d1696e7e6e3c88cc3231b97f3eb50c63cb9f50724cf2c cat /app/logs/access_log.2018-05-14.log
  4. 10.0.2.15 - - [14/May/2018:07:27:57 +0000] "GET /lmsia-abc/api/ HTTP/1.1" 200 11
  5. # check pod 2 access log
  6. $ docker exec -i -t 608decbb198dcbdce5442a4401eeeec1cb316e483ddba2d5c993ea10081a5e6a cat /app/logs/access_log.2018-05-14.log
  7. 10.0.2.15 - - [14/May/2018:07:27:56 +0000] "GET /lmsia-abc/api/ HTTP/1.1" 200 11

这里需要说明下’docker exec -i -t’,是针对Docker容器执行命令,要执行的命令即后面的cat /app/logs….

查看了两个Pod对应的Container的日志,可以发现:虽然我们的curl是访问的虚拟IP,但是流量被均衡地分发到了2个后端容器上。至此,我们已经通过Service实现了多节点的自动负载均衡。

需要指出的是:Kubernetes的虚拟IP内置了多种实现,目前以ipvs性能最好,具体可以查看Virtual IPs and service proxies

现在让我们来回顾下这一节的标题:”微服务的自动发现”。对于服务发现这个需求,我们目前的效果似乎并不这么完美,为什么这样说呢?我们目前是通过虚拟IP直接访问的服务,但在实际生产环境中,每个Service创建的虚拟IP并不固定,我们不可能将这些虚拟IP分别配置在依赖的众多微服务中。

幸运的是,Kubernetes早就为我们解决了这个问题。在创建Service的同时,Kubernetes还为我们创建了一条DNS记录,我们可以通过域名直接访问虚拟IP:

  1. docker exec -i -t 608decbb198dcbdce5442a4401eeeec1cb316e483ddba2d5c993ea10081a5e6a busybox wget -q -O - http://lmsia-abc-server-service:8080/lmsia-abc/api/
  2. Hello, REST

如上所示,通过lmsia-abc-server-service这个域名,就可以成功地访问虚拟IP了。对于ClusterIP的Service,域名的默认组成是’服务名.服务所在命名空间.svc.cluster.集群域名’,或者简单使用服务名^1,上面例子中我们采用的就是后者。

让我们用一张图来回顾下服务发现、负载均衡流程:

基于Kubernetes的服务发现与负载均衡

如上图所示:

  1. 约定好微服务Service的命名方式
  2. 通过DNS服务获取微服务Service对应的虚拟IP(VIP)
  3. 访问VIP和端口(3000)
  4. Kubernetes的VIP自动完成了负载均衡,转发到后端Service B的3个节点(Pod/Docker)上

至此,我们借助Kubernetes的Service功能,”近似完美”地实现了服务的注册与发现。

为什么讲”近似完美”呢?这里还会有一个小坑。熟悉DNS协议的朋友知道,为了提升查询效率,DNS被设计成可以多级缓存的。在Java的JVM虚拟机上,也会进行DNS缓存,但这个缓存有效期默认是-1即永久。这也就意味着,如果我们删除这个Service重新创建,那么虚拟IP的变更将不会自动反馈到相应微服务的JVM中。

为了解决这个小坑,一般建议修改JVM的安全设置,修改缓存TTL时间,具体可以参考亚马逊AWS的这篇介绍

我们为本章构建的Docker镜像也自动解决了这个问题:

  1. FROM anapsix/alpine-java:8_server-jre
  2. WORKDIR /app
  3. RUN mkdir -p /app/logs
  4. ADD lmsia-abc-server.jar /app
  5. CMD ["java", "-jar", "lmsia-abc-server.jar"]

其中anapsix/alpine-java:8_server-jre是我们依赖的基础镜像,它将DNS Cache设置为了10秒钟,读者也可以直接使用这个基础镜像。

需要特别说明的时:若想使用上述的自动发现机制,必须使用Kubernetes的DNS服务,它默认是开启的:

  1. kubectl -n kube-system get svc kube-dns
  2. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  3. kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 3d

通过Kubernetes创建的Pod(Docker),已经自动配置了上述DNS。若想在在集群外使用这个DNS,有两种方案: