服务发现的魔术 - DNS, Service, 与 Ingress 控制器
第一部分:集群内部的“电话簿” - DNS 与 Service
我们已经知道,Pod 的 IP 地址是临时的,因此不能直接使用。在之前的系列中,我们学习了 Service
资源,它为一组 Pod 提供了一个稳定的、虚拟的 ClusterIP。这是解决问题的第一步。
但让应用去记住一串数字 IP(如 10.96.100.200
)依然是不现实的。我们需要的是一个像 backend-service
这样稳定、可读的名字。这正是 Kubernetes 内置 DNS 服务的用武之地。
CoreDNS 的工作原理
每个 Kubernetes 集群都会默认部署一个 DNS 服务,目前最常用的是 CoreDNS(它本身也作为一个 Deployment
和 Service
运行在 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
时:
- 应用发出对
backend-svc
的 DNS 查询请求。 - 根据
resolv.conf
中的search
规则,这个短名称会被自动补全为完整的域名 (FQDN):backend-svc.default.svc.cluster.local
。 - 这个查询请求被发送到 CoreDNS。
- CoreDNS 持续地监视着 Kubernetes API。它知道
backend-svc.default
这个 Service 对应的 ClusterIP 是什么。 - CoreDNS 返回这个 ClusterIP 给
frontend-pod
。 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: LoadBalancer
或 type: 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-app
和 bar-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 如何为我们提供这些强大的流量管理能力。敬请期待!