服务发现的魔术 - DNS, Service, 与 Ingress 控制器


第一部分:集群内部的“电话簿” - DNS 与 Service

我们已经知道,Pod 的 IP 地址是临时的,因此不能直接使用。在之前的系列中,我们学习了 Service 资源,它为一组 Pod 提供了一个稳定的、虚拟的 ClusterIP。这是解决问题的第一步。

但让应用去记住一串数字 IP(如 10.96.100.200)依然是不现实的。我们需要的是一个像 backend-service 这样稳定、可读的名字。这正是 Kubernetes 内置 DNS 服务的用武之地。

CoreDNS 的工作原理

每个 Kubernetes 集群都会默认部署一个 DNS 服务,目前最常用的是 CoreDNS(它本身也作为一个 DeploymentService 运行在 kube-system 命名空间下)。 当一个 Pod 被创建时,kubelet 会自动地配置其 /etc/resolv.conf 文件,将 DNS 查询指向 CoreDNS 服务的 ClusterIP。

一个 Pod 内的 /etc/resolv.conf 文件通常如下所示:

nameserver 10.96.0.10             # CoreDNS 服务的 ClusterIP
search default.svc.cluster.local svc.cluster.local cluster.local # 搜索域
options ndots:5

当你在 default 命名空间的 frontend-pod 中,尝试访问 backend-svc 时:

  1. 应用发出对 backend-svc 的 DNS 查询请求。
  2. 根据 resolv.conf 中的 search 规则,这个短名称会被自动补全为完整的域名 (FQDN):backend-svc.default.svc.cluster.local
  3. 这个查询请求被发送到 CoreDNS。
  4. CoreDNS 持续地监视着 Kubernetes API。它知道 backend-svc.default 这个 Service 对应的 ClusterIP 是什么。
  5. CoreDNS 返回这个 ClusterIP 给 frontend-pod
  6. frontend-pod 拿到 ClusterIP 后,向其发起请求。后续的流量转发就由我们熟悉的 kube-proxy 接管了。

总结: Service 提供了稳定的 IP,CoreDNS 提供了稳定的名称。两者结合,完美地解决了集群内的“东西向”服务发现问题。


第二部分:打开集群的大门 - Ingress 与 Ingress Controller

解决了内部交通,我们再来看如何处理外部流量(“南北向”流量)。Kubernetes 提供了几种方式来暴露服务:

  • Service 类型 NodePort: 在每个节点上都暴露一个相同的、固定的端口。外部流量可以通过 [任意节点IP]:[NodePort] 来访问服务。缺点:端口管理混乱,不适合生产环境。
  • Service 类型 LoadBalancer: 在公有云上,这会自动向云服务商申请一个外部负载均衡器(如 AWS ELB/NLB),并将其流量指向服务的 NodePort。缺点每暴露一个服务,就需要一个昂贵的负载均衡器,成本高昂,且管理复杂。

为了解决这些问题,Kubernetes 提供了更强大、更灵活的 L7 路由解决方案:Ingress

Ingress vs Ingress Controller

这是一个必须厘清的关键概念:

  • Ingress 资源: 它是一个 API 对象,一种声明。它定义了 HTTP/HTTPS 流量应该如何被路由到集群内部的 Service。你可以定义基于主机名(Host)或路径(Path)的路由规则。
  • Ingress Controller: 它是一个实际运行的程序,是 Ingress 规则的执行者。它持续地监视 Ingress 资源,并根据这些资源的定义,动态地配置其内部的负载均衡器(通常是 Nginx, Traefik, HAProxy 等)。

简单来说:Ingress 资源是你写的“交通规则”,而 Ingress Controller 是在路口指挥交通的“智能交警”。没有交警,规则本身不会生效。

Ingress Controller 通常作为一个 Deployment 运行在集群内部,并通过一个 type: LoadBalancertype: NodePort 的 Service 对外暴露。这样,我们只需要一个外部负载均衡器,就可以通过配置不同的 Ingress 规则,来管理对无数个内部服务的访问,极大地节约了成本并简化了管理。


第三部分:动手实战:使用 Nginx Ingress Controller

前提: 一个可用的 Kubernetes 集群(例如 kind)。

1. 安装 Nginx Ingress Controller

我们将使用社区维护最广泛的 Nginx Ingress Controller。对于 kind 环境,官方提供了专门的安装清单:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.1/deploy/static/provider/kind/deploy.yaml

等待 ingress-nginx 命名空间下的 Pod 都进入 Running 状态。

2. 部署两个示例应用

我们部署 foo-appbar-app,每个都有自己的 Deployment 和 ClusterIP Service。

# apps.yaml
apiVersion: v1
kind: Service
metadata: {name: foo-svc}
spec:selector: {app: foo}ports: [{port: 8080}]
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: foo-deploy}
spec:replicas: 1selector: {matchLabels: {app: foo}}template:metadata: {labels: {app: foo}}spec: {containers: [{name: app, image: "hashicorp/http-echo", args: ["-text=foo"]}]}
---
apiVersion: v1
kind: Service
metadata: {name: bar-svc}
spec:selector: {app: bar}ports: [{port: 8080}]
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: bar-deploy}
spec:replicas: 1selector: {matchLabels: {app: bar}}template:metadata: {labels: {app: bar}}spec: {containers: [{name: app, image: "hashicorp/http-echo", args: ["-text=bar"]}]}

kubectl apply -f apps.yaml

3. 创建 Ingress 资源

现在,我们来编写“交通规则”。

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: example-ingress
spec:rules:- host: "myapp.example.com"http:paths:- path: /foopathType: Prefixbackend:service:name: foo-svcport: {number: 8080}- path: /barpathType: Prefixbackend:service:name: bar-svcport: {number: 8080}

kubectl apply -f ingress.yaml

4. 测试路由规则

由于我们在本地 kind 集群,无法直接使用域名。我们需要直接向 Ingress Controller 的 Pod 发送请求,并通过 Host 头来模拟域名访问。

# 获取 Ingress Controller 的 Pod IP
# 注意:你的 pod 名称和 IP 可能会不同
IC_POD_IP=$(kubectl get pods -n ingress-nginx -l app.kubernetes.io/component=controller -o jsonpath='{.items[0].status.podIP}')# 访问 /foo路径
kubectl exec -it <一个你的 Pod,例如 foo-deploy-xxx> -- curl -H "Host: myapp.example.com" http://$IC_POD_IP/foo
# 预期输出: foo# 访问 /bar路径
kubectl exec -it <一个你的 Pod,例如 foo-deploy-xxx> -- curl -H "Host: myapp.example.com" http://$IC_POD_IP/bar
# 预期输出: bar

实验成功!这证明我们的 Ingress Controller 已经根据 Ingress 资源中的规则,正确地将流量分发到了不同的后端服务。


总结与展望

今天,我们为我们的“云原生城市”构建了高效的交通系统。我们通过 Service 和 CoreDNS,解决了内部服务之间“如何找到彼此”的问题。接着,我们通过 Ingress 和 Ingress Controller,建立了一个强大的、统一的“城门”,学会了如何管理从外部世界进入集群的7层流量。

我们的应用现在不仅内部互联互通,也能够安全、高效地对外提供服务了。

至此,一个功能完备的 Kubernetes 网络基础已经搭建完毕。我们有了 CNI 提供的底层网络,有了 Service 和 Ingress 提供的服务访问能力,也有了 NetworkPolicy 提供的安全保障。下一步是什么?

如果我们需要的不仅仅是路由,而是更精细的控制呢?比如,对服务间的调用进行自动重试、动态地切分 1% 的流量到新版本进行金丝雀测试、或者为每一次调用都自动加上 mTLS 加密?这些 L7 的高级能力,正是服务网格 (Service Mesh) 的用武之地。

在下一篇中,我们将进入云原生网络的应用层,深入探讨服务网格 Istio 如何为我们提供这些强大的流量管理能力。敬请期待!