第9章:K8s 核心概念(下)- 服务发现与配置

118 阅读9分钟

第9章:K8s 核心概念(下)- 服务发现与配置

在上一章,我们的 my-app 应用已经通过 Deployment 在 K8s 中稳定运行了。但它就像一座“孤岛”:

  1. 对外:集群外部的用户无法访问它。
  2. 对内:如果集群内有其他服务(比如一个前端应用)想要调用它,也很困难。

为什么困难?因为 Pod 的 IP 地址是动态的、不固定的。每当 Deployment 进行更新或 Pod 发生故障被重建时,新 Pod 的 IP 都会改变。我们不能依赖这个不稳定的 IP 地址。

K8s 用一个绝妙的资源解决了这个问题:Service

9.1 Service:让你的应用被“看见”

Service 是 K8s 网络世界的核心。你可以把它想象成一个应用的 “固定门牌号”“内部总机号码”

  • 核心职责:为一个或多个功能相同的 Pod,提供一个稳定的、统一的访问入口
  • 工作原理
    1. 你创建一个 Service,并为它定义一个选择器 (Selector),这个选择器会“圈出”所有带有特定标签 (Label) 的 Pod。
    2. Service 会获得一个虚拟的、固定的 IP 地址(称为 ClusterIP)和一个稳定的 DNS 名称
    3. 当有流量访问这个 Service 的地址时,Service 会自动地将流量负载均衡到它背后所代理的、健康的 Pod 上。

这个过程对客户端是完全透明的。客户端(无论是集群内的其他 Pod 还是外部用户)只需要知道 Service 的固定地址,而完全不需要关心后端 Pod 的数量、IP 地址或健康状况。

Service 的几种类型

根据你想如何暴露应用,Service 提供了几种不同的 type

  1. ClusterIP

    • 这是默认类型。
    • Service 只会获得一个集群内部的 IP 地址。
    • 用途:仅用于集群内部服务之间的通信。比如,一个 Web 后端服务需要被前端服务调用,但不需要暴露给外部用户。
  2. NodePort

    • 在 ClusterIP 的基础上,K8s 会在每一个 Node 节点上,都开放一个相同的、固定的端口(范围通常是 30000-32767)。
    • 用途:用于将服务对外暴露。你可以通过访问 http://<任何一个Node节点的IP>:<NodePort> 来访问你的服务。这在开发和测试环境中非常方便。
  3. LoadBalancer

    • 这是 NodePort 的增强版。
    • 用途:在公有云环境(如 AWS, GCP, Azure)下,当你创建一个 LoadBalancer 类型的 Service 时,云服务商会自动为你创建一个外部负载均衡器,并将其绑定到这个 Service。这是在生产环境中暴露服务的标准方式

9.2 ConfigMap & Secret:将配置与代码解耦

一个健壮的应用,应该将配置代码分离。我们不应该把数据库地址、API 密钥等信息硬编码在 Docker 镜像里。

K8s 提供了两种专门的资源来管理配置信息:

  1. ConfigMap

    • 用途:用于存储非敏感的、键值对形式的配置数据。比如,应用的日志级别、功能开关、主题名称等。
  2. Secret

    • 用途:专门用于存储敏感信息,如数据库密码、API Token、TLS 证书等。
    • 特点:Secret 中的数据是以 Base64 编码的形式存储的(注意:编码不是加密!),但 K8s 会以更安全的方式来管理和分发它。

创建好 ConfigMap 或 Secret 后,我们可以通过两种主要方式将它们“注入”到 Pod 中:

  • 作为环境变量:将键值对直接注入为容器的环境变量。
  • 作为数据卷挂载:将键值对挂载成文件,让容器内的应用去读取。

9.3 [实战] 为我们的应用创建 Service,并从外部访问

是时候为我们的 my-app-deployment 打开通往世界的大门了。

第一步:创建 Service YAML 文件

my-app 目录下,创建一个 service.yaml 文件:

# API 版本
apiVersion: v1
# 资源类型为 Service
kind: Service
# 元数据
metadata:
  name: my-app-service
# 规格
spec:
  # Service 类型,我们用 NodePort 来从外部访问
  type: NodePort
  # 选择器,告诉 Service 要代理哪些 Pod
  # 这里的标签必须和 Deployment 中 Pod 模板的标签完全一致
  selector:
    app: my-app
  # 端口定义
  ports:
    - protocol: TCP
      # Service 自身监听的端口
      port: 80
      # 流量要转发到 Pod 的哪个端口
      targetPort: 8080
      # 在 Node 节点上暴露的端口
      # 如果不指定,K8s 会自动分配一个
      # nodePort: 30007

第二步:应用配置

kubectl apply -f service.yaml

验证 Pod 状态

在访问之前,确保你的 Pod 真的在运行:

# 查看 Pod 状态
kubectl get pods
# 查看 Pod 详细信息
kubectl describe pods
# 检查 Service 是否匹配到了 Pod
kubectl get endpoints my-app-service

如果 kubectl get endpoints my-app-service 显示为空,说明 Service 没有找到匹配的 Pod,请检查 Deployment 的标签是否与 Service 的选择器一致。

第三步:查看 Service 并访问

# 查看 Service
kubectl get service my-app-service
# 或者简写
kubectl get svc

你会看到类似这样的输出,记下 PORT(S) 那一列中 80 后面映射的 NodePort 端口号(比如 31006)。

NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE
my-app-serviceNodePort10.107.33.3880:31006/TCP8s

重要:访问 Minikube 中的服务

由于 Minikube 运行在虚拟机中,直接通过 NodePort 访问可能会遇到网络问题(我在 macOS + Docker Desktop 环境就遇到了)。我们有几种方式来访问服务:

方法 1:使用 minikube service 命令(推荐)

# 自动打开浏览器并处理端口转发
minikube service my-app-service

运行结果:

mac@192 docker-k8s-little-book-source-code % minikube service my-app-service
|-----------|----------------|-------------|---------------------------|
| NAMESPACE |      NAME      | TARGET PORT |            URL            |
|-----------|----------------|-------------|---------------------------|
| default   | my-app-service |          80 | http://192.168.49.2:31006 |
|-----------|----------------|-------------|---------------------------|
🏃  为服务 my-app-service 启动隧道。
|-----------|----------------|-------------|------------------------|
| NAMESPACE |      NAME      | TARGET PORT |          URL           |
|-----------|----------------|-------------|------------------------|
| default   | my-app-service |             | http://127.0.0.1:53952 |
|-----------|----------------|-------------|------------------------|
🎉  正通过默认浏览器打开服务 default/my-app-service...
❗  因为你正在使用 darwin 上的 Docker 驱动程序,所以需要打开终端才能运行它。

访问http://127.0.0.1:53952(注意:这个每次运行的时候端口号会不一样),就会看到我们的示例出来了

方法 2:使用端口转发(最可靠)

# 将 Service 端口转发到本地 8080 端口
kubectl port-forward service/my-app-service 8080:80
# 然后在浏览器访问 http://localhost:8080

这里因为指定的8080端口,就不会每次不一样了

方法 3:直接访问(某些环境可行)

获取 Minikube 节点的 IP 地址:

minikube ip

假设得到的 IP 是 192.168.49.2,NodePort 是 31006。现在,打开浏览器,访问 http://192.168.49.2:31006

📋 适合方法 3 minikube ip 方案的情况:

  • Linux 系统 + 裸机 Minikube (不是 Docker Desktop)
  • Windows 系统 + Hyper-V 驱动
  • macOS + VirtualBox/Parallels 驱动 (不是 Docker Desktop)
  • 云环境 中的 Minikube ❌ 不适合的情况:
  • macOS + Docker Desktop
  • Windows + Docker Desktop

成功了! 你已经通过 NodePort 访问到了 K8s 集群内部的 Pod。

9.4 [实战] 使用 ConfigMap 注入环境变量

我们来修改一下应用,让它返回的问候语可以通过环境变量来配置。

第一步:创建 ConfigMap

创建一个 configmap.yaml 文件:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-app-config
data:
  # 定义我们的键值对配置
  GREETING_MESSAGE: "Hello from ConfigMap!"

应用它:kubectl apply -f configmap.yaml

第二步:修改 Deployment

修改 deployment.yaml(示例代码中我们创建一个 deployment.1.3.yaml),让它从 ConfigMap 中读取数据并注入为环境变量。

# ...
spec:
  template:
    # ...
    spec:
      containers:
      - name: my-app-container
        image: my-app:1.3
        ports:
        - containerPort: 8080
        # 新增 env 部分
        env:
          - name: GREETING # 在容器内定义的环境变量名
            valueFrom:
              configMapKeyRef:
                # 引用哪个 ConfigMap
                name: my-app-config
                # 引用那个 Key
                key: GREETING_MESSAGE

第三步:修改应用代码并部署

修改 app.js 来读取这个环境变量:
(示例代码中我们创建一个 app.1.3.js)

// ...
const GREETING = process.env.GREETING || 'Hello, default world!';

app.get('/', (req, res) => {
  res.send(GREETING);
});
// ...

pnpm build:minikube:1.3构建新镜像 (my-app:1.3),并重新 kubectl apply -f deployment.yaml

eval $(minikube docker-env) && docker build -f Dockerfile.1.3 -t my-app:1.3 ./
kubectl apply -f deployment.1.3.yaml

当滚动更新完成后,再次访问你的 NodePort 地址,你会发现页面返回的信息已经变成了 "Hello from ConfigMap!"。我们成功地将配置从代码中解耦了!

chapter_09_01.png转存失败,建议直接上传图片文件 # 第9章:K8s 核心概念(下)- 服务发现与配置

在上一章,我们的 my-app 应用已经通过 Deployment 在 K8s 中稳定运行了。但它就像一座“孤岛”:

  1. 对外:集群外部的用户无法访问它。
  2. 对内:如果集群内有其他服务(比如一个前端应用)想要调用它,也很困难。

为什么困难?因为 Pod 的 IP 地址是动态的、不固定的。每当 Deployment 进行更新或 Pod 发生故障被重建时,新 Pod 的 IP 都会改变。我们不能依赖这个不稳定的 IP 地址。

K8s 用一个绝妙的资源解决了这个问题:Service

9.1 Service:让你的应用被“看见”

Service 是 K8s 网络世界的核心。你可以把它想象成一个应用的 “固定门牌号”“内部总机号码”

  • 核心职责:为一个或多个功能相同的 Pod,提供一个稳定的、统一的访问入口
  • 工作原理
    1. 你创建一个 Service,并为它定义一个选择器 (Selector),这个选择器会“圈出”所有带有特定标签 (Label) 的 Pod。
    2. Service 会获得一个虚拟的、固定的 IP 地址(称为 ClusterIP)和一个稳定的 DNS 名称
    3. 当有流量访问这个 Service 的地址时,Service 会自动地将流量负载均衡到它背后所代理的、健康的 Pod 上。

这个过程对客户端是完全透明的。客户端(无论是集群内的其他 Pod 还是外部用户)只需要知道 Service 的固定地址,而完全不需要关心后端 Pod 的数量、IP 地址或健康状况。

Service 的几种类型

根据你想如何暴露应用,Service 提供了几种不同的 type

  1. ClusterIP

    • 这是默认类型。
    • Service 只会获得一个集群内部的 IP 地址。
    • 用途:仅用于集群内部服务之间的通信。比如,一个 Web 后端服务需要被前端服务调用,但不需要暴露给外部用户。
  2. NodePort

    • 在 ClusterIP 的基础上,K8s 会在每一个 Node 节点上,都开放一个相同的、固定的端口(范围通常是 30000-32767)。
    • 用途:用于将服务对外暴露。你可以通过访问 http://<任何一个Node节点的IP>:<NodePort> 来访问你的服务。这在开发和测试环境中非常方便。
  3. LoadBalancer

    • 这是 NodePort 的增强版。
    • 用途:在公有云环境(如 AWS, GCP, Azure)下,当你创建一个 LoadBalancer 类型的 Service 时,云服务商会自动为你创建一个外部负载均衡器,并将其绑定到这个 Service。这是在生产环境中暴露服务的标准方式

9.2 ConfigMap & Secret:将配置与代码解耦

一个健壮的应用,应该将配置代码分离。我们不应该把数据库地址、API 密钥等信息硬编码在 Docker 镜像里。

K8s 提供了两种专门的资源来管理配置信息:

  1. ConfigMap

    • 用途:用于存储非敏感的、键值对形式的配置数据。比如,应用的日志级别、功能开关、主题名称等。
  2. Secret

    • 用途:专门用于存储敏感信息,如数据库密码、API Token、TLS 证书等。
    • 特点:Secret 中的数据是以 Base64 编码的形式存储的(注意:编码不是加密!),但 K8s 会以更安全的方式来管理和分发它。

创建好 ConfigMap 或 Secret 后,我们可以通过两种主要方式将它们“注入”到 Pod 中:

  • 作为环境变量:将键值对直接注入为容器的环境变量。
  • 作为数据卷挂载:将键值对挂载成文件,让容器内的应用去读取。

9.3 [实战] 为我们的应用创建 Service,并从外部访问

是时候为我们的 my-app-deployment 打开通往世界的大门了。

第一步:创建 Service YAML 文件

my-app 目录下,创建一个 service.yaml 文件:

# API 版本
apiVersion: v1
# 资源类型为 Service
kind: Service
# 元数据
metadata:
  name: my-app-service
# 规格
spec:
  # Service 类型,我们用 NodePort 来从外部访问
  type: NodePort
  # 选择器,告诉 Service 要代理哪些 Pod
  # 这里的标签必须和 Deployment 中 Pod 模板的标签完全一致
  selector:
    app: my-app
  # 端口定义
  ports:
    - protocol: TCP
      # Service 自身监听的端口
      port: 80
      # 流量要转发到 Pod 的哪个端口
      targetPort: 8080
      # 在 Node 节点上暴露的端口
      # 如果不指定,K8s 会自动分配一个
      # nodePort: 30007

第二步:应用配置

kubectl apply -f service.yaml

验证 Pod 状态

在访问之前,确保你的 Pod 真的在运行:

# 查看 Pod 状态
kubectl get pods
# 查看 Pod 详细信息
kubectl describe pods
# 检查 Service 是否匹配到了 Pod
kubectl get endpoints my-app-service

如果 kubectl get endpoints my-app-service 显示为空,说明 Service 没有找到匹配的 Pod,请检查 Deployment 的标签是否与 Service 的选择器一致。

第三步:查看 Service 并访问

# 查看 Service
kubectl get service my-app-service
# 或者简写
kubectl get svc

你会看到类似这样的输出,记下 PORT(S) 那一列中 80 后面映射的 NodePort 端口号(比如 31006)。

NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE
my-app-serviceNodePort10.107.33.3880:31006/TCP8s

重要:访问 Minikube 中的服务

由于 Minikube 运行在虚拟机中,直接通过 NodePort 访问可能会遇到网络问题(我在 macOS + Docker Desktop 环境就遇到了)。我们有几种方式来访问服务:

方法 1:使用 minikube service 命令(推荐)

# 自动打开浏览器并处理端口转发
minikube service my-app-service

运行结果:

mac@192 docker-k8s-little-book-source-code % minikube service my-app-service
|-----------|----------------|-------------|---------------------------|
| NAMESPACE |      NAME      | TARGET PORT |            URL            |
|-----------|----------------|-------------|---------------------------|
| default   | my-app-service |          80 | http://192.168.49.2:31006 |
|-----------|----------------|-------------|---------------------------|
🏃  为服务 my-app-service 启动隧道。
|-----------|----------------|-------------|------------------------|
| NAMESPACE |      NAME      | TARGET PORT |          URL           |
|-----------|----------------|-------------|------------------------|
| default   | my-app-service |             | http://127.0.0.1:53952 |
|-----------|----------------|-------------|------------------------|
🎉  正通过默认浏览器打开服务 default/my-app-service...
❗  因为你正在使用 darwin 上的 Docker 驱动程序,所以需要打开终端才能运行它。

访问http://127.0.0.1:53952(注意:这个每次运行的时候端口号会不一样),就会看到我们的示例出来了

方法 2:使用端口转发(最可靠)

# 将 Service 端口转发到本地 8080 端口
kubectl port-forward service/my-app-service 8080:80
# 然后在浏览器访问 http://localhost:8080

这里因为指定的8080端口,就不会每次不一样了

方法 3:直接访问(某些环境可行)

获取 Minikube 节点的 IP 地址:

minikube ip

假设得到的 IP 是 192.168.49.2,NodePort 是 31006。现在,打开浏览器,访问 http://192.168.49.2:31006

📋 适合方法 3 minikube ip 方案的情况:

  • Linux 系统 + 裸机 Minikube (不是 Docker Desktop)
  • Windows 系统 + Hyper-V 驱动
  • macOS + VirtualBox/Parallels 驱动 (不是 Docker Desktop)
  • 云环境 中的 Minikube

❌ 不适合的情况:

  • macOS + Docker Desktop
  • Windows + Docker Desktop

成功了! 你已经通过 NodePort 访问到了 K8s 集群内部的 Pod。

9.4 [实战] 使用 ConfigMap 注入环境变量

我们来修改一下应用,让它返回的问候语可以通过环境变量来配置。

第一步:创建 ConfigMap

创建一个 configmap.yaml 文件:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-app-config
data:
  # 定义我们的键值对配置
  GREETING_MESSAGE: "Hello from ConfigMap!"

应用它:kubectl apply -f configmap.yaml

第二步:修改 Deployment

修改 deployment.yaml(示例代码中我们创建一个 deployment.1.3.yaml),让它从 ConfigMap 中读取数据并注入为环境变量。

# ...
spec:
  template:
    # ...
    spec:
      containers:
      - name: my-app-container
        image: my-app:1.3
        ports:
        - containerPort: 8080
        # 新增 env 部分
        env:
          - name: GREETING # 在容器内定义的环境变量名
            valueFrom:
              configMapKeyRef:
                # 引用哪个 ConfigMap
                name: my-app-config
                # 引用那个 Key
                key: GREETING_MESSAGE

第三步:修改应用代码并部署

修改 app.js 来读取这个环境变量:
(示例代码中我们创建一个 app.1.3.js)

// ...
const GREETING = process.env.GREETING || 'Hello, default world!';

app.get('/', (req, res) => {
  res.send(GREETING);
});
// ...

pnpm build:minikube:1.3构建新镜像 (my-app:1.3),并重新 kubectl apply -f deployment.yaml

eval $(minikube docker-env) && docker build -f Dockerfile.1.3 -t my-app:1.3 ./
kubectl apply -f deployment.1.3.yaml

当滚动更新完成后,再次访问你的 NodePort 地址,你会发现页面返回的信息已经变成了 "Hello from ConfigMap!"。我们成功地将配置从代码中解耦了!

chapter_09_01.png

9.5 本章小结

你现在已经掌握了 K8s 中服务暴露和配置管理的关键知识。

  • 本章回顾

    • 我们理解了 Service 是 Pod 的稳定访问入口,并能实现负载均衡
    • 我们学习了 Service 的几种类型,并通过 NodePort 成功地从集群外部访问了我们的应用。
    • 我们学会了使用 ConfigMapSecret 来将配置与代码解耦
    • 我们通过实战,成功地将 ConfigMap 中的数据作为环境变量注入到了 Pod 中。
  • 避坑指南

    • 在 macOS 上使用 Docker Desktop 时,Minikube 的 NodePort 服务 不会直接暴露到宿主机网络,这种情况下 NodePort 服务无法直接通过 minikube ip:nodePort 访问
    • 需要通过 minikube service 命令创建本地隧道来访问

我们的应用现在既能被访问,配置也变得灵活。但还有一个环节没有闭合:如果我们的应用是有状态的,比如一个数据库,它的数据该如何持久化呢?下一章,我们将深入 K8s 的存储世界。

9.5 本章小结

你现在已经掌握了 K8s 中服务暴露和配置管理的关键知识。

  • 本章回顾

    • 我们理解了 Service 是 Pod 的稳定访问入口,并能实现负载均衡
    • 我们学习了 Service 的几种类型,并通过 NodePort 成功地从集群外部访问了我们的应用。
    • 我们学会了使用 ConfigMapSecret 来将配置与代码解耦
    • 我们通过实战,成功地将 ConfigMap 中的数据作为环境变量注入到了 Pod 中。
  • 避坑指南

    • 在 macOS 上使用 Docker Desktop 时,Minikube 的 NodePort 服务 不会直接暴露到宿主机网络,这种情况下 NodePort 服务无法直接通过 minikube ip:nodePort 访问
    • 需要通过 minikube service 命令创建本地隧道来访问

我们的应用现在既能被访问,配置也变得灵活。但还有一个环节没有闭合:如果我们的应用是有状态的,比如一个数据库,它的数据该如何持久化呢?下一章,我们将深入 K8s 的存储世界。