浅谈 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 的名字和它所在的命名空间时,也就等于是知道了它的域名。
做个试验,试试~~
查询 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#53Name: 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
,再翻到上面对对。
通过域名访问 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 在这里。
还没有评论,来说两句吧...