Kubernetes内部服务发现

  • A+
所属分类:Kubernetes
文章目录[隐藏]

前面我们讲解了 Service 的用法,我们可以通过 Service 生成的 ClusterIP(VIP)来访问 Pod提供的服务,但是在使用的时候还有⼀个问题:我怎么知道某个应用的 VIP 呢?比如我们有两个应用,⼀个是 api 应用,⼀个是 db 应用,两个应用都是通过 Deployment 进行管理的,并且都通过 Service暴露出了端口提供服务。api 需要连接到 db 这个应用,我们只知道 db 应用的名称和 db 对应的Service 的名称,但是并不知道它的 VIP 地址,我们前面的 Service 内容中学习到通过ClusterIP 就可以访问到后⾯的 Pod 服务,如果我们知道了 VIP 的地址是不是就行了呢?

 

APIServer

我们可以从 apiserver 中直接查询获取到对应 service 的后端 Endpoints信息,所以最简单的办法是从 apiserver 中直接查询,如果偶尔⼀个特殊的应用,我们通过 apiserver 去查询到 Service 后面的Endpoints 直接使用是没问题的,但是如果每个应用都在启动的时候去查询依赖的服务,这不但增加了应用的复杂度,也导致了应用需要依赖 Kubernetes 了,耦合度太高了,不具有通用性。

 

环境变量

为了解决上⾯的问题,在之前的版本中,Kubernetes 采用了环境变量的方法,每个 Pod 启动的时候,会通过环境变量设置所有服务的 IP 和 port 信息,这样 Pod 中的应用可以通过读取环境变量来获取依赖服务的地址信息,这种⽅法使用起来相对简单,但是有⼀个很大的问题就是依赖的服务必须在 Pod启动之前就存在,不然是不会被注入到环境变量中的。比如我们⾸先创建⼀个 Nginx 服务:(testnginx.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  labels:
    k8s-app: nginx-demo
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    name: nginx-service
spec:
  ports:
  - port: 5000
    targetPort: 80
  selector:
    app: nginx

Kubernetes内部服务发现

创建上面的服务我们可以看到两个 Pod 和⼀个名为 nginx-service 的服务创建成功了,该 Service 监听的端口是5000,同时它会把流量转发给它代理的所有 Pod(这里就拥有 app: nginx 标签的两个 Pod)。

现在我们再来创建⼀个普通的 Pod,观察下该 Pod 中的环境变量是否包含上⾯的 nginx-service 的服务信息:(test-pod.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: test-api
spec:
  containers:
  - name: test-api
    image: busybox
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh", "-c", "env"]

Kubernetes内部服务发现

然后创建该测试的 Pod,等 Pod 创建完成后,我们查看日志信息:

Kubernetes内部服务发现

我们可以看到打印了很多环境变量处理,其中就包括我们刚刚创建的 nginx-service 这个服务,有HOST、PORT、PROTO、ADDR 等,也包括其他已经存在的 Service 的环境变量。

如果我们需要在这个 Pod 里面访问 nginx-service 的服务,我们可以直接通过NGINX_SERVICE_SERVICE_HOST 和 NGINX_SERVICE_SERVICE_PORT 来访问就可以了,但是我们也知道如果这个 Pod 启动起来的时候如果 nginx-service 服务还没启动起来,在环境变量中我们是无法获取到这些信息的,当然我们可以通过 initContainer 之类的方法来确保 nginx-service 启动后再启动Pod,但是这种方法毕竟增加了 Pod 启动的复杂性,所以这不是最优的方法。

 

CoreDNS

好了、言归正传,由于上面环境变量这种方式的局限性,我们需要⼀种更加智能的方案,其实我们可以自己去选用⼀套比较理想或者适合自己的方案:那就是可以直接使用 Service 的名称,因为 Service 的名称不会变化,我们不需要去关心分配的 ClusterIP 的地址,因为这个地址并不是固定不变的,所以如果我们直接使用 Service 的名字,然后对应的 ClusterIP 地址的转换能够自动完成就很好了。我们知道名字和 IP 直接的转换是不是和我们平时访问的网站非常类似。他们之间的转换功能通过 DNS 就可以解决了,同样的,Kubernetes 也提供了 DNS 的方案来解决上面的服务发现的问题。

DNS 服务不是⼀个独立的系统服务,而是作为⼀种 addon 插件而存在,也就是说不是 Kubernetes 集群必须安装的,当然我们强烈推荐安装,可以将这个插件看成是⼀种运行在 Kubernetes 集群上的⼀种比较特殊的应用,现在比较推荐的插件有两个:kube-dns 和 CoreDNS。从Kubernetes 1.11版本开始kubeadm团队就将CoreDNS作为默认选项,我们在前⾯使用 kubeadm 搭建集群的时候虽然下载了 kube-dns 镜像,但是在初始化集群安装的时候提示我们需要安装CoreDNS,如果不记得了可以回头去看⼀看。如果没有开启CoreDNS的小伙伴可以通过下面的命令开启:

kubeadm init --feature-gates=CoreDNS=true

 

Kubernetes CoreDNS pod 中包括 2 个容器,可以通过 kubectl 工具查看:

Kubernetes内部服务发现

我们可以看到有2个 coredns 容器,用如下命令可以很清楚的看到 kube-dns 包含的 2 个容器详情:

kubectl describe pod coredns-6955765f44-n88lg -n kube-system

 

什么是CoreDNS?

我们先来看看什么是CoreDNS。CoreDNS 是新晋的 CNCF 孵化项目(社区也计划将其作为 Kubernetes 默认的 DNS 方案)。CoreDNS 的目标是成为 cloud-native 环境下的 DNS 服务器和服务发现解决方案。

Kubernetes内部服务发现

CoreDNS特点

  • 插件化(Plugins)

基于 Caddy 服务器框架,CoreDNS 实现了一个插件链的架构,将大量应用端的逻辑抽象成 plugin (下文将混用 plugin 和 插件 这两个词汇)的形式(如 Kubernetes 的 DNS 服务发现,Prometheus 监控等)暴露给使用者。CoreDNS 以预配置的方式将不同的 plugin 串成一条链,按序执行 plugin 的逻辑。从编译层面,用户选择所需的 plugin 编译到最终的可执行文件中,使得运行效率更高。CoreDNS 采用 Go 编写,所以从具体代码层面来看,每个 plugin 其实都是实现了其定义的 interface 的组件而已。第三方只要按照 CoreDNS Plugin API 去编写自定义插件,就可以很方便地集成于 CoreDNS;

  • 配置简单化

引入表达力更强的 DSL,即 Corefile 形式的配置文件(也是基于 Caddy 框架开发);

  • 一体化的解决方案

区别于 kube-dns,CoreDNS 编译出来就是一个单独的二进制可执行文件,内置了 cache,backend storage ,health check 等功能,无需第三方组件来辅助实现其他功能,从而使得部署更方便,内存管理更为安全;其实从功能角度来看,CoreDNS 更像是一个通用 DNS 方案(类似于 BIND),然后通过插件模式来极大地扩展自身功能,从而可以适用于不同的场景(比如 Kubernetes)。

 

CoreDNS架构

整个 CoreDNS 服务都建立在一个使用 Go 编写的 HTTP/2 Web 服务器 Caddy · GitHub 上,CoreDNS 整个项目可以作为一个 Caddy 的教科书用法。

Kubernetes内部服务发现

CoreDNS 的大多数功能都是由插件来实现的,插件和服务本身都使用了 Caddy 提供的一些功能,所以项目本身也不是特别的复杂。

 

CoreDNS插件

作为基于 Caddy 的 Web 服务器,CoreDNS 实现了一个插件链的架构,将很多 DNS 相关的逻辑都抽象成了一层一层的插件,包括 Kubernetes 等功能,每一个插件都是一个遵循如下协议的结构体:

type (
    Plugin func(Handler) Handler

    Handler interface {
        ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error)
        Name() string
    }
)

所以只需要为插件实现 ServeDNS 以及 Name 这两个接口并且写一些用于配置的代码就可以将插件集成到 CoreDNS 中。

 

CoreFile

另一个 CoreDNS 的特点就是它能够通过简单易懂的 DSL 定义 DNS 服务,在 Corefile 中就可以组合多个插件对外提供服务:

coredns.io:5300 {
    file db.coredns.io
}

example.io:53 {
    log
    errors
    file db.example.io
}

example.net:53 {
    file db.example.net
}

.:53 {
    kubernetes
    proxy . 8.8.8.8
    log
    errors
    cache
}

对于上面的配置文件,CoreDNS 会根据每一个代码块前面的区和端点对外暴露两个端点提供服务:

 

Kubernetes内部服务发现

该配置文件对外暴露了两个 DNS 服务,其中一个监听在 5300 端口,另一个在 53 端口,请求这两个服务时会根据不同的域名选择不同区中的插件进行处理。

 

CoreDNS原理

CoreDNS 可以通过四种方式对外直接提供 DNS 服务,分别是 UDP、gRPC、HTTPS 和 TLS:

Kubernetes内部服务发现

但是无论哪种类型的 DNS 服务,最终队会调用以下的 ServeDNS 方法,为服务的调用者提供 DNS 服务。

Kubernetes内部服务发现

当然、除了通过 ServeDNS 调用下一个插件之外,我们也可以调用 WriteMsg 方法并结束整个调用链。从插件的堆叠到顺序调用以及错误处理,现在我们对 CoreDNS 的工作原理已经非常清楚了。

注:CoreDNS 实现的功能和 KubeDNS 是⼀致的,不过 CoreDNS 的所有功能都集成在了同⼀个容器中,在最新版的1.11.0版本中官方已经推荐使用 CoreDNS了,大家也可以安装 KubeDNS 来代替 CoreDNS,其他使用方法都是⼀致的。

 

测试DNS

现在我们来使用⼀个简单 Pod 来测试下 Service 的域名访问:

kubectl run busybox --rm -it --image=docker.io/busybox:1.28.4 /bin/sh

注:这里要使用老版本的busybox镜像1.28.4、新版的busybox使用nslookup命令会报错。

 

Kubernetes内部服务发现

我们进入到 Pod 中,查看/etc/resolv.conf中的内容,可以看到 nameserver 的地址10.96.0.10,该 IP地址是在安装 CoreDNS 插件的时候集群分配的⼀个固定的静态 IP 地址,我们可以通过下面的命令进行查看:

Kubernetes内部服务发现

也就是说 Pod 现在默认的 nameserver 就是 kube-dns 的地址,现在我们来访问一下前面我们创建的 nginx-service 服务:

Kubernetes内部服务发现

可以看到上面我们使用 wget 命令去访问 nginx-service 服务的域名的时候被 hang 住了,没有得到期望的结果,这是因为上⾯我们建⽴ Service 的时候暴露的端口是 5000。

加上 5000 端口,就正常访问到服务,再试⼀试访问:nginx-service.default.svc、nginxservice.default、nginx-service,不出意外这些域名都可以正常访问并得到期望的结果。到这里我们就实现了在集群内部通过 Service 的域名形式进⾏互相通信了,大家也可以试着看看访问不同 namespace 下面的服务。

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin
avatar

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: