浅谈 kubernetes 服务发现

柔光的暖阳◎ 2022-12-15 15:29 292阅读 0赞

1. 什么叫服务发现?

service ip 在 service 的整个生命周期中都是不会发生变化的,所以当给 pod 创建 service 后,就可以通过 service ip 稳定的访问到 pod,service 后面的 pod 可能会重建,或者数量发生变化,导致 pod 的 ip 地址变化,但是通过稳定不变的 service ip 总能访问到这些 pod。

那问题来了,pod-a 是怎么通过 svc-b 访问到 pod-b 中的应用程序的,更直白点,pod-a 是怎么发现(访问) svc-b 的?

在回答这个问题之间,我们先来看下平时都是怎么访问 web 程序的:

场景一:我们创建一个 Spring Boot HelloWorld 项目并在本地启动,在浏览器中输入:http://127.0.0.1.8080就可以看到后端返回的 “hello world” 字符串。

场景二:访问百度,在浏览器中输入 www.baidu.com 就可以访问到百度的主页面。

上述情况中,场景一直接使用 IP 地址访问,场景二使用域名访问。这是我们平常访问 web 程序的两种方式,在kubernetes 中 pod 访问 service 也是通过这两种方式实现的。访问执行的关键点是要提前知道 IP 地址或者域名,如果二者都不知道,那访问个毛线。两个场景中的访问发起者都是人,同时http://127.0.0.1.8080 无论如何变化,人眼一看便知, www.baidu.com 的域名是备了案,不会变的,所以用户找到并且输入正确的 IP 地址都不是难事,那 kubernetes 中的 pod 是如何提前知道 service 的 IP 地址或者域名的呢?

当 pod 知道 service 的 IP 地址或者域名,那么这个 service 就被“发现了”,“服务发现” 就是这个意思。

2. 基于 IP 地址的服务发现

这种方式在有些书中也叫做 ”通过环境变量发现服务“,为什么这么叫呢,原因在下面。复现下这种场景:

先创建 service

  1. # flask-svc.yaml
  2. apiVersion: v1
  3. kind: Service
  4. metadata:
  5. name: flask-hello
  6. spec:
  7. selector:
  8. app: flask-hello
  9. ports:
  10. - port: 80
  11. targetPort: 5001

执行命令:

  1. kubectl create -f flask-svc.yaml

查看 flask-hello service

  1. kubectl get svc
  2. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  3. flask-hello ClusterIP 10.101.113.175 <none> 80/TCP 29h
  4. kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 113d

当前集群分配给 flask-hello 的 IP 地址是:10.101.113.175。记住这个 IP 地址,后面要对照看的。

再创建两个 pod。

  1. # flask-hello-deployment.yaml
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. name: flask-hello
  6. labels:
  7. app: flask-hello-deploy
  8. spec:
  9. replicas: 2
  10. selector:
  11. matchLabels:
  12. app: flask-hello
  13. template:
  14. metadata:
  15. name: flask-hello
  16. labels:
  17. app: flask-hello
  18. spec:
  19. containers:
  20. - name: flask-hello
  21. image: diego1109/flask-hello:latest
  22. ports:
  23. - name: http
  24. containerPort: 5001

创建 deployment

  1. kubectl create -f flask-hello-deployment.yaml

查看 pod

  1. kubectl get pods
  2. NAME READY STATUS RESTARTS AGE
  3. flask-hello-694d455dfc-hjd6g 1/1 Running 0 3m57s
  4. flask-hello-694d455dfc-xrdp7 1/1 Running 0 3m57s

查看 pod 中的环境变量

  1. kubectl exec flask-hello-694d455dfc-hjd6g -- env
  2. PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  3. HOSTNAME=flask-hello-694d455dfc-hjd6g
  4. FLASK_HELLO_PORT_80_TCP=tcp://10.101.113.175:80
  5. KUBERNETES_SERVICE_PORT=443
  6. FLASK_HELLO_SERVICE_PORT=80
  7. FLASK_HELLO_PORT=tcp://10.101.113.175:80
  8. KUBERNETES_SERVICE_PORT_HTTPS=443
  9. KUBERNETES_PORT=tcp://10.96.0.1:443
  10. KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
  11. KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
  12. FLASK_HELLO_PORT_80_TCP_ADDR=10.101.113.175
  13. KUBERNETES_SERVICE_HOST=10.96.0.1
  14. FLASK_HELLO_SERVICE_HOST=10.101.113.175 (<-- 看这里!)
  15. FLASK_HELLO_PORT_80_TCP_PROTO=tcp
  16. FLASK_HELLO_PORT_80_TCP_PORT=80 (<-- 看这里!)
  17. KUBERNETES_PORT_443_TCP_PROTO=tcp
  18. KUBERNETES_PORT_443_TCP_PORT=443
  19. HOME=/root

第 13 行、第15 行分别是 flask-hello service 的 IP 地址和端口号。原来 Kubernetes 将 service 的 IP 地址和端口号都设置成 pod 的环境变量了。那就可以在 pod-a 中直接通过环境变量访问 service 了,进而访问 pod-b中的应用程序。试试~~~

进入 pod

  1. kubectl exec -it flask-hello-694d455dfc-hjd6g -- bash

curl service

  1. curl -X GET "http://${FLASK_HELLO_SERVICE_HOST}:${FLASK_HELLO_PORT_80_TCP_PORT}"
  2. Hello k8s !

在这种方式中,pod 通过 环境变量发现了 service 的 IP 和 port ,进而访问到了 pod-b。”通过环境变量发现服务“ 这个名字描述的也挺形象的。

注意一个细节:在上面的 case 中,我们是先创建 service 再创建 pod,所以 pod 的环境变量中有 service 的 ip 和 port。那如果把顺序颠倒一下,先创建 pod 再创建 service,结果会怎么样呢?答案是:pod 的环境变量中不会有 service 的 ip 和 port,(这个场景大家可以自己测试下)。

当 pod 的环境变量中没有 service 的 IP 和 port 时,pod 该怎么”发现“ service 呢?kubernetes 提供的另外一种服务发现方式可以应对这种场景。

3. 基于 DNS 的服务发现

先来看下平时我们是怎么使用 DNS 的。当使用域名访问 web 应用程序时,首先要做的事情是解析域名,获取到该域名对应的 IP 地址,再使用 IP 地址去访问 web 应用程序。当浏览器中输入 www.baidu.com 时,(假设本地电脑没有域名缓存)会先将该域名发送到域名解析服务器(DNS),DNS 会去查找 www.baidu.com 对应的 IP 地址是多少,并将结构返回给浏览器所在的电脑。

本地电脑的 /etc/resolv.conf 文件中的 nameserver 198.18.0.2 表示给本地电脑配置的域名解析服务器的 IP 地址。大家可以试试,如果把该字段屏蔽掉,或者将 IP 地址随便修改成一个不存在的,再在浏览器中输入www.baidu.com 时,就会显示请求失败。

kubernetes 集群中的域名解析也是这么做的,接下来就梳理下集群中域名解析过程。

域名解析首先得有个”域名解析服务器“ 的东西存在,那它在哪里呢?

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

Kube-system 命名空间中 kube-dns 就是集群的”域名解析服务器“,记住它的 IP 地址 10.96.0.10。 Kube-dns 是 service,它是个连接件或者导航器之类的东西,真正的执行逻辑处理的是它后面的 pod,所以域名解析过程应该是发生在 pod 中的。

  1. kubectl get pods -n kube-system
  2. NAME READY STATUS RESTARTS AGE
  3. calico-kube-controllers-75d555c48-cj4tk 1/1 Running 4 123d
  4. calico-node-2f2md 1/1 Running 1 123d
  5. calico-node-drdx2 1/1 Running 1 123d
  6. calico-node-rxflt 1/1 Running 4 123d
  7. coredns-7ff77c879f-bsn6c 1/1 Running 4 123d
  8. coredns-7ff77c879f-c62qp 1/1 Running 4 123d
  9. etcd-master-aliyun 1/1 Running 4 123d
  10. kube-apiserver-master-aliyun 1/1 Running 4 123d
  11. kube-controller-manager-master-aliyun 1/1 Running 4 123d
  12. kube-proxy-mfn7v 1/1 Running 1 123d
  13. kube-proxy-svvgr 1/1 Running 1 123d
  14. kube-proxy-zzl9d 1/1 Running 5 123d
  15. kube-scheduler-master-aliyun 1/1 Running 4 123d

第6行,第7行 应该就是执行域名解析的 pod 了,这两个 pod 知道集群中运行的所有的 service。因为 kube-dns service 的 label 正好和这两个 pod 的 label 是匹配的。我一直想 exec 进去看下 pod 里面到底写的什么,一直都没成功…,继续寻找正确的打开方式。

集群中个的域名解析服务器找到了,那 pod 是怎么知道这个 DNS 的呢?

  1. kubectl exec flask-hello-694d455dfc-hjd6g -- cat /etc/resolv.conf
  2. nameserver 10.96.0.10
  3. search default.svc.cluster.local svc.cluster.local cluster.local
  4. options ndots:5

很明显了,kubernetes 在 pod 创建时就已经在其内部的 /etc/resolv.conf 文件中配置了域名解析服务器的地址。而且这个地址就是 kube-dns service 的 ip 地址。原来在 pod 中使域名访问时,所有的域名查询请求都会被 kube-dns 响应,查找到对应的 IP 地址并返回。

到这里服务就又被发现了,不过还差一点点细节,接着往下看看呗~~

域名www.baidu.com 是人为设计且备案了的,它几乎是不会再改变的。 那集群中每个 service 的域名是什么呢?在哪能查到呢?

kubernetes 有一套域名规则,集群中定义的每个 service 都会被分配一个域名。这里我们只讲 ”普通 service“(而不是”无头 service“) 的 AAAA 记录。这种域名会被解析成对应 service 的 IP 地址。这里别被概念给吓到,很简单的,service 有两种,普通和无头,域名也有好多中格式,其中普通 service 的 AAAA 格式的域名会被解析成 service IP。当然也会其他格式的域名会被解析成其他组件的 IP ,不过这里我们先不去管。

先看下域名的 AAAA 记录格式长什么样子:

  1. svc-name.svc-namespace.svc.cluster-domain.example

这个格式翻译下就是

  1. svc的名字.svc所在的命名空间的名字.svc(标识,固定的).cluster.local(后缀,也是固定的)

所以当知道 service 的名字和它所在的命名空间时,也就等于是知道了它的域名。

做个试验,试试~~

  1. 查询 flask-hello service 的 IP 地址

    kubectl exec flask-hello-694d455dfc-hjd6g — nslookup flask-hello.default.svc.cluster.local

    Server: 10.96.0.10
    Address: 10.96.0.10#53

    Name: flask-hello.default.svc.cluster.local
    Address: 10.101.113.175

第1行、第2行表示使用的域名解析服务器的 IP 地址,翻到上面去对对,就是 kube-dns。第4行、第5行表示 域名:flask-hello.default.svc.cluster.local 对应的 IP 地址是: 10.101.113.175 ,再翻到上面对对。

  1. 通过域名访问 flask-hello service

    kubetcl exec flask-hello-694d455dfc-hjd6g — curl -X GET http://flask-hello.default.svc.cluster.local:80

    % Total % Received % Xferd Average Speed Time Time Time Current

    1. Dload Upload Total Spent Left Speed

    100 20 100 20 0 0 2000 0 —:—:— —:—:— —:—:— 2000
    Hello k8s !

访问到 flask-hello service 后面连接到的 pod 是 work 的。

细节补上了,到此为止,基于 DNS 的服务发现就算是基本介绍完了。

最后在啰嗦一个细节了,重要与否不好衡量,但是会变的方便。

执行命令:

  1. kubectl exec flask-hello-694d455dfc-hjd6g -- cat /etc/resolv.conf
  2. nameserver 10.96.0.10
  3. search default.svc.cluster.local svc.cluster.local cluster.local
  4. options ndots:5

第2行有三个字符串,看起来应该是跟域名有关的东东,那具体是干什么用的呢。

svc-name.svc-namespace.svc.cluster-domain.example 这种格式的域名有个专有名词:完全限定域名(fully qualified domain name,FQDN),表示这是域名格式最完整的形式。

当域名不完整时,也是能被解析的:

  1. kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default.svc
  2. Server: 10.96.0.10
  3. Address: 10.96.0.10#53
  4. Name: flask-hello.default.svc.cluster.local
  5. Address: 10.101.113.175

解析无误。同样,下面两种格式的域名也能被解析成功。 (自己可以试试)

  1. kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default
  2. kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello

之所以能解析成功,是因为第2行那三个字符串是拿来做拼接的,kubernetes 会将用户输入的域名和 search 后面的三个字符串做拼接,拼成 FQDN 格式再做解析。至于拼接的细节和策略,我没有研究过,有兴趣的可以自己尝试下。

做个试验:

  1. kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default.svc.cluster
  2. Server: 10.96.0.10
  3. Address: 10.96.0.10#53
  4. ** server can't find flask-hello.default.svc.cluster: NXDOMAIN
  5. command terminated with exit code 1

因为没有 local 后缀,所以 flask-hello.default.svc.cluster 没法被拼接成 FQDN。

再做个试验:

修改 search

  1. search default.svc.cluster.local svc.cluster.local

再执行:

  1. kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default.svc
  2. Server: 10.96.0.10
  3. Address: 10.96.0.10#53
  4. ** server can't find flask-hello.default.svc: NXDOMAIN
  5. command terminated with exit code 1

也是找不见 service 的。

到这基本结束啦,关于 handless service 在这里。

发表评论

表情:
评论列表 (有 0 条评论,292人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Dubbo服务

    一、Dubbo是什么? Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案,说白了Dubbo就是个远程服务调用的分布