20-综合实战:微服务部署

综合实战:微服务部署

概念引入

恭喜你走到了最后一篇!前面文章覆盖了从 Pod 到集群运维的全部知识。但知识散落在各处------就像你有一箱乐高积木,每块都认识,但从来没拼过一个完整作品。

这篇就是拼乐高的时刻。

我们要从零部署一个完整的微服务应用:有前端、有后端、有数据库、有缓存,还有自动扩缩容、网络隔离、Ingress 路由......面试时你不需要讲 29 个零散概念,只需要讲 一个项目------然后面试官会从项目里追问出所有知识点。

目标架构

#mermaid-svg-hNKz1omOdTFq5ugE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hNKz1omOdTFq5ugE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hNKz1omOdTFq5ugE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hNKz1omOdTFq5ugE .error-icon{fill:#552222;}#mermaid-svg-hNKz1omOdTFq5ugE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hNKz1omOdTFq5ugE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hNKz1omOdTFq5ugE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hNKz1omOdTFq5ugE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hNKz1omOdTFq5ugE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hNKz1omOdTFq5ugE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hNKz1omOdTFq5ugE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hNKz1omOdTFq5ugE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hNKz1omOdTFq5ugE .marker.cross{stroke:#333333;}#mermaid-svg-hNKz1omOdTFq5ugE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hNKz1omOdTFq5ugE p{margin:0;}#mermaid-svg-hNKz1omOdTFq5ugE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hNKz1omOdTFq5ugE .cluster-label text{fill:#333;}#mermaid-svg-hNKz1omOdTFq5ugE .cluster-label span{color:#333;}#mermaid-svg-hNKz1omOdTFq5ugE .cluster-label span p{background-color:transparent;}#mermaid-svg-hNKz1omOdTFq5ugE .label text,#mermaid-svg-hNKz1omOdTFq5ugE span{fill:#333;color:#333;}#mermaid-svg-hNKz1omOdTFq5ugE .node rect,#mermaid-svg-hNKz1omOdTFq5ugE .node circle,#mermaid-svg-hNKz1omOdTFq5ugE .node ellipse,#mermaid-svg-hNKz1omOdTFq5ugE .node polygon,#mermaid-svg-hNKz1omOdTFq5ugE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hNKz1omOdTFq5ugE .rough-node .label text,#mermaid-svg-hNKz1omOdTFq5ugE .node .label text,#mermaid-svg-hNKz1omOdTFq5ugE .image-shape .label,#mermaid-svg-hNKz1omOdTFq5ugE .icon-shape .label{text-anchor:middle;}#mermaid-svg-hNKz1omOdTFq5ugE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hNKz1omOdTFq5ugE .rough-node .label,#mermaid-svg-hNKz1omOdTFq5ugE .node .label,#mermaid-svg-hNKz1omOdTFq5ugE .image-shape .label,#mermaid-svg-hNKz1omOdTFq5ugE .icon-shape .label{text-align:center;}#mermaid-svg-hNKz1omOdTFq5ugE .node.clickable{cursor:pointer;}#mermaid-svg-hNKz1omOdTFq5ugE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hNKz1omOdTFq5ugE .arrowheadPath{fill:#333333;}#mermaid-svg-hNKz1omOdTFq5ugE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hNKz1omOdTFq5ugE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hNKz1omOdTFq5ugE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hNKz1omOdTFq5ugE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hNKz1omOdTFq5ugE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hNKz1omOdTFq5ugE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hNKz1omOdTFq5ugE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hNKz1omOdTFq5ugE .cluster text{fill:#333;}#mermaid-svg-hNKz1omOdTFq5ugE .cluster span{color:#333;}#mermaid-svg-hNKz1omOdTFq5ugE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hNKz1omOdTFq5ugE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hNKz1omOdTFq5ugE rect.text{fill:none;stroke-width:0;}#mermaid-svg-hNKz1omOdTFq5ugE .icon-shape,#mermaid-svg-hNKz1omOdTFq5ugE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hNKz1omOdTFq5ugE .icon-shape p,#mermaid-svg-hNKz1omOdTFq5ugE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hNKz1omOdTFq5ugE .icon-shape .label rect,#mermaid-svg-hNKz1omOdTFq5ugE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hNKz1omOdTFq5ugE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hNKz1omOdTFq5ugE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hNKz1omOdTFq5ugE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 安全层
数据层
后端层
前端层
入口层 --- Ingress
/
/api/*
限制访问
🌐 用户请求
nginx-ingress-controller

路由规则:/ → frontend

/api/* → backend
frontend Deployment

Nginx 静态页面

replicas: 2
frontend-svc

ClusterIP:80
backend Deployment

http-echo API 模拟

replicas: 2
backend-svc

ClusterIP:8080
HPA

min:2 max:10

cpu > 70% → 扩容
PDB

minAvailable: 1

保证滚动更新不中断
PostgreSQL StatefulSet

PVC 持久化

replicas: 1
pg-svc

Headless (clusterIP: None)
Redis Deployment

缓存

replicas: 1
redis-svc

ClusterIP:6379
NetworkPolicy

deny-all → 白名单放行

DB 只允许 backend 访问

学完本篇你将能够:

  • 从零部署一个包含前端、后端、数据库、缓存的完整微服务应用
  • 理解每个 YAML 字段的含义,能独立排查部署问题
  • 在面试中用这个项目串联 Deployment、Service、Ingress、HPA、NetworkPolicy、PDB 等 15+ 个知识点

原理讲解:分层架构

整个系统分四层,每层解决不同的问题。面试时画这个图,然后逐层讲解:

复制代码
┌─────────────────────────────────────────────────────────────────┐
│  入口层:Ingress                                                │
│  解决问题:外部流量怎么进来?怎么路由到不同的服务?                    │
│  • nginx-ingress-controller 监听 Ingress 资源                    │
│  • /      → frontend-svc:80     (用户访问网页)                  │
│  • /api/* → backend-svc:8080   (前端调用后端 API)               │
├─────────────────────────────────────────────────────────────────┤
│  应用层:frontend + backend                                      │
│  解决问题:怎么处理用户请求?                                       │
│  • frontend: Nginx 提供静态页面,/api/ 请求反向代理到 backend       │
│  • backend:  API 服务(本实验用 http-echo 模拟真实后端)            │
│  • initContainer: backend 启动前等 PostgreSQL DNS 就绪            │
│  • 探针: liveness + readiness 确保流量只到健康的 Pod               │
├─────────────────────────────────────────────────────────────────┤
│  数据层:PostgreSQL + Redis                                      │
│  解决问题:数据存在哪?怎么保证不丢?                                │
│  • PostgreSQL: StatefulSet + PVC(数据持久化,Pod 重启数据不丢)    │
│  • Redis: Deployment(缓存,丢了可以重建)                         │
├─────────────────────────────────────────────────────────────────┤
│  安全 + 弹性层                                                   │
│  解决问题:怎么保证安全?怎么应对流量波动?                           │
│  • NetworkPolicy: deny-all 基础上白名单放行(最小权限原则)         │
│  • HPA: 根据 CPU 使用率自动扩缩 backend Pod 数量                   │
│  • PDB: 保证滚动更新/节点维护时至少有 1 个 backend Pod 可用          │
└─────────────────────────────────────────────────────────────────┘

请求的完整生命周期

面试高频问题:"一个请求从用户到数据库,经过了 K8s 的哪些组件?"
PostgreSQL backend Pod (http-echo) frontend Pod (nginx) Ingress Controller (nginx Pod) 用户浏览器 PostgreSQL backend Pod (http-echo) frontend Pod (nginx) Ingress Controller (nginx Pod) 用户浏览器 #mermaid-svg-8oMb0di3AKtYNhXk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8oMb0di3AKtYNhXk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8oMb0di3AKtYNhXk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8oMb0di3AKtYNhXk .error-icon{fill:#552222;}#mermaid-svg-8oMb0di3AKtYNhXk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8oMb0di3AKtYNhXk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8oMb0di3AKtYNhXk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8oMb0di3AKtYNhXk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8oMb0di3AKtYNhXk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8oMb0di3AKtYNhXk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8oMb0di3AKtYNhXk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8oMb0di3AKtYNhXk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8oMb0di3AKtYNhXk .marker.cross{stroke:#333333;}#mermaid-svg-8oMb0di3AKtYNhXk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8oMb0di3AKtYNhXk p{margin:0;}#mermaid-svg-8oMb0di3AKtYNhXk .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8oMb0di3AKtYNhXk text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-8oMb0di3AKtYNhXk .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8oMb0di3AKtYNhXk .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-8oMb0di3AKtYNhXk .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-8oMb0di3AKtYNhXk .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-8oMb0di3AKtYNhXk #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-8oMb0di3AKtYNhXk .sequenceNumber{fill:white;}#mermaid-svg-8oMb0di3AKtYNhXk #sequencenumber{fill:#333;}#mermaid-svg-8oMb0di3AKtYNhXk #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-8oMb0di3AKtYNhXk .messageText{fill:#333;stroke:none;}#mermaid-svg-8oMb0di3AKtYNhXk .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8oMb0di3AKtYNhXk .labelText,#mermaid-svg-8oMb0di3AKtYNhXk .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-8oMb0di3AKtYNhXk .loopText,#mermaid-svg-8oMb0di3AKtYNhXk .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-8oMb0di3AKtYNhXk .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8oMb0di3AKtYNhXk .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-8oMb0di3AKtYNhXk .noteText,#mermaid-svg-8oMb0di3AKtYNhXk .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-8oMb0di3AKtYNhXk .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8oMb0di3AKtYNhXk .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8oMb0di3AKtYNhXk .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8oMb0di3AKtYNhXk .actorPopupMenu{position:absolute;}#mermaid-svg-8oMb0di3AKtYNhXk .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-8oMb0di3AKtYNhXk .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8oMb0di3AKtYNhXk .actor-man circle,#mermaid-svg-8oMb0di3AKtYNhXk line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-8oMb0di3AKtYNhXk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 场景 1:用户访问首页 场景 2:前端调用后端 API 场景 3:完整数据流(真实后端) GET http://localhost/ 匹配 / → frontend-svc 返回 Nginx 默认页面 GET http://localhost/api/health 匹配 /api/* → backend-svc {"status":"ok","service":"backend"} GET http://localhost/api/users 转发请求 SELECT * FROM users 返回数据 JSON 响应

面试加分点: 提到 Ingress Controller 本身也是一个 Pod(运行在 ingress-nginx namespace),外部流量先到它,再由它根据 Ingress 规则转发到对应的 Service → Pod。


动手实验

配套实验位于 docs/labs/beginner/microservice-deploy/

本实验用 9 个 YAML 文件 部署完整的微服务栈。下面逐个文件讲解,然后一键部署。

步骤 1:部署整个微服务栈

bash 复制代码
cd docs/labs/beginner/microservice-deploy
bash setup.sh

setup.sh 做了这些事:

bash 复制代码
# 1. 创建 Kind 集群(1 控制面 + 2 工作节点)
# 2. 安装 Nginx Ingress Controller(处理外部流量入口)
# 3. 安装 metrics-server(给 HPA 提供 CPU 指标)
#    └─ Kind 需要加 --kubelet-insecure-tls 参数
# 4. 创建 prod namespace
# 5. 按顺序部署:Secret → PostgreSQL → Redis → Backend → Frontend → Ingress → NetworkPolicy → HPA/PDB
# 6. 等待所有组件就绪

⏳ 首次运行需要拉取多个镜像(nginx、postgres、redis、http-echo、busybox),可能需要 5-10 分钟。

部署完成后,检查全局状态:

bash 复制代码
kubectl get all -n prod

预期输出(关键部分):

复制代码
NAME                            READY   STATUS    RESTARTS   AGE
pod/backend-xxx-abc             1/1     Running   0          2m
pod/backend-xxx-def             1/1     Running   0          2m
pod/frontend-xxx-ghi            1/1     Running   0          2m
pod/frontend-xxx-jkl            1/1     Running   0          2m
pod/postgres-0                  1/1     Running   0          2m
pod/redis-xxx-mno               1/1     Running   0          2m

NAME                  TYPE        CLUSTER-IP      PORT(S)
service/backend-svc   ClusterIP   10.96.x.x       8080/TCP
service/frontend-svc  ClusterIP   10.96.x.x       80/TCP
service/pg-svc        ClusterIP   None            5432/TCP    ← Headless!
service/redis-svc     ClusterIP   10.96.x.x       6379/TCP

NAME                         READY   UP-TO-DATE   AVAILABLE
deployment.apps/backend      2/2     2            2
deployment.apps/frontend     2/2     2            2
deployment.apps/redis        1/1     1            1

NAME                                    REFERENCE           TARGETS   MINPODS   MAXPODS
horizontalpodautoscaler/backend-hpa     Deployment/backend  0%/70%    2         10

NAME                              MIN AVAILABLE   ALLOWED DISRUPTIONS
poddisruptionbudget/backend-pdb   1               1

💡 注意 pg-svc 的 CLUSTER-IP 是 None ------ 这是 Headless Service(#15 StatefulSet 讲的),DNS 直接解析到 Pod IP,不经过负载均衡。PostgreSQL 只有一个实例,不需要负载均衡。


步骤 2:逐层理解每个组件

下面按部署顺序讲解每个 YAML 文件的关键配置。

2.1 Namespace:环境隔离(#11)
yaml 复制代码
# manifests/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: prod    # 所有组件都部署在这个 namespace 下

为什么用 Namespace? 生产环境通常有 devstagingprod 三个 namespace。资源限额、RBAC、NetworkPolicy 都可以按 namespace 隔离。

2.2 Secret:敏感信息(#07)
yaml 复制代码
# manifests/secrets.yaml(关键部分)
apiVersion: v1
kind: Secret
metadata:
  name: pg-secret       # ← backend 和 postgres 都会引用这个名字
  namespace: prod
type: Opaque
stringData:              # stringData 自动 Base64 编码(比 data 方便)
  POSTGRES_USER: app
  POSTGRES_PASSWORD: demo123
  POSTGRES_DB: myapp

面试要点: stringData vs data------stringData 写明文,K8s 自动编码;data 要你自己写 Base64。生产环境用 Sealed Secrets 或 External Secrets Operator,不要把明文 Secret 提交到 Git。

2.3 PostgreSQL:StatefulSet + Headless Service + PVC(#15, #10, #06)

这是整个系统中最有状态的组件,用了三个 K8s 概念:

yaml 复制代码
# manifests/postgres.yaml
apiVersion: v1
kind: Service
metadata:
  name: pg-svc
  namespace: prod
spec:
  clusterIP: None        # ← Headless Service:没有虚拟 IP
  selector:              #   DNS 直接解析到 Pod IP
    app: postgres        #   → pg-0.pg-svc.prod.svc.cluster.local
  ports:
  - port: 5432
---
apiVersion: apps/v1
kind: StatefulSet        # ← 不是 Deployment!
metadata:
  name: postgres
  namespace: prod
spec:
  serviceName: pg-svc    # ← StatefulSet 必须关联一个 Headless Service
  replicas: 1            #   单实例就够了(演示用)
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16-alpine
        envFrom:
        - secretRef:
            name: pg-secret     # ← 把 Secret 注入为环境变量
        ports:
        - containerPort: 5432
        volumeMounts:
        - name: pg-data
          mountPath: /var/lib/postgresql/data  # ← 数据存到 PVC
        resources:
          requests:
            cpu: 100m            # ← 资源请求(#13)
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 256Mi
        livenessProbe:           # ← 存活探针(#12)
          exec:
            command: ["pg_isready", "-U", "app"]  # pg 自带的健康检查命令
          initialDelaySeconds: 10
          periodSeconds: 5
        readinessProbe:          # ← 就绪探针
          exec:
            command: ["pg_isready", "-U", "app"]
          initialDelaySeconds: 5
          periodSeconds: 3
  volumeClaimTemplates:          # ← PVC 模板(#10)
  - metadata:
      name: pg-data              #   StatefulSet 自动为每个 Pod 创建 PVC
    spec:
      accessModes: ["ReadWriteOnce"]  # 只能被一个节点挂载读写
      resources:
        requests:
          storage: 1Gi           #   即使 Pod 被删除重建,PVC 和数据还在

关键问题:为什么用 StatefulSet 而不是 Deployment?

特性 Deployment StatefulSet
Pod 名 随机(backend-abc123) 有序(postgres-0)
存储 所有 Pod 共享 PVC 每个 Pod 独立 PVC
启动顺序 并行 有序(0→1→2)
网络标识 pod-name.service.ns.svc
适用场景 无状态(Web 服务) 有状态(数据库、消息队列)
2.4 Redis:Deployment(#04)
yaml 复制代码
# manifests/redis.yaml
apiVersion: apps/v1
kind: Deployment         # ← Redis 做缓存,用 Deployment 就够了
metadata:
  name: redis
  namespace: prod
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        ports:
        - containerPort: 6379
        resources:
          requests:
            cpu: 50m
            memory: 64Mi
          limits:
            cpu: 200m
            memory: 128Mi
        livenessProbe:
          tcpSocket:          # ← TCP 探针:端口通了就算活着
            port: 6379
          initialDelaySeconds: 5
          periodSeconds: 5

面试要点: Redis 在这里做缓存(非持久化),数据丢了可以从数据库重建,所以用 Deployment 更简单。如果 Redis 做持久化存储(RDB/AOF),那也应该用 StatefulSet。

2.5 Backend:Deployment + ConfigMap + InitContainer + 探针(#04, #07, #21, #12)

Backend 是整个系统中配置最复杂的组件,串联了 4 个知识点:

yaml 复制代码
# manifests/backend.yaml
# ① ConfigMap:非敏感配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: backend-config
  namespace: prod
data:
  DB_HOST: "pg-svc.prod.svc.cluster.local"     # ← 用 FQDN 访问 PostgreSQL
  DB_PORT: "5432"
  REDIS_HOST: "redis-svc.prod.svc.cluster.local" # ← 用 FQDN 访问 Redis
  REDIS_PORT: "6379"
---
# ② Service:暴露 backend 给 frontend 和 Ingress
apiVersion: v1
kind: Service
metadata:
  name: backend-svc
  namespace: prod
spec:
  selector:
    app: backend          # ← 通过标签选择 backend Pod
  ports:
  - port: 8080
    targetPort: 8080
---
# ③ Deployment:管理 backend Pod
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: prod
spec:
  replicas: 2             # ← 2 个副本(和 HPA minReplicas 一致)
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      # ④ InitContainer:在 backend 启动前等 PostgreSQL 就绪
      initContainers:
      - name: wait-for-db
        image: busybox:1.36
        command:
        - sh
        - -c
        - |
          echo "Waiting for PostgreSQL..."
          # nslookup 查 Headless Service 的 DNS
          # pg-svc 是 Headless Service,只有 Pod 就绪后 DNS 才能解析
          until nslookup pg-svc.prod.svc.cluster.local; do sleep 2; done
          echo "PostgreSQL is up!"
      containers:
      - name: backend
        # 本实验用 http-echo 模拟真实后端
        # 真实项目这里会是你的 Python/Go/Java 应用镜像
        image: hashicorp/http-echo
        args:
        - "-text={\"status\":\"ok\",\"service\":\"backend\"}"  # 所有请求返回这个 JSON
        - "-listen=:8080"
        ports:
        - containerPort: 8080
        env:
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:      # ← 从 ConfigMap 读配置
              name: backend-config
              key: DB_HOST
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:         # ← 从 Secret 读密码
              name: pg-secret
              key: POSTGRES_PASSWORD
        resources:
          requests:
            cpu: 50m              # ← HPA 根据这个值计算 CPU 利用率
            memory: 64Mi
          limits:
            cpu: 200m
            memory: 128Mi
        livenessProbe:            # ← 存活探针:/health 返回 200 就活着
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:           # ← 就绪探针:200 才接收流量
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 3
          periodSeconds: 3

InitContainer 的工作原理(#21):

复制代码
Pod 启动流程:
  initContainer: wait-for-db
    ├─ nslookup pg-svc.prod.svc.cluster.local
    ├─ 失败 → sleep 2 → 重试
    ├─ ...(循环等待)
    ├─ 成功 → initContainer 退出(exit 0)
    └─ 主容器 backend 才开始启动

为什么需要等?
  → backend 启动时会尝试连接数据库
  → 如果数据库还没准备好,backend 直接崩溃退出
  → K8s 会反复重启 backend(CrashLoopBackOff)
  → InitContainer 避免了这种"启动竞争"

⚠️ 实验说明: 本实验的 backend 用 hashicorp/http-echo 模拟------它对所有路径都返回固定的 JSON,不会真正连接数据库。真实项目中,你需要用自己的应用镜像(Flask/Spring Boot/Go)替换这个 image,并在代码里连接 DB_HOSTREDIS_HOST

2.6 Frontend:Deployment + Nginx ConfigMap(#04, #07)
yaml 复制代码
# manifests/frontend.yaml
# ① ConfigMap:Nginx 反向代理配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: frontend-config
  namespace: prod
data:
  default.conf: |
    server {
        listen 80;
        root /usr/share/nginx/html;
        index index.html;
        location / {
            try_files $uri /index.html;   # SPA 路由 fallback
        }
        location /api/ {
            # /api/xxx 请求转发到 backend-svc
            proxy_pass http://backend-svc.prod.svc.cluster.local:8080/;
        }
    }
---
# ② Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: prod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: nginx:1.27
        ports:
        - containerPort: 80
        volumeMounts:
        - name: config
          mountPath: /etc/nginx/conf.d/default.conf  # ← 覆盖 Nginx 默认配置
          subPath: default.conf                       #   只替换一个文件
        resources:
          requests:
            cpu: 50m
            memory: 64Mi
          limits:
            cpu: 100m
            memory: 128Mi
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
      volumes:
      - name: config
        configMap:
          name: frontend-config  # ← 把 ConfigMap 挂载为文件

Nginx 配置的巧妙之处: 前端既能直接服务静态页面(/),又能把 API 请求代理给 backend(/api/)。这样从 Ingress 的角度,//api/* 都先到各自的 Service,但 frontend 的 Nginx 也会二次代理 /api/ 请求------提供了双保险。

2.7 Ingress:外部流量入口(#23)
yaml 复制代码
# manifests/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: prod
  annotations:
    # 重写 URL:/api/users → backend 收到 /users
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx    # ← 由 nginx-ingress-controller 处理
  rules:
  - host: localhost          # ← Kind 环境用 localhost
    http:
      paths:
      - path: /api(/|$)(.*)  # ← 正则匹配:/api、/api/、/api/users 等
        pathType: ImplementationSpecific
        backend:
          service:
            name: backend-svc
            port:
              number: 8080
      - path: /              # ← 其他所有请求
        pathType: Prefix
        backend:
          service:
            name: frontend-svc
            port:
              number: 80

URL 重写规则解读:

复制代码
用户请求                    Ingress 匹配              Backend 收到
─────────────────────────────────────────────────────────────────
GET /api/users       →    /api(/|$)(.*) 匹配     →    /users
                           $2 = "users"
                           rewrite-target: /$2

GET /api/health      →    /api(/|$)(.*) 匹配     →    /health
                           $2 = "health"

GET /                →    / Prefix 匹配          →    /
                           转发到 frontend-svc

💡 rewrite-target: /$2 的意思是:把正则捕获组 $2(即 /api/ 后面的部分)作为新的路径发给 backend。所以 backend 不需要知道 /api 前缀的存在。

2.8 NetworkPolicy:网络隔离(#22)

这是面试最爱问的部分------"你的微服务之间怎么保证安全?"

yaml 复制代码
# manifests/networkpolicy.yaml

# ① 默认拒绝所有入站流量(deny-all 基线)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: prod
spec:
  podSelector: {}        # ← 选中 prod namespace 下的所有 Pod
  policyTypes:
  - Ingress              # ← 没有 ingress 规则 → 全部拒绝

# ② 白名单:允许 frontend 接收外部流量
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-ingress
  namespace: prod
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes:
  - Ingress
  ingress:
  - {}                   # ← 空规则 = 允许所有来源(frontend 需要接收 Ingress 流量)

# ③ 白名单:backend 只允许 frontend 和 Ingress Controller 访问
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-from-frontend
  namespace: prod
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend          # ← 允许 frontend Pod
    ports:
    - protocol: TCP
      port: 8080
  - from:
    - namespaceSelector: {}      # ← 允许任何 namespace 中的
      podSelector:               #   ingress-nginx Pod
        matchLabels:             #   (因为 Ingress Controller 在 ingress-nginx namespace)
          app.kubernetes.io/name: ingress-nginx
    ports:
    - protocol: TCP
      port: 8080

# ④ 白名单:PostgreSQL 只允许 backend 访问
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-db-from-backend
  namespace: prod
spec:
  podSelector:
    matchLabels:
      app: postgres
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend           # ← 只有 backend 能连数据库
    ports:
    - protocol: TCP
      port: 5432

# ⑤ 白名单:Redis 只允许 backend 访问(同上)

NetworkPolicy 的安全模型:

复制代码
默认 deny-all → 逐条白名单放行

frontend:  允许所有入站(Ingress Controller 需要访问它)
backend:   只允许 frontend + Ingress Controller 入站
postgres:  只允许 backend 入站(frontend 直连 DB → 被拒绝 ✅)
redis:     只允许 backend 入站
2.9 HPA + PDB:弹性 + 可用性(#08, #25)
yaml 复制代码
# manifests/hpa-pdb.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: backend-hpa
  namespace: prod
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: backend              # ← 控制 backend 的副本数
  minReplicas: 2               # ← 最少 2 个 Pod
  maxReplicas: 10              # ← 最多 10 个 Pod
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70 # ← 平均 CPU 利用率超过 70% → 扩容
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: backend-pdb
  namespace: prod
spec:
  minAvailable: 1              # ← 任何时刻至少 1 个 backend Pod 可用
  selector:
    matchLabels:
      app: backend

HPA 和 PDB 的关系(面试高频陷阱):

场景 HPA 想做什么 PDB 允许吗 结果
CPU 高 → 扩到 5 个 扩容 ✅ 不管 正常扩容
CPU 低 → 缩到 1 个 缩容 minAvailable: 1 但缩到 1 后做滚动更新时 0 个可用 缩容被阻止
节点维护 → drain 驱逐 Pod ✅ 只要还剩 ≥ 1 个 正常驱逐

步骤 3:端到端测试

部署完成后,逐层验证系统是否正常工作:

bash 复制代码
# ① 通过 Ingress 访问前端(HTTP,Kind 端口映射到 localhost:80)
curl -s http://localhost/ | head -5
# 预期:返回 Nginx 欢迎页面的 HTML

# ② 通过 Ingress 访问后端 API(健康检查端点)
curl -s http://localhost/api/health
# 预期:{"status":"ok"}
# 说明:http-echo 内置了 /health 端点,返回简化的状态

# ③ 测试 backend 的其他路径
curl -s http://localhost/api/users
# 预期:{"status":"ok","service":"backend"}
# 说明:http-echo 对非 /health 路径返回 -text 参数指定的完整 JSON

⚠️ 注意: 本实验用 http-echo 模拟后端,所有路径返回固定 JSON(/health 除外)。真实项目中替换为你的应用镜像后,/api/users 会返回实际的用户数据。

验证 Service 间通信(frontend 的 nginx 镜像自带 curl):

bash 复制代码
# frontend 能访问 backend 吗?(应该可以 --- NetworkPolicy 已放行)
kubectl exec -n prod deploy/frontend -- \
  curl -s --max-time 3 http://backend-svc:8080/api/test
# 预期:{"status":"ok","service":"backend"}

# backend 能访问 PostgreSQL 吗?(TCP 端口应该可达)
kubectl exec -n prod deploy/backend -c backend -- \
  sh -c 'echo quit | timeout 3 nc pg-svc 5432' 2>&1 && \
  echo "✅ PostgreSQL 端口可达" || \
  echo "检查 NetworkPolicy 或 PostgreSQL 状态"

步骤 4:验证 NetworkPolicy 隔离

这是最有意思的验证------证明网络隔离真的生效了:

bash 复制代码
# 测试 1:frontend 直接访问 PostgreSQL(应该被拒绝!)
# NetworkPolicy allow-db-from-backend 只允许 app: backend 的 Pod 访问 pg
kubectl exec -n prod deploy/frontend -- \
  curl -s --max-time 3 http://pg-svc:5432 && \
  echo "⚠️ 可以访问(不应该)" || \
  echo "✅ 连接超时 --- NetworkPolicy 生效,frontend 无法直连数据库"

# 测试 2:Redis 同理,frontend 也不应该能访问
kubectl exec -n prod deploy/frontend -- \
  curl -s --max-time 3 http://redis-svc:6379 && \
  echo "⚠️ 可以访问(不应该)" || \
  echo "✅ 连接超时 --- NetworkPolicy 生效,frontend 无法访问 Redis"

# 测试 3:backend 访问 PostgreSQL(应该成功)
kubectl exec -n prod deploy/backend -c backend -- \
  sh -c 'echo quit | timeout 3 nc pg-svc 5432' 2>&1 && \
  echo "✅ backend 可以访问 PostgreSQL" || \
  echo "TCP 可达(非 HTTP 协议,nc 报错但端口是通的)"

面试加分点: 解释 NetworkPolicy 是 "deny by default + allow by exception" 的安全模型,类似防火墙。先 deny-all,再逐条白名单放行------这比逐个写 "deny xxx" 更安全,因为新增的 Pod 默认就被隔离了。


步骤 5:验证 HPA 自动扩缩

bash 复制代码
# 查看当前 HPA 状态
kubectl get hpa -n prod
# 预期:TARGETS 列显示当前 CPU 利用率,如 "5%/70%"

# 用循环并发请求制造负载(http-echo CPU 很低,可能不会触发扩容)
for i in $(seq 1 200); do
  curl -s http://localhost/api/health > /dev/null &
done
wait

# 持续观察 HPA 是否检测到 CPU 变化
kubectl get hpa -n prod --watch
# 如果 CPU 利用率超过 70%,会看到 replicas 从 2 增加到更多

# Ctrl+C 退出 watch

💡 为什么 HPA 可能不触发? http-echo 是非常轻量的 Go 程序,CPU 使用极低。真实后端(Python/Java)处理请求会消耗明显 CPU,HPA 就容易触发了。这个实验主要验证 HPA 配置正确且 metrics-server 正常采集指标。

验证 metrics-server 工作正常:

bash 复制代码
kubectl top pods -n prod
# 预期:显示每个 Pod 的 CPU 和内存使用量
# 如果显示 "no metrics available",等 30 秒再试(metrics-server 每 15 秒采集一次)

步骤 6:验证 PDB

bash 复制代码
kubectl get pdb -n prod
# 预期:
# NAME           MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS
# backend-pdb    1               N/A               1

kubectl describe pdb backend-pdb -n prod
# 关注字段:
# - minAvailable: 1      → 任何时候至少 1 个 Pod 可用
# - currentHealthy: 2    → 当前 2 个 Pod 健康
# - allowedDisruptions: 1 → 允许同时中断 1 个 Pod(2 - 1 = 1)

PDB 在什么场景下生效?

复制代码
✅ 会触发 PDB 的操作:
  • kubectl drain node(节点维护)
  • 集群自动缩容(Cluster Autoscaler)
  • 滚动更新时删除旧 Pod

❌ 不会触发 PDB 的操作:
  • kubectl delete pod(手动删除,PDB 不管)
  • 节点意外宕机(不是主动驱逐)

步骤 7:一键查看所有资源

用这几个命令全面检查系统状态:

bash 复制代码
# 所有资源概览
kubectl get all -n prod

# NetworkPolicy
kubectl get networkpolicy -n prod
# 预期:5 条策略(deny-all + 4 条白名单)

# 查看 Ingress 路由规则
kubectl describe ingress app-ingress -n prod

# 查看 backend 的环境变量配置(验证 ConfigMap + Secret 注入)
# http-echo 容器极精简无 shell,用 describe 查看配置
kubectl describe pod -n prod -l app=backend | grep -A3 "Environment:"
# 预期看到 backend 容器部分:
#   Environment:
#     DB_HOST:      <set to the key 'DB_HOST' of config map 'backend-config'>
#     DB_PASSWORD:  <set to the key 'POSTGRES_PASSWORD' in secret 'pg-secret'>

步骤 8:清理

bash 复制代码
bash teardown.sh
# 删除 prod namespace → 删除 Kind 集群

自检问题

  1. 基础 这个项目中,为什么 PostgreSQL 用 StatefulSet 而 Redis 用 Deployment?

  2. 理解 如果 backend 的 HPA minReplicas: 2,PDB 的 minAvailable: 2,当 CPU 降低 HPA 想缩容到 2 个 Pod 时,PDB 会不会阻止?如果 HPA 想缩到 1 个呢?

  3. 应用 面试中面试官问:"你的微服务项目里,一个用户请求从浏览器到数据库,经过了 K8s 的哪些组件?" 请用本项目的架构回答。

  4. 分析 如果 backend Pod 一直卡在 Init:0/1 状态,你怎么排查?写出排查步骤和对应的 kubectl 命令。

查看答案

  1. PostgreSQL 需要:① 稳定的网络标识(pg-0.pg-svc.prod.svc.cluster.local,Pod 重启后 DNS 不变)② 独立的持久化存储(PVC,Pod 删了数据不丢)③ 有序启停------这些都是 StatefulSet 的能力。Redis 做缓存 (非持久化),数据丢了重建就行,Deployment 更简单。但如果 Redis 开启 RDB/AOF 持久化,也应该用 StatefulSet。

  2. HPA 缩到 2 个 Pod 时,PDB minAvailable: 2 不会阻止 ------因为缩容后还有 2 个 Pod,满足 minAvailable。但如果 HPA 想缩到 1 个 ,PDB 会阻止 ------因为缩到 1 个后,如果恰好有节点维护要驱逐这 1 个 Pod,就没有 Pod 可用了。经验法则:PDB 的 minAvailable 应该 ≤ HPA 的 minReplicas,否则缩容后无法承受任何中断。

  3. 完整路径:

    用户浏览器
    → DNS 解析(本地用 localhost)
    → Ingress Controller Pod(ingress-nginx namespace,nginx 反向代理)
    → 根据 Ingress 规则匹配路径
    → /api/* → backend-svc(ClusterIP)→ kube-proxy 负载均衡 → backend Pod
    → backend Pod 内 initContainer 已确认 PostgreSQL 就绪
    → backend 代码连接 pg-svc.prod.svc.cluster.local:5432
    → NetworkPolicy allow-db-from-backend 放行(app: backend → app: postgres:5432)
    → PostgreSQL Pod(StatefulSet,数据写到 PVC)
    → / → frontend-svc → frontend Pod(Nginx 返回静态页面)

额外加分:提到 Service 的 kube-proxy 做负载均衡、NetworkPolicy 的 deny-all 白名单模型、探针保证只把流量发给健康 Pod。

  1. 排查步骤:
bash 复制代码
# 1. 看 initContainer 日志(最常见:数据库没起来)
kubectl logs <backend-pod> -n prod -c wait-for-db

# 2. 看 PostgreSQL 是否在运行
kubectl get pods -n prod -l app=postgres
kubectl logs postgres-0 -n prod

# 3. 看 DNS 是否能解析
kubectl exec -n prod <backend-pod> -c wait-for-db -- nslookup pg-svc.prod.svc.cluster.local

# 4. 看 NetworkPolicy 是否阻止了 DNS(CoreDNS 在 kube-system)
#    deny-all 只影响 Ingress 类型,不影响 Egress,所以 DNS 不会被阻止
#    但如果有人加了 Egress deny-all,就需要放行 UDP 53

# 5. 看事件
kubectl describe pod <backend-pod> -n prod | tail -20

最常见原因:PostgreSQL 镜像拉取失败、PostgreSQL Pod 启动失败(PVC 问题)、或 Secret 密码错误导致 pg 拒绝启动。


面试怎么讲这个项目

面试时用 STAR 法则(Situation → Task → Action → Result)讲 2-3 分钟,然后面试官会从你的描述中追问细节:

"我做了一个 K8s 微服务项目,前端 Nginx + 后端 API + PostgreSQL + Redis,全部部署在 K8s 上。

架构上 ,我用 Ingress 做入口路由,/ 到前端、/api/* 到后端并做 URL rewrite。后端用 Deployment 2 副本 + HPA 自动扩缩容,PostgreSQL 用 StatefulSet + PVC 持久化。

安全上,我用 NetworkPolicy 做 deny-all + 白名单------数据库只允许后端 Pod 访问,前端直连数据库会被拒绝。敏感信息用 Secret,非敏感用 ConfigMap。

可用性上,后端有 PDB 保证滚动更新不中断,有 liveness/readiness 探针保证只把流量发给健康 Pod,还有 initContainer 等数据库就绪后再启动主容器。"

面试官可能追问的方向:

你的话 面试官追问 对应文章
"HPA 自动扩缩容" "HPA 怎么采集 CPU 指标的?" #08 扩缩容
"StatefulSet + PVC" "PVC 的 StorageClass 是什么?" #10 存储
"NetworkPolicy deny-all" "怎么调试 NetworkPolicy 不通的问题?" #22 网络策略
"initContainer 等数据库" "initContainer 失败了怎么办?" #21 多容器
"PDB 保证不中断" "PDB 和 HPA 冲突了怎么办?" #25 高可用
"Ingress URL rewrite" "Ingress Controller 和 Ingress 的区别?" #23 Ingress

毕业总结

🎉 恭喜你完成了 K8s Guide 初学者轨道的全部 30 篇课程!

你学到的技能树

复制代码
📦 基础入门 (01-10)        🔧 生产化 (11-20)        🚀 进阶实战 (21-30)
─────────────────         ────────────────        ──────────────────
• K8s 是什么              • 多租户隔离              • 多容器模式
• 本地 Kind 集群           • 健康检查                • 网络策略隔离
• Pod 管理                • 资源管控                • Ingress 生产实战
• Deployment 部署          • RBAC 权限               • Pod 安全加固
• ReplicaSet 控制器        • 有状态 + 守护进程        • 高可用保障
• Service 服务发现         • 批处理 + 定时任务        • Pod 身份认证
• 配置管理                • Helm 包管理             • K8s 扩展模型
• 扩缩容 + 滚动发布        • 日志 + 监控             • Kustomize 配置
• 网络基础                • 排障方法论               • 集群升级运维
• 存储基础                • Gateway API            • 完整微服务部署

下一步建议

  • 🔍 求职者轨道:深入系统全景图 → 领域深潜 → 面试题库
  • 📝 CKA/CKAD 认证:初学者轨道覆盖了考试 ~70% 的知识点
  • 🛠️ 实战练手:用本项目的架构,换成你熟悉的语言栈(Flask/Spring Boot/Go)实现一个真正的 CRUD API

🎓 毕业啦

📚 本文来自 K8s Guide ------ 开源免费的 Kubernetes 中文学习指南

  • 🗺️ 初学者轨道 + 面试轨道,从零基础到拿 Offer 一站式覆盖
  • 🧪 每篇文章配套 Kind 实验脚本,本地一键运行
  • 🔗 本文源码:docs/beginner/20-gateway-api.md
  • ⭐ 如果对你有帮助,欢迎 Star! github.com/callmebg/k8s-guide
相关推荐
奈斯先生Vector10 小时前
告别工具碎片化:基于 Nano Banana 全模态 AI 聚合架构搭建“文本-图像-视频”自动化协同生产线
运维·数据库·人工智能·架构·自动化·aigc·音视频
小码哥哥16 小时前
如何评价“构建企业级 AI 知识库“这一趋势?从技术架构到落地实践的完整分析
人工智能·架构
张忠琳18 小时前
【NPU】Ascend Docker Runtime v26.0.1 之三 runtime/dcmi/ — 超深度逐行分析
云原生·容器·kubernetes·npu·docker-runtime
Vince的修炼之路19 小时前
RAG 文档处理技术深度分析:PDF 解析、表格提取、OCR 识别全链路
人工智能·架构
童谣121 小时前
越华环保:美丽蓝天项目申报方案数字化研判架构设计与实践
架构
沉迷学习 日益消瘦1 天前
13-Ingress 生产实战
运维·kubernetes
Dr.kangder1 天前
嵌入式软件程序分析技术:原理、方法与实战
servlet·架构·嵌入式·dsp开发
Vince的修炼之路1 天前
大模型 Skills 技术深度分析
人工智能·架构
Dr.kangder1 天前
嵌入式软件单元测试:从理论到实践
架构·单元测试·嵌入式·测试覆盖率