K8s 最佳实践与综合面试题
面试官:如果让你从零开始在 K8s 上部署一个 Java 微服务,你会怎么做?
你:我会创建一个完整的部署清单,包括 Deployment(管理副本)、Service(服务发现)、ConfigMap/Secret(配置分离)、资源配额(Request/Limit)、探针(健康检查)、HPA(弹性伸缩)、反亲和性(高可用)等关键配置,确保服务稳定、可观测、易维护。
面试官:具体配置如何设计?资源配额如何估算?如何保证零停机?
这是一道综合性问题,考察对 K8s 的整体把握,能说清每个配置项背后原理的候选人,才具备生产实战能力。
链式追问一:微服务上 K8s 完整清单
Section titled “链式追问一:微服务上 K8s 完整清单”Q1:一个完整的 Java 微服务 K8s 部署清单包含哪些要素?必考
Section titled “Q1:一个完整的 Java 微服务 K8s 部署清单包含哪些要素?”七要素完整配置:
# 要素 1:Deployment(管理 Pod 副本)apiVersion: apps/v1kind: Deploymentmetadata: name: order-service labels: app: order-servicespec: replicas: 3 selector: matchLabels: app: order-service strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 # 滚动更新:最多 1 个 Pod 不可用 maxSurge: 1 # 滚动更新:最多超配 1 个 Pod
# 要素 2:Pod 模板 template: metadata: labels: app: order-service version: v1.2.0 spec: # 要素 6:调度策略(Pod 反亲和,避免单点) affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: order-service topologyKey: kubernetes.io/hostname
# 优雅关闭 terminationGracePeriodSeconds: 60
containers: - name: order-service image: registry.example.com/order-service:1.2.0 ports: - containerPort: 8080
# 要素 2:资源配额(必须设置!) resources: requests: cpu: "500m" # 调度保证:500m(0.5 核) memory: "512Mi" # 调度保证:512Mi limits: cpu: "1000m" # 运行上限:1 核 memory: "1Gi" # 运行上限:1Gi
# 要素 3:环境变量(从 ConfigMap/Secret 注入) env: - name: SPRING_PROFILES_ACTIVE value: "prod" - name: DB_HOST valueFrom: configMapKeyRef: name: order-config key: db-host - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password
# 要素 4:探针(零停机发布的关键) livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 60 # 启动后 60s 开始探测 periodSeconds: 10 failureThreshold: 3
readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 periodSeconds: 5 failureThreshold: 3
startupProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 10 periodSeconds: 5 failureThreshold: 30 # 150s 内启动完成
# 要素 4:优雅关闭钩子 lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 15"]
# 挂载配置 volumeMounts: - name: config-volume mountPath: /app/config
volumes: - name: config-volume configMap: name: order-config
---# 要素 3:ConfigMap(普通配置)apiVersion: v1kind: ConfigMapmetadata: name: order-configdata: application.yml: | server: port: 8080 spring: datasource: url: jdbc:mysql://mysql:3306/orders username: order_user
---# 要素 3:Secret(敏感信息)apiVersion: v1kind: Secretmetadata: name: db-secrettype: Opaquedata: password: cGFzc3dvcmQ= # echo -n "password" | base64
---# 要素 5:Service(服务发现)apiVersion: v1kind: Servicemetadata: name: order-servicespec: type: ClusterIP selector: app: order-service ports: - port: 80 targetPort: 8080
---# 要素 7:HPA(弹性伸缩)apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: order-service-hpaspec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: order-service minReplicas: 3 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70
---# ServiceAccount + RBAC(安全配置)apiVersion: v1kind: ServiceAccountmetadata: name: order-service-sa---apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: order-service-rolerules:- apiGroups: [""] resources: ["configmaps", "secrets"] verbs: ["get", "list"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: order-service-rolebindingsubjects:- kind: ServiceAccount name: order-service-saroleRef: kind: Role name: order-service-role apiGroup: rbac.authorization.k8s.io七要素清单总结:
| 要素 | 配置项 | 作用 | 缺失后果 |
|---|---|---|---|
| Deployment | replicas、strategy | 管理副本、滚动更新 | 无法扩缩容、更新失败无法回滚 |
| Resources | requests、limits | 资源配额、调度依据 | 节点资源耗尽、OOM Kill、HPA 失效 |
| Probe | liveness、readiness、startup | 健康检查、零停机 | Pod 死锁不重启、滚动更新流量失败 |
| 优雅关闭 | preStop、terminationGracePeriod | 优雅关闭连接 | 请求中断、数据丢失 |
| 配置分离 | ConfigMap、Secret | 配置与镜像分离 | 镜像包含敏感信息、环境切换困难 |
| 反亲和性 | podAntiAffinity | 多副本分散节点 | 单节点故障导致服务全挂 |
| HPA | minReplicas、maxReplicas | 弹性伸缩 | 流量突增时服务雪崩 |
本质一句话:完整的 K8s 部署清单不仅是 Pod 运行,更包括资源管理、健康检查、配置分离、高可用、弹性伸缩,是生产级服务的基石。
Q2:ConfigMap 和 Secret 的区别?如何安全使用 Secret?必考
Section titled “Q2:ConfigMap 和 Secret 的区别?如何安全使用 Secret?”ConfigMap vs Secret 对比:
| 维度 | ConfigMap | Secret |
|---|---|---|
| 用途 | 普通配置(端点、参数、环境变量) | 敏感信息(密码、Token、证书) |
| 存储格式 | 明文存储在 etcd | Base64 编码存储在 etcd |
| 大小限制 | 1MB(单个 ConfigMap) | 1MB(单个 Secret) |
| 挂载方式 | 环境变量 / 文件挂载 | 环境变量 / 文件挂载 |
| 安全性 | 无加密 | Base64(非加密),需配合 etcd 加密 |
| 更新机制 | 更新后 Pod 需重启或热加载 | 同 ConfigMap |
Secret 类型:
# 1. Opaque(通用 Secret,自定义数据)apiVersion: v1kind: Secretmetadata: name: db-secrettype: Opaquedata: password: cGFzc3dvcmQ= # Base64 编码
# 2. kubernetes.io/dockerconfigjson(镜像拉取密钥)kubectl create secret docker-registry regcred \ --docker-server=<your-registry-server> \ --docker-username=<your-username> \ --docker-password=<your-password>
# 3. kubernetes.io/tls(TLS 证书)kubectl create secret tls tls-secret \ --cert=path/to/tls.crt \ --key=path/to/tls.key
# 4. ServiceAccount Token(自动创建)# 每个命名空间默认创建 default serviceaccountSecret 安全最佳实践:
1. etcd 加密(Encryption at Rest) ┌─────────────────────────────────┐ │ Secret 明文(Base64) │ └──────────┬──────────────────────┘ │ KMS 插件加密 ↓ ┌─────────────────────────────────┐ │ etcd 存储(加密后) │ └─────────────────────────────────┘
配置: apiServer: encryption-provider-config: /etc/kubernetes/encryption-config.yaml
encryption-config.yaml: resources: - resources: ["secrets"] providers: - aescbc: keys: - name: key1 secret: <base64-encoded-32-byte-key> - identity: {}
效果:Secret 在 etcd 中加密存储,防止 etcd 泄露导致密钥泄露
2. RBAC 限制访问 ├── 最小权限原则:只允许特定 ServiceAccount 读取 Secret └── 避免使用 default serviceaccount(权限过大)
示例: apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: secret-reader rules: - apiGroups: [""] resources: ["secrets"] resourceNames: ["db-secret"] # 只允许读取特定 Secret verbs: ["get"]
3. 外部密钥管理系统(Vault、AWS Secrets Manager) ┌──────────────────────────────────┐ │ 应用启动时读取外部密钥管理系统 │ │ └── HashiCorp Vault / AWS SM │ └──────────┬───────────────────────┘ │ API 调用 ↓ ┌──────────────────────────────────┐ │ 应用内存中持有密钥 │ │ (不写入 K8s Secret) │ └──────────────────────────────────┘
优势: ✓ Secret 不存储在 K8s(更安全) ✓ 密钥轮换自动化 ✓ 审计日志
4. 避免环境变量注入(推荐文件挂载) ├── 环境变量泄露风险:ps -ef、日志、崩溃转储 └── 文件挂载更安全:只挂载到容器内,不易泄露
配置: volumes: - name: secret-volume secret: secretName: db-secret volumeMounts: - name: secret-volume mountPath: /app/secrets readOnly: true实战案例:
# 完整的 Secret 安全配置apiVersion: v1kind: Secretmetadata: name: db-secret annotations: # 使用 KMS 加密 kms.aws/encrypted: "true"type: Opaquedata: password: cGFzc3dvcmQ=
---# Pod 使用 Secret(文件挂载)apiVersion: v1kind: Podmetadata: name: my-appspec: serviceAccountName: app-sa # 使用专用 ServiceAccount containers: - name: app image: myapp:1.0 volumeMounts: - name: secret-volume mountPath: /app/secrets readOnly: true # 只读挂载 volumes: - name: secret-volume secret: secretName: db-secret items: - key: password path: db-password.txt本质一句话:ConfigMap 存普通配置,Secret 存敏感信息,但 Secret 的 Base64 不是加密,生产环境需配合 etcd 加密、RBAC 限制、外部密钥管理系统。
链式追问二:调度策略与高可用
Section titled “链式追问二:调度策略与高可用”Q3:K8s 调度器如何决定 Pod 放在哪个节点?必考
Section titled “Q3:K8s 调度器如何决定 Pod 放在哪个节点?”调度流程三步:
K8s 调度流程:
1. 预选(Predicates):过滤不合格节点 ├── PodFitsResources:节点剩余资源 ≥ Pod Request ├── PodFitsHostPorts:节点端口未被占用 ├── PodMatchNodeSelector:节点标签匹配 nodeSelector ├── PodToleratesNodeTaints:Pod 容忍节点污点 ├── CheckNodeCondition:节点状态正常(Ready) └── PodAffinityFilter:满足 Pod 亲和性/反亲和性
结果:候选节点列表(可能为空 → Pod Pending)
2. 优选(Priorities):为候选节点打分 ├── LeastRequestedPriority:资源空闲最多的节点得分高 │ └── score = (node.capacity - node.requested) / node.capacity ├── BalancedResourceAllocation:CPU 和内存使用均衡 ├── ImageLocalityPriority:节点已有镜像的得分高 ├── NodeAffinityPriority:节点亲和性权重 └── TaintTolerationPriority:污点容忍权重
结果:每个候选节点一个分数(0-100)
3. 绑定(Bind):选择最高分节点 ├── 如果多个节点同分 → 随机选择 └── 将 Pod 绑定到节点(更新 Pod.spec.nodeName)调度器配置示例:
# 节点选择(nodeSelector)spec: nodeSelector: disk-type: ssd # 节点必须有 label: disk-type=ssd
---# 节点亲和性(nodeAffinity,更灵活)spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: # 硬性要求 nodeSelectorTerms: - matchExpressions: - key: zone operator: In values: [cn-hangzhou-a, cn-hangzhou-b] preferredDuringSchedulingIgnoredDuringExecution: # 软性偏好 - weight: 80 preference: matchExpressions: - key: disk-type operator: In values: [ssd]污点(Taint)与容忍(Toleration):
污点(Taint):标记节点专用
场景 1:GPU 节点专用 kubectl taint nodes gpu-node nvidia.com/gpu=true:NoSchedule └── 普通Pod无法调度到GPU节点 只有配置了容忍的Pod才能调度: tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule"
场景 2:节点维护 kubectl taint nodes node-1 key=value:NoExecute └── NoExecute:不容忍的Pod立即驱逐
污点 Effect 类型: ├── NoSchedule:不容忍的Pod不能调度(已存在的Pod不受影响) ├── PreferNoSchedule:尽量不调度(软性约束) └── NoExecute:不容忍的Pod立即驱逐
容忍(Toleration)配置: tolerations: - key: "key1" operator: "Equal" # 或 Exists(不比较value) value: "value1" effect: "NoSchedule"Pod 亲和性/反亲和性:
# Pod 反亲和性:避免同一应用的 Pod 聚集在同一节点affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: # 硬性要求 - labelSelector: matchLabels: app: order-service topologyKey: kubernetes.io/hostname # 含义:同一节点(hostname)不能有两个 app=order-service 的 Pod
# Pod 亲和性:相关服务调度到同一节点(减少网络延迟)affinity: podAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: cache-service topologyKey: kubernetes.io/hostname # 含义:尽量调度到有 cache-service 的节点拓扑分布约束(TopologySpreadConstraints,K8s 1.19+):
# 更精细的 Pod 分布控制topologySpreadConstraints:- maxSkew: 1 # 最大偏差 1 个 Pod topologyKey: zone # 按可用区分布 whenUnsatisfiable: DoNotSchedule # 无法满足时不调度 labelSelector: matchLabels: app: order-service
# 效果:# zone-a: 2 Pod# zone-b: 3 Pod# zone-c: 2 Pod# 最大偏差 = max(3-2, 2-2) = 1(满足要求)本质一句话:调度器通过预选过滤不合格节点,优选打分选择最优节点,支持 nodeSelector、亲和性、污点容忍等策略实现精细化调度控制。
Q4:如何保证 Pod 分散在不同节点(高可用)?高频
Section titled “Q4:如何保证 Pod 分散在不同节点(高可用)?”高可用部署策略:
高可用核心:避免单点故障
单节点故障影响: ├── 无反亲和性:3个Pod全在node-1 → node-1故障 → 服务全挂 └── 有反亲和性:Pod分散在node-1/2/3 → node-1故障 → 剩余2个Pod服务正常
实现方式: 1. Pod 反亲和性(PodAntiAffinity) 2. 拓扑分布约束(TopologySpreadConstraints) 3. PodDisruptionBudget(PDB,防止同时驱逐过多Pod)方案对比:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Pod 反亲和性 | 简单、K8s 原生支持 | 只能按节点维度,无法精确控制数量 | 小规模集群(<100节点) |
| 拓扑分布约束 | 精确控制偏差、支持多维度(节点/可用区) | 需 K8s 1.19+ | 大规模集群、多可用区部署 |
| PodDisruptionBudget | 防止节点维护时服务中断 | 只能防止主动驱逐,不能防止节点故障 | 所有生产服务 |
配置示例:
# 方案 1:Pod 反亲和性(硬性要求)affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app: order-service topologyKey: kubernetes.io/hostname # 含义:每个节点最多 1 个 order-service Pod
# 局限:如果 replicas > 节点数,Pod 无法调度(Pending)
---# 方案 2:Pod 反亲和性(软性偏好)affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: order-service topologyKey: kubernetes.io/hostname # 含义:尽量分散,实在不行也可以聚集
# 优点:replicas > 节点数时仍可调度
---# 方案 3:拓扑分布约束(推荐)topologySpreadConstraints:- maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: order-service
# 效果:每个节点的 Pod 数量差异不超过 1
---# 方案 4:多可用区部署topologySpreadConstraints:- maxSkew: 1 topologyKey: topology.kubernetes.io/zone # 按可用区分布 whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: order-service
# 效果:每个可用区的 Pod 数量均衡PodDisruptionBudget(PDB):
# PDB:防止节点维护时服务中断apiVersion: policy/v1kind: PodDisruptionBudgetmetadata: name: order-service-pdbspec: minAvailable: 2 # 至少保持 2 个 Pod 运行 selector: matchLabels: app: order-service
# 或使用 maxUnavailablespec: maxUnavailable: 1 # 最多允许 1 个 Pod 不可用
# 作用:# kubectl drain node-1(驱逐节点上的Pod)# → PDB 检查:如果驱逐后可用Pod < minAvailable → 拒绝驱逐# → 确保滚动更新/节点维护时服务可用实战案例:
场景:订单服务 3 副本,部署在 3 个可用区
节点分布: zone-a: node-1, node-2 zone-b: node-3, node-4 zone-c: node-5, node-6
配置: replicas: 3 topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone
调度结果: zone-a: node-1 → order-service-1 zone-b: node-3 → order-service-2 zone-c: node-5 → order-service-3
故障模拟: node-1 故障 → order-service-1 被调度到 zone-a 的 node-2 zone-a 故障 → order-service-1 被调度到 zone-b 或 zone-c
效果: ✓ 单节点故障:服务正常(剩余2个Pod) ✓ 单可用区故障:服务正常(剩余2个Pod在其他可用区)本质一句话:通过 Pod 反亲和性或拓扑分布约束将 Pod 分散在不同节点/可用区,配合 PDB 防止主动驱逐导致服务中断,实现真正的高可用。
链式追问三:监控告警与日志
Section titled “链式追问三:监控告警与日志”Q5:K8s 如何做监控和日志收集?必考
Section titled “Q5:K8s 如何做监控和日志收集?”监控技术栈(Prometheus + Grafana):
监控架构:
┌──────────────────────────────────────┐│ 应用暴露 /actuator/prometheus ││ 指标: ││ - http_requests_total ││ - jvm_memory_used_bytes ││ - hikaricp_connections_active │└──────────────┬───────────────────────┘ │ Prometheus 拉取(每 15s) ↓┌──────────────────────────────────────┐│ Prometheus ││ ├── 时序数据库(TSDB) ││ ├── PromQL 查询语言 ││ └── 告警规则(Alerting Rules) │└──────────────┬───────────────────────┘ │ 数据查询 ↓┌──────────────────────────────────────┐│ Grafana ││ ├── 可视化仪表盘 ││ ├── 告警通知(邮件/钉钉/Slack) ││ └── 多数据源支持 │└──────────────┬───────────────────────┘ │ 告警触发 ↓┌──────────────────────────────────────┐│ AlertManager ││ └── 告警路由、分组、静默 │└──────────────────────────────────────┘Prometheus 监控指标:
# Prometheus 配置(prometheus.yml)global: scrape_interval: 15s # 每 15s 拉取一次指标
scrape_configs:- job_name: 'kubernetes-pods' kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] action: keep regex: true - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] action: replace target_label: __metrics_path__ regex: (.+)
# Pod 注解(应用侧)metadata: annotations: prometheus.io/scrape: "true" prometheus.io/path: "/actuator/prometheus" prometheus.io/port: "8080"告警规则配置:
# 告警规则(alerting-rules.yml)groups:- name: app-alerts rules: # 告警 1:Pod CPU 使用率 > 80% - alert: HighCPUUsage expr: | rate(container_cpu_usage_seconds_total{container="order-service"}[5m]) > 0.8 for: 5m labels: severity: warning annotations: summary: "Pod {{ $labels.pod }} CPU使用率过高" description: "CPU使用率 {{ $value }}%,持续 5min"
# 告警 2:HTTP 错误率 > 5% - alert: HighErrorRate expr: | sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.05 for: 2m labels: severity: critical annotations: summary: "HTTP 错误率过高"
# 告警 3:Pod 重启次数 > 3次/小时 - alert: PodRestartTooOften expr: | increase(kube_pod_container_status_restarts_total[1h]) > 3 labels: severity: warning annotations: summary: "Pod {{ $labels.pod }} 频繁重启"日志收集(ELK Stack):
日志架构:
┌──────────────────────────────────────┐│ Pod 标准输出(stdout/stderr) ││ ├── /var/log/containers/*.log ││ └── /var/log/pods/*.log │└──────────────┬───────────────────────┘ │ Fluentd 采集 ↓┌──────────────────────────────────────┐│ Fluentd(DaemonSet,每个节点一个) ││ ├── 解析日志格式 ││ ├── 添加元数据(Pod名、命名空间) ││ └── 过滤、缓冲 │└──────────────┬───────────────────────┘ │ 发送到 Elasticsearch ↓┌──────────────────────────────────────┐│ Elasticsearch ││ ├── 索引日志(按日期分片) ││ ├── 全文检索 ││ └── 聚合分析 │└──────────────┬───────────────────────┘ │ 查询接口 ↓┌──────────────────────────────────────┐│ Kibana ││ ├── 日志查询界面 ││ ├── 可视化图表 ││ └── 仪表盘 │└──────────────────────────────────────┘Fluentd 配置示例:
# Fluentd ConfigMapapiVersion: v1kind: ConfigMapmetadata: name: fluentd-configdata: fluent.conf: | <source> @type tail path /var/log/containers/*.log pos_file /var/log/fluentd-containers.log.pos tag kubernetes.* read_from_head true <parse> @type json time_format %Y-%m-%dT%H:%M:%S.%NZ </parse> </source>
<filter kubernetes.**> @type kubernetes_metadata @id filter_kube_metadata kubernetes_url "#{ENV['KUBERNETES_SERVICE_HOST']}:#{ENV['KUBERNETES_SERVICE_PORT']}" </filter>
<match kubernetes.**> @type elasticsearch host "#{ENV['ELASTICSEARCH_HOST']}" port "#{ENV['ELASTICSEARCH_PORT']}" logstash_format true logstash_prefix k8s-logs <buffer> @type file path /var/log/fluentd/buffer flush_interval 5s </buffer> </match>
---# Fluentd DaemonSetapiVersion: apps/v1kind: DaemonSetmetadata: name: fluentdspec: selector: matchLabels: app: fluentd template: metadata: labels: app: fluentd spec: containers: - name: fluentd image: fluent/fluentd-kubernetes-daemonset:v1.14 env: - name: ELASTICSEARCH_HOST value: "elasticsearch.logging.svc.cluster.local" - name: ELASTICSEARCH_PORT value: "9200" volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true - name: config-volume mountPath: /fluentd/etc/fluent.conf subPath: fluent.conf volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers - name: config-volume configMap: name: fluentd-config日志查询示例(Kibana):
查询 1:查询特定 Pod 的错误日志 kubernetes.pod_name: "order-service-123-*" AND level: ERROR
查询 2:查询过去 1 小时的异常堆栈 @timestamp: [now-1h TO now] AND message: "Exception"
查询 3:聚合分析:错误日志 TOP 10 { "aggs": { "top_errors": { "terms": { "field": "error_type.keyword", "size": 10 } } } }本质一句话:监控用 Prometheus + Grafana 采集指标和可视化,日志用 Fluentd + Elasticsearch + Kibana 收集和查询,两者配合实现完整的可观测性。