4. Kubernetes进阶:Service、Ingress、ConfigMap与Secret

好,咱们继续往下走。前面几章我们把Pod、Deployment这些基础概念聊透了,现在该聊聊真正让Kubernetes变得「有用」的几个核心组件了。说白了,你部署了一堆Pod,它们怎么互相通信?怎么对外暴露?配置和敏感信息又该怎么管理?这就是今天要解决的四个问题。

4.1 Service:Pod的稳定入口

Pod这东西,说白了就是「朝生暮死」的。今天跑得好好的,明天一升级IP就变了。你总不能每次重启都去改配置文件吧?Service就是来解决这个问题的——它给一组Pod提供一个固定的虚拟IP和DNS名称。

我个人习惯把Service理解成一个「负载均衡器+服务发现」的组合体。它通过Label Selector找到匹配的Pod,然后把流量分发过去。

4.1.1 Service的四种类型

类型 访问方式 适用场景
ClusterIP 集群内部虚拟IP 内部服务间调用,默认类型
NodePort 节点IP + 固定端口 开发测试、临时对外暴露
LoadBalancer 云厂商LB + 外部IP 生产环境对外暴露
ExternalName CNAME到外部域名 将外部服务映射到集群内

嗯,这里要注意:NodePort的端口范围默认是30000-32767。我曾经在生产环境踩过坑,安全组忘了开这个范围的端口,结果服务一直连不上,排查了半天才发现是防火墙的问题。

4.1.2 动手创建一个Service

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80        # Service暴露的端口
      targetPort: 8080 # Pod里容器的端口
  type: ClusterIP

你想想看,这个YAML干了什么?它告诉Kubernetes:帮我创建一个叫my-app-svc的Service,把所有带有app=my-app标签的Pod的8080端口,映射到Service的80端口上。其他Pod访问my-app-svc:80就能找到这些Pod了。

小技巧:我个人习惯在Service里加上sessionAffinity: ClientIP,这样同一个客户端的请求会始终路由到同一个Pod。做有状态服务的时候特别有用。

4.2 Ingress:七层流量入口

Service解决了Pod的访问问题,但有个尴尬的地方——每个Service只能暴露一个端口。你想想看,一个微服务架构可能有几十个服务,每个都开一个NodePort?那端口管理就是噩梦。

Ingress就是干这个的。它工作在七层(HTTP/HTTPS),可以根据域名、路径把流量路由到不同的Service。说白了,它就是Kubernetes里的「反向代理+路由网关」。

4.2.1 Ingress的工作原理

Ingress本身只是一个资源定义,真正干活的是Ingress Controller。常见的实现有Nginx Ingress Controller、Traefik、HAProxy等。我项目里用得最多的是Nginx Ingress Controller,稳定、社区活跃、文档齐全。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /v1
            pathType: Prefix
            backend:
              service:
                name: v1-service
                port:
                  number: 80
          - path: /v2
            pathType: Prefix
            backend:
              service:
                name: v2-service
                port:
                  number: 80

这段配置的意思是:当用户访问api.example.com/v1时,流量打到v1-service;访问/v2时,打到v2-service。是不是很直观?

避坑指南:我曾经在配置HTTPS时忘了加TLS证书的Secret引用,结果Ingress一直报502。记住,Ingress的TLS证书必须提前创建好Secret,并且在Ingress的tls字段里引用。

4.3 ConfigMap:配置与代码分离

做开发的都知道,配置不应该硬编码在代码里。环境不同(开发、测试、生产),配置就不同。ConfigMap就是Kubernetes用来管理配置的利器。

说白了,ConfigMap就是一个键值对存储。你可以把它挂载成环境变量,也可以挂载成文件。我个人更推荐挂载成文件的方式,因为配置更新后,Pod不需要重建就能生效(虽然有些应用需要手动reload)。

4.3.1 创建ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  app.properties: |
    db.host=mysql-service
    db.port=3306
    db.name=myapp
  redis.host: redis-service
  redis.port: "6379"

这里有个细节:value可以是多行文本(用|符号),也可以是单行字符串。注意数字类型的值要加引号,否则YAML解析会出问题。

4.3.2 在Pod中使用ConfigMap

apiVersion: v1
kind: Pod
metadata:
  name: my-app-pod
spec:
  containers:
    - name: my-app
      image: my-app:latest
      volumeMounts:
        - name: config-volume
          mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: app-config

这样,ConfigMap里的app.properties文件就会被挂载到Pod的/etc/config目录下。应用直接读取这个文件就能拿到配置了。

经验之谈:ConfigMap更新后,挂载的文件会自动更新,但应用不会自动reload。我一般会在应用里加一个文件监听机制,或者用Sidecar容器来做配置热更新。

4.4 Secret:敏感信息的保险箱

ConfigMap存的是非敏感配置,那密码、API Key、证书这些敏感信息怎么办?用Secret。它的用法和ConfigMap几乎一样,区别在于数据是Base64编码的,而且Kubernetes会加密存储(如果开启了加密功能)。

嗯,这里要纠正一个常见误区:Base64不是加密,只是编码。任何拿到Base64字符串的人都能解码。所以Secret的真正安全依赖于Kubernetes的RBAC和存储加密。

4.4.1 创建Secret

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  username: cm9vdA==        # root的Base64
  password: cGFzc3dvcmQxMjM= # password123的Base64

你也可以用kubectl命令来创建,省去手动编码的麻烦:

kubectl create secret generic db-secret \
  --from-literal=username=root \
  --from-literal=password=password123

4.4.2 在Pod中使用Secret

apiVersion: v1
kind: Pod
metadata:
  name: my-app-pod
spec:
  containers:
    - name: my-app
      image: my-app:latest
      env:
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: username
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password

这样,Pod启动时就会自动从Secret里读取username和password,设置成环境变量。应用代码里直接读环境变量就行,不用关心敏感信息是怎么存储的。

曾经踩过的坑:有一次我把Secret挂载成文件,结果应用一直报权限错误。排查了半天才发现,Secret挂载的文件默认权限是0444(只读),而我的应用需要写权限。解决方案是在Secret的volume里设置defaultMode: 0644。

4.5 综合实践:一个完整的微服务配置

最后,咱们把今天学的四个组件串起来,看一个完整的例子。假设我们有一个订单服务,需要连接数据库和Redis:

# 1. 创建Secret存储数据库密码
apiVersion: v1
kind: Secret
metadata:
  name: order-secret
type: Opaque
data:
  db-password: bXlzcWxwYXNz
---
# 2. 创建ConfigMap存储非敏感配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: order-config
data:
  db.host: mysql-service
  db.port: "3306"
  redis.host: redis-service
  redis.port: "6379"
---
# 3. 创建Service暴露内部服务
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order
  ports:
    - port: 80
      targetPort: 8080
---
# 4. 创建Ingress对外暴露
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-ingress
spec:
  rules:
    - host: order.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80

你想想看,这个架构的好处是什么?配置和敏感信息分离,Service提供稳定的内部访问入口,Ingress统一管理外部流量。每个组件各司其职,互不干扰。

好了,这一章的内容就到这儿。Service、Ingress、ConfigMap、Secret这四个组件,说白了就是Kubernetes里最常用的「基础设施」。把它们搞明白了,你就能搭建出既灵活又安全的微服务体系。下一章咱们聊聊存储,PersistentVolume和PersistentVolumeClaim,到时候见。