Kubernetes 服务发现

时间:2024-07-24 09:37:50

什么是服务发现?

服务发现就是一种提供服务发布和查找的服务,是基于服务架构(SOA)应用的核心服务,需具备以下关键特性:

  1. 注册(Registration),新增服务到服务列表;
  2. 目录(Directory),即服务列表;
  3. 查找(Lookup),通过服务名找到服务。

服务发现的关键在于服务元数据(metadata)的存储,包括服务名、服务 IP、服务端口等信息。

Kubernetes 支持两种服务发现方式,环境变量和 DNS。

环境变量

当 Pod 创建时,Kubernetes 会将每个活跃的 Service 的相关环境变量设置到 Pod 中。值得注意的是,这些环境变量不会因为相关 Service 改变而改变(笔者亲手试验过)。

Kubernetes 会设置两类环境变量,分别是:

  1. Kubernetes Service 环境变量
  2. Docker Link 环境变量

Kubernetes Service 环境变量形如(假定服务名为 latte,且访问端口为 8080):

LATTE_SERVICE_HOST=10.100.251.57
LATTE_SERVICE_PORT=8080

Docker Link 环境变量形如(假定服务名为 latte,且访问端口为 8080):

LATTE_PORT_8080_TCP_ADDR=10.100.251.57
LATTE_PORT_8080_TCP_PORT=8080
LATTE_PORT_8080_TCP_PROTO=tcp
LATTE_PORT=tcp://10.100.251.57:8080
LATTE_PORT_8080_TCP=tcp://10.100.251.57:8080

可以通过进入 Pod 的终端,执行 env 命令查看设置的环境变量验证。

kubectl exec -ti <pod-name> env --namespace=<my-namespace>

此种方式的服务发现缺点很明显:

  1. 依赖的服务必须先运行起来,否则 Pod 无法发现;
  2. 如依赖的服务宕机或绑定新地址,Pod 无法发现,仍然持有旧的地址。

幸好,我们还有另一种服务发现机制。

DNS 服务

在讲述 Kubernetes 中使用 DNS 进行服务发现之前,我们不得不先了解下 Linux 中是如何进行 DNS 查询的。

Linux 中 DNS 查询原理

在 Linux 的 /etc/ 目录中,存在 3 个我们需要关注的文件,分别是(参考:http://man7.org/linux/man-pages/man5/host.conf.5.html):

  1. /etc/host.conf:DNS 解析器配置,包含 trim、multi、order、reorder 和 nospoof 等等配置。
  2. /etc/hosts:本地 hosts 数据库,存放本地的域名到 IP 的配置。
  3. /etc/resolv.conf:DNS 解析器配置,包含 nameserver、domain、search、sortlist 和 options 等配置。

下面是一台 Linux 服务器中 3 个相关文件的内容:

# /etc/host.conf
multi on
# /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost6 localhost6.localdomain6
# /etc/resolv.conf
; generated by /usr/sbin/dhclient-script
search us-west-2.compute.internal
options timeout:2 attempts:5
nameserver 192.168.0.2

/etc/resolv.conf 配置解释如下:

配置项 功能 备注
nameserver DNS 服务器 值必须是 IP 地址
domain 本地域名 域中的查询可以使用相对于本地域名的短名称
search 主机名查询列表 默认只包含本地域名。阈值为 6 个域名,256 个字符。
options 选项 修改内部 DNS 解析器变量值。

options 中常见的配置项有:

配置项 功能
ndots 所有查询中,如果.的个数少于给定的数值,则会根据search中配置的列表依次在对应域中先进行搜索,如果没有返回,则最后再直接查询域名本身。阈值为 15。
timeout 等待 DNS 服务器响应的超时时间,单位为秒。阈值为 30 s。
attempts 向同一个 DNS 服务器发起重试的次数,单位为次。阈值为 5。

笔者在本地试验时发现,文件 /etc/resolv.conf 是网络连接时自动生成的,依据是:

  1. 当本机处以断网状态时,cat /etc/resolv.conf 返回空文本;
  2. 当本机连上网络时,cat /etc/resolv.conf 返回以下内容:
#
# macOS Notice
#
# This file is not consulted for DNS hostname resolution, address
# resolution, or the DNS query routing mechanism used by most
# processes on this system.
#
# To view the DNS configuration used by this system, use:
# scutil --dns
#
# SEE ALSO
# dns-sd(1), scutil(8)
#
# This file is automatically generated.
#
nameserver 58.250.162.58
nameserver 8.8.8.8

第一个 DNS 服务器是中国联通的,通过访问 https://whois.domaintools.com/58.250.162.58 可知;

第二个 DNS服务器是 Google 的,通过 nslookup 8.8.8.8 或者访问 https://whois.domaintools.com/8.8.8.8 可知。

Kubernetes 中 DNS 查询原理

Kubernetes 中有两个可选的 DNS 服务插件(处在 kube-system 命名空间):

插件 说明
kube-dns 其代码已经从 kubernetes 库中分离到单独的仓库维护,见 https://github.com/kubernetes/dns
CoreDNS 支持 Kubernetes v1.9 及以上;Kubernetes v1.12 起,官方推荐使用来替换 kube-dns;Kubernetes v1.13 起,成为默认 DNS 服务;占用内存小,查询速度快。

注意:kube-dns 在 Kubernetes 中有多重含义,要注意区别。

  1. 与 CoreDNS 对比时,使用狭义,表示名为 kube-dns 的 DNS 服务;
  2. 当泛指时,表示 Kubernetes 中的 DNS 服务。

使用 kubeadm 创建 v1.11 及以后的 Kubernetes 集群,默认启用 CoreDNS(处于 GA 状态,见 Software release life cycle)。(来源

笔者通过 aws 提供的 eksctl 工具创建的 v1.15 的集群默认也是启用了 CoreDNS,查阅 eksctl 源码可以获取其默认启用的插件。

Kubernetes 通过修改每个 Pod 中每个容器的域名解析配置文件 /etc/resolv.conf 来达到服务发现的目的。在笔者创建的集群中获取其中一个容器的域名解析配置文件如下:

# /etc/resolv.conf
nameserver 10.100.0.10
search cafe.svc.cluster.local svc.cluster.local cluster.local us-west-2.compute.internal
options ndots:5

其含义是:DNS 服务器为 10.100.0.10,当查询关键词中 . 的数量少于 5 个,则根据 search 中配置的域名进行查询,当查询都没有返回正确响应时再尝试直接查询关键词本身。

例如,执行 host -v cn.bing.com 后我们将会看到:

Trying "cn.bing.com.cafe.svc.cluster.local"
Trying "cn.bing.com.svc.cluster.local"
Trying "cn.bing.com.cluster.local"
Trying "cn.bing.com.us-west-2.compute.internal"
Trying "cn.bing.com"
...

解析过程是如此缓慢,当对某些服务访问频繁时建议额外配置 DNS 记录。

注:获取过程如下

# 1) 查询指定命名空间中的所有 pod
kubectl get pods --namespace=cafe
# 2) 进入其中一个 pod 的交互终端
kubectl exec -ti macchiato-6746674bdd-5hmtw sh --namespace=cafe
# 3) 查看 /etc/resolv.conf
cat /etc/resolv.conf

DNS 服务器会监听着集群内所有 Service API,以在服务不可用时移除记录,在新服务创建时插入新记录。

这些记录存放在哪里呢?

答案是:存储在 kube-dns 插件中的 cache 也可配置到 etcd。

存储的 DNS 记录有哪些种类呢?

我们过去或多或少了解到的 DNS 记录有以下几种:

类别 作用
A Address record,域名到 IP 地址的记录
CNAME Canonical name record,别名记录,设置域名的别名
NS Name server record,域名服务器记录
MX Mail exchange record,邮件服务记录
TXT Text record,为某条记录设置说明
AAAA IPv6 address record,域名到 IPv6 地址 ( 128 = 32 * 4 )的记录

这次我们要多认识一个称之为 SRV(Service locator)的 DNS 记录,用来通用化地定位服务。

Kubernetes 的 DNS 服务(简称为 kube-dns)支持 Service 的 A 记录、 SRV 记录和 CNAME 记录

我们知道 Kubernetes 中的 Service 是 Pod 的逻辑分组,有 Cluster IP 和 Label Selector 有无之别。没有设置 Cluster IP 的我们称之为 Headless Service,否则称之为 Normal Service。设置了 Label Selector 的会同时产生一个 Endpoints 对象,声明集群内部 Service 的访问端点。

假定有一个 cafe 命名空间下名为 latte 的 Normal Service,开放了名为 http 的 TCP 端口 8080,kube-dns 会为其生成以下的 A 记录和 SRV 记录:

latte.cafe.svc.cluster.local. 5 IN A 10.100.71.56
_http._tcp.latte.cafe.svc.cluster.local. 30 IN SRV 10 100 443 latte.cafe.svc.cluster.local.

注:查询 DNS 记录的方法

(1)安装 dig 工具

将下面的部署配置保存到文件 dnsutils.yaml,然后执行 kubectl apply -f dnsutils.yaml 部署。

apiVersion: v1
kind: Pod
metadata:
name: dnsutils
namespace: default
spec:
containers:
- name: dnsutils
image: tutum/dnsutils
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
restartPolicy: Always

(2)使用 dig 工具获取 DNS 记录

# 用法
dig @<DNS服务器> <记录类型> <域名> <可选值>
# 示例
## 1)获取 DNS 服务地址
kubectl get svc kube-dns -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.100.0.10 <none> 53/UDP,53/TCP 8d
## 2)进入 dnsutils 的 shell 终端
kubectl exec -ti dnsutils sh
## 3)查询 latte 服务的 A 记录
dig @10.100.0.10 A latte.cafe.svc.cluster.local +noall +answer

假如有一个 cafe 命名空间下名为 mocha 的 Headless Service,kube-dns 会为其生成以下的 A 记录集(域名到 Pod IPs 的映射):

mocha.cafe.svc.cluster.local. 5 IN A 192.168.62.111
mocha.cafe.svc.cluster.local. 5 IN A 192.168.62.112
mocha.cafe.svc.cluster.local. 5 IN A 192.168.62.113

如若有一个 cafe 命名空间下名为 macchiato 的 Headless 但设置了以下 Endpoints 的 Service:

kind: Endpoints
apiVersion: v1
metadata:
name: macchiato
namespace: cafe
subsets:
- addresses:
- ip: 1.2.3.4
ports:
- port: 9376

kube-dns 会为其生成以下的 A 记录:

macchiato.cafe.svc.cluster.local. 4 IN A 1.2.3.4

如果有一个 cafe 命名空间下名为 cappuccino 的 Headless 但设置了以下 ExternalName 的 Service:

kind: Service
apiVersion: v1
metadata:
name: cappuccino
namespace: cafe
spec:
type: ExternalName
externalName: cappuccino.cafe.com

kube-dns 会为其生成以下的 CNAME 记录:

cappuccino.cafe.svc.cluster.local. 10 IN CNAME cappuccino.cafe.com.

Kubernetes 的 DNS 服务除了支持 Service 的 DNS 记录外,还支持 Pod 的 A 记录,使用 hostname + subdomain 方式实现。仔细阅读以下部署配置。

apiVersion: v1
kind: Service
metadata:
name: default-subdomain
spec:
selector:
name: busybox
clusterIP: None
ports:
- name: foo # Actually, no port is needed.
port: 1234
targetPort: 1234
---
apiVersion: v1
kind: Pod
metadata:
name: busybox1
labels:
name: busybox
spec:
hostname: busybox-1
subdomain: default-subdomain
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
name: busybox
---
apiVersion: v1
kind: Pod
metadata:
name: busybox2
labels:
name: busybox
spec:
hostname: busybox-2
subdomain: default-subdomain
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
name: busybox

我们发现其创建了:

  1. name 为 busybox1,hostname 为 busybox-1,subdomain 为 default-subdomain 的 Pod;
  2. name 为 busybox2,hostname 为 busybox-2,subdomain 为 default-subdomain 的 Pod;
  3. name 为 default-subdomain 的 Headless Service。

产生以下 A 记录:

busybox-1.default-subdomain.svc.cluster.local. 4 IN A 192.168.51.119
busybox-2.default-subdomain.svc.cluster.local. IN A
busybox-2.default-subdomain.svc.cluster.local. 4 IN A 192.168.36.188
default-subdomain.svc.cluster.local. 4 IN A 192.168.62.187
default-subdomain.svc.cluster.local. 4 IN A 192.168.62.188

这些记录是怎样的一种格式呢?

参见:https://github.com/kubernetes/dns/blob/master/docs/specification.md

调试 DNS 服务

使用 busybox 调试 DNS 服务,因为 busybox 中有 nslookup 工具可以使用。

(1)保存以下文本到文件 busybox.yaml(此处使用命名空间为 cafe )

apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: cafe
spec:
containers:
- name: busybox
image: busybox:1.28
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
restartPolicy: Always

(2)应用 busybox.yaml,并查看状态

kubectl apply -f busybox.yaml --namespace=cafe
kubectl get pods busybox --namespace=cafe

(3)进入终端交互界面并支持 nslookup 查询服务 latte

kubectl exec -ti busybox sh --namespace=cafe
nslookup latte

存根域及上游 DNS

当无自定义配置时,不匹配的 DNS 查询(比如上文说的cn.bing.com)会使用从 Node 中继承的 nameserver 进行解析。

当有自定义的配置时,会在 DNS 缓存层查询无果后,根据查询名称后缀决定去往的 DNS 解析器:

  • 查询名称带有集群后缀的(比如 ".cluster.local"),转发到 kube-dns。
  • 查询名称带有存根域名后缀的(比如 ".acme.local"),转发到 custom DNS。
  • 查询名称不匹配的(比如 "widget.com"),转发到 upstream DNS。

Kubernetes 服务发现

以上配置使用 Kubernetes 的ConfigMap 插件,配置如下:

apiVersion: v1
kind: ConfigMap
metadata:
name: kube-dns
namespace: kube-system
data:
stubDomains: |
{"acme.local": ["1.2.3.4"]}
upstreamNameservers: |
["8.8.8.8","8.8.4.4"]