企业级生产工具链实战
本文介绍了GitHub Actions高级CI/CD流水线的实战配置,包含代码质量检查、并行测试、镜像构建和分阶段部署等关键环节。配置采用矩阵策略实现多模块并行测试,支持MySQL和Redis服务容器化测试环境。通过Docker Buildx实现多平台镜像构建(amd64/arm64),包含供应链安全特性(SBOM和来源证明)。部署流程采用环境隔离策略:develop分支自动部署到staging环
十、CI/CD深度实战
10.1 GitHub Actions高级特性
# .github/workflows/advanced-deploy.yml
# 高级CI/CD流水线:矩阵构建、缓存优化、并行测试
name: Advanced CI/CD Pipeline
on:
push:
branches: [main, develop, 'release/**']
pull_request:
branches: [main]
# 并发控制:同一分支只运行最新的流水线
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
JAVA_VERSION: '17'
REGISTRY: registry.example.com
IMAGE_NAME: shop/backend
jobs:
# ===== 代码质量检查 =====
code-quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # SonarQube需要完整历史
- uses: actions/setup-java@v4
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: temurin
cache: maven
- name: SonarQube代码扫描
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
run: |
mvn verify sonar:sonar \
-Dsonar.projectKey=shop-backend \
-Dsonar.host.url=$SONAR_HOST_URL \
-Dsonar.login=$SONAR_TOKEN
# ===== 并行测试(矩阵策略)=====
test:
runs-on: ubuntu-latest
strategy:
matrix:
# 并行运行不同模块的测试
module: [user-service, order-service, payment-service]
fail-fast: false # 一个失败不影响其他
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: test123
MYSQL_DATABASE: shop_test
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s
redis:
image: redis:7-alpine
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: temurin
cache: maven
- name: 运行 ${{ matrix.module }} 测试
run: |
mvn test -pl ${{ matrix.module }} \
-Dspring.datasource.url=jdbc:mysql://localhost:3306/shop_test \
-Dspring.data.redis.host=localhost
- name: 上传测试覆盖率报告
uses: codecov/codecov-action@v3
with:
flags: ${{ matrix.module }}
# ===== 构建和推送镜像 =====
build-push:
needs: [code-quality, test]
runs-on: ubuntu-latest
if: github.event_name == 'push'
outputs:
image-digest: ${{ steps.build.outputs.digest }}
image-tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: temurin
cache: maven
- name: 构建JAR
run: mvn clean package -DskipTests -B
- name: 设置Docker Buildx(多平台构建)
uses: docker/setup-buildx-action@v3
- name: 生成镜像元数据
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=sha-
type=ref,event=branch
type=semver,pattern={{version}}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
- name: 登录镜像仓库
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: 构建并推送(多平台)
id: build
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64 # 支持ARM(如Apple M1服务器)
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: true # 生成构建来源证明(供应链安全)
sbom: true # 生成软件物料清单
# ===== 部署到不同环境 =====
deploy-staging:
needs: build-push
runs-on: ubuntu-latest
environment: staging
if: github.ref == 'refs/heads/develop'
steps:
- uses: actions/checkout@v4
- name: 部署到Staging
uses: azure/k8s-deploy@v4
with:
namespace: shop-staging
manifests: k8s/staging/
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
deploy-production:
needs: [build-push, deploy-staging]
runs-on: ubuntu-latest
environment:
name: production
url: https://api.example.com
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: 部署到生产(金丝雀)
run: |
# 先部署金丝雀版本(10%流量)
kubectl set image deployment/shop-backend-canary \
backend=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }} \
-n shop
# 等待金丝雀稳定(5分钟)
sleep 300
# 检查错误率
ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query" \
--data-urlencode 'query=rate(http_server_requests_seconds_count{status=~"5.."}[5m])/rate(http_server_requests_seconds_count[5m])*100' \
| jq '.data.result[0].value[1]' -r)
if (( $(echo "$ERROR_RATE > 1" | bc -l) )); then
echo "错误率过高(${ERROR_RATE}%),回滚金丝雀"
kubectl rollout undo deployment/shop-backend-canary -n shop
exit 1
fi
# 全量发布
kubectl set image deployment/shop-backend \
backend=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }} \
-n shop
10.2 Jenkins高级实践:共享库与多分支流水线
// vars/deployToK8s.groovy - Jenkins共享库
// 将部署逻辑封装为可复用的函数
def call(Map config) {
def namespace = config.namespace ?: 'default'
def deployment = config.deployment
def image = config.image
def timeout = config.timeout ?: 300
echo "部署 ${deployment} 到 ${namespace},镜像:${image}"
withKubeConfig([credentialsId: 'kubeconfig']) {
sh """
kubectl set image deployment/${deployment} \
app=${image} \
-n ${namespace}
kubectl rollout status deployment/${deployment} \
-n ${namespace} \
--timeout=${timeout}s
"""
}
// 部署后验证
def healthUrl = config.healthUrl ?: "http://${deployment}-svc.${namespace}/actuator/health"
retry(3) {
sleep(10)
sh "curl -f ${healthUrl} || exit 1"
}
echo "部署成功!"
}
// Jenkinsfile - 使用共享库的简洁流水线
@Library('jenkins-shared-lib') _
pipeline {
agent any
parameters {
string(name: 'IMAGE_TAG', defaultValue: '', description: '镜像标签')
booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: '跳过测试')
choice(name: 'DEPLOY_ENV', choices: ['staging', 'production'], description: '部署环境')
}
stages {
stage('构建') {
steps {
sh "mvn clean package ${params.SKIP_TESTS ? '-DskipTests' : ''} -B"
}
}
stage('Docker构建推送') {
steps {
script {
def tag = params.IMAGE_TAG ?: env.GIT_COMMIT[0..7]
docker.withRegistry('https://registry.example.com', 'registry-creds') {
def image = docker.build("shop/backend:${tag}")
image.push()
image.push('latest')
}
}
}
}
stage('部署') {
steps {
script {
def tag = params.IMAGE_TAG ?: env.GIT_COMMIT[0..7]
// 调用共享库函数
deployToK8s(
namespace: params.DEPLOY_ENV == 'production' ? 'shop' : 'shop-staging',
deployment: 'shop-backend',
image: "registry.example.com/shop/backend:${tag}",
healthUrl: "https://api.example.com/actuator/health"
)
}
}
}
}
post {
success {
// 更新Jira工单状态
jiraComment(
issueKey: env.JIRA_ISSUE,
body: "✅ 部署成功:${env.BUILD_URL}"
)
}
failure {
// 自动创建故障工单
jiraNewIssue(
site: 'JIRA',
issue: [
fields: [
project: [key: 'OPS'],
summary: "部署失败:${env.JOB_NAME} #${env.BUILD_NUMBER}",
issuetype: [name: 'Bug']
]
]
)
}
}
}
10.3 ArgoCD:GitOps持续部署
GitOps核心理念:
- Git仓库是唯一的真实来源(Single Source of Truth)
- 所有基础设施变更通过Git提交触发
- 自动同步:Git状态 = 集群状态
传统CI/CD vs GitOps:
传统:代码提交 → CI构建 → CD推送部署(Push模式)
GitOps:代码提交 → CI构建 → 更新Git配置 → ArgoCD拉取同步(Pull模式)
GitOps优势:
- 完整的变更审计(Git历史)
- 轻松回滚(git revert)
- 集群状态可重现
- 安全(ArgoCD在集群内,无需外部访问K8s API)
# argocd-app.yaml - ArgoCD应用配置
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: shop-backend
namespace: argocd
spec:
project: default
# 源:Git仓库中的K8s配置
source:
repoURL: https://github.com/example/shop-k8s-config
targetRevision: main
path: environments/production/shop-backend
# 目标:部署到哪个集群和namespace
destination:
server: https://kubernetes.default.svc
namespace: shop
# 同步策略
syncPolicy:
automated:
prune: true # 自动删除Git中已移除的资源
selfHeal: true # 自动修复手动修改(保持Git为真实来源)
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
# 健康检查
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # 忽略副本数差异(HPA会修改)
# ArgoCD常用命令
# 安装ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# 获取初始密码
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
# 登录
argocd login localhost:8080 --username admin --password <password>
# 创建应用
argocd app create shop-backend \
--repo https://github.com/example/shop-k8s-config \
--path environments/production/shop-backend \
--dest-server https://kubernetes.default.svc \
--dest-namespace shop \
--sync-policy automated
# 查看应用状态
argocd app get shop-backend
argocd app list
# 手动同步
argocd app sync shop-backend
# 回滚到上一版本
argocd app rollback shop-backend
# 查看同步历史
argocd app history shop-backend
十一、监控体系深度实战
11.1 Spring Boot自定义监控指标实战
// 完整的业务监控示例:电商订单监控
@Configuration
public class MetricsConfig {
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags(
@Value("${spring.application.name}") String appName) {
// 给所有指标添加公共标签
return registry -> registry.config()
.commonTags("application", appName,
"env", System.getenv().getOrDefault("SPRING_PROFILES_ACTIVE", "unknown"));
}
}
@Service
@Slf4j
public class OrderMetricsService {
private final MeterRegistry registry;
// 订单创建计数器(按状态分类)
private final Counter orderSuccessCounter;
private final Counter orderFailCounter;
// 订单金额分布(直方图)
private final DistributionSummary orderAmountSummary;
// 订单处理耗时(计时器)
private final Timer orderProcessTimer;
// 待支付订单数(实时仪表盘)
private final AtomicLong pendingPaymentCount = new AtomicLong(0);
public OrderMetricsService(MeterRegistry registry) {
this.registry = registry;
this.orderSuccessCounter = Counter.builder("order.created")
.tag("result", "success")
.description("成功创建的订单数")
.register(registry);
this.orderFailCounter = Counter.builder("order.created")
.tag("result", "failure")
.description("创建失败的订单数")
.register(registry);
// 金额分布:统计订单金额的分布情况
this.orderAmountSummary = DistributionSummary.builder("order.amount")
.description("订单金额分布(元)")
.baseUnit("yuan")
.publishPercentiles(0.5, 0.75, 0.95, 0.99)
.publishPercentileHistogram()
.minimumExpectedValue(1.0)
.maximumExpectedValue(100000.0)
.register(registry);
this.orderProcessTimer = Timer.builder("order.process.duration")
.description("订单处理耗时")
.publishPercentiles(0.5, 0.95, 0.99)
.register(registry);
// 注册实时指标
Gauge.builder("order.pending.payment.count", pendingPaymentCount, AtomicLong::get)
.description("待支付订单数")
.register(registry);
}
public Order createOrder(OrderDTO dto) {
return orderProcessTimer.record(() -> {
try {
Order order = doCreateOrder(dto);
orderSuccessCounter.increment();
orderAmountSummary.record(order.getTotalAmount().doubleValue());
pendingPaymentCount.incrementAndGet();
return order;
} catch (Exception e) {
orderFailCounter.increment();
// 记录失败原因标签
registry.counter("order.error",
"type", e.getClass().getSimpleName()).increment();
throw e;
}
});
}
public void onOrderPaid(Long orderId) {
pendingPaymentCount.decrementAndGet();
registry.counter("order.paid").increment();
}
}
// 自定义健康检查指标
@Component
public class ExternalServiceHealthIndicator implements HealthIndicator {
@Autowired
private PaymentServiceClient paymentClient;
@Override
public Health health() {
try {
long start = System.currentTimeMillis();
boolean available = paymentClient.ping();
long duration = System.currentTimeMillis() - start;
if (available) {
return Health.up()
.withDetail("responseTime", duration + "ms")
.withDetail("service", "payment-service")
.build();
} else {
return Health.down()
.withDetail("reason", "支付服务不可用")
.build();
}
} catch (Exception e) {
return Health.down()
.withException(e)
.build();
}
}
}
11.2 Grafana仪表盘实战:从零搭建Spring Boot监控面板
Spring Boot监控面板应包含的核心面板:
第一行:关键指标概览(单值面板)
┌──────────┬──────────┬──────────┬──────────┐
│ QPS │ 错误率 │ P99延迟 │ 在线实例 │
│ 1234/s │ 0.1% │ 45ms │ 3 │
└──────────┴──────────┴──────────┴──────────┘
第二行:趋势图
┌──────────────────────┬──────────────────────┐
│ QPS趋势(折线图) │ 响应时间分位数 │
│ P50/P95/P99 │ P50/P95/P99 │
└──────────────────────┴──────────────────────┘
第三行:JVM监控
┌──────────────────────┬──────────────────────┐
│ JVM堆内存使用 │ GC暂停时间 │
│ Used/Max │ Young GC/Full GC │
└──────────────────────┴──────────────────────┘
第四行:业务指标
┌──────────────────────┬──────────────────────┐
│ 订单创建趋势 │ 支付成功率 │
│ 成功/失败 │ 实时待支付订单数 │
└──────────────────────┴──────────────────────┘
// Grafana面板配置示例(QPS单值面板)
{
"title": "当前QPS",
"type": "stat",
"fieldConfig": {
"defaults": {
"unit": "reqps",
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 500},
{"color": "red", "value": 1000}
]
}
}
},
"options": {
"reduceOptions": {
"calcs": ["lastNotNull"]
},
"orientation": "auto",
"colorMode": "background"
},
"targets": [
{
"expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\"}[1m]))",
"legendFormat": "QPS"
}
]
}
# Grafana告警配置(通过API)
# 创建告警规则(Grafana 8.x+统一告警)
curl -X POST http://admin:admin@localhost:3000/api/ruler/grafana/api/v1/rules/shop \
-H "Content-Type: application/json" \
-d '{
"name": "shop-alerts",
"interval": "1m",
"rules": [
{
"alert": "HighErrorRate",
"expr": "sum(rate(http_server_requests_seconds_count{status=~\"5..\"}[5m])) / sum(rate(http_server_requests_seconds_count[5m])) * 100 > 1",
"for": "3m",
"labels": {
"severity": "warning",
"team": "backend"
},
"annotations": {
"summary": "HTTP错误率过高",
"description": "错误率 {{ $value | printf \"%.2f\" }}% 超过1%阈值"
}
}
]
}'
11.3 ELK Stack完整搭建与使用
# docker-compose-elk.yml - ELK Stack本地搭建
version: '3.8'
services:
elasticsearch:
image: elasticsearch:8.11.0
container_name: elk-es
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms1g -Xmx1g
- bootstrap.memory_lock=true
ulimits:
memlock:
soft: -1
hard: -1
ports:
- "9200:9200"
volumes:
- es_data:/usr/share/elasticsearch/data
networks:
- elk-net
logstash:
image: logstash:8.11.0
container_name: elk-logstash
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline
- ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml
ports:
- "5044:5044" # Beats输入
- "5000:5000" # TCP输入
- "9600:9600" # API
environment:
LS_JAVA_OPTS: "-Xmx512m -Xms512m"
depends_on:
- elasticsearch
networks:
- elk-net
kibana:
image: kibana:8.11.0
container_name: elk-kibana
environment:
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
ports:
- "5601:5601"
depends_on:
- elasticsearch
networks:
- elk-net
# Filebeat:采集容器日志
filebeat:
image: elastic/filebeat:8.11.0
container_name: elk-filebeat
user: root
volumes:
- ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
- logstash
networks:
- elk-net
volumes:
es_data:
networks:
elk-net:
driver: bridge
# filebeat/filebeat.yml - 采集Docker容器日志
filebeat.inputs:
- type: container
paths:
- /var/lib/docker/containers/*/*.log
processors:
- add_docker_metadata:
host: "unix:///var/run/docker.sock"
# 只采集shop相关容器的日志
- drop_event:
when:
not:
contains:
docker.container.labels.com.docker.compose.service: "shop"
processors:
- decode_json_fields:
fields: ["message"]
target: ""
overwrite_keys: true
output.logstash:
hosts: ["logstash:5044"]
# logstash/pipeline/shop.conf
input {
beats {
port => 5044
}
}
filter {
# 解析Spring Boot JSON日志
if [fields][app_type] == "spring-boot" {
json {
source => "message"
target => "log"
}
# 提取关键字段到顶层
mutate {
rename => {
"[log][level]" => "log_level"
"[log][logger_name]" => "logger"
"[log][message]" => "log_message"
"[log][stack_trace]" => "stack_trace"
}
}
# 解析时间戳
date {
match => ["[log][@timestamp]", "ISO8601"]
target => "@timestamp"
}
# 提取TraceId(链路追踪关联)
if [log][mdc][traceId] {
mutate {
add_field => { "trace_id" => "%{[log][mdc][traceId]}" }
}
}
# 标记ERROR日志
if [log_level] == "ERROR" {
mutate {
add_tag => ["error"]
}
}
}
# 过滤健康检查日志(减少噪音)
if [log_message] =~ "actuator/health" {
drop {}
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
# 按应用和日期分索引
index => "shop-logs-%{[docker][container][labels][com.docker.compose.service]}-%{+YYYY.MM.dd}"
}
# 同时输出到标准输出(调试用)
# stdout { codec => rubydebug }
}
Kibana常用操作:
1. 创建索引模式
Management → Stack Management → Index Patterns
→ Create index pattern → shop-logs-* → @timestamp
2. 日志搜索(Discover)
常用KQL查询:
log_level: "ERROR" # 查看所有ERROR日志
log_level: "ERROR" and logger: "com.example.shop.order" # 特定类的ERROR
trace_id: "abc123" # 按TraceId查询完整链路
log_message: "OutOfMemoryError" # 搜索OOM错误
@timestamp >= "2024-01-01T10:00:00" # 时间范围
3. 创建可视化
Visualize → Create visualization
- 折线图:按时间统计ERROR数量
- 饼图:各服务日志级别分布
- 数据表:TOP 10错误类型
4. 创建告警(Kibana Alerting)
Management → Rules and Connectors → Create rule
- 类型:Elasticsearch query
- 条件:ERROR日志数量 > 10/分钟
- 通知:钉钉Webhook
11.4 SkyWalking分布式链路追踪实战
分布式系统中,一个请求可能经过多个服务,出问题时很难定位是哪个环节出了故障。SkyWalking通过在每个服务中注入Agent,自动采集调用链数据,无需修改业务代码。
链路追踪解决的问题:
┌─────────────────────────────────────────────────────────────────┐
│ 用户请求 → 网关 → 用户服务 → 订单服务 → 库存服务 → 支付服务 │
│ │
│ 问题:响应慢,但不知道是哪个服务慢 │
│ 解决:SkyWalking记录每个服务的耗时,生成完整调用链 │
│ │
│ TraceId: abc-123 │
│ ├── 网关 5ms │
│ ├── 用户服务 12ms │
│ ├── 订单服务 ████████ 350ms ← 瓶颈在这里! │
│ │ ├── MySQL查询 280ms │
│ │ └── Redis 8ms │
│ └── 支付服务 20ms │
└─────────────────────────────────────────────────────────────────┘
# docker-compose-skywalking.yml
version: '3.8'
services:
# OAP Server:链路数据收集和分析
oap:
image: apache/skywalking-oap-server:9.7.0
container_name: skywalking-oap
environment:
SW_STORAGE: elasticsearch
SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200
SW_HEALTH_CHECKER: default
JAVA_OPTS: "-Xms512m -Xmx1g"
ports:
- "11800:11800" # gRPC端口(Agent上报)
- "12800:12800" # HTTP端口(UI查询)
depends_on:
- elasticsearch
networks:
- sw-net
# UI:可视化界面
ui:
image: apache/skywalking-ui:9.7.0
container_name: skywalking-ui
environment:
SW_OAP_ADDRESS: http://oap:12800
ports:
- "8080:8080"
depends_on:
- oap
networks:
- sw-net
elasticsearch:
image: elasticsearch:8.11.0
container_name: sw-es
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms512m -Xmx1g
networks:
- sw-net
networks:
sw-net:
driver: bridge
# Dockerfile - 集成SkyWalking Agent
FROM eclipse-temurin:17-jre AS base
# 下载SkyWalking Agent
ADD https://archive.apache.org/dist/skywalking/java-agent/9.1.0/apache-skywalking-java-agent-9.1.0.tgz /tmp/
RUN tar -xzf /tmp/apache-skywalking-java-agent-9.1.0.tgz -C /opt/ \
&& rm /tmp/apache-skywalking-java-agent-9.1.0.tgz
WORKDIR /app
COPY target/*.jar app.jar
# 通过JVM参数挂载Agent(无需修改代码)
ENTRYPOINT ["java", \
"-javaagent:/opt/skywalking-agent/skywalking-agent.jar", \
"-Dskywalking.agent.service_name=${SW_SERVICE_NAME:-shop-service}", \
"-Dskywalking.collector.backend_service=${SW_OAP_ADDRESS:-oap:11800}", \
"-jar", "app.jar"]
# K8s Deployment中注入SkyWalking Agent(推荐方式:initContainer)
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-order-service
spec:
template:
spec:
initContainers:
# 用initContainer将Agent复制到共享Volume
- name: skywalking-agent-init
image: apache/skywalking-java-agent:9.1.0-java17
command: ["sh", "-c", "cp -r /skywalking/agent /agent"]
volumeMounts:
- name: sw-agent
mountPath: /agent
containers:
- name: app
image: registry.example.com/shop/order-service:1.0.0
env:
- name: JAVA_TOOL_OPTIONS
value: >-
-javaagent:/agent/skywalking-agent.jar
-Dskywalking.agent.service_name=order-service
-Dskywalking.collector.backend_service=skywalking-oap.monitoring:11800
volumeMounts:
- name: sw-agent
mountPath: /agent
volumes:
- name: sw-agent
emptyDir: {}
// SkyWalking自定义Span(标注关键业务操作)
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.apache.skywalking.apm.toolkit.trace.Tag;
import org.apache.skywalking.apm.toolkit.trace.Tags;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
@Service
public class OrderServiceImpl implements OrderService {
// @Trace注解:将此方法加入链路追踪
@Trace(operationName = "createOrder")
@Tags({
@Tag(key = "userId", value = "arg[0]"), // 记录参数
@Tag(key = "productId", value = "arg[1]")
})
public Order createOrder(Long userId, Long productId, Integer quantity) {
try {
// 手动添加业务标签到当前Span
ActiveSpan.tag("orderAmount", calculateAmount(productId, quantity).toString());
Order order = doCreateOrder(userId, productId, quantity);
ActiveSpan.tag("orderId", order.getId().toString());
return order;
} catch (InsufficientStockException e) {
// 标记Span为错误状态
ActiveSpan.error(e);
throw e;
}
}
// 跨线程传递TraceId(异步场景)
public void asyncNotify(Order order) {
// 获取当前TraceId,传递给异步线程
String traceId = TraceContext.traceId();
CompletableFuture.runAsync(() -> {
// 在异步线程中继续链路
try (TraceContext.CloseableContext ctx = TraceContext.capture()) {
log.info("异步通知, traceId={}, orderId={}", traceId, order.getId());
notificationService.sendOrderCreated(order);
}
});
}
}
SkyWalking核心功能使用指南:
1. 服务拓扑图(Topology)
- 自动生成服务间调用关系图
- 显示每条链路的调用次数、成功率、平均响应时间
- 快速识别服务依赖和瓶颈
2. 链路追踪(Trace)
- 按TraceId搜索完整调用链
- 查看每个Span的耗时和参数
- 定位慢查询、慢接口
3. 性能剖析(Profile)
- 对指定端点进行CPU采样
- 生成火焰图,定位热点代码
- 无需重启应用,动态开启
4. 告警规则配置(alarm-settings.yml)
rules:
service_resp_time_rule:
metrics-name: service_resp_time
threshold: 1000 # 响应时间超过1秒
period: 10 # 最近10分钟
count: 3 # 触发3次
message: "服务 {name} 响应时间超过1秒"
service_sla_rule:
metrics-name: service_sla
threshold: 8000 # 成功率低于80%(8000/10000)
period: 10
count: 2
message: "服务 {name} 成功率低于80%"
十二、生产故障排查实战手册
12.1 故障排查方法论
生产环境出现问题时,需要快速、系统地定位根因。遵循以下排查流程可以大幅缩短MTTR(平均恢复时间)。
生产故障排查流程:
┌─────────────────────────────────────────────────────────────────┐
│ 故障发生 │
│ ↓ │
│ 1. 确认影响范围 │
│ (哪些服务?哪些用户?多少比例?) │
│ ↓ │
│ 2. 快速止血(优先恢复服务) │
│ (回滚?限流?降级?扩容?) │
│ ↓ │
│ 3. 收集现场信息 │
│ (日志、监控、链路追踪) │
│ ↓ │
│ 4. 定位根因 │
│ (代码?配置?资源?外部依赖?) │
│ ↓ │
│ 5. 修复并验证 │
│ ↓ │
│ 6. 复盘总结(故障报告) │
└─────────────────────────────────────────────────────────────────┘
12.2 CPU飙高排查实战
# 场景:线上服务CPU突然飙到90%+,需要快速定位
# Step 1:找到CPU最高的Java进程
top -c
# 记录PID,例如:PID=12345
# Step 2:找到该进程中CPU最高的线程
top -H -p 12345
# 记录线程PID(十进制),例如:TID=12389
# Step 3:将线程PID转为十六进制
printf '%x\n' 12389
# 输出:3065
# Step 4:导出线程堆栈
jstack 12345 > /tmp/thread_dump.txt
# Step 5:在堆栈中搜索该线程(用十六进制nid)
grep -A 30 "nid=0x3065" /tmp/thread_dump.txt
常见CPU飙高原因及对应堆栈特征:
┌──────────────────┬────────────────────────────────────────────┐
│ 原因 │ 堆栈特征 │
├──────────────────┼────────────────────────────────────────────┤
│ 死循环 │ 同一个方法反复出现,无阻塞 │
│ 频繁GC │ GC线程占用高,堆内存接近上限 │
│ 正则表达式回溯 │ java.util.regex.Pattern 相关方法 │
│ JSON序列化 │ Jackson/Fastjson 相关方法 │
│ 加密解密 │ javax.crypto 相关方法 │
│ 日志同步写入 │ FileOutputStream.write 阻塞 │
└──────────────────┴────────────────────────────────────────────┘
# 自动化CPU排查脚本(保存为 cpu_diagnose.sh)
#!/bin/bash
PID=$1
if [ -z "$PID" ]; then
echo "用法: ./cpu_diagnose.sh <PID>"
exit 1
fi
echo "=== CPU诊断报告 ==="
echo "时间: $(date)"
echo "进程: $PID"
echo ""
# 获取TOP 5 CPU线程
echo "=== TOP 5 CPU线程 ==="
top -H -b -n 1 -p $PID | head -20
# 导出堆栈
DUMP_FILE="/tmp/jstack_${PID}_$(date +%Y%m%d%H%M%S).txt"
jstack $PID > $DUMP_FILE
echo "堆栈已保存到: $DUMP_FILE"
# 找出RUNNABLE线程
echo ""
echo "=== RUNNABLE线程数量 ==="
grep -c "RUNNABLE" $DUMP_FILE
echo ""
echo "=== RUNNABLE线程堆栈(前50行)==="
grep -A 10 "RUNNABLE" $DUMP_FILE | head -50
12.3 内存溢出(OOM)排查实战
# JVM启动参数:OOM时自动导出堆转储文件
JAVA_OPTS="-Xms2g -Xmx2g \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/data/dumps/heap_$(date +%Y%m%d%H%M%S).hprof \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-Xloggc:/data/logs/gc.log"
# 手动触发堆转储(不重启服务)
jmap -dump:format=b,file=/tmp/heap.hprof <PID>
# 查看堆内存使用情况
jmap -heap <PID>
# 查看对象统计(找出占用内存最多的类)
jmap -histo <PID> | head -30
// 常见OOM场景及代码示例
// 场景1:内存泄漏 - 静态集合持有对象引用
@Component
public class CacheManager {
// ❌ 错误:静态Map无限增长,GC无法回收
private static final Map<String, Object> cache = new HashMap<>();
public void put(String key, Object value) {
cache.put(key, value); // 只进不出,迟早OOM
}
// ✅ 正确:使用有界缓存,设置最大容量和过期时间
private final Cache<String, Object> boundedCache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(30, TimeUnit.MINUTES)
.build();
}
// 场景2:大查询导致OOM
@Service
public class ReportService {
// ❌ 错误:一次性加载百万数据到内存
public void exportAllOrders() {
List<Order> allOrders = orderDao.findAll(); // 可能返回100万条
// 处理...
}
// ✅ 正确:分页流式处理
public void exportAllOrdersStream() {
int pageSize = 1000;
int pageNum = 0;
List<Order> page;
do {
page = orderDao.findByPage(pageNum++, pageSize);
page.forEach(this::processOrder);
// 每批处理完后,page对象可被GC回收
} while (page.size() == pageSize);
}
}
MAT(Memory Analyzer Tool)分析堆转储文件:
1. 下载MAT:https://eclipse.dev/mat/downloads.php
2. 打开堆转储文件:File → Open Heap Dump → 选择.hprof文件
3. 关键分析视图:
- Leak Suspects Report:自动分析内存泄漏嫌疑
- Dominator Tree:按内存占用排序,找出最大对象
- Histogram:按类统计实例数量和内存占用
- Thread Overview:查看各线程的栈和局部变量
4. 常见分析步骤:
a. 先看 Leak Suspects,通常能直接定位问题
b. 在 Dominator Tree 中找占用最多的对象
c. 右键 → List objects → with incoming references(谁持有它)
d. 追溯引用链,找到GC Root
12.4 接口响应慢排查实战
# 场景:某接口P99延迟从50ms突然升到2000ms
# Step 1:确认是哪个接口慢(通过Grafana/SkyWalking)
# 找到慢接口:POST /api/orders
# Step 2:查看该接口的慢日志
# 在ELK中搜索:
# log_message: "/api/orders" AND duration > 1000
# Step 3:查看数据库慢查询
# MySQL慢查询日志
tail -f /var/log/mysql/slow.log
# 或通过SQL查询当前慢查询
SELECT * FROM information_schema.PROCESSLIST
WHERE TIME > 5
ORDER BY TIME DESC;
# Step 4:查看Redis慢日志
redis-cli SLOWLOG GET 10
# Step 5:查看JVM GC情况(GC停顿会导致所有请求变慢)
jstat -gcutil <PID> 1000 10
# 关注FGC(Full GC次数)和FGCT(Full GC总耗时)
// 接口慢的常见原因和解决方案
// 原因1:N+1查询问题
// ❌ 错误:查询订单列表,再逐个查询用户信息
public List<OrderVO> listOrders() {
List<Order> orders = orderDao.findAll();
return orders.stream().map(order -> {
OrderVO vo = new OrderVO(order);
// 每个订单都发一次SQL查询用户,100个订单 = 101次SQL
User user = userDao.findById(order.getUserId());
vo.setUserName(user.getName());
return vo;
}).collect(Collectors.toList());
}
// ✅ 正确:批量查询,一次SQL解决
public List<OrderVO> listOrdersOptimized() {
List<Order> orders = orderDao.findAll();
// 收集所有userId
Set<Long> userIds = orders.stream()
.map(Order::getUserId).collect(Collectors.toSet());
// 一次批量查询
Map<Long, User> userMap = userDao.findByIds(userIds).stream()
.collect(Collectors.toMap(User::getId, u -> u));
// 组装结果
return orders.stream().map(order -> {
OrderVO vo = new OrderVO(order);
vo.setUserName(userMap.get(order.getUserId()).getName());
return vo;
}).collect(Collectors.toList());
}
12.5 K8s Pod故障排查实战
# Pod常见故障排查命令速查
# 查看Pod状态
kubectl get pods -n shop -o wide
# 查看Pod详细信息(重点看Events部分)
kubectl describe pod <pod-name> -n shop
# 查看Pod日志
kubectl logs <pod-name> -n shop
kubectl logs <pod-name> -n shop --previous # 查看上一次崩溃的日志
kubectl logs <pod-name> -n shop -f # 实时跟踪
kubectl logs <pod-name> -n shop --tail=100 # 最后100行
# 进入Pod容器调试
kubectl exec -it <pod-name> -n shop -- /bin/sh
# 查看Pod资源使用
kubectl top pod <pod-name> -n shop
Pod常见状态及排查方向:
┌─────────────────────┬──────────────────────────────────────────────┐
│ Pod状态 │ 排查方向 │
├─────────────────────┼──────────────────────────────────────────────┤
│ Pending │ 资源不足?节点选择器不匹配?PVC未绑定? │
│ CrashLoopBackOff │ 应用启动失败,看 logs --previous │
│ OOMKilled │ 内存超出limits,调大limits或优化内存 │
│ ImagePullBackOff │ 镜像不存在?仓库认证失败?网络问题? │
│ Evicted │ 节点资源不足被驱逐,检查节点磁盘/内存 │
│ Terminating(卡住) │ Finalizer未清除,强制删除:--force --grace-period=0 │
└─────────────────────┴──────────────────────────────────────────────┘
# 实战:排查CrashLoopBackOff
# 1. 查看崩溃原因
kubectl logs shop-order-7d9f8b-xxx -n shop --previous
# 输出:java.lang.IllegalStateException: Failed to load ApplicationContext
# Caused by: Cannot connect to MySQL: Connection refused
# 2. 检查ConfigMap中的数据库配置
kubectl get configmap shop-config -n shop -o yaml
# 3. 检查MySQL Service是否正常
kubectl get svc mysql-svc -n shop
kubectl get endpoints mysql-svc -n shop # 确认有Endpoint
# 4. 在Pod内测试连通性
kubectl run debug --image=busybox -it --rm -n shop -- sh
# 在容器内:
wget -qO- http://mysql-svc:3306 # 测试TCP连通
# 实战:排查OOMKilled
kubectl describe pod shop-order-7d9f8b-xxx -n shop
# 关注:
# Limits: memory: 512Mi
# Last State: Terminated Reason: OOMKilled
# 查看内存使用趋势(Prometheus查询)
# container_memory_working_set_bytes{pod="shop-order-7d9f8b-xxx"}
十三、成本优化与资源规划
13.1 K8s资源规划最佳实践
合理设置 requests 和 limits 是K8s资源管理的核心,设置不当会导致资源浪费或服务不稳定。
requests vs limits 的区别:
┌─────────────────────────────────────────────────────────────────┐
│ requests:调度依据,K8s保证Pod能获得的最低资源 │
│ limits:使用上限,超出后CPU被限速,内存超出则OOMKill │
│ │
│ 节点资源:8核16G │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Pod A: requests=2C/4G limits=4C/8G │ │
│ │ Pod B: requests=2C/4G limits=4C/8G │ │
│ │ Pod C: requests=2C/4G limits=4C/8G │ │
│ │ Pod D: requests=2C/4G limits=4C/8G ← 调度失败! │ │
│ │ requests总和=8C/16G,已满 │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
# 生产环境资源配置参考(Spring Boot服务)
resources:
requests:
cpu: "500m" # 0.5核,保证最低CPU
memory: "512Mi" # 512MB,保证最低内存
limits:
cpu: "2000m" # 2核,突发流量时可用
memory: "1Gi" # 1GB,超出则OOMKill
# 资源配置原则:
# 1. requests设为正常负载下的实际使用量(通过监控获取)
# 2. limits设为requests的2-4倍(应对流量突发)
# 3. 内存limits不要设太高,防止单Pod内存泄漏影响整个节点
# 4. CPU可以超卖(limits > requests),内存不建议超卖
# 使用VPA(Vertical Pod Autoscaler)自动推荐资源配置
# 安装VPA
kubectl apply -f https://github.com/kubernetes/autoscaler/releases/latest/download/vertical-pod-autoscaler.yaml
# 创建VPA对象(推荐模式,只给建议不自动修改)
cat <<EOF | kubectl apply -f -
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: shop-order-vpa
namespace: shop
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: shop-order-service
updatePolicy:
updateMode: "Off" # Off=只推荐,Auto=自动更新
EOF
# 查看VPA推荐值
kubectl describe vpa shop-order-vpa -n shop
# 输出示例:
# Recommendation:
# Container Recommendations:
# Container Name: app
# Lower Bound: cpu: 100m, memory: 256Mi
# Target: cpu: 300m, memory: 512Mi ← 推荐值
# Upper Bound: cpu: 1000m, memory: 2Gi
13.2 多环境成本优化策略
# 开发环境:最小化资源,节省成本
# dev-values.yaml
replicaCount: 1
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
# 关闭不必要的组件
monitoring:
enabled: false
autoscaling:
enabled: false
# 使用低规格数据库
mysql:
resources:
requests:
cpu: "100m"
memory: "256Mi"
# 生产环境:高可用配置
# prod-values.yaml
replicaCount: 3
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2000m"
memory: "1Gi"
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
# 反亲和性:Pod分散到不同节点,避免单点故障
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["shop-order-service"]
topologyKey: kubernetes.io/hostname
13.3 镜像体积优化实战
镜像体积对比(Spring Boot应用):
┌──────────────────────────────┬──────────┬──────────────────────┐
│ 基础镜像 │ 大小 │ 说明 │
├──────────────────────────────┼──────────┼──────────────────────┤
│ openjdk:17 │ 471MB │ 包含完整JDK,不推荐 │
│ eclipse-temurin:17-jdk │ 456MB │ 包含完整JDK,不推荐 │
│ eclipse-temurin:17-jre │ 249MB │ 只含JRE,推荐 │
│ eclipse-temurin:17-jre-alpine│ 185MB │ Alpine版,更小 │
│ eclipse-temurin:17-jre-jammy │ 228MB │ Ubuntu版,兼容性好 │
│ gcr.io/distroless/java17 │ 218MB │ 无Shell,最安全 │
└──────────────────────────────┴──────────┴──────────────────────┘
# 极致优化的Dockerfile:利用Spring Boot分层特性
FROM eclipse-temurin:17-jre-alpine AS builder
WORKDIR /app
COPY target/*.jar app.jar
# Spring Boot 2.3+ 支持分层提取
RUN java -Djarmode=layertools -jar app.jar extract
# 最终镜像:利用Docker层缓存,依赖层几乎不变
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 按变化频率从低到高分层(低频层在前,充分利用缓存)
COPY --from=builder /app/dependencies/ ./ # 第三方依赖(最少变化)
COPY --from=builder /app/spring-boot-loader/ ./ # Spring Boot Loader
COPY --from=builder /app/snapshot-dependencies/ ./ # SNAPSHOT依赖
COPY --from=builder /app/application/ ./ # 业务代码(最常变化)
# 安全加固:非root用户运行
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# JVM调优参数
ENV JAVA_OPTS="-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:+UseG1GC \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/tmp/heap.hprof"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]
# 镜像安全扫描(使用Trivy)
# 安装Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
# 扫描镜像漏洞
trivy image registry.example.com/shop/order-service:1.0.0
# 只显示HIGH和CRITICAL级别漏洞
trivy image --severity HIGH,CRITICAL registry.example.com/shop/order-service:1.0.0
# 在CI/CD中集成(发现CRITICAL漏洞则构建失败)
trivy image --exit-code 1 --severity CRITICAL registry.example.com/shop/order-service:1.0.0
十四、DevOps工具链进阶面试题
14.1 Docker高频面试题
Q1:Docker容器和虚拟机的区别?
虚拟机架构:
┌──────────┬──────────┬──────────┐
│ App A │ App B │ App C │
├──────────┼──────────┼──────────┤
│ Guest │ Guest │ Guest │
│ OS │ OS │ OS │
├──────────┴──────────┴──────────┤
│ Hypervisor │
├────────────────────────────────┤
│ Host OS │
├────────────────────────────────┤
│ Hardware │
└────────────────────────────────┘
Docker容器架构:
┌──────────┬──────────┬──────────┐
│ App A │ App B │ App C │
├──────────┼──────────┼──────────┤
│Container │Container │Container │
│ Runtime │ Runtime │ Runtime │
├──────────┴──────────┴──────────┤
│ Docker Engine │
├────────────────────────────────┤
│ Host OS │
├────────────────────────────────┤
│ Hardware │
└────────────────────────────────┘
核心区别:
- 虚拟机:完整OS隔离,启动慢(分钟级),资源占用大
- 容器:共享Host OS内核,启动快(秒级),资源占用小
- 隔离性:虚拟机 > 容器(容器共享内核,存在逃逸风险)
Q2:Docker镜像分层原理是什么?
镜像分层结构(以Spring Boot镜像为例):
Layer 5: 业务代码 (application/) ← 最常变化
Layer 4: SNAPSHOT依赖 ← 偶尔变化
Layer 3: Spring Boot Loader ← 很少变化
Layer 2: 第三方依赖 (dependencies/) ← 很少变化
Layer 1: eclipse-temurin:17-jre-alpine ← 基础镜像
原理:
- 每个RUN/COPY/ADD指令创建一个新层
- 层是只读的,通过Union FS叠加
- 容器运行时在最上层添加可写层(Container Layer)
- 相同的层在多个镜像间共享,节省存储空间
实际意义:
- 构建时:未变化的层直接使用缓存,加速构建
- 推送时:只推送变化的层,节省带宽
- 拉取时:已有的层不重复下载
Q3:如何减小Docker镜像体积?
1. 选择小体积基础镜像
- 用 eclipse-temurin:17-jre-alpine 替代 openjdk:17
- 用 distroless 镜像(无Shell,更安全)
2. 多阶段构建
- 构建阶段用完整JDK+Maven
- 运行阶段只保留JRE+JAR
3. 利用Spring Boot分层
- 依赖层和代码层分离,充分利用缓存
4. 清理构建缓存
RUN mvn package -B && rm -rf ~/.m2/repository
5. 使用.dockerignore
target/
.git/
*.md
src/test/
Q4:Docker网络模式有哪些?
| 网络模式 | 说明 | 适用场景 |
|---|---|---|
| bridge | 默认模式,容器通过虚拟网桥通信 | 单机多容器 |
| host | 容器直接使用宿主机网络 | 高性能场景 |
| none | 无网络,完全隔离 | 安全敏感场景 |
| overlay | 跨主机容器通信 | Docker Swarm/K8s |
| macvlan | 容器拥有独立MAC地址 | 需要直接接入物理网络 |
14.2 Kubernetes高频面试题
Q5:K8s中Pod、Deployment、Service的关系?
关系图:
┌─────────────────────────────┐
│ Deployment │
│ (管理Pod的期望状态) │
│ replicas: 3 │
└──────────────┬──────────────┘
│ 创建/管理
┌──────────────▼──────────────┐
│ ReplicaSet │
│ (确保Pod数量符合期望) │
└──────────────┬──────────────┘
│ 创建/管理
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Pod 1 │ │ Pod 2 │ │ Pod 3 │
│app:shop │ │app:shop │ │app:shop │
└────┬────┘ └────┬────┘ └────┬────┘
└──────────────────┬┘──────────────────┘
│ 通过Label Selector选择
┌────────────▼────────────┐
│ Service │
│ (稳定的访问入口) │
│ selector: app=shop │
│ ClusterIP: 10.96.0.100 │
└─────────────────────────┘
Q6:K8s如何实现零停机滚动更新?
滚动更新过程(replicas=3,maxSurge=1,maxUnavailable=0):
初始状态:
[v1] [v1] [v1]
Step 1:创建1个新Pod(maxSurge=1,最多多1个)
[v1] [v1] [v1] [v2-启动中]
Step 2:v2就绪后,删除1个旧Pod
[v1] [v1] [v2]
Step 3:再创建1个新Pod
[v1] [v1] [v2] [v2-启动中]
Step 4:v2就绪后,删除1个旧Pod
[v1] [v2] [v2]
Step 5:再创建1个新Pod
[v1] [v2] [v2] [v2-启动中]
Step 6:v2就绪后,删除最后1个旧Pod
[v2] [v2] [v2]
关键配置:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 最多多出1个Pod
maxUnavailable: 0 # 始终保持3个可用(零停机关键)
Q7:K8s的健康检查探针有什么区别?
| 探针类型 | 触发时机 | 失败后果 | 适用场景 |
|---|---|---|---|
| livenessProbe | 持续检查 | 重启容器 | 检测死锁、无响应 |
| readinessProbe | 持续检查 | 从Service摘除 | 检测是否准备好接流量 |
| startupProbe | 启动阶段 | 重启容器 | 慢启动应用(替代liveness初始延迟) |
# 三种探针配合使用示例
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60 # 等待应用启动
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
failureThreshold: 3 # 连续3次失败才摘除
startupProbe:
httpGet:
path: /actuator/health
port: 8080
failureThreshold: 30 # 最多等待30*10=300秒
periodSeconds: 10
Q8:ConfigMap和Secret的区别及使用场景?
ConfigMap:存储非敏感配置
- 数据库URL、Redis地址、应用参数
- 明文存储,kubectl get configmap 可直接查看
- 支持热更新(挂载为文件时,修改ConfigMap后Pod内文件自动更新)
Secret:存储敏感信息
- 数据库密码、API密钥、TLS证书
- Base64编码存储(注意:不是加密!)
- 生产环境建议配合Vault或KMS加密存储
使用方式对比:
# ConfigMap → 环境变量
env:
- name: DB_URL
valueFrom:
configMapKeyRef:
name: shop-config
key: db-url
# Secret → 环境变量
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: shop-secret
key: db-password
14.3 CI/CD高频面试题
Q9:说说你们团队的CI/CD流程?
标准回答模板(结合实际项目):
"我们团队使用GitHub Actions实现CI/CD,整体流程如下:
1. 开发阶段:
- 开发在feature分支开发,提交PR到develop分支
- PR触发CI流水线:代码检查 → 单元测试 → 集成测试
- 测试通过后,代码审查,合并到develop
2. 测试环境部署:
- develop分支合并触发自动部署到测试环境
- 运行自动化测试(接口测试、E2E测试)
- 测试通过后,发起PR到main分支
3. 生产环境部署:
- main分支合并触发生产部署流水线
- 构建Docker镜像,推送到Harbor私有仓库
- 通过Helm更新K8s Deployment
- 滚动更新,自动健康检查
- 部署完成后发送钉钉通知
4. 回滚机制:
- 每次部署前记录当前版本
- 发现问题可一键回滚到上一版本
- helm rollback shop-order 0 # 回滚到上一版本"
Q10:蓝绿部署和金丝雀发布的区别?
蓝绿部署:
┌─────────────────────────────────────────────────────────────┐
│ 当前状态:蓝色环境(v1)接收100%流量 │
│ │
│ [蓝 v1] [蓝 v1] [蓝 v1] ← 100%流量 │
│ [绿 v2] [绿 v2] [绿 v2] ← 0%流量(已部署,待切换) │
│ │
│ 切换:修改Service selector,瞬间将100%流量切到绿色 │
│ 回滚:再切回蓝色,秒级回滚 │
│ │
│ 优点:切换快,回滚快 │
│ 缺点:需要双倍资源 │
└─────────────────────────────────────────────────────────────┘
金丝雀发布:
┌─────────────────────────────────────────────────────────────┐
│ 阶段1:5%流量到v2,观察指标 │
│ [v1] [v1] [v1] [v1] [v1] [v1] [v1] [v1] [v1] [v2] │
│ │
│ 阶段2:指标正常,扩大到20% │
│ [v1] [v1] [v1] [v1] [v1] [v1] [v1] [v1] [v2] [v2] │
│ │
│ 阶段3:继续扩大到50%、100% │
│ │
│ 优点:风险可控,逐步验证 │
│ 缺点:需要流量控制能力(Istio/Nginx),过程较长 │
└─────────────────────────────────────────────────────────────┘
选择建议:
- 重大版本变更、不确定性高 → 金丝雀发布
- 紧急修复、需要快速切换 → 蓝绿部署
- 普通迭代 → 滚动更新
14.4 监控告警高频面试题
Q11:Prometheus的数据模型是什么?
Prometheus数据模型:时序数据库
每条数据由以下部分组成:
metric_name{label1="value1", label2="value2"} value timestamp
示例:
http_requests_total{method="POST", uri="/api/orders", status="200"} 1234 1704067200000
四种指标类型:
┌──────────────┬────────────────────────────────────────────┐
│ Counter │ 只增不减的计数器 │
│ │ 示例:请求总数、错误总数 │
│ │ 常用函数:rate()、increase() │
├──────────────┼────────────────────────────────────────────┤
│ Gauge │ 可增可减的仪表盘 │
│ │ 示例:当前连接数、内存使用量 │
│ │ 常用函数:直接使用当前值 │
├──────────────┼────────────────────────────────────────────┤
│ Histogram │ 分布统计(分桶) │
│ │ 示例:请求延迟分布 │
│ │ 常用函数:histogram_quantile() │
├──────────────┼────────────────────────────────────────────┤
│ Summary │ 客户端计算分位数 │
│ │ 示例:P50/P95/P99延迟 │
│ │ 注意:不支持跨实例聚合 │
└──────────────┴────────────────────────────────────────────┘
Q12:如何设计一套完整的告警体系?
告警体系设计原则:
1. 告警分级
P0(紧急):服务不可用,立即电话通知
P1(严重):核心功能异常,5分钟内响应
P2(警告):性能下降,30分钟内响应
P3(提示):潜在风险,工作时间处理
2. 告警内容要素(5W1H)
- What:什么指标异常(错误率超过5%)
- Where:哪个服务/实例(shop-order-service Pod-1)
- When:什么时间发生(2024-01-01 10:00:00)
- Why:可能的原因(数据库连接池耗尽)
- How:如何处理(查看日志/重启Pod/扩容)
- Value:当前值是多少(错误率:8.5%,阈值:5%)
3. 避免告警疲劳
- 设置合理的持续时间(for: 5m,避免抖动触发)
- 相关告警合并(同一服务的多个告警聚合)
- 定期review告警规则,删除无效告警
- 告警静默(维护窗口期间)
4. 告警闭环
- 告警触发 → 通知到人 → 确认处理 → 解决 → 复盘
- 记录每次告警的处理过程
- 定期分析告警趋势,优化系统
十五、生产运维最佳实践
15.1 变更管理规范
生产变更三原则:
1. 可回滚(Rollback)
- 每次变更前确认回滚方案
- 数据库变更使用可逆的DDL(先加列,不删列)
- 代码变更通过Helm版本管理,一键回滚
2. 可观测(Observable)
- 变更后立即观察关键指标(错误率、延迟、CPU)
- 设置变更观察期(至少15分钟)
- 准备好监控大屏,变更时盯屏
3. 最小化影响(Minimal Impact)
- 避免在业务高峰期变更
- 优先使用滚动更新,避免全量重启
- 数据库变更在低峰期执行
生产变更检查清单:
变更前:
□ 变更方案已评审
□ 回滚方案已准备
□ 测试环境已验证
□ 通知相关团队
□ 确认当前系统状态正常(无告警)
变更中:
□ 按步骤执行,不跳步
□ 每步执行后观察指标
□ 保持通讯畅通
□ 记录执行时间和结果
变更后:
□ 观察15分钟,确认指标正常
□ 验证核心功能可用
□ 更新变更记录
□ 通知相关团队变更完成
15.2 数据库变更最佳实践
-- 生产数据库变更规范
-- ✅ 正确:先加列(允许NULL或有默认值),不影响现有数据
ALTER TABLE `order`
ADD COLUMN `remark` VARCHAR(500) NULL COMMENT '备注' AFTER `status`;
-- ✅ 正确:加索引使用 ALGORITHM=INPLACE(不锁表)
ALTER TABLE `order`
ADD INDEX `idx_create_time` (`create_time`),
ALGORITHM=INPLACE, LOCK=NONE;
-- ❌ 错误:直接删列(可能导致代码报错,应先废弃再删)
ALTER TABLE `order` DROP COLUMN `old_field`;
-- ✅ 正确的删列流程:
-- Step 1:代码中移除对该列的引用,发布
-- Step 2:等待一个发布周期,确认无引用
-- Step 3:再执行 DROP COLUMN
-- ✅ 大表数据迁移:使用pt-online-schema-change(不锁表)
-- pt-online-schema-change --alter "ADD COLUMN remark VARCHAR(500)" \
-- --execute D=shop,t=order,h=localhost,u=root,p=password
15.3 日志规范与最佳实践
// 生产环境日志最佳实践
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public Order createOrder(OrderDTO dto) {
// 1. 关键业务操作记录INFO日志
log.info("开始创建订单, userId={}, productId={}, quantity={}",
dto.getUserId(), dto.getProductId(), dto.getQuantity());
try {
// 2. 外部调用记录耗时
long start = System.currentTimeMillis();
boolean stockOk = inventoryService.deductStock(
dto.getProductId(), dto.getQuantity());
log.info("库存扣减完成, productId={}, cost={}ms",
dto.getProductId(), System.currentTimeMillis() - start);
if (!stockOk) {
// 3. 业务异常记录WARN(不是ERROR,是预期内的情况)
log.warn("库存不足, productId={}, quantity={}",
dto.getProductId(), dto.getQuantity());
throw new InsufficientStockException("库存不足");
}
Order order = buildOrder(dto);
orderDao.insert(order);
log.info("订单创建成功, orderId={}, amount={}",
order.getId(), order.getTotalAmount());
return order;
} catch (InsufficientStockException e) {
throw e; // 业务异常直接抛出,不记录ERROR
} catch (Exception e) {
// 4. 非预期异常记录ERROR,必须包含异常堆栈
log.error("订单创建失败, userId={}, productId={}",
dto.getUserId(), dto.getProductId(), e);
throw new OrderCreateException("订单创建失败", e);
}
}
}
# logback-spring.xml - 生产环境日志配置
# 输出JSON格式,方便ELK采集
<configuration>
<springProperty scope="context" name="appName" source="spring.application.name"/>
<!-- 生产环境:JSON格式输出到文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/data/logs/${appName}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/data/logs/${appName}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>500MB</maxFileSize>
<maxHistory>7</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<!-- 添加自定义字段 -->
<customFields>{"app":"${appName}","env":"${SPRING_PROFILES_ACTIVE:-unknown}"}</customFields>
<!-- 包含MDC字段(TraceId等) -->
<includeMdcKeyName>traceId</includeMdcKeyName>
<includeMdcKeyName>spanId</includeMdcKeyName>
</encoder>
</appender>
<!-- 异步写入,避免日志IO阻塞业务线程 -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>1024</queueSize>
<appender-ref ref="FILE"/>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC_FILE"/>
</root>
<!-- 降低框架日志级别,减少噪音 -->
<logger name="org.springframework" level="WARN"/>
<logger name="org.hibernate" level="WARN"/>
<logger name="com.zaxxer.hikari" level="WARN"/>
</configuration>
十六、综合实战:从零搭建完整DevOps环境
16.1 环境规划
完整DevOps环境架构:
开发者本地
↓ git push
GitHub/GitLab
↓ 触发Webhook
CI服务器(Jenkins/GitHub Actions)
↓ 构建 → 测试 → 打包
Harbor私有镜像仓库
↓ 镜像推送完成
K8s集群(通过Helm部署)
├── 开发环境(dev namespace)
├── 测试环境(staging namespace)
└── 生产环境(prod namespace)
↓ 运行时
监控体系
├── Prometheus(指标采集)
├── Grafana(可视化)
├── ELK(日志管理)
└── SkyWalking(链路追踪)
16.2 一键搭建本地开发环境脚本
#!/bin/bash
# setup-dev-env.sh - 一键搭建本地开发环境
set -e # 遇到错误立即退出
echo "=========================================="
echo " 电商系统本地开发环境搭建脚本"
echo "=========================================="
# 检查依赖
check_dependency() {
if ! command -v $1 &> /dev/null; then
echo "❌ 缺少依赖: $1,请先安装"
exit 1
fi
echo "✅ $1 已安装"
}
echo "检查依赖..."
check_dependency docker
check_dependency docker-compose
check_dependency java
check_dependency mvn
# 创建必要目录
mkdir -p data/{mysql,redis,elasticsearch}
mkdir -p logs/{app,nginx}
mkdir -p config/{nginx,logstash}
echo "启动基础服务..."
docker-compose -f docker-compose-dev.yml up -d mysql redis
echo "等待MySQL启动..."
until docker exec shop-mysql mysqladmin ping -h localhost --silent; do
echo " 等待MySQL..."
sleep 2
done
echo "✅ MySQL已就绪"
echo "初始化数据库..."
docker exec -i shop-mysql mysql -uroot -p123456 < sql/init.sql
echo "✅ 数据库初始化完成"
echo "启动其他服务..."
docker-compose -f docker-compose-dev.yml up -d
echo ""
echo "=========================================="
echo " 环境搭建完成!"
echo "=========================================="
echo " MySQL: localhost:3306"
echo " Redis: localhost:6379"
echo " Nginx: http://localhost:80"
echo " Kibana: http://localhost:5601"
echo " Grafana: http://localhost:3000 (admin/admin)"
echo "=========================================="
16.3 生产部署完整流程演练
# 完整的生产部署流程(手动执行版)
# ===== Step 1:构建镜像 =====
APP_VERSION=$(git describe --tags --always)
IMAGE_NAME="registry.example.com/shop/order-service:${APP_VERSION}"
echo "构建镜像: ${IMAGE_NAME}"
docker build -t ${IMAGE_NAME} .
docker push ${IMAGE_NAME}
# ===== Step 2:更新K8s配置 =====
# 方式A:直接更新镜像(快速)
kubectl set image deployment/shop-order-service \
app=${IMAGE_NAME} -n shop
# 方式B:通过Helm更新(推荐,有版本记录)
helm upgrade shop-order ./helm/shop-order \
--namespace shop \
--set image.tag=${APP_VERSION} \
--set image.repository=registry.example.com/shop/order-service \
--atomic \ # 部署失败自动回滚
--timeout 5m \ # 超时时间
--wait # 等待所有Pod就绪
# ===== Step 3:验证部署 =====
echo "等待部署完成..."
kubectl rollout status deployment/shop-order-service -n shop --timeout=5m
echo "验证Pod状态..."
kubectl get pods -n shop -l app=shop-order-service
echo "验证服务健康..."
kubectl exec -n shop deploy/shop-order-service -- \
wget -qO- http://localhost:8080/actuator/health
# ===== Step 4:冒烟测试 =====
echo "执行冒烟测试..."
INGRESS_IP=$(kubectl get ingress shop-ingress -n shop -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl -f "http://${INGRESS_IP}/api/health" || {
echo "❌ 冒烟测试失败,开始回滚..."
helm rollback shop-order -n shop
exit 1
}
echo "✅ 部署成功!版本: ${APP_VERSION}"
16.4 故障演练(Chaos Engineering)
# 使用chaos-mesh进行故障注入演练
# 安装chaos-mesh
helm repo add chaos-mesh https://charts.chaos-mesh.org
helm install chaos-mesh chaos-mesh/chaos-mesh \
--namespace=chaos-testing --create-namespace
# 演练1:Pod随机重启(验证服务高可用)
cat <<EOF | kubectl apply -f -
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
name: pod-kill-test
namespace: shop
spec:
action: pod-kill
mode: one # 随机杀死1个Pod
selector:
namespaces: [shop]
labelSelectors:
app: shop-order-service
scheduler:
cron: "@every 2m" # 每2分钟执行一次
EOF
# 演练2:网络延迟(验证超时处理)
cat <<EOF | kubectl apply -f -
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: network-delay-test
namespace: shop
spec:
action: delay
mode: all
selector:
namespaces: [shop]
labelSelectors:
app: shop-order-service
delay:
latency: "500ms" # 注入500ms延迟
correlation: "25"
jitter: "100ms"
duration: "5m" # 持续5分钟
EOF
# 演练3:CPU压力(验证HPA自动扩容)
cat <<EOF | kubectl apply -f -
apiVersion: chaos-mesh.org/v1alpha1
kind: StressChaos
metadata:
name: cpu-stress-test
namespace: shop
spec:
mode: one
selector:
namespaces: [shop]
labelSelectors:
app: shop-order-service
stressors:
cpu:
workers: 2 # 2个CPU压力线程
load: 80 # 80% CPU负载
duration: "3m"
EOF
十七、工具链速查手册
17.1 Docker常用命令速查
┌────────────────────────────────────────────────────────────────────┐
│ Docker 命令速查表 │
├──────────────────────┬─────────────────────────────────────────────┤
│ 镜像操作 │ │
│ docker build │ docker build -t name:tag . │
│ docker push/pull │ docker push/pull registry/name:tag │
│ docker images │ 列出本地镜像 │
│ docker rmi │ docker rmi image_id │
│ docker image prune │ 清理未使用的镜像 │
├──────────────────────┼─────────────────────────────────────────────┤
│ 容器操作 │ │
│ docker run │ docker run -d -p 8080:8080 --name app img │
│ docker ps │ docker ps -a(含已停止) │
│ docker logs │ docker logs -f --tail=100 container │
│ docker exec │ docker exec -it container /bin/sh │
│ docker stop/rm │ 停止/删除容器 │
│ docker stats │ 实时查看容器资源使用 │
├──────────────────────┼─────────────────────────────────────────────┤
│ 网络/存储 │ │
│ docker network ls │ 列出网络 │
│ docker volume ls │ 列出数据卷 │
│ docker inspect │ 查看容器/镜像详细信息 │
├──────────────────────┼─────────────────────────────────────────────┤
│ Compose操作 │ │
│ docker-compose up -d │ 后台启动所有服务 │
│ docker-compose down │ 停止并删除容器 │
│ docker-compose logs │ 查看服务日志 │
│ docker-compose ps │ 查看服务状态 │
│ docker-compose scale │ 扩缩容:--scale service=3 │
└──────────────────────┴─────────────────────────────────────────────┘
17.2 kubectl常用命令速查
┌────────────────────────────────────────────────────────────────────┐
│ kubectl 命令速查表 │
├──────────────────────┬─────────────────────────────────────────────┤
│ 查看资源 │ │
│ get pods │ kubectl get pods -n ns -o wide │
│ get all │ kubectl get all -n ns │
│ describe │ kubectl describe pod name -n ns │
│ logs │ kubectl logs pod -n ns -f --tail=100 │
│ top │ kubectl top pod/node │
├──────────────────────┼─────────────────────────────────────────────┤
│ 操作资源 │ │
│ apply │ kubectl apply -f manifest.yaml │
│ delete │ kubectl delete -f manifest.yaml │
│ scale │ kubectl scale deploy name --replicas=3 │
│ rollout │ kubectl rollout status/history/undo deploy │
│ set image │ kubectl set image deploy/name c=img:tag │
├──────────────────────┼─────────────────────────────────────────────┤
│ 调试 │ │
│ exec │ kubectl exec -it pod -n ns -- /bin/sh │
│ port-forward │ kubectl port-forward pod 8080:8080 -n ns │
│ cp │ kubectl cp pod:/path /local/path -n ns │
│ debug │ kubectl debug pod -it --image=busybox │
├──────────────────────┼─────────────────────────────────────────────┤
│ 配置管理 │ │
│ config get-contexts │ 查看所有集群上下文 │
│ config use-context │ 切换集群 │
│ create secret │ kubectl create secret generic name --from-literal=k=v │
└──────────────────────┴─────────────────────────────────────────────┘
17.3 Helm常用命令速查
┌────────────────────────────────────────────────────────────────────┐
│ Helm 命令速查表 │
├──────────────────────┬─────────────────────────────────────────────┤
│ 仓库管理 │ │
│ repo add │ helm repo add name url │
│ repo update │ helm repo update │
│ search repo │ helm search repo keyword │
├──────────────────────┼─────────────────────────────────────────────┤
│ 发布管理 │ │
│ install │ helm install release chart -n ns -f vals.yaml│
│ upgrade │ helm upgrade release chart --set key=val │
│ rollback │ helm rollback release [revision] │
│ uninstall │ helm uninstall release -n ns │
├──────────────────────┼─────────────────────────────────────────────┤
│ 查看状态 │ │
│ list │ helm list -n ns │
│ status │ helm status release -n ns │
│ history │ helm history release -n ns │
│ get values │ helm get values release -n ns │
├──────────────────────┼─────────────────────────────────────────────┤
│ 调试 │ │
│ template │ helm template release chart -f vals.yaml │
│ lint │ helm lint ./chart │
│ dry-run │ helm install --dry-run --debug │
└──────────────────────┴─────────────────────────────────────────────┘
17.4 常用PromQL速查
┌────────────────────────────────────────────────────────────────────┐
│ PromQL 速查表 │
├──────────────────────────────────────────────────────────────────┤
│ HTTP指标 │
│ QPS: │
│ sum(rate(http_server_requests_seconds_count[1m])) │
│ │
│ 错误率: │
│ sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))│
│ / sum(rate(http_server_requests_seconds_count[5m])) * 100 │
│ │
│ P99延迟: │
│ histogram_quantile(0.99, │
│ sum(rate(http_server_requests_seconds_bucket[5m])) by (le)) │
├──────────────────────────────────────────────────────────────────┤
│ JVM指标 │
│ 堆内存使用率: │
│ jvm_memory_used_bytes{area="heap"} │
│ / jvm_memory_max_bytes{area="heap"} * 100 │
│ │
│ GC频率: │
│ rate(jvm_gc_pause_seconds_count[5m]) │
│ │
│ 线程数: │
│ jvm_threads_live_threads │
├──────────────────────────────────────────────────────────────────┤
│ 数据库连接池 │
│ 连接池使用率: │
│ hikaricp_connections_active / hikaricp_connections_max * 100 │
│ │
│ 等待连接数: │
│ hikaricp_connections_pending │
└────────────────────────────────────────────────────────────────────┘
补充练习题
综合实战练习
练习1:完整CI/CD流水线搭建
要求:
- 在GitHub上创建一个Spring Boot项目仓库
- 编写GitHub Actions工作流,实现:
- push到main分支时触发
- 执行
mvn test单元测试 - 构建Docker镜像并推送到Docker Hub
- 通过
kubectl set image更新K8s Deployment
- 验证:提交代码后,K8s中的Pod自动更新到新版本
练习2:监控告警配置
要求:
- 在本地搭建 Prometheus + Grafana
- 接入Spring Boot Actuator指标
- 配置以下告警规则:
- HTTP错误率超过5%持续3分钟
- JVM堆内存使用率超过85%
- 数据库连接池等待数超过10
- 配置告警通知到钉钉机器人
练习3:故障排查演练
场景:模拟以下故障并排查:
- 将应用的数据库连接配置改错,观察Pod状态变化,排查CrashLoopBackOff
- 将内存limits设置为128Mi(不够用),触发OOMKilled,排查并修复
- 在应用中加入一个死循环接口,调用后CPU飙高,用jstack定位问题线程
练习4:零停机部署演练
要求:
- 部署一个有3个副本的Spring Boot应用
- 在持续发送请求的同时(用ab或wrk压测),执行滚动更新
- 验证整个更新过程中没有请求失败(错误率为0)
- 尝试蓝绿部署:部署新版本后,通过修改Service selector切换流量
补充学习检查清单
- 能够编写多阶段Dockerfile,将Spring Boot镜像体积控制在200MB以内
- 能够用Docker Compose搭建包含MySQL、Redis、Nginx的完整本地环境
- 能够编写K8s Deployment/Service/Ingress/ConfigMap/Secret的YAML
- 能够通过Helm管理K8s应用的多环境部署
- 能够编写GitHub Actions流水线,实现代码提交后自动部署
- 能够搭建Prometheus + Grafana,配置Spring Boot监控面板
- 能够配置ELK采集Spring Boot日志,在Kibana中搜索和分析
- 能够集成SkyWalking,查看服务调用链路
- 能够排查CPU飙高、OOM、接口慢等常见生产问题
- 能够执行零停机滚动更新,并在出问题时快速回滚
- 熟悉Docker、K8s、CI/CD相关的大厂面试题
- 能够在面试中清晰描述团队的DevOps工具链和实践经验
十八、服务网格(Service Mesh)入门
18.1 为什么需要Service Mesh?
随着微服务数量增多,服务间通信的治理变得复杂:超时重试、熔断限流、链路追踪、mTLS加密……如果每个服务都自己实现,代码重复且难以统一管理。Service Mesh将这些能力下沉到基础设施层(Sidecar代理),业务代码完全无感知。
传统微服务 vs Service Mesh:
传统方式(每个服务自己处理):
┌─────────────────────────────────────────────────────────────┐
│ 服务A 服务B │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ 业务代码 │ │ 业务代码 │ │
│ │ + Ribbon负载均衡 │ ───► │ + Hystrix熔断 │ │
│ │ + Sleuth链路追踪 │ │ + Sleuth链路追踪 │ │
│ │ + 重试逻辑 │ │ + 限流逻辑 │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ 问题:治理逻辑分散在各服务,升级困难,多语言难统一 │
└─────────────────────────────────────────────────────────────┘
Service Mesh方式(Sidecar代理统一处理):
┌─────────────────────────────────────────────────────────────┐
│ 服务A Pod 服务B Pod │
│ ┌──────────┬──────────┐ ┌──────────┬──────────┐ │
│ │ 业务代码 │ Envoy │ ────► │ Envoy │ 业务代码 │ │
│ │ (只关注 │ Sidecar │ │ Sidecar │ (只关注 │ │
│ │ 业务) │ 负责所有 │ │ 负责所有 │ 业务) │ │
│ │ │ 治理能力 │ │ 治理能力 │ │ │
│ └──────────┴──────────┘ └──────────┴──────────┘ │
│ ↕ 统一上报 │
│ Istio Control Plane │
│ (统一配置、策略、可观测性) │
└─────────────────────────────────────────────────────────────┘
18.2 Istio核心功能实战
# 安装Istio(使用istioctl)
curl -L https://istio.io/downloadIstio | sh -
export PATH=$PWD/istio-1.20.0/bin:$PATH
# 安装到K8s集群(demo配置,适合学习)
istioctl install --set profile=demo -y
# 为命名空间开启自动注入Sidecar
kubectl label namespace shop istio-injection=enabled
# 验证注入(Pod应该有2个容器:app + istio-proxy)
kubectl get pods -n shop
# NAME READY STATUS
# shop-order-7d9f8b-xxx 2/2 Running ← 2/2表示有Sidecar
# 流量管理:VirtualService + DestinationRule
# DestinationRule:定义服务的子集(版本)
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: shop-order-dr
namespace: shop
spec:
host: shop-order-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 10
# 熔断配置
outlierDetection:
consecutive5xxErrors: 5 # 连续5次5xx错误
interval: 30s # 检测间隔
baseEjectionTime: 30s # 熔断持续时间
maxEjectionPercent: 50 # 最多熔断50%实例
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
# VirtualService:定义路由规则(金丝雀发布)
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: shop-order-vs
namespace: shop
spec:
hosts:
- shop-order-service
http:
- match:
# 特定用户(测试人员)路由到v2
- headers:
x-canary-user:
exact: "true"
route:
- destination:
host: shop-order-service
subset: v2
# 其他用户:90%到v1,10%到v2
- route:
- destination:
host: shop-order-service
subset: v1
weight: 90
- destination:
host: shop-order-service
subset: v2
weight: 10
# 超时和重试配置
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: shop-order-vs
namespace: shop
spec:
hosts:
- shop-order-service
http:
- route:
- destination:
host: shop-order-service
timeout: 3s # 请求超时3秒
retries:
attempts: 3 # 最多重试3次
perTryTimeout: 1s # 每次重试超时1秒
# 只对网络错误和5xx重试(幂等操作才能重试)
retryOn: "gateway-error,connect-failure,retriable-4xx"
18.3 mTLS双向认证
# 开启严格mTLS(服务间通信必须加密)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: shop
spec:
mtls:
mode: STRICT # 严格模式:只允许mTLS流量
---
# 授权策略:只允许特定服务访问订单服务
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: order-service-authz
namespace: shop
spec:
selector:
matchLabels:
app: shop-order-service
rules:
- from:
# 只允许来自网关和用户服务的请求
- source:
principals:
- "cluster.local/ns/shop/sa/shop-gateway"
- "cluster.local/ns/shop/sa/shop-user-service"
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/orders*"]
十九、安全加固实战
19.1 容器安全最佳实践
# 安全加固的Dockerfile
FROM eclipse-temurin:17-jre-alpine
# 1. 创建非root用户(最小权限原则)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 2. 设置工作目录权限
WORKDIR /app
RUN chown appuser:appgroup /app
# 3. 复制文件并设置只读权限
COPY --chown=appuser:appgroup target/*.jar app.jar
RUN chmod 444 app.jar
# 4. 切换到非root用户
USER appuser
# 5. 不暴露特权端口(使用8080而非80)
EXPOSE 8080
# 6. 使用exec格式(避免shell注入)
ENTRYPOINT ["java", "-jar", "app.jar"]
# K8s安全上下文配置
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
# Pod级别安全上下文
securityContext:
runAsNonRoot: true # 禁止root运行
runAsUser: 1000 # 指定UID
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault # 启用seccomp过滤系统调用
containers:
- name: app
# 容器级别安全上下文
securityContext:
allowPrivilegeEscalation: false # 禁止提权
readOnlyRootFilesystem: true # 根文件系统只读
capabilities:
drop: ["ALL"] # 删除所有Linux能力
add: ["NET_BIND_SERVICE"] # 只保留必要能力
# 挂载可写目录(根文件系统只读时需要)
volumeMounts:
- name: tmp
mountPath: /tmp
- name: logs
mountPath: /app/logs
volumes:
- name: tmp
emptyDir: {}
- name: logs
emptyDir: {}
19.2 Secret安全管理:集成HashiCorp Vault
为什么不能直接用K8s Secret存密码?
K8s Secret的问题:
- Base64编码不是加密,kubectl get secret 可直接解码
- Secret存储在etcd中,etcd被攻破则所有密码泄露
- 无法做到密码轮换(更换密码需要重新部署)
- 无审计日志(谁在什么时间访问了哪个密码)
Vault的优势:
- 真正的加密存储
- 动态密码(每次申请都生成新密码,用完即废)
- 细粒度访问控制
- 完整的审计日志
- 支持密码自动轮换
# 安装Vault(K8s方式)
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault \
--namespace vault --create-namespace \
--set "server.dev.enabled=true" # 开发模式,生产用HA模式
# 初始化Vault(生产环境)
kubectl exec -n vault vault-0 -- vault operator init \
-key-shares=5 \ # 5个解封密钥
-key-threshold=3 # 需要3个才能解封
# 存储数据库密码
kubectl exec -n vault vault-0 -- vault kv put \
secret/shop/database \
username=shop_user \
password=SuperSecret123!
# 读取密码(验证)
kubectl exec -n vault vault-0 -- vault kv get secret/shop/database
# Spring Boot集成Vault(通过Vault Agent Injector)
# 在Pod注解中声明需要的Secret
apiVersion: apps/v1
kind: Deployment
spec:
template:
metadata:
annotations:
# 开启Vault注入
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "shop-order-service"
# 将数据库密码注入为文件
vault.hashicorp.com/agent-inject-secret-database: "secret/shop/database"
# 自定义文件格式(生成Spring Boot可读的properties)
vault.hashicorp.com/agent-inject-template-database: |
{{- with secret "secret/shop/database" -}}
spring.datasource.username={{ .Data.data.username }}
spring.datasource.password={{ .Data.data.password }}
{{- end }}
spec:
serviceAccountName: shop-order-service
containers:
- name: app
env:
# 告诉Spring Boot从Vault注入的文件加载配置
- name: SPRING_CONFIG_ADDITIONAL_LOCATION
value: "file:/vault/secrets/database"
19.3 网络安全:Nginx防护配置
# nginx-security.conf - 生产安全配置
# 隐藏Nginx版本信息
server_tokens off;
# 防止点击劫持
add_header X-Frame-Options "SAMEORIGIN" always;
# 防止MIME类型嗅探
add_header X-Content-Type-Options "nosniff" always;
# XSS防护
add_header X-XSS-Protection "1; mode=block" always;
# HSTS(强制HTTPS,1年)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# CSP(内容安全策略)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'" always;
server {
listen 443 ssl http2;
server_name api.example.com;
# SSL配置
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# 限制请求体大小(防止大文件上传攻击)
client_max_body_size 10m;
# 全局限流(每个IP每秒最多20个请求)
limit_req zone=api_limit burst=50 nodelay;
limit_req_status 429;
# 禁止访问隐藏文件
location ~ /\. {
deny all;
return 404;
}
# API代理
location /api/ {
# 接口级别更严格的限流
limit_req zone=api_strict burst=10 nodelay;
proxy_pass http://backend_pool;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时配置
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
}
# 限流区域定义(在http块中)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s;
limit_req_zone $binary_remote_addr zone=api_strict:10m rate=5r/s;
二十、性能压测与容量规划
20.1 压测工具对比与选择
常用压测工具对比:
┌──────────┬──────────┬──────────────────────────────────────────┐
│ 工具 │ 特点 │ 适用场景 │
├──────────┼──────────┼──────────────────────────────────────────┤
│ ab │ 简单易用 │ 快速验证单接口性能,不适合复杂场景 │
│ wrk │ 高性能 │ 单接口高并发压测,Lua脚本扩展 │
│ JMeter │ 功能全面 │ 复杂业务场景,GUI操作,支持分布式压测 │
│ Gatling │ 代码驱动 │ 场景复杂,需要版本控制,Scala DSL │
│ k6 │ 现代化 │ JavaScript编写场景,CI/CD集成友好 │
└──────────┴──────────┴──────────────────────────────────────────┘
# wrk压测示例
# 基础压测:4线程,100并发,持续30秒
wrk -t4 -c100 -d30s http://localhost:8080/api/orders
# 输出解读:
# Running 30s test @ http://localhost:8080/api/orders
# 4 threads and 100 connections
# Thread Stats Avg Stdev Max +/- Stdev
# Latency 45.23ms 12.34ms 234.56ms 89.12%
# Req/Sec 543.21 45.67 678.00 72.34%
# 65123 requests in 30.05s, 45.23MB read
# Requests/sec: 2167.45 ← QPS
# Transfer/sec: 1.50MB
# 带Lua脚本的POST请求压测
cat > post_order.lua << 'EOF'
wrk.method = "POST"
wrk.body = '{"userId":1,"productId":100,"quantity":1}'
wrk.headers["Content-Type"] = "application/json"
wrk.headers["Authorization"] = "Bearer test-token"
EOF
wrk -t4 -c100 -d60s -s post_order.lua http://localhost:8080/api/orders
// k6压测脚本(更贴近真实场景)
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
// 自定义指标:错误率
const errorRate = new Rate('errors');
// 压测配置:阶梯式加压
export const options = {
stages: [
{ duration: '1m', target: 50 }, // 1分钟内从0升到50并发
{ duration: '3m', target: 50 }, // 保持50并发3分钟
{ duration: '1m', target: 200 }, // 1分钟内升到200并发
{ duration: '3m', target: 200 }, // 保持200并发3分钟
{ duration: '1m', target: 0 }, // 1分钟内降到0
],
thresholds: {
http_req_duration: ['p(99)<500'], // 99%请求在500ms内
errors: ['rate<0.01'], // 错误率低于1%
},
};
export default function () {
// 模拟真实用户行为:登录 → 查询商品 → 下单
const loginRes = http.post('http://localhost:8080/api/auth/login', JSON.stringify({
username: 'testuser',
password: 'password123'
}), { headers: { 'Content-Type': 'application/json' } });
check(loginRes, { '登录成功': (r) => r.status === 200 });
const token = loginRes.json('data.token');
sleep(1); // 模拟用户思考时间
// 查询商品
const productRes = http.get('http://localhost:8080/api/products/100', {
headers: { 'Authorization': `Bearer ${token}` }
});
check(productRes, { '查询商品成功': (r) => r.status === 200 });
sleep(0.5);
// 下单
const orderRes = http.post('http://localhost:8080/api/orders', JSON.stringify({
productId: 100,
quantity: 1
}), {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
const success = check(orderRes, { '下单成功': (r) => r.status === 200 });
errorRate.add(!success);
sleep(2);
}
20.2 压测结果分析与容量规划
压测结果分析框架:
1. 确定系统瓶颈
┌─────────────────────────────────────────────────────────┐
│ 压测时同时观察: │
│ - 应用层:QPS、延迟、错误率(Grafana) │
│ - JVM层:GC频率、堆内存、线程数 │
│ - 数据库:慢查询、连接池使用率、CPU │
│ - 系统层:CPU、内存、网络IO、磁盘IO │
│ │
│ 瓶颈判断: │
│ CPU高 → 计算密集,考虑优化算法或扩容CPU │
│ 内存高 → 内存泄漏或缓存不足,分析堆转储 │
│ DB连接池满 → 慢查询或连接数不足,优化SQL或增加连接数 │
│ GC频繁 → 对象创建过多,优化对象复用 │
└─────────────────────────────────────────────────────────┘
2. 容量规划公式
目标QPS = 峰值QPS × 安全系数(通常1.5-2倍)
所需实例数 = 目标QPS / 单实例QPS
示例:
- 单实例压测QPS = 500
- 预期峰值QPS = 1000
- 安全系数 = 1.5
- 目标QPS = 1000 × 1.5 = 1500
- 所需实例数 = 1500 / 500 = 3个实例
3. HPA配置建议
- CPU触发阈值:70%(留30%余量应对突发)
- 内存触发阈值:80%
- 最小实例数:2(保证高可用)
- 最大实例数:根据容量规划结果 × 2
20.3 JVM调优实战
# 生产环境JVM参数模板(Spring Boot + G1GC)
JAVA_OPTS="\
# 堆内存:容器内存的75%(留25%给堆外内存)
-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:InitialRAMPercentage=50.0 \
# 使用G1GC(JDK9+默认,适合大堆)
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \ # 目标GC停顿时间200ms
-XX:G1HeapRegionSize=16m \ # Region大小(堆/2048)
-XX:G1NewSizePercent=20 \ # 新生代最小占比
-XX:G1MaxNewSizePercent=40 \ # 新生代最大占比
# OOM时自动导出堆转储
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/data/dumps/ \
# GC日志(用于事后分析)
-Xlog:gc*:file=/data/logs/gc.log:time,uptime:filecount=5,filesize=50m \
# 元空间(避免频繁Full GC)
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m \
# 字符串去重(减少内存占用)
-XX:+UseStringDeduplication"
// 常见JVM调优场景
// 场景1:频繁Full GC
// 原因:老年代空间不足,通常是内存泄漏或堆设置太小
// 排查:
// jstat -gcutil <PID> 1000 10
// 观察 OU(老年代使用率)是否持续增长
// 如果持续增长 → 内存泄漏,用MAT分析堆转储
// 如果稳定在高位 → 堆设置太小,增大-Xmx
// 场景2:Young GC频繁(每秒多次)
// 原因:新生代太小,对象晋升太快
// 解决:增大新生代比例
// -XX:G1NewSizePercent=30
// -XX:G1MaxNewSizePercent=50
// 场景3:大对象直接进老年代
// 原因:对象大小超过G1 Region的50%
// 解决:增大Region大小,或避免创建大对象
// -XX:G1HeapRegionSize=32m
// 场景4:元空间OOM
// java.lang.OutOfMemoryError: Metaspace
// 原因:动态生成类过多(反射、动态代理、Groovy脚本)
// 解决:增大元空间上限
// -XX:MaxMetaspaceSize=512m
二十一、多云与混合云部署
21.1 多云部署策略
多云部署的常见原因:
- 避免云厂商锁定
- 不同地区使用不同云(合规要求)
- 灾备:主云故障时切换到备云
- 成本优化:不同业务选择最优价格的云
多云架构示意:
┌─────────────────────────────────────────────────────────────┐
│ 全局流量调度(DNS/GTM) │
│ ↓ │
│ ┌─────────────────┴─────────────────┐ │
│ ▼ ▼ │
│ 阿里云(主) 腾讯云(备) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ K8s集群 │ │ K8s集群 │ │
│ │ 应用服务 │ │ 应用服务 │ │
│ │ MySQL主库 │ ──同步──► │ MySQL从库 │ │
│ │ Redis主 │ ──同步──► │ Redis从 │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
# 使用Terraform管理多云基础设施(基础示例)
# main.tf
terraform {
required_providers {
alicloud = {
source = "aliyun/alicloud"
version = "~> 1.200"
}
}
}
# 阿里云K8s集群
resource "alicloud_cs_managed_kubernetes" "shop_cluster" {
name = "shop-prod-cluster"
cluster_spec = "ack.pro.small"
kubernetes_version = "1.28.3-aliyun.1"
worker_vswitch_ids = [alicloud_vswitch.worker.id]
pod_cidr = "172.20.0.0/16"
service_cidr = "172.21.0.0/20"
worker_instance_types = ["ecs.c7.xlarge"]
worker_number = 3
# 开启自动扩缩容
addons {
name = "cluster-autoscaler"
}
}
# 输出kubeconfig
output "kubeconfig" {
value = alicloud_cs_managed_kubernetes.shop_cluster.certificate_authority
sensitive = true
}
21.2 GitOps实践:ArgoCD
GitOps核心理念:
- Git仓库是唯一的事实来源(Single Source of Truth)
- 所有变更通过Git提交触发,而非手动kubectl
- 系统自动将实际状态同步到Git中声明的期望状态
传统部署 vs GitOps:
传统:
开发者 → kubectl apply → K8s集群
(谁改了什么?什么时候改的?无法追溯)
GitOps:
开发者 → git push → Git仓库 → ArgoCD自动同步 → K8s集群
(所有变更有完整的Git历史,可审计、可回滚)
# 安装ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f \
https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# 获取初始密码
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
# 访问UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# 浏览器访问:https://localhost:8080
# ArgoCD Application:声明要同步的应用
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: shop-order-service
namespace: argocd
spec:
project: default
# 源:Git仓库中的Helm Chart
source:
repoURL: https://github.com/example/shop-k8s-configs
targetRevision: main
path: helm/shop-order-service
helm:
valueFiles:
- values-prod.yaml
# 目标:K8s集群和命名空间
destination:
server: https://kubernetes.default.svc
namespace: shop
# 同步策略
syncPolicy:
automated:
prune: true # 自动删除Git中已移除的资源
selfHeal: true # 手动修改K8s资源后自动恢复到Git状态
syncOptions:
- CreateNamespace=true
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 3m
二十二、大厂DevOps实践总结
22.1 大厂DevOps成熟度模型
DevOps成熟度五个阶段:
Level 1 - 初始阶段
- 手动部署,依赖个人经验
- 没有自动化测试
- 发布频率:月/季度
Level 2 - 基础自动化
- 有CI流水线(自动构建+测试)
- 手动触发部署
- 发布频率:周
Level 3 - 持续交付
- CI/CD全自动化
- 多环境自动晋级
- 发布频率:天
Level 4 - 持续部署
- 代码合并即自动部署到生产
- 完善的监控和告警
- 发布频率:小时
Level 5 - 优化阶段(大厂水平)
- 混沌工程,主动发现问题
- 自动扩缩容,弹性应对流量
- 全链路压测,容量精准规划
- 发布频率:分钟级
22.2 大厂面试中的DevOps加分项
面试官最想听到的DevOps实践经验:
1. 解决了什么实际问题
✅ "我们之前部署需要30分钟,经常出错,我搭建了CI/CD流水线后
部署时间缩短到5分钟,错误率降低了90%"
2. 遇到了什么挑战,如何解决
✅ "在做蓝绿部署时,发现数据库Schema变更无法做到零停机,
我们通过'扩展-收缩'模式解决:先加新列(兼容新旧代码),
部署新版本,再删旧列"
3. 有量化的数据支撑
✅ "通过优化Docker镜像(多阶段构建+分层),镜像从800MB降到180MB,
CI/CD时间从15分钟降到8分钟"
4. 主动思考和改进
✅ "我发现告警太多导致告警疲劳,主动梳理了告警规则,
将无效告警从200条减少到30条,响应速度提升了3倍"
22.3 工具链选型决策树
如何选择合适的工具?
容器化:
单机开发 → Docker + Docker Compose
多机生产 → Kubernetes
CI/CD:
开源项目/小团队 → GitHub Actions(免费额度够用)
企业内网/复杂流水线 → Jenkins
云原生/GitOps → ArgoCD + GitHub Actions
监控:
指标监控 → Prometheus + Grafana(开源标准)
日志管理 → ELK Stack 或 Loki + Grafana
链路追踪 → SkyWalking(Java友好)或 Jaeger
镜像仓库:
公开项目 → Docker Hub
企业私有 → Harbor(功能全面,支持镜像扫描)
云厂商 → 阿里云ACR / 腾讯云TCR
服务网格:
流量治理需求强 → Istio(功能最全,但复杂)
轻量级需求 → Linkerd(简单易用)
暂不需要 → 用Spring Cloud自带的治理能力
知识总结思维导图
二十三、云原生应用开发最佳实践
23.1 十二要素应用(12-Factor App)
云原生应用应该遵循的12条原则,确保应用可以在任何云平台上运行。
12-Factor App 原则详解:
1. 基准代码(Codebase)
一份代码,多处部署
✅ 正确:一个Git仓库,通过配置区分dev/staging/prod
❌ 错误:每个环境一份代码副本
2. 依赖(Dependencies)
显式声明依赖,不依赖系统工具
✅ 正确:pom.xml声明所有依赖,Maven自动下载
❌ 错误:依赖系统预装的库(如ImageMagick)
3. 配置(Config)
配置存储在环境变量中,不硬编码
✅ 正确:数据库URL从环境变量读取
❌ 错误:配置写死在application.properties
4. 后端服务(Backing Services)
把数据库、缓存等视为附加资源,通过URL访问
✅ 正确:通过环境变量切换MySQL实例
❌ 错误:代码中硬编码数据库地址
5. 构建、发布、运行(Build, Release, Run)
严格分离构建和运行阶段
✅ 正确:CI构建镜像 → 推送仓库 → K8s拉取运行
❌ 错误:在生产服务器上编译代码
6. 进程(Processes)
应用作为无状态进程运行
✅ 正确:Session存Redis,任意Pod都能处理请求
❌ 错误:Session存本地内存,Pod重启丢失
7. 端口绑定(Port Binding)
通过端口绑定提供服务,不依赖外部Web服务器
✅ 正确:Spring Boot内嵌Tomcat,监听8080端口
❌ 错误:依赖外部Tomcat容器
8. 并发(Concurrency)
通过进程模型扩展(水平扩展)
✅ 正确:增加Pod副本数应对流量
❌ 错误:单实例垂直扩容(加CPU/内存)
9. 易处理(Disposability)
快速启动和优雅终止
✅ 正确:应用启动<30秒,收到SIGTERM后优雅关闭
❌ 错误:启动需要5分钟,强制kill导致数据丢失
10. 开发环境与生产环境等价(Dev/Prod Parity)
开发、测试、生产环境尽量一致
✅ 正确:都用Docker + K8s,只是规格不同
❌ 错误:开发用H2,生产用MySQL
11. 日志(Logs)
日志作为事件流输出到stdout
✅ 正确:应用输出到stdout,由容器运行时收集
❌ 错误:应用直接写文件,需要挂载Volume
12. 管理进程(Admin Processes)
管理任务作为一次性进程运行
✅ 正确:数据迁移用K8s Job,执行完自动退出
❌ 错误:在应用启动时执行数据迁移
23.2 Spring Boot云原生配置
// 云原生Spring Boot应用配置示例
// 1. 优雅关闭(响应K8s的SIGTERM信号)
// application.yml
server:
shutdown: graceful # 优雅关闭
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 最多等待30秒
// 2. 健康检查端点
@Configuration
public class HealthConfig {
@Bean
public HealthIndicator customHealthIndicator() {
return () -> {
// 检查关键依赖是否可用
boolean dbOk = checkDatabase();
boolean redisOk = checkRedis();
if (dbOk && redisOk) {
return Health.up()
.withDetail("database", "ok")
.withDetail("redis", "ok")
.build();
} else {
return Health.down()
.withDetail("database", dbOk ? "ok" : "down")
.withDetail("redis", redisOk ? "ok" : "down")
.build();
}
};
}
}
// 3. 配置外部化(从环境变量读取)
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {
// 从环境变量 APP_DATABASE_URL 读取
private String databaseUrl;
// 从环境变量 APP_REDIS_HOST 读取
private String redisHost;
// 从环境变量 APP_MAX_CONNECTIONS 读取,默认100
private int maxConnections = 100;
// getters and setters...
}
# K8s Deployment中注入环境变量
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
env:
# 从ConfigMap读取
- name: APP_DATABASE_URL
valueFrom:
configMapKeyRef:
name: shop-config
key: database-url
# 从Secret读取
- name: APP_DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: shop-secret
key: database-password
# 直接设置
- name: SPRING_PROFILES_ACTIVE
value: "prod"
# 从Pod信息读取
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
23.3 应用可观测性增强
// 使用Micrometer增强可观测性
@RestController
@RequestMapping("/api/orders")
@Slf4j
public class OrderController {
private final OrderService orderService;
private final MeterRegistry meterRegistry;
@PostMapping
@Timed(value = "order.create", description = "订单创建耗时")
public Result<Order> createOrder(@RequestBody OrderDTO dto) {
// 记录业务指标
meterRegistry.counter("order.create.attempt",
"product_id", dto.getProductId().toString()).increment();
try {
Order order = orderService.createOrder(dto);
// 记录订单金额分布
meterRegistry.summary("order.amount",
"status", "success").record(order.getTotalAmount().doubleValue());
return Result.success(order);
} catch (InsufficientStockException e) {
// 记录失败原因
meterRegistry.counter("order.create.failure",
"reason", "insufficient_stock").increment();
throw e;
}
}
}
// 自定义Actuator端点
@Component
@Endpoint(id = "business-metrics")
public class BusinessMetricsEndpoint {
@Autowired
private OrderService orderService;
@ReadOperation
public Map<String, Object> businessMetrics() {
Map<String, Object> metrics = new HashMap<>();
// 实时业务指标
metrics.put("todayOrderCount", orderService.getTodayOrderCount());
metrics.put("todayRevenue", orderService.getTodayRevenue());
metrics.put("pendingPaymentCount", orderService.getPendingPaymentCount());
return metrics;
}
}
二十四、灾难恢复与业务连续性
24.1 备份策略
3-2-1备份原则:
- 3份副本:生产数据 + 2份备份
- 2种介质:本地磁盘 + 云存储
- 1份异地:防止机房级灾难
备份类型对比:
┌──────────────┬──────────┬──────────┬──────────────────┐
│ 备份类型 │ 恢复速度 │ 存储空间 │ 适用场景 │
├──────────────┼──────────┼──────────┼──────────────────┤
│ 全量备份 │ 快 │ 大 │ 每周一次 │
│ 增量备份 │ 慢 │ 小 │ 每天一次 │
│ 差异备份 │ 中 │ 中 │ 折中方案 │
│ 快照 │ 极快 │ 小 │ 云盘快照,秒级 │
└──────────────┴──────────┴──────────┴──────────────────┘
# MySQL自动备份脚本
#!/bin/bash
# mysql-backup.sh
BACKUP_DIR="/data/backups/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7
# 全量备份
mysqldump -h localhost -u backup_user -p'password' \
--single-transaction \
--routines \
--triggers \
--events \
--all-databases \
| gzip > ${BACKUP_DIR}/full_backup_${DATE}.sql.gz
# 上传到云存储(阿里云OSS)
ossutil cp ${BACKUP_DIR}/full_backup_${DATE}.sql.gz \
oss://my-backup-bucket/mysql/
# 删除7天前的本地备份
find ${BACKUP_DIR} -name "full_backup_*.sql.gz" -mtime +${RETENTION_DAYS} -delete
# 验证备份完整性
gunzip -t ${BACKUP_DIR}/full_backup_${DATE}.sql.gz
if [ $? -eq 0 ]; then
echo "备份成功: full_backup_${DATE}.sql.gz"
else
echo "备份失败!" | mail -s "MySQL备份失败" admin@example.com
fi
# K8s CronJob定时备份
apiVersion: batch/v1
kind: CronJob
metadata:
name: mysql-backup
namespace: shop
spec:
schedule: "0 2 * * *" # 每天凌晨2点
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: mysql:8.0
command:
- /bin/sh
- -c
- |
mysqldump -h mysql-svc -u root -p${MYSQL_ROOT_PASSWORD} \
--single-transaction --all-databases \
| gzip > /backup/backup_$(date +%Y%m%d_%H%M%S).sql.gz
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
volumeMounts:
- name: backup-volume
mountPath: /backup
volumes:
- name: backup-volume
persistentVolumeClaim:
claimName: mysql-backup-pvc
restartPolicy: OnFailure
24.2 灾难恢复演练
灾难恢复演练流程:
目标:验证在主数据中心完全不可用时,能否在30分钟内切换到备用数据中心
演练步骤:
1. 准备阶段(T-1天)
- 通知所有相关人员演练时间
- 准备演练脚本和检查清单
- 确认备用数据中心状态正常
2. 执行阶段(T0)
T+0分钟:宣布演练开始,模拟主数据中心故障
T+5分钟:切换DNS,将流量指向备用数据中心
T+10分钟:验证应用服务可访问
T+15分钟:验证数据库主从切换成功
T+20分钟:执行冒烟测试,验证核心功能
T+30分钟:宣布演练成功,恢复正常
3. 复盘阶段(T+1天)
- 记录实际耗时 vs 目标耗时
- 分析遇到的问题和改进点
- 更新灾难恢复手册
# 灾难恢复自动化脚本
#!/bin/bash
# disaster-recovery.sh
set -e
echo "=========================================="
echo " 灾难恢复流程启动"
echo "=========================================="
# Step 1:切换数据库主从
echo "[1/5] 切换MySQL主从..."
mysql -h backup-mysql -u admin -p'password' -e "STOP SLAVE; RESET SLAVE ALL;"
mysql -h backup-mysql -u admin -p'password' -e "SET GLOBAL read_only = OFF;"
echo "✅ MySQL已切换为主库"
# Step 2:更新K8s ConfigMap(指向新的数据库)
echo "[2/5] 更新K8s配置..."
kubectl patch configmap shop-config -n shop \
--type merge \
-p '{"data":{"database-host":"backup-mysql.shop.svc.cluster.local"}}'
echo "✅ ConfigMap已更新"
# Step 3:滚动重启应用(加载新配置)
echo "[3/5] 重启应用..."
kubectl rollout restart deployment/shop-order-service -n shop
kubectl rollout status deployment/shop-order-service -n shop --timeout=5m
echo "✅ 应用已重启"
# Step 4:更新DNS(切换流量)
echo "[4/5] 更新DNS..."
# 调用云厂商API更新DNS记录
aliyun alidns UpdateDomainRecord \
--RecordId 123456 \
--RR api \
--Type A \
--Value 47.100.200.100 # 备用数据中心IP
echo "✅ DNS已更新(生效需要5-10分钟)"
# Step 5:验证服务可用性
echo "[5/5] 验证服务..."
sleep 60 # 等待DNS生效
for i in {1..10}; do
if curl -f -s http://api.example.com/health > /dev/null; then
echo "✅ 服务验证成功"
break
fi
echo "等待服务就绪... ($i/10)"
sleep 10
done
echo "=========================================="
echo " 灾难恢复完成!"
echo "=========================================="
二十五、团队协作与DevOps文化
25.1 DevOps团队组织结构
传统组织 vs DevOps组织:
传统瀑布式:
开发团队 → 测试团队 → 运维团队
(各自为政,交接环节多,责任不清)
DevOps全功能团队:
┌─────────────────────────────────────────────────────────┐
│ 产品特性团队(Feature Team) │
│ ┌──────────┬──────────┬──────────┬──────────┐ │
│ │ 后端开发 │ 前端开发 │ 测试 │ 运维 │ │
│ │ (2-3人) │ (1-2人) │ (1人) │ (1人) │ │
│ └──────────┴──────────┴──────────┴──────────┘ │
│ 共同负责:需求 → 开发 → 测试 → 部署 → 运维 │
└─────────────────────────────────────────────────────────┘
优势:
- 端到端负责,减少交接
- 快速反馈,问题及时解决
- 团队自主性强,效率高
25.2 代码审查最佳实践
代码审查检查清单:
功能性:
□ 代码实现了需求的功能
□ 边界条件处理正确
□ 错误处理完善
可读性:
□ 命名清晰,见名知意
□ 逻辑清晰,易于理解
□ 注释恰当,解释了"为什么"
性能:
□ 无明显性能问题(N+1查询、死循环)
□ 数据库查询有索引
□ 大数据量处理考虑分页
安全:
□ 无SQL注入风险
□ 敏感信息不硬编码
□ 权限校验完善
测试:
□ 有单元测试覆盖核心逻辑
□ 测试用例充分
DevOps:
□ 日志记录充分
□ 监控指标完善
□ 配置外部化
// 代码审查示例:优化前 vs 优化后
// ❌ 审查意见:存在N+1查询问题
public List<OrderVO> listOrders() {
List<Order> orders = orderDao.findAll();
return orders.stream().map(order -> {
OrderVO vo = new OrderVO(order);
// 每个订单都查一次用户,100个订单 = 101次SQL
User user = userDao.findById(order.getUserId());
vo.setUserName(user.getName());
return vo;
}).collect(Collectors.toList());
}
// ✅ 修改后:批量查询,性能提升100倍
public List<OrderVO> listOrders() {
List<Order> orders = orderDao.findAll();
// 收集所有userId,批量查询
Set<Long> userIds = orders.stream()
.map(Order::getUserId)
.collect(Collectors.toSet());
Map<Long, User> userMap = userDao.findByIds(userIds).stream()
.collect(Collectors.toMap(User::getId, u -> u));
// 组装结果
return orders.stream().map(order -> {
OrderVO vo = new OrderVO(order);
User user = userMap.get(order.getUserId());
vo.setUserName(user != null ? user.getName() : "未知用户");
return vo;
}).collect(Collectors.toList());
}
25.3 事故复盘(Postmortem)
事故复盘模板:
1. 事故概述
- 发生时间:2024-01-15 10:30 - 11:15
- 影响范围:订单服务不可用,影响100%用户
- 严重程度:P0(最高级)
- 根本原因:数据库连接池耗尽
2. 时间线
10:30 - 监控告警:订单服务错误率100%
10:32 - 值班人员确认,开始排查
10:35 - 发现数据库连接池满,所有请求等待连接超时
10:40 - 紧急重启应用,临时恢复
10:45 - 分析日志,发现慢查询导致连接长时间占用
11:00 - 优化慢查询SQL,添加索引
11:15 - 服务完全恢复,监控正常
3. 根本原因分析(5 Whys)
Q1: 为什么服务不可用?
A1: 数据库连接池耗尽
Q2: 为什么连接池耗尽?
A2: 有慢查询占用连接时间过长
Q3: 为什么有慢查询?
A3: 订单表缺少create_time索引
Q4: 为什么缺少索引?
A4: 新增查询条件时未评审SQL性能
Q5: 为什么未评审?
A5: 缺少SQL审查流程
4. 改进措施
短期(1周内):
- [已完成] 为订单表添加create_time索引
- [进行中] 增大连接池上限(20 → 50)
- [进行中] 添加慢查询告警(>1秒)
长期(1个月内):
- [计划中] 建立SQL审查流程,所有SQL需DBA审核
- [计划中] 引入数据库连接池监控,提前预警
- [计划中] 定期进行压测,发现性能瓶颈
5. 经验教训
- 数据库变更必须评估性能影响
- 监控要覆盖连接池等关键资源
- 事故演练要包含数据库故障场景
二十六、DevOps工具链终极总结
26.1 从零到一的DevOps演进路径
DevOps成熟度演进路线图:
阶段1:手动部署(1-2周)
目标:建立基础CI
工具:GitHub + GitHub Actions
产出:代码提交自动触发构建和测试
阶段2:容器化(2-3周)
目标:应用容器化,本地环境标准化
工具:Docker + Docker Compose
产出:一键启动完整开发环境
阶段3:自动化部署(3-4周)
目标:实现CD,自动部署到测试环境
工具:K8s + Helm
产出:代码合并自动部署到测试环境
阶段4:监控可观测(2-3周)
目标:建立监控体系,快速发现问题
工具:Prometheus + Grafana + ELK
产出:完整的监控大屏和告警规则
阶段5:生产部署(2-3周)
目标:安全地部署到生产环境
工具:蓝绿部署 + 金丝雀发布
产出:零停机部署,快速回滚能力
阶段6:持续优化(持续进行)
目标:提升系统稳定性和效率
工具:混沌工程 + 全链路压测
产出:系统容量规划,故障自愈能力
26.2 工具选型决策矩阵
根据团队规模和需求选择工具:
┌─────────────┬──────────────┬──────────────┬──────────────┐
│ 团队规模 │ 小团队(5人内) │ 中型(5-20人) │ 大型(20+人) │
├─────────────┼──────────────┼──────────────┼──────────────┤
│ 代码托管 │ GitHub │ GitLab │ GitLab EE │
│ CI/CD │ GitHub Actions│ GitLab CI │ Jenkins │
│ 容器编排 │ Docker Compose│ K8s │ K8s + Istio │
│ 镜像仓库 │ Docker Hub │ Harbor │ Harbor HA │
│ 配置管理 │ ConfigMap │ ConfigMap │ Vault │
│ 监控 │ Prometheus │ Prometheus │ Prometheus HA│
│ 日志 │ 文件日志 │ ELK Stack │ ELK + Kafka │
│ 链路追踪 │ 不需要 │ SkyWalking │ SkyWalking │
└─────────────┴──────────────┴──────────────┴──────────────┘
26.3 DevOps关键指标(DORA Metrics)
DORA四大关键指标(衡量DevOps成熟度):
1. 部署频率(Deployment Frequency)
精英团队:按需部署(每天多次)
高效团队:每周一次到每月一次
中等团队:每月一次到每半年一次
低效团队:少于每半年一次
2. 变更前置时间(Lead Time for Changes)
从代码提交到生产部署的时间
精英团队:< 1小时
高效团队:1天 - 1周
中等团队:1周 - 1个月
低效团队:> 1个月
3. 变更失败率(Change Failure Rate)
导致生产故障的部署比例
精英团队:0-15%
高效团队:16-30%
中等团队:31-45%
低效团队:> 45%
4. 服务恢复时间(Time to Restore Service)
从故障发生到恢复的时间
精英团队:< 1小时
高效团队:< 1天
中等团队:1天 - 1周
低效团队:> 1周
# 在CI/CD中收集DORA指标
# .github/workflows/metrics.yml
name: Collect DORA Metrics
on:
push:
branches: [main]
deployment_status:
jobs:
metrics:
runs-on: ubuntu-latest
steps:
- name: Record Deployment
run: |
# 记录部署时间
curl -X POST https://metrics.example.com/api/deployments \
-H "Content-Type: application/json" \
-d '{
"service": "shop-order-service",
"commit": "${{ github.sha }}",
"deployed_at": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
"lead_time_seconds": ${{ github.event.deployment.created_at - github.event.head_commit.timestamp }}
}'
- name: Check Deployment Status
if: github.event.deployment_status.state == 'failure'
run: |
# 记录部署失败
curl -X POST https://metrics.example.com/api/failures \
-H "Content-Type: application/json" \
-d '{
"service": "shop-order-service",
"commit": "${{ github.sha }}",
"failed_at": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
}'
最终总结:成为DevOps工程师的学习路径
学习路线图
第1个月:基础能力
□ 熟练使用Linux命令行
□ 掌握Git版本控制
□ 学习Docker基础(镜像、容器、网络、存储)
□ 编写Dockerfile和docker-compose.yml
第2个月:容器编排
□ 学习K8s核心概念(Pod、Deployment、Service)
□ 掌握kubectl常用命令
□ 编写K8s YAML配置文件
□ 理解K8s网络和存储
第3个月:CI/CD
□ 搭建GitHub Actions流水线
□ 学习Jenkins Pipeline
□ 掌握Helm包管理
□ 实现自动化部署
第4个月:监控可观测
□ 搭建Prometheus + Grafana
□ 配置告警规则
□ 学习PromQL查询语言
□ 集成ELK日志系统
第5个月:高级特性
□ 学习服务网格(Istio)
□ 掌握蓝绿部署、金丝雀发布
□ 学习混沌工程
□ 性能压测和容量规划
第6个月:生产实践
□ 参与真实项目的DevOps实践
□ 处理生产环境故障
□ 优化CI/CD流水线
□ 编写运维文档和SOP
推荐学习资源
书籍:
1. 《凤凰项目》- DevOps理念入门
2. 《持续交付》- CI/CD经典著作
3. 《Kubernetes权威指南》- K8s深入学习
4. 《SRE:Google运维解密》- 大厂运维实践
在线课程:
1. Kubernetes官方文档和教程
2. Docker官方文档
3. Prometheus官方文档
4. CNCF(云原生计算基金会)项目
实践平台:
1. Katacoda - 在线K8s实验环境
2. Play with Docker - 在线Docker环境
3. GitHub - 开源项目学习
4. 自己搭建本地K8s集群(Minikube/Kind)
认证考试:
1. CKA(Certified Kubernetes Administrator)
2. CKAD(Certified Kubernetes Application Developer)
3. Docker Certified Associate
面试准备建议
技术深度:
- 不要只会用工具,要理解原理
- Docker:镜像分层、网络模式、存储驱动
- K8s:调度原理、网络模型、控制器模式
- CI/CD:流水线设计、制品管理、环境晋级
实战经验:
- 准备3-5个DevOps实践案例
- 每个案例包含:背景、方案、结果、收获
- 量化成果:部署时间缩短X%,故障率降低Y%
问题解决:
- 准备故障排查案例
- 展示排查思路和解决过程
- 体现主动思考和持续改进
软技能:
- 跨团队协作能力
- 文档编写能力
- 自动化思维
- 持续学习能力
恭喜你完成了《企业级生产工具链实战》的学习!这份文档涵盖了从Docker基础到K8s生产运维、从CI/CD到监控告警、从故障排查到性能优化的完整DevOps知识体系。
记住:DevOps不仅是工具的使用,更是一种文化和思维方式。持续学习、自动化一切、快速反馈、持续改进,这些理念将伴随你的整个职业生涯。
祝你在DevOps的道路上越走越远,成为企业不可或缺的技术骨干!
二十三、云原生应用开发最佳实践
23.1 十二要素应用(12-Factor App)
云原生应用设计原则(12-Factor):
1. 代码库(Codebase)
一个代码库,多个部署环境
✅ Git仓库统一管理,通过分支/标签区分环境
2. 依赖(Dependencies)
显式声明依赖,不依赖系统工具
✅ Maven/Gradle管理依赖,Docker镜像包含所有依赖
3. 配置(Config)
配置与代码分离,通过环境变量注入
✅ K8s ConfigMap/Secret,不要硬编码配置
4. 后端服务(Backing Services)
数据库、缓存等作为附加资源,可随时替换
✅ 通过环境变量配置连接信息,不绑定特定实例
5. 构建、发布、运行(Build, Release, Run)
严格分离构建和运行阶段
✅ CI构建镜像 → CD发布到K8s → 运行时不可变
6. 进程(Processes)
应用作为无状态进程运行
✅ 不在本地存储会话,使用Redis等外部存储
7. 端口绑定(Port Binding)
通过端口对外提供服务
✅ Spring Boot内嵌Tomcat,监听8080端口
8. 并发(Concurrency)
通过进程模型扩展
✅ K8s HPA水平扩展Pod数量
9. 易处理(Disposability)
快速启动和优雅关闭
✅ 健康检查 + PreStop Hook
10. 开发环境与生产环境等价(Dev/Prod Parity)
开发、测试、生产环境尽量一致
✅ Docker保证环境一致性
11. 日志(Logs)
日志作为事件流输出到stdout
✅ 不写本地文件,由日志收集器统一采集
12. 管理进程(Admin Processes)
管理任务作为一次性进程运行
✅ K8s Job/CronJob执行数据迁移、定时任务
23.2 Spring Boot云原生配置
// application.yml - 云原生配置示例
spring:
application:
name: shop-order-service
# 配置从环境变量读取(12-Factor原则)
datasource:
url: ${DB_URL:jdbc:mysql://localhost:3306/shop}
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:password}
hikari:
maximum-pool-size: ${DB_POOL_SIZE:20}
minimum-idle: ${DB_POOL_MIN_IDLE:5}
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
# Actuator健康检查(K8s探针使用)
management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
endpoint:
health:
probes:
enabled: true # 开启liveness和readiness端点
show-details: always
health:
livenessState:
enabled: true
readinessState:
enabled: true
# 优雅关闭(K8s PreStop Hook配合)
server:
shutdown: graceful # 优雅关闭
port: ${SERVER_PORT:8080}
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 最多等待30秒
// 优雅关闭实现:确保请求处理完再退出
@Component
@Slf4j
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
log.info("收到关闭信号,开始优雅关闭...");
// 1. 停止接收新请求(通过readiness探针实现)
log.info("停止接收新请求");
// 2. 等待现有请求处理完成
log.info("等待现有请求处理完成...");
try {
Thread.sleep(5000); // 等待5秒,让现有请求处理完
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 3. 关闭线程池
log.info("关闭线程池...");
taskExecutor.shutdown();
try {
if (!taskExecutor.getThreadPoolExecutor().awaitTermination(20, TimeUnit.SECONDS)) {
log.warn("线程池未能在20秒内关闭,强制关闭");
taskExecutor.getThreadPoolExecutor().shutdownNow();
}
} catch (InterruptedException e) {
taskExecutor.getThreadPoolExecutor().shutdownNow();
Thread.currentThread().interrupt();
}
log.info("优雅关闭完成");
}
}
# K8s Deployment配合优雅关闭
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
lifecycle:
preStop:
exec:
# PreStop Hook:K8s发送SIGTERM前先执行
# 等待10秒,让负载均衡器摘除此Pod
command: ["/bin/sh", "-c", "sleep 10"]
# 健康检查
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
# 关键:失败后立即从Service摘除
failureThreshold: 1
# 优雅关闭超时时间(必须大于preStop + shutdown timeout)
terminationGracePeriodSeconds: 60
二十四、大规模集群管理实战
24.1 多集群管理策略
多集群场景:
- 多地域部署(北京、上海、广州)
- 多环境隔离(dev、staging、prod)
- 灾备集群(主集群故障时切换)
多集群管理工具对比:
┌──────────────┬────────────────────────────────────────┐
│ 工具 │ 特点 │
├──────────────┼────────────────────────────────────────┤
│ kubectl │ 手动切换context,适合少量集群 │
│ kubectx │ 快速切换context,命令行友好 │
│ Rancher │ Web UI统一管理,适合运维团队 │
│ ArgoCD │ GitOps多集群部署,声明式管理 │
│ Flux │ GitOps,更轻量级 │
└──────────────┴────────────────────────────────────────┘
# kubectl多集群管理
# 查看所有集群配置
kubectl config get-contexts
# 切换集群
kubectl config use-context prod-beijing
# 同时操作多个集群(使用kubectx + kubens)
# 安装kubectx
brew install kubectx
# 快速切换集群
kubectx prod-beijing
kubectx prod-shanghai
# 快速切换命名空间
kubens shop
# 在所有集群执行命令(自定义脚本)
for ctx in prod-beijing prod-shanghai prod-guangzhou; do
echo "=== $ctx ==="
kubectl --context=$ctx get pods -n shop
done
24.2 大规模节点管理
# 节点管理常用操作
# 查看节点资源使用情况
kubectl top nodes
# 标记节点(用于调度)
kubectl label nodes node-1 disktype=ssd
kubectl label nodes node-2 zone=beijing
# 节点污点(Taint):阻止Pod调度到特定节点
# 场景:节点维护、专用节点(GPU节点只跑AI任务)
kubectl taint nodes node-1 maintenance=true:NoSchedule
# 容忍(Toleration):允许Pod调度到有污点的节点
# 在Deployment中配置:
spec:
template:
spec:
tolerations:
- key: "maintenance"
operator: "Equal"
value: "true"
effect: "NoSchedule"
# 驱逐节点上的所有Pod(节点维护前)
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data
# 恢复节点调度
kubectl uncordon node-1
# 节点亲和性:指定Pod调度到特定节点
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values: ["ssd"]
24.3 资源配额与限制
# ResourceQuota:限制命名空间的总资源使用
apiVersion: v1
kind: ResourceQuota
metadata:
name: shop-quota
namespace: shop
spec:
hard:
requests.cpu: "50" # 最多申请50核CPU
requests.memory: 100Gi # 最多申请100GB内存
limits.cpu: "100" # 最多使用100核CPU
limits.memory: 200Gi # 最多使用200GB内存
persistentvolumeclaims: "10" # 最多10个PVC
pods: "100" # 最多100个Pod
services.loadbalancers: "5" # 最多5个LoadBalancer
---
# LimitRange:限制单个Pod/Container的资源范围
apiVersion: v1
kind: LimitRange
metadata:
name: shop-limit-range
namespace: shop
spec:
limits:
# Container级别限制
- type: Container
max:
cpu: "4" # 单容器最多4核
memory: 8Gi # 单容器最多8GB
min:
cpu: "100m" # 单容器最少100m
memory: 128Mi # 单容器最少128MB
default:
cpu: "500m" # 默认limits
memory: 512Mi
defaultRequest:
cpu: "200m" # 默认requests
memory: 256Mi
# Pod级别限制
- type: Pod
max:
cpu: "8"
memory: 16Gi
二十五、DevOps文化与团队协作
25.1 DevOps文化核心理念
DevOps不仅是工具,更是文化和理念:
CALMS模型:
C - Culture(文化)
- 打破Dev和Ops的壁垒
- 共同对产品质量负责
- 失败是学习机会,不是追责
A - Automation(自动化)
- 自动化一切可自动化的流程
- 减少人为错误
- 提高效率和一致性
L - Lean(精益)
- 小批量、高频率发布
- 快速反馈,持续改进
- 消除浪费(等待、返工)
M - Measurement(度量)
- 用数据驱动决策
- 关键指标:部署频率、变更前置时间、MTTR、变更失败率
- 持续监控和优化
S - Sharing(分享)
- 知识共享,避免信息孤岛
- 复盘总结,沉淀最佳实践
- 跨团队协作
25.2 DevOps关键指标(DORA Metrics)
DORA四大关键指标(衡量DevOps成熟度):
1. 部署频率(Deployment Frequency)
精英团队:每天多次
高水平:每周一次到每月一次
中等水平:每月一次到每半年一次
低水平:少于每半年一次
2. 变更前置时间(Lead Time for Changes)
从代码提交到生产部署的时间
精英团队:少于1小时
高水平:1天到1周
中等水平:1周到1个月
低水平:1个月到6个月
3. 平均恢复时间(MTTR - Mean Time To Recovery)
服务故障后恢复的平均时间
精英团队:少于1小时
高水平:少于1天
中等水平:1天到1周
低水平:1周到1个月
4. 变更失败率(Change Failure Rate)
导致服务降级或需要回滚的变更比例
精英团队:0-15%
高水平:16-30%
中等水平:31-45%
低水平:46-60%
25.3 故障复盘模板(Postmortem)
# 故障复盘报告模板
## 基本信息
- 故障时间:2024-01-15 10:30 - 11:15(45分钟)
- 影响范围:订单服务不可用,影响100%用户
- 严重级别:P0(服务完全不可用)
- 负责人:张三
## 故障现象
- 用户反馈:下单失败,提示"系统繁忙"
- 监控告警:订单服务错误率100%,所有Pod处于CrashLoopBackOff状态
- 业务影响:预计损失订单约500单,金额约10万元
## 根因分析
直接原因:
- 数据库连接池耗尽,所有请求超时
根本原因:
- 凌晨发布时,误将数据库连接池配置从50改为5
- 配置变更未经过Code Review
- 缺少配置变更的自动化测试
## 时间线
- 10:30 - 监控告警:订单服务错误率突增
- 10:32 - 值班人员确认故障,拉群通知
- 10:35 - 查看日志,发现大量数据库连接超时
- 10:40 - 检查数据库,连接数正常,怀疑应用配置问题
- 10:45 - 对比配置,发现连接池配置异常
- 10:50 - 决定回滚到上一版本
- 11:00 - 回滚完成,服务恢复
- 11:15 - 确认服务稳定,解除告警
## 做得好的地方
- 监控告警及时,2分钟内发现问题
- 团队响应迅速,5分钟内拉群协作
- 决策果断,15分钟内决定回滚
- 回滚流程顺畅,10分钟完成回滚
## 需要改进的地方
- 配置变更未经Review,缺少卡点
- 缺少配置合理性校验(连接池不应小于10)
- 缺少灰度发布,一次性全量发布风险大
- 缺少自动化回滚机制
## 行动计划
| 行动项 | 负责人 | 截止日期 | 状态 |
|-------|-------|---------|------|
| 配置变更强制Code Review | 张三 | 2024-01-20 | 进行中 |
| 添加配置合理性校验 | 李四 | 2024-01-22 | 待开始 |
| 实施金丝雀发布策略 | 王五 | 2024-01-25 | 待开始 |
| 配置监控告警(连接池使用率) | 赵六 | 2024-01-18 | 已完成 |
## 经验教训
1. 配置即代码,必须经过Review和测试
2. 关键配置要有合理性校验
3. 生产变更必须灰度发布,不能一次性全量
4. 完善的监控是快速定位问题的关键
最终总结
通过本文档的学习,你已经掌握了企业级生产工具链的完整体系:
从容器化(Docker多阶段构建、镜像优化)到编排(Kubernetes资源管理、故障排查),从CI/CD流水线(GitHub Actions、Jenkins、GitOps)到可观测性(Prometheus监控、ELK日志、SkyWalking链路追踪),从安全加固(容器安全、Vault密钥管理)到性能优化(压测、JVM调优、容量规划)。
这些技能不仅是大厂面试的必考点,更是实际工作中每天都会用到的核心能力。建议你:
- 动手实践:在本地搭建完整的DevOps环境,亲自操作每个工具
- 模拟故障:通过Chaos Engineering主动制造故障,锻炼排查能力
- 持续学习:关注云原生社区(CNCF),了解最新技术趋势
- 总结沉淀:将实践经验整理成文档,形成自己的知识库
记住:DevOps不是一蹴而就的,而是持续改进的过程。从自动化构建开始,逐步完善监控、告警、日志、链路追踪,最终形成完整的可观测性体系。
祝你在DevOps的道路上越走越远,早日成为大厂认可的全栈工程师!
更多推荐


所有评论(0)