时速云第十五期微信群直播分享新鲜出炉,本期有时速云工程师黄鑫为小伙伴们带来了关于“ Kubernetes 如何使用 kube-dns 实现服务发现”的技术分享,关于如何发现 Pod 提供的服务、如何使用 Service 发现服务,以及如何使用 kube-dns 发现服务这些让你蓝瘦香菇的问题,统统都有完整解答,以下奉上分享的文字版!(视频回放地址: http://t.cn/RVfbQG5 )
• Kubernetes 中如何发现服务
• 如何发现 Pod 提供的服务
• 如何使用 Service 发现服务
• 如何使用 kube-dns 发现服务
• kube-dns 原理
• 组成
• 域名格式
• 配置
注:本次分享内容基于 Kubernetes 1.2 版本!
下面从一个简单的例子开始讲解。
◆ 发现 Pod 提供的服务
首先使用 nginx-deployment.yaml 文件创建一个 Nginx Deployment ,文件内容如图所示:
首先创建两个运行 Nginx 服务的 Pod :
使用 kubectl create -f nginx-deployment.yaml 指令创建,这样便可以得到两个运行 nginx 服务的 Pod 。待 Pod 运行之后查看一下它们的 IP ,并在 k8s 集群内通过 podIP 和 containerPort 来访问 Nginx 服务:
获取 Pod IP :
在集群内访问 Nginx 服务:
看到这里相信很多人会有以下疑问:
每次收到获取 podIP 太扯了,总不能每次都要手动改程序或者配置才能访问服务吧,要怎么提前知道 podIP 呢?
Pod 在运行中可能会重建, IP 变了怎么解?
如何在多个 Pod 中实现负载均衡嘞? 这些问题使用 k8s Service 就可以解决。
◆ 使用 Service 发现服务
下面为两个 Nginx Pod 创建一个 Service 。使用 nginx-service.yaml 文件进行创建,文件内容如下:
创建之后,仍需要获取 Service 的 Cluster-IP ,再结合 Port 访问 Nginx 服务。
Service 可以将 pod IP 封装起来,即使 Pod 发生重建,依然可以通过 Service 来访问 Pod 提供的服务。此外, Service 还解决了负载均衡的问题,大家可以多访问几次 Service ,然后通过 kubectl logs <pod name="">来查看两个 Nginx Pod 的访问日志来确认。
获取 IP :
在集群内访问 Service :
虽然 Service 解决了 Pod 的服务发现和负载均衡问题,但存在着类似的问题:不提前知道 Service 的 IP ,还是需要改程序或配置啊。看到这里有没有感觉身体被掏空?
接下来聊聊 kube-dns 是如何解决上面这个问题的。
◆ 使用 kube-dns 发现服务
kube-dns 可以解决 Service 的发现问题, k8s 将 Service 的名称当做域名注册到 kube-dns 中,通过 Service 的名称就可以访问其提供的服务。
可能有人会问如果集群中没有部署 kube-dns 怎么办?没关系,实际上 kube-dns 插件只是运行在 kube-system 命名空间下的 Pod ,完全可以手动创建它。可以在 k8s 源码( v1.2 )的 cluster/addons/dns 目录下找到两个模板( skydns-rc.yaml.in 和 skydns-svc.yaml.in )来创建,为大家准备的完整示例文件会在分享结束后提供获取方式, PPT 中只截取了部分内容。
通过 skydns-rc.yaml 文件创建 kube-dns Pod ,其中包含了四个 containers ,这里开始简单过一下文件的主要部分,稍后做详细介绍。
第一部分可以看到 kube-dns 使用了 RC 来管理 Pod ,可以提供最基本的故障重启功能。
创建 kube-dns Pod ,其中包含了 4 个 containers
接下来是第一个容器 etcd ,它的用途是保存 DNS 规则。
第二个容器 kube2sky ,作用是写入 DNS 规则。
第三个容器是 skydns ,提供 DNS 解析服务。
最后一个容器是 healthz ,提供健康检查功能。
有了 Pod 之后,还需要创建一个 Service 以便集群中的其他 Pod 访问 DNS 查询服务。通过 skydns-svc.yaml 创建 Service ,内容如下:
创建完 kube-dns Pod 和 Service ,并且 Pod 运行后,便可以访问 kube-dns 服务。
下面创建一个 Pod ,并在该 Pod 中访问 Nginx 服务:
创建之后等待 kube-dns 处于运行状态
再新建一个 Pod ,通过其访问 Nginx 服务
在 curl-util Pod 中通过 Service 名称访问 my-nginx Service :
只要知道需要的服务名称就可以访问,使用 kube-dns 发现服务就是那么简单。
虽然领略了使用 kube-dns 发现服务的便利性,但相信有很多人也是一头雾水: kube-dns 到底怎么工作的?在集群中启用了 kube-dns 插件,怎么就能通过名称访问 Service 了呢?
◆ Kube-dns 组成
之前已经了解到 kube-dns 是由四个容器组成的,它们扮演的角色可以通过下面这张图来理解。
其中:
● SkyDNS 是用于服务发现的开源框架,构建于 etcd 之上。作用是为 k8s 集群中的 Pod 提供 DNS 查询接口。项目托管于 https://github.com/skynetservices/skydns
● etcd 是一种开源的分布式 key-value 存储,其功能与 ZooKeeper 类似。在 kube-dns 中的作用为存储 SkyDNS 需要的各种数据,写入方为 kube2sky ,读取方为 SkyDNS 。项目托管于 https://github.com/coreos/etcd 。
● kube2sky 是 k8s 实现的一个适配程序,它通过名为 kubernetes 的 Service (通过 kubectl get svc 可以查看到该 Service ,由集群自动创建)调用 k8s 的 list 和 watch API 来监听 k8s Service 资源的变更,从而修改 etcd 中的 SkyDNS 记录。代码可以在 k8s 源码( v1.2 )的 cluster/addons/dns/kube2sky/目录中找到。
● exec-healthz 是 k8s 提供的一种辅助容器,多用于 side car 模式中。它的原理是定期执行指定的 Linux 指令,从而判断当前 Pod 中关键容器的健康状态。在 kube-dns 中的作用就是通过 nslookup 指令检查 DNS 查询服务的健康状态, k8s livenessProbe 通过访问 exec-healthz 提供的 Http API 了解健康状态,并在出现故障时重启容器。其源码位于 https://github.com/kubernetes/contrib/tree/master/exec-healthz 。
● 从图中可以发现, Pod 查询 DNS 是通过 ServiceName.Namespace 子域名来查询的,但在之前的示例中只用了 Service 名称,什么原理呢?其实当我们只使用 Service 名称时会默认 Namespace 为 default ,而上面示例中的 my-nginx Service 就是在 default Namespace 中,因此是可以正常运行的。关于这一点,后续再深入介绍。
● skydns-rc.yaml 中可以发现 livenessProbe 是设置在 kube2sky 容器中的,其意图应该是希望通过重启 kube2sky 来重新写入 DNS 规则。
◆ 域名格式
接下来了解一下 kube-dns 支持的域名格式,具体为:<service_name>.<namespace>.svc.<cluster_domain>。
其中 cluster_domain 可以使用 kubelet 的--cluster-domain=SomeDomain 参数进行设置,同时也要保证 kube2sky 容器的启动参数中--domain 参数设置了相同的值。通常设置为 cluster.local 。那么之前示例中的 my-nginx Service 对应的完整域名就是 my-nginx.default.svc.cluster.local 。看到这里,相信很多人会有疑问,既然完整域名是这样的,那为什么在 Pod 中只通过 Service 名称和 Namespace 就能访问 Service 呢?下面来解释其中原因。
◆ 域名解析配置
为了在 Pod 中调用其他 Service , kubelet 会自动在容器中创建域名解析配置(/etc/resolv.conf ),内容为:
感兴趣的可以在网上查找一些 resolv.conf 的资料来了解具体的含义。之所以能够通过 Service 名称和 Namespace 就能访问 Service ,就是因为 search 配置的规则。在解析域名时会自动拼接成完整域名去查询 DNS 。
刚才提到的 kubelet --cluster-domain 参数与 search 的具体配置是相对应的。而 kube2sky 容器的--domain 参数影响的是写入到 etcd 中的域名, kube2sky 会获取 Service 的名称和 Namespace ,并使用--domain 参数拼接完整域名。这也就是让两个参数保持一致的原因。
◆ NS 相关配置
kube-dns 可以让 Pod 发现其他 Service ,那 Pod 又是如何自动发现 kube-dns 的呢?在上一节中的 /etc/resolv.conf 中可以看到 nameserver ,这个配置就会告诉 Pod 去哪访问域名解析服务器。
相应的,可以在之前提到的 skydns-svc.yaml 中看到 spec.clusterIP 配置了相同的值。通常来说创建一个 Service 并不需要指定 clusterIP , k8s 会自动为其分配,但 kube-dns 比较特殊,需要指定 clusterIP 使其与 /etc/resolv.conf 中的 nameserver 保持一致。
修改 nameserver 配置同样需要修改两个地方,一个是 kubelet 的--cluster-dns 参数,另一个就是 kube-dns Service 的 clusterIP 。
接下来重新梳理一下本文的主要内容:
● 在 k8s 集群中,服务是运行在 Pod 中的, Pod 的发现和副本间负载均衡是我们面临的问题。
● 通过 Service 可以解决这两个问题,但访问 Service 也需要对应的 IP ,因此又引入了 Service 发现的问题。
● 得益于 kube-dns 插件,我们可以通过域名来访问集群内的 Service ,解决了 Service 发现的问题。
● 为了让 Pod 中的容器可以使用 kube-dns 来解析域名, k8s 会修改容器的 /etc/resolv.conf 配置。 有了以上机制的保证,就可以在 Pod 中通过 Service 名称和 namespace 非常方便地访问对应的服务了。
答:两个问题可以抽象为 k8s 集群内与集群外服务连通性问题,我们从两个方面讲:
一、集群内访问集群外
这个问题比较简单,集群内的 Pod 会继承 Node 上的 DNS 解析规则。因此只要 Node 可以访问的服务, Pod 中也可以访问到。
另外,在 1.4 版本中, k8s 支持了一种 ExternalName 类型的 Service ,可以与一个公网域名绑定,通过该 Service 可以访问对应公网服务。
二、集群外访问集群内
可以将 Service 设置为 NodePort 类型,这样通过任意 Node 的 IP 和 Service Port 便可以访问 Service 。适合对外的 Service 比较少的场景。
通过 kube-proxy 可以对外暴露集群内的服务。
根据实际情况在集群内自定义实现反向代理。
问:我想问下 etcd 这个容器可以省掉吗? k8s 集群有 etcd 不可以共用吗?
答:理论上可以共用 etcd 。从隔离性的角度考虑来说还是分开好,这样 kube-dns 服务不会对整个 k8s 集群的稳定性产生影响。另外如果把 kube-dns 看做一个微服务的话,那么应该保证内部组件不依赖外部,可以独立运行。
下次分享将于 10 月 27 日进行,感兴趣的小伙伴赶紧添加微信号:时速云小助手(tenxcloud6)观看直播吧!