编写代码只是交付软件的一半。另一半是构建、测试、发布到服务器并保持其稳定运行,理想情况下每周多次发布,而且不用深夜救火。DevOps 就是让这一切成为常规操作的一套实践,而容器、CI/CD 流水线和云基础设施则是让它落地的工具。
DevOps 的真正含义
DevOps 不是一个职位,也不是一种工具。它的理念是:构建软件的人和运维软件的人共同对两者负责,并以高度自动化作为支撑。DORA 研究项目用四个指标概括交付表现:
- 部署频率:你多久向生产环境发布一次。
- 变更前置时间:一次提交需要多久才能进入生产环境。
- 变更失败率:发布引发需要修复的故障的比例。
- 服务恢复时间:当一次发布失败时,你能多快恢复。
高绩效团队能同时在这四项上表现出色。事实证明,频繁发布小的变更,比偶尔发布大批量变更更快,也更安全。
使用 Docker 实现容器化
容器镜像把应用连同其确切的运行时、系统库和默认配置一起打包。容器则是该镜像的一个运行实例。由于镜像在笔记本、CI 和生产环境中完全一致,“在我电脑上能跑”的问题就不复存在。
多阶段构建能让生产镜像保持小巧和安全:依赖安装和应用编译都在临时阶段完成,只有产物被复制到精简的运行时镜像中。下面是一个使用 output: 'standalone' 构建的 Next.js 应用示例:
# syntax=docker/dockerfile:1
FROM node:24-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
FROM node:24-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN corepack enable && pnpm build
FROM node:24-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
# Never run the application as root
RUN addgroup -S app && adduser -S app -G app
COPY --from=build --chown=app:app /app/.next/standalone ./
COPY --from=build --chown=app:app /app/.next/static ./.next/static
COPY --from=build --chown=app:app /app/public ./public
USER app
EXPOSE 3000
CMD ["node", "server.js"]值得从第一天起就采用的容器最佳实践:
- 添加
.dockerignore,让node_modules、.git和本地密钥永远不会进入构建上下文。 - 把基础镜像固定到具体版本,并定期重新构建以获取安全补丁。
- 每个容器只运行一个进程,并把日志输出到标准输出,由平台统一收集。
- 通过环境变量传入配置,密钥完全不放进镜像。
- 把数据存放在卷或托管服务中,绝不放在随时可能被替换的容器内部。
对于本地开发和小规模部署,Docker Compose 可以在一个文件中描述多个容器及其连接方式:
services:
app:
build: .
ports:
- '3000:3000'
environment:
DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U app']
interval: 5s
retries: 10
volumes:
db-data:CI/CD 流水线
持续集成(CI)是指每次推送都会自动构建和测试,让问题在几分钟内暴露,而不是在发布前几天才发现。持续交付(CD)更进一步:每个通过流水线的变更都会被打包好、随时可以部署;而在持续部署中,它会被自动发布到生产环境。
一个扎实的 GitHub Actions 基础流水线,会在每个 pull request 和每次推送到 main 时安装依赖、类型检查、运行测试并构建:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v5
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
- run: pnpm test
- run: pnpm build在此基础上,为团队反复踩坑的地方添加阶段:依赖和容器镜像的漏洞扫描、在预览环境中运行的端到端测试,以及只在 main 分支全部通过后才运行的部署任务。让流水线保持快速:十分钟的流水线会被认真执行,四十分钟的流水线会被想办法绕过。
部署策略
如何发布和发布什么同样重要。目标是零停机,并且出问题时有一条快速、平淡无奇的回退之路:
- 滚动更新:每次替换少量实例。简单,也是大多数平台的默认方式,但新旧版本会并行运行一段时间。
- 蓝绿部署:在旧版本旁边运行新版本,测试通过后一次性切换全部流量。回滚就是切回去。
- 金丝雀发布:先把一小部分流量(比如 5%)导向新版本,观察错误率和延迟,再逐步扩大。
- 功能开关:以关闭状态部署代码,之后再为选定用户开启。这把部署和发布分离开来。
数据库变更同样需要谨慎。使用扩展-收缩(expand and contract)模式:先以向后兼容的方式新增列或表,部署使用它们的代码,等到之后的版本再删除旧结构。这样在发布和回滚过程中,旧版本的应用依然可以正常工作。
云基础设施,简单来说
云服务商提供了一系列选择:从裸虚拟机(IaaS),到托管平台和容器服务(PaaS),再到只需提供代码的无服务器函数。越往后,你需要自己管理的越少,能掌控的也越少。对大多数团队来说,最佳平衡点是:难的部分交给托管服务(数据库、队列、对象存储、负载均衡器),应用本身使用容器。
使用 Terraform、OpenTofu 或 Pulumi 等工具,以代码的形式定义这些基础设施。这样服务器、网络和 DNS 都纳入版本控制,像代码一样被评审,并能在几分钟内重建,包括一个真正与生产环境一致的预发布环境。
服务器管理与安全要点
- 最小权限:每个人、每个服务、每个 CI 任务只获得所需的权限。
- 密钥应存放在密钥管理服务或平台的加密配置中,绝不放进代码仓库或镜像。
- 定期打补丁,覆盖操作系统、基础镜像和依赖,并尽可能自动化。
- 私有网络:数据库不暴露在公网上,只对外开放负载均衡器。
- 全站 HTTPS,证书自动续期。
- 备份只有恢复过才算数:自动化备份、加密、异地存储,并定期演练恢复。
- 监控与告警覆盖可用性、错误率和延迟,配备值班轮换和常见事故的书面处理手册。
要点回顾
- 容器让构建可复现。使用多阶段构建、精简的基础镜像和非 root 用户。
- 把通往生产环境的路径自动化:每个变更都以相同方式构建、测试和扫描。
- 频繁发布小的变更,并借助蓝绿部署、金丝雀发布或功能开关降低发布风险。
- 用代码管理基础设施,有状态的部分使用托管服务,并演练从备份中恢复。
总结
DevOps 带来的不只是速度,更是从容。当构建可复现、流水线能及早发现问题、每次发布都能在一分钟内回滚时,部署就不再是一件大事,而是日常工作。先从一个 Dockerfile 和一条运行测试的 CI 流水线开始,再一次自动化一个手动步骤。
延伸阅读
- #CI/CD
- #Cloud
- #DevOps
- #Docker




