跑通运维港——Docker、K8s 、普罗米修斯
跑通运维港——Docker、K8s 、普罗米修斯
如果把运维比作一座港口,那 Docker 是集装箱,Kubernetes 是调度系统,Prometheus 是监控室,其组件Grafana 就是监控室的大屏。本文用这套比喻,帮你一次性串起云原生运维核心技术。
一、Docker —— 集装箱
在没有集装箱的年代,货物散着搬:水果要冷藏、衣服怕潮、化工品要隔离,每种货都得单独安排装卸方式。软件部署也一样——"在我电脑上能跑啊"这句话之所以经典,就是因为每台机器的操作系统版本、依赖库、环境变量都不一样。
Docker 做的事情就是把你的应用连同它需要的一切(代码、运行时、依赖、配置)打包成一个标准集装箱,这个集装箱叫镜像(Image)。不管里面装的是 Java 服务还是 Python 脚本,只要集装箱规格统一,任何一台装了 Docker 的机器都能原封不动地把它跑起来。
几个关键概念对应到港口:
- 镜像(Image):集装箱的设计图纸,只读模板,可以复制无数份。
- 容器(Container):按图纸造出来、正在运输中的那个实体集装箱,是镜像的运行实例。
- Dockerfile:造箱子的工序说明书,写明先装什么后装什么、暴露哪个端口、启动命令是什么。
- Registry(仓库):集装箱堆场,Docker Hub 就是最大的公共堆场,私有仓库则是企业自己的堆场。
一个最简 Dockerfile 长这样:
FROM openjdk:17-slim
COPY target/app.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
四行代码,一个集装箱就造好了。docker build -t my-app . 打包,docker run -p 8080:8080 my-app 开箱即用。
镜像分层:为什么构建那么快?
Docker 镜像不是一个整块文件,而是由一层层只读层(Layer)叠起来的。Dockerfile 中每一条指令(FROM、RUN、COPY……)都会产生新的一层。构建时 Docker 会缓存每一层,如果某条指令和上次完全一样且输入没变,就直接复用缓存,不重新执行。
这就是为什么你应该把变化频率低的指令放前面(比如安装依赖),把变化频繁的放后面(比如 COPY 你的代码)。这样改一行业务代码,前面几十层依赖安装全部命中缓存,构建从几分钟缩到几秒。
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline # 依赖层,很少变,缓存命中率高
COPY src ./src
RUN mvn package -DskipTests # 代码层,经常变
FROM eclipse-temurin:17-jre-alpine # 最终镜像只保留运行时
COPY --from=build /app/target/*.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
上面这段还展示了多阶段构建(Multi-stage Build):第一阶段用完整的 Maven 环境编译,第二阶段只拷贝一个 jar 到精简的 JRE 镜像里。最终镜像不包含 Maven、源码、编译中间产物,体积可以从 800MB 缩到 100MB 出头。生产环境只跑最终镜像,又小又安全。
容器网络:箱子之间怎么通信?
Docker 默认使用 bridge 模式:每个容器分配一个虚拟 IP,挂在 docker0 网桥下,容器之间通过 IP 互通,对外通过端口映射(-p 8080:8080)暴露。还有 host 模式(容器直接共享宿主机网络栈,性能最好但没有隔离)和 none 模式(完全断网,适合纯计算任务)。
实际项目中多个容器需要协作(比如应用 + MySQL + Redis),手动 docker run 三次太麻烦,这时候用 docker-compose:
version: "3.8"
services:
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123
volumes:
- mysql-data:/var/lib/mysql
redis:
image: redis:7-alpine
volumes:
mysql-data:
一个 docker-compose up -d 三个容器全部拉起,互相之间通过服务名(mysql、redis)直接访问,不用记 IP。
数据卷:集装箱里的东西不能丢
容器本身是临时的,删了重建后里面写的数据就没了。但数据库文件、上传的图片这些必须持久化。Docker 的解决方案是数据卷(Volume):把容器内的某个目录挂载到宿主机上,容器销毁后数据依然在。
docker run -v /host/data:/container/data my-app
上面 -v 左边是宿主机路径,右边是容器内路径。也可以用命名卷(-v my-volume:/container/data),Docker 自己管理存储位置,跨容器共享更方便。
二、Kubernetes —— 港口调度系统
一个集装箱好管理,但当你有几百上千个集装箱、几十台起重机(服务器)的时候,问题就来了:哪个箱子放哪台机器?某台起重机坏了箱子怎么转移?双十一货量暴增怎么临时加箱子?
Kubernetes(简称 K8s)就是这套港口调度系统。你不用关心具体哪台机器在跑哪个容器,只需要告诉 K8s:“我要 3 个副本的订单服务、2 个副本的支付服务”,它自己决定放在哪、挂了怎么拉起来、流量怎么分。
核心概念:
- Pod:最小的调度单位,可以理解为"一组绑在一起运输的集装箱"。通常一个 Pod 里跑一个主容器。
- Deployment:声明"我要几个 Pod、用什么镜像、怎么滚动更新"。K8s 会持续保证实际状态和你声明的一致——少了就补,多了就杀,镜像版本不对就滚动替换。
- Service:给一组 Pod 一个固定的访问入口(虚拟 IP + 端口),不管背后的 Pod 怎么漂移、扩缩,外部始终通过 Service 访问。
- Node:港口里的一台起重机(一台物理机或虚拟机),Pod 被调度到 Node 上运行。
一个典型的 Deployment 声明:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order
image: my-registry/order-service:1.2.0
ports:
- containerPort: 8080
你只管写"我要 3 个",K8s 负责让现实永远等于这个期望。这就是声明式 API 的魅力。
调度流程:箱子怎么被分配到起重机上?
当你 kubectl apply 一个 Deployment 后,背后发生的事情大致是:API Server 收到请求写入 etcd → Controller Manager 发现"期望 3 个 Pod 但实际 0 个",创建 3 个 Pod 对象(此时状态为 Pending)→ Scheduler 根据一系列打分规则(资源够不够、亲和性、污点容忍等)给每个 Pod 选一个最合适的 Node → 选中 Node 上的 kubelet 拉镜像、启动容器。
整个过程你不需要干预,但理解这个流程能帮你看懂 kubectl describe pod 输出中的 Events 信息——比如 Pod 一直 Pending,多半是"没有 Node 满足资源请求"。
Pod 生命周期与探针
一个 Pod 从创建到销毁会经历:Pending(等待调度/拉镜像)→ Running(至少一个容器在跑)→ Succeeded / Failed(终态)。
但"Running"不代表"能正常服务"。容器可能启动了但应用还在初始化,也可能进程没挂但已经死锁了。K8s 用三种探针(Probe) 来解决这个问题:
- startupProbe:应用启动慢(比如 Java 要 30 秒预热),在它通过之前,其他探针不生效,防止应用还没启动完就被杀掉。
- livenessProbe:定期检测"进程还活着吗"。连续失败 N 次,K8s 会重启容器。典型场景:应用死锁了,进程还在但不响应。
- readinessProbe:检测"能接流量吗"。失败时 Pod 从 Service 的 Endpoints 中摘除,不再接收新请求,但不会重启。典型场景:依赖的下游服务暂时不可用。
containers:
- name: order
image: my-registry/order-service:1.2.0
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /actuator/health
port: 8080
failureThreshold: 30
periodSeconds: 2
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
Spring Boot Actuator 天然暴露这些端点,配合 K8s 探针几乎是零成本接入。
配置管理:ConfigMap 与 Secret
集装箱里不应该硬编码配置(数据库地址、API Key 等),否则换个环境就得重新造箱子。K8s 的做法是把配置抽出来:
- ConfigMap:存放非敏感配置(数据库 URL、开关参数),可以挂载为文件或注入为环境变量。
- Secret:存放敏感信息(密码、Token),Base64 编码存储(注意:不是加密,生产环境建议配合 RBAC 或外部密钥管理系统)。
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DB_URL: "jdbc:mysql://mysql-svc:3306/orders"
LOG_LEVEL: "info"
Pod 中引用:
envFrom:
- configMapRef:
name: app-config
这样同一个镜像在开发、测试、生产环境跑,只需要换 ConfigMap,镜像本身不用动。
Ingress:港口的对外大门
Service 解决了集群内部访问的问题,但外部用户(浏览器、App)怎么进来?答案是 Ingress——它相当于港口的大门 + 前台,根据域名和路径把请求路由到不同的 Service:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: main-ingress
spec:
rules:
- host: order.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-service
port:
number: 8080
- host: pay.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: pay-service
port:
number: 8080
Ingress 本身只是一条规则,真正干活的是 Ingress Controller(比如 Nginx Ingress Controller、Traefik),它监听 Ingress 资源变化并配置反向代理。
HPA:货量暴增时自动加箱子
双十一流量来了,手动 kubectl scale 太慢。K8s 的 HPA(Horizontal Pod Autoscaler) 可以根据 CPU 利用率、内存、甚至自定义指标自动扩缩 Pod 数量:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
含义:当 order-service 的平均 CPU 利用率超过 70% 就扩容,最多扩到 20 个 Pod;流量下去后自动缩回最少 3 个。整个过程全自动,你只需要确保 Pod 设置了 resources requests(否则 HPA 算不出利用率)。
三、Prometheus —— 港口监控室
港口跑起来了,但你不能靠肉眼盯着几百台起重机。你需要一个监控室,里面有人 7×24 小时记录每台设备的转速、温度、负载,一旦异常就拉警报。
Prometheus 就是这个监控室。它的工作方式是主动去拉(Pull)数据:每个服务暴露一个 /metrics 接口,Prometheus 每隔 15 秒(可配置)去采集一次,把数据存进自己的时序数据库。
几个关键点:
- Exporter:装在设备上的传感器。比如 Node Exporter 采集机器 CPU/内存/磁盘,cAdvisor 采集容器指标,应用自己也可以暴露业务指标。
- PromQL:监控室的查询语言。比如
rate(http_requests_total[5m])表示最近 5 分钟每秒请求数,up == 0表示某个目标挂了。 - Alertmanager:警报广播站。你在 Prometheus 里配规则(比如"CPU 超过 90% 持续 5 分钟"),触发后交给 Alertmanager 去发钉钉、发邮件、打电话。
一条简单的告警规则:
groups:
- name: basic
rules:
- alert: HighCpuUsage
expr: 100 - (avg by(instance)(irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
for: 5m
labels:
severity: warning
annotations:
summary: "CPU 使用率超过 90%"
时序数据模型:监控室怎么记录数据?
Prometheus 存储的不是表格,而是时间序列(Time Series)。每一条时间序列由"指标名 + 一组标签"唯一标识,后面跟着一串按时间排列的数值点。比如:
http_requests_total{method="GET", handler="/orders", status="200"}
→ [1720000000, 1024] [1720000015, 1031] [1720000030, 1045] ...
标签(Label)是 Prometheus 数据模型的核心。同一个指标名加上不同标签组合,就是不同的时间序列。查询时你可以按标签过滤、聚合、分组,这就是 PromQL 强大的基础。
四种指标类型:传感器有不同种类
Prometheus 定义了四种 Metric 类型,对应不同的测量场景:
- Counter(计数器):只增不减的累计值。比如请求总数、错误总数。查询时通常配合
rate()算每秒速率,因为绝对值本身意义不大。 - Gauge(仪表盘):可增可减的瞬时值。比如当前内存使用量、在线连接数、温度。直接看当前值就有意义。
- Histogram(直方图):把观测值分到预定义的桶(bucket)里计数。比如请求耗时分布:多少请求 < 100ms、多少在 100ms~500ms、多少 > 1s。适合算 P99 延迟。
- Summary(摘要):在客户端直接计算分位数(quantile)。和 Histogram 类似但分位数在采集端算好,服务端不能重新聚合。
实际开发中最常用的是 Counter 和 Histogram。Spring Boot Actuator + Micrometer 默认就会暴露 JVM 内存(Gauge)、HTTP 请求数(Counter)、请求耗时(Histogram)等指标,加个依赖就有。
服务发现:传感器太多怎么自动登记?
手动在 prometheus.yml 里一个个写 target 地址,服务一多就崩溃了。Prometheus 支持服务发现(Service Discovery),自动从各种来源获取要采集的目标列表:
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__
在 K8s 环境中,Prometheus 通过 API Server 自动发现所有带 prometheus.io/scrape: "true" 注解的 Pod,新服务上线、旧服务下线都自动感知,不用改配置重启。
Recording Rules:把常用查询预计算好
有些 PromQL 很复杂(比如按服务聚合的 P99 延迟),每次打开 Grafana 都现算一遍太慢。Recording Rules 让你把复杂表达式预先计算成一条新的时间序列,查询时直接读结果:
groups:
- name: recording
rules:
- record: job:http_request_duration_seconds:p99
expr: histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (job, le))
之后在 Grafana 里直接查 job:http_request_duration_seconds:p99 就行,响应速度从几秒变成毫秒级。
Prometheus 只管采集、存储和告警,数据怎么好看地展示出来,它不管——那是下一位的事。
四、Grafana —— 监控室大屏
监控室里光有数据不行,领导来视察你得有大屏。Grafana 就是这块大屏。
它本身不存数据,而是对接 Prometheus(以及 Elasticsearch、MySQL、InfluxDB 等几十种数据源),用 PromQL 查出数据后渲染成折线图、仪表盘、热力图、拓扑图。
实际使用中你大概率不会从零画面板,Grafana 社区有海量现成模板(Dashboard),导入一个 ID 就能用。比如导入 1860(Node Exporter Full),一整台机器的 CPU、内存、磁盘、网络指标就全部铺好了。
它还能配告警通知(和 Alertmanager 互补),支持按团队/项目分文件夹管理面板,支持变量下拉框让你在一个面板里切换查看不同服务、不同环境的指标。
五、四件套怎么串起来
把整座港口连起来看,数据流是这样的:
开发者写好代码
→ Docker 打包成镜像,推到 Registry(造好集装箱,送进堆场)
→ K8s 从 Registry 拉镜像,调度到 Node 上跑起来(调度系统安排起重机吊装)
→ 每个 Pod 暴露 /metrics 接口(每台设备装上传感器)
→ Prometheus 定时拉取指标存入时序库(监控室记录数据)
→ Grafana 从 Prometheus 查数据渲染大屏(大屏实时展示)
→ Alertmanager 触发告警通知到人(警报响了,运维去处理)
一句话总结:Docker 解决"怎么打包",K8s 解决"怎么跑",Prometheus 解决"怎么看",Grafana 解决"怎么展示"。四者各司其职,组合起来就是一套完整的云原生运维底座。
六、初学者进阶路线
如果你刚接触这套技术栈,不要试图一口气全学会。按阶段来,每个阶段都有一个可交付的"小成果",正反馈会推着你往下走。
第一阶段:Docker 入门(1~2 周)
目标是把自己的一个 Spring Boot 或 Node.js 项目容器化跑起来。具体做这几件事:写一个 Dockerfile,理解每条指令的作用;用 docker build 构建镜像,用 docker run 跑起来并验证端口映射;试试多阶段构建,对比一下镜像体积的变化;用 docker-compose 把应用 + MySQL + Redis 一起编排起来,体会"一键拉起整套环境"的爽感。
这个阶段结束后,你应该能回答:镜像分层缓存是怎么回事?容器和虚拟机有什么区别?数据卷解决了什么问题?
第二阶段:K8s 基础操作(2~3 周)
用 Minikube 或 Kind 在本地起一个单节点集群(别一上来就搭多节点,本地够用了)。然后按这个顺序练:kubectl run 跑一个 Pod → 写 Deployment YAML 管理副本 → 创建 Service 暴露访问 → 配置 ConfigMap 注入环境变量 → 给 Pod 加上 liveness 和 readiness 探针 → 写一个 Ingress 按域名路由。
每做一步就用 kubectl get pods、kubectl describe pod、kubectl logs 去观察状态变化。这个阶段的核心收获是理解"声明式"思维:你描述期望状态,K8s 负责让现实收敛到期望。
第三阶段:监控体系搭建(1~2 周)
给应用加上 Micrometer + Actuator 依赖,确认 /actuator/prometheus 端点能输出指标。然后本地用 docker-compose 起 Prometheus + Grafana(社区有现成的 compose 文件),配置 Prometheus 抓取你的应用,在 Grafana 里导入 Spring Boot 模板面板(ID: 12900),看到自己应用的 QPS、延迟、JVM 内存曲线。
再进一步:写一条告警规则(比如"5xx 错误率 > 5% 持续 2 分钟"),接上 Alertmanager 发到自己的邮箱或钉钉。当告警真的响起来的时候,你会对整套监控体系有非常具象的理解。
第四阶段:K8s 进阶(持续)
有了前面的基础,可以逐步深入:HPA 自动扩缩容、资源 requests/limits 与 QoS 等级、RBAC 权限控制、Helm Chart 打包部署、Namespace 多环境隔离、PV/PVC 持久化存储、NetworkPolicy 网络策略。这些不需要一次学完,在实际项目中遇到哪个就学哪个。
第五阶段:生产级运维(长期)
到了这个阶段,关注点从"怎么跑起来"变成"怎么跑得稳":CI/CD 流水线(GitLab CI / Jenkins / ArgoCD)实现代码提交到自动部署;日志体系(EFK 或 Loki)补齐可观测性的另一块拼图;链路追踪(Jaeger / SkyWalking)定位分布式调用链中的瓶颈;Prometheus 高可用(Thanos / VictoriaMetrics)解决长期存储和跨集群查询。
一些学习资源推荐
Docker 官方文档的 Get Started 部分写得非常友好,跟着做一遍就够入门了。K8s 推荐先读官方文档的 Concepts 部分建立全局认知,再跟着 “Kubernetes the Hard Way” 或者 B 站上尚硅谷/黑马的 K8s 教程动手。Prometheus 和 Grafana 的官方文档都有很好的入门教程,另外《Prometheus 监控实战》这本书适合系统性学习。
最重要的一条建议:一定要动手。看十遍文章不如自己 kubectl apply 一次然后看它报错、排查、修复。运维这东西,踩坑踩出来的理解远比看来的深刻。
更多推荐
所有评论(0)