浅谈 kubernetes 服务发现

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

# flask-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: flask-hello
spec:
  selector:
    app: flask-hello
  ports:
    - port: 80
      targetPort: 5001

执行命令:

kubectl create -f flask-svc.yaml

查看 flask-hello service

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

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

再创建两个 pod。

# flask-hello-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-hello
  labels:
    app: flask-hello-deploy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: flask-hello
  template:
    metadata:
      name: flask-hello
      labels:
        app: flask-hello
    spec:
      containers:
        - name: flask-hello
          image: diego1109/flask-hello:latest
          ports:
            - name: http
              containerPort: 5001

创建 deployment

kubectl create -f flask-hello-deployment.yaml

查看 pod

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

查看 pod 中的环境变量

kubectl exec flask-hello-694d455dfc-hjd6g -- env
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=flask-hello-694d455dfc-hjd6g
FLASK_HELLO_PORT_80_TCP=tcp://10.101.113.175:80
KUBERNETES_SERVICE_PORT=443
FLASK_HELLO_SERVICE_PORT=80
FLASK_HELLO_PORT=tcp://10.101.113.175:80
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
FLASK_HELLO_PORT_80_TCP_ADDR=10.101.113.175
KUBERNETES_SERVICE_HOST=10.96.0.1
FLASK_HELLO_SERVICE_HOST=10.101.113.175  (<-- 看这里!)
FLASK_HELLO_PORT_80_TCP_PROTO=tcp
FLASK_HELLO_PORT_80_TCP_PORT=80		(<-- 看这里!)
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
HOME=/root

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

进入 pod

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

curl service

curl -X GET "http://${FLASK_HELLO_SERVICE_HOST}:${FLASK_HELLO_PORT_80_TCP_PORT}"
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 集群中的域名解析也是这么做的,接下来就梳理下集群中域名解析过程。

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

kubectl get svc -n kube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
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 中的。

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

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

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

kubectl exec flask-hello-694d455dfc-hjd6g -- cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
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 记录格式长什么样子:

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

这个格式翻译下就是

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
                                 Dload  Upload   Total   Spent    Left  Speed
100    20  100    20    0     0   2000      0 --:--:-- --:--:-- --:--:--  2000
Hello k8s ! 

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

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

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

执行命令:

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

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

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

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

kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default.svc
Server:		10.96.0.10
Address:	10.96.0.10#53

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

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

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

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

做个试验:

kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default.svc.cluster
Server:		10.96.0.10
Address:	10.96.0.10#53

** server can't find flask-hello.default.svc.cluster: NXDOMAIN

command terminated with exit code 1

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

再做个试验:

修改 search

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

再执行:

kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default.svc
Server:		10.96.0.10
Address:	10.96.0.10#53

** server can't find flask-hello.default.svc: NXDOMAIN

command terminated with exit code 1

也是找不见 service 的。

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值