尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Day58-K8s部署Spring Boot微服务:ConfigMap+Secret+HPA

Day58-K8s部署Spring Boot微服务:ConfigMap+Secret+HPA 配置进镜像、弹性靠人手——三个都会炸的 P0 隐患把配置打进镜像、密码硬编码进代码、弹性伸缩靠人手动敲命令——这三个坑任何一个在生产环境爆了都是 P0。典型场面镜像里硬编码了测试库密码上线连不上数据库、探针反复失败、Pod 全部 CrashLoopBackOff白天流量一涨 CPU 打满网关超时像雪崩蔓延只能一群人边改镜像边手动扩容。K8s 用三个标准对象一次性解决ConfigMap 管非敏感配置与镜像分离、Secret 管敏感凭据、HPA 管基于指标的自动扩缩容。本文按生产标准把 Spring Boot 微服务搬进 K8s先用 ConfigMap 剥离环境配置、用 Secret 存密码与 API Key含 base64 只是编码的提醒再给出可直接 apply 的生产级 Deployment三探针分工、非 root 运行、资源限制接着演示 Spring AI 服务读取注入的环境变量最后配 HPAmetrics-server 自定义指标并对 AI 服务不能只看 CPU给出进阶方案。读完能交付一个配置分离、凭据安全、能随流一、ConfigMap让配置从镜像里滚出去ConfigMap 是 K8s 里专门用来存非敏感配置的对象。它的作用就一个把环境相关的配置数据库地址、缓存地址、日志级别、业务开关等从容器镜像里剥离出来。镜像里只放代码和不变的东西环境相关的配置交给 ConfigMap。这样同一个镜像在开发、测试、生产三套环境里都能复用。1.1 准备一个 ConfigMap下面这个例子给一个 Spring Boot Spring AI 的问答服务配置外部依赖# configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: ai-service-config namespace: default data: # 应用层配置 SPRING_PROFILES_ACTIVE: prod SERVER_PORT: 8080 LOGGING_LEVEL_ROOT: INFO # 外部依赖地址 SPRING_DATASOURCE_URL: jdbc:postgresql://pg-primary.default.svc.cluster.local:5432/ai_db SPRING_REDIS_HOST: redis.default.svc.cluster.local SPRING_REDIS_PORT: 6379 # Spring AI 配置模型地址、默认模型名 SPRING_AI_DASHSCOPE_API_URL: https://dashscope.aliyuncs.com/api/v1 SPRING_AI_DASHSCOPE_CHAT_OPTIONS_MODEL: qwen-turboK8s 会把data里的键值对注入到容器环境变量。Spring Boot 的application.yml或者ConfigurationProperties会自动读取这些SPRING_*开头的变量因为它们遵循 relaxed binding 规则。1.2 两种使用姿势方式适用场景注意点env/envFrom注入环境变量少量键值对、需要被应用直接读取变量名会被转换成大写注意兼容挂载为 Volume 文件整个配置文件例如application-prod.yml修改 ConfigMap 后Volume 里的文件会热更新但应用需自己监听重载Spring Boot 云原生部署推荐用环境变量方式。简单、直观、和 12-Factor App 一致。二、Secret别把密码当成普通配置Secret 和 ConfigMap 长得很像但它专门存敏感数据数据库密码、API Key、TLS 证书、OAuth2 Client Secret 等。很多人有个误解以为 Secret 里的base64是加密。不是它只是编码。任何拿到 YAML 的人都能一眼解码。所以 Secret 的核心价值是与 ConfigMap 职责分离最小权限管理K8s 对 Secret 有额外的存储和处理限制默认不落地 etcd 明文可开启静态加密可以挂载为文件避免密码出现在环境变量里被ps/env看到。2.1 创建 Secret先准备好密码文件避免在 shell history 里留下明文# 推荐从文件创建命令行历史不会留密码 echo -n Pssw0rd!2024 db-password.txt echo -n sk-xxxxxxxxxxxxxxxx ai-api-key.txt ​ kubectl create secret generic ai-service-secret \ --from-fileDB_PASSWORDdb-password.txt \ --from-fileAI_API_KEYai-api-key.txt \ --dry-runclient -o yaml secret.yaml ​ # 用完立刻删掉本地明文文件 rm db-password.txt ai-api-key.txt生成的secret.yaml大概长这样# secret.yaml apiVersion: v1 kind: Secret metadata: name: ai-service-secret namespace: default type: Opaque stringData: # 这里用 stringDatakubectl 会自动帮你 base64 DB_PASSWORD: Pssw0rd!2024 AI_API_KEY: sk-xxxxxxxxxxxxxxxx生产环境务必启用 etcd 静态加密并配合 RBAC 限制谁能读取 Secret。更严格的企业可以用 External Secrets Operator 对接 Vault / 阿里云 KMS。三、Deployment把 ConfigMap 和 Secret 用起来下面是一个可以直接 apply 的生产级 Deployment。重点看几个地方envFrom把 ConfigMap 和 Secret 批量注入环境变量配置了resources.requests/limits这是 HPA 计算 CPU 利用率的前提配置了startup/liveness/readiness三个探针使用非 root 用户运行容器。# deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: ai-service namespace: default labels: app: ai-service spec: replicas: 2 # HPA 接管后会动态调整这里只是初始值 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 # 滚动更新时不允许中断 selector: matchLabels: app: ai-service template: metadata: labels: app: ai-service annotations: prometheus.io/scrape: true prometheus.io/port: 8080 prometheus.io/path: /actuator/prometheus spec: securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 containers: - name: ai-service image: registry.cn-hangzhou.aliyuncs.com/demo/ai-service:1.2.0 imagePullPolicy: Always ports: - containerPort: 8080 name: http envFrom: - configMapRef: name: ai-service-config - secretRef: name: ai-service-secret resources: requests: cpu: 250m memory: 512Mi limits: cpu: 1000m memory: 1Gi # 启动探针应用启动慢时用防止启动阶段被 liveness 误杀 startupProbe: httpGet: path: /actuator/health/liveness port: http initialDelaySeconds: 10 periodSeconds: 5 failureThreshold: 12 # 最多给 60 秒启动时间 # 存活探针应用卡死时重启容器 livenessProbe: httpGet: path: /actuator/health/liveness port: http periodSeconds: 10 failureThreshold: 3 # 就绪探针流量只打给 Ready 的 Pod readinessProbe: httpGet: path: /actuator/health/readiness port: http periodSeconds: 5 failureThreshold: 33.1 三个探针的分工很多人把三个探针配成一样的路径其实浪费。Spring Boot 3.x 的 Actuator 已经内置分组/actuator/health/liveness只要 JVM 还活着就返回 UP。适合 livenessProbe。/actuator/health/readiness应用依赖数据库、Redis都就绪才返回 UP。适合 readinessProbe。如果你把它们都配成/actuator/health会出现一种很坑的情况数据库挂了 readiness 应该让 Pod 下线但 liveness 也会失败K8s 就会把 Pod 重启。结果数据库没好Pod 反复重启形成“重启风暴”。3.2 Spring Boot 端开启 Actuator 健康分组# application.yml management: endpoints: web: exposure: include: health,info,prometheus,metrics endpoint: health: probes: enabled: true # 自动生成 liveness/readiness 分组 show-details: always metrics: tags: application: ${spring.application.name}四、Java 代码让应用拥抱 K8s 环境下面是一段极简的 Spring AI 服务代码演示如何读取 ConfigMap/Secret 注入的环境变量并暴露一个问答接口。// AiChatController.java package com.example.ai.controller; ​ import org.springframework.ai.chat.client.ChatClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; ​ RestController RequestMapping(/api/ai) public class AiChatController { ​ private final ChatClient chatClient; ​ // 从 Secret 注入的 API Key 已经被 Spring AI starter 自动读取 // 这里仅作演示如何显式读取环境变量做校验或日志 Value(${spring.ai.dashscope.api-key:}) private String apiKey; ​ Value(${spring.ai.dashscope.chat.options.model:qwen-turbo}) private String modelName; ​ public AiChatController(ChatClient.Builder builder) { this.chatClient builder .defaultSystem(你是一位经验丰富的 Java 后端工程师回答简洁、有代码示例。) .build(); } ​ PostMapping(/chat) public String chat(RequestParam String question) { if (apiKey null || apiKey.isBlank()) { throw new IllegalStateException(AI API Key 未配置请检查 Secret 是否挂载); } return chatClient.prompt() .user(question) .call() .content(); } ​ GetMapping(/model) public String currentModel() { return 当前模型: modelName; } }4.1 配置类读取环境变量// AiServiceProperties.java package com.example.ai.config; ​ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; ​ Component ConfigurationProperties(prefix ai.service) public class AiServiceProperties { private int maxTokens 1024; private double temperature 0.7; private boolean logRequest false; ​ // getter / setter 省略... }通过envFrom注入的环境变量例如AI_SERVICE_MAX_TOKENS2048Spring Boot 会自动绑定到AiServiceProperties上。配合 ConfigMap你可以在不重新打包镜像的情况下调整业务参数。五、HPA让服务自己“长”出来Horizontal Pod AutoscalerHPA是 K8s 的横向扩缩容控制器。它会根据 CPU、内存或自定义指标自动调整 Deployment 的副本数。5.1 先装 metrics-serverHPA 需要 metrics-server 提供 Pod 的实时资源使用数据kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml ​ # 如果内网环境没有有效证书加上 --kubelet-insecure-tls kubectl patch deployment metrics-server -n kube-system \ --typejson \ -p[{op: add, path: /spec/template/spec/containers/0/args/-, value: --kubelet-insecure-tls}]5.2 HPA 配置示例# hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: ai-service-hpa namespace: default spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: ai-service minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60 # CPU 平均利用率达到 60% 开始扩容 behavior: scaleUp: stabilizationWindowSeconds: 30 policies: - type: Percent value: 100 periodSeconds: 15 # 15 秒内最多扩容 100% scaleDown: stabilizationWindowSeconds: 300 # 缩容更保守等 5 分钟确认流量真的降了 policies: - type: Percent value: 10 periodSeconds: 605.3 压测验证# 1. 查看当前状态 kubectl get hpa ai-service-hpa --watch ​ # 2. 用 hey 或 wrk 打流量 hey -z 60s -c 50 -q 10 \ -m POST \ -d question解释一下Spring%20Boot自动配置原理 \ http://ai-service-ip/api/ai/chat ​ # 3. 观察 Pod 数量变化 kubectl get pods -l appai-service --watch5.4 AI 服务为什么不要只看 CPU大模型调用有个特点单次请求 CPU 不高但耗时很长。如果你只按 CPU 扩容可能在请求排队时已经超时了。更合理的做法是用自定义指标比如正在处理的请求数in-flight requests请求队列长度Token 生成延迟 P99可以用 Prometheus Adapter 把这些指标暴露给 HPAmetrics: - type: Pods pods: metric: name: http_server_requests_seconds_max target: type: AverageValue averageValue: 1.5 # 平均响应时间超过 1.5s 就扩容这算是给 AI 服务做弹性伸缩的进阶版本后续在云厂商托管 K8s 上可以用更成熟的 Custom Metrics API。六、完整部署流程把前面的 YAML 按顺序 apply 一遍# 1. 创建配置和凭据 kubectl apply -f secret.yaml kubectl apply -f configmap.yaml ​ # 2. 部署应用 kubectl apply -f deployment.yaml ​ # 3. 暴露服务 kubectl apply -f service.yaml ​ # 4. 开启弹性伸缩 kubectl apply -f hpa.yaml ​ # 5. 验证 kubectl get pods -l appai-service kubectl get svc ai-service kubectl get hpa ai-service-hpaService 可以用 ClusterIP配合 Ingress 做七层路由# service.yaml apiVersion: v1 kind: Service metadata: name: ai-service namespace: default spec: selector: app: ai-service ports: - port: 80 targetPort: 8080 name: http type: ClusterIP七、实战建议老兵的三个血泪经验1. Secret 不要只依赖 base64生产必须上加密和权限开启 etcd 静态加密用 RBAC 限制 Secret 读取范围只给需要的 ServiceAccount考虑 External Secrets Operator把凭据存在 Vault / 云厂商 KMS 里K8s 里只存引用。2. HPA 的阈值不是默认值 50%而是压测出来的先用hey/wrk/ JMeter 压出服务的 CPU-吞吐量曲线找到 CPU 利用率与响应时间开始恶化的拐点把 HPA 目标设在这个点之前设置behavior.scaleDown.stabilizationWindowSeconds防止流量抖动导致 Pod 反复创建销毁。3. readiness 探针必须能真实反映服务能力不要只检查/actuator/health要检查/actuator/health/readiness如果依赖了数据库、Redis、消息队列要让 readiness 在这些依赖不可用时返回 DOWN配合PodDisruptionBudget保证发布和缩容时至少有 N 个可用副本。# pdb.yaml apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: ai-service-pdb spec: minAvailable: 1 selector: matchLabels: app: ai-service下篇预告“云原生不是把应用搬到 K8s 上而是让应用学会在云里自己呼吸。”ConfigMap 让配置流动起来Secret 让敏感信息有边界HPA 让服务能随流量起伏而生长。把这三件事做对你的 Spring Boot 服务才算真正在 K8s 里站稳了脚跟。下篇 Day59我们走进阿里云 ACK 生产级集群节点池怎么规划、SLS 日志采集怎么配、ARMS 监控告警怎么做。云上的坑比本地 Minikube 多得多但也爽得多。参考版本Spring Boot 3.2.xSpring AI 1.0.0-M3Kubernetes 1.29metrics-server v0.7.x
返回列表