跳转到内容

Linux、容器与部署

部署不是把代码上传到服务器。它要把一个确定的构建产物放进受控运行环境,配置好网络、权限、资源和数据迁移,验证它真的可用,并保留回滚路径。

Linux、容器和编排系统解决的,是同一组问题的不同层次:进程怎样运行,资源怎样隔离和限制,多个实例怎样调度,发布怎样可重复。

1. Linux 的四个核心对象

跳转到“1. Linux 的四个核心对象”

Linux 把设备和目录组织成一棵树,常见目录各有职责:

目录 内容
/bin、/usr/bin 基础可执行文件
/etc 系统与应用配置
/var 日志、缓存、数据库等可变数据
/home 用户目录
/tmp 临时文件,可能被清理
/proc、/sys 内核暴露的运行状态
/dev 设备文件

生产应用的数据不应只放在容器可写层或临时目录。日志、数据库和上传文件要挂载到持久卷或对象存储。

文件权限通常由所有者、所属组和其他人的读/写/执行位组成。chmod 改权限,chown 改所有者。目录的执行位表示能否进入或遍历。

终端窗口
ls -l
chmod 640 .env
chown app:app /srv/app/config.yml

不要用 chmod 777 解决权限问题。它让所有用户都能写,可能扩大攻击面。服务应以最小权限用户运行,而不是默认 root。

每个进程有 PID、父进程、用户、打开的文件、环境变量和资源限制。常用观察命令:

终端窗口
ps aux
top
ss -ltnp
lsof -p <pid>

进程收到 SIGTERM 应优雅退出;SIGKILL 无法被捕获,只能强制结束。容器停止时也遵循类似信号机制,应用要正确处理终止并完成清理。

systemd 管理服务启动、依赖、重启和日志。一个服务单元通常声明:

[Unit]
Description=Example API
After=network.target
[Service]
User=app
WorkingDirectory=/srv/app
ExecStart=/srv/app/server
Restart=on-failure
[Install]
WantedBy=multi-user.target

排查服务时先看:

终端窗口
systemctl status example
journalctl -u example -n 100 --no-pager

退出码、启动参数、工作目录、用户权限和日志通常比“服务没起来”这句话更有用。

2. 容器不是轻量虚拟机

跳转到“2. 容器不是轻量虚拟机”

容器共享宿主机内核,通过 namespace 隔离进程、网络、挂载点、主机名和用户,通过 cgroup 限制 CPU、内存和 I/O。它不像虚拟机那样启动完整客户机操作系统,因此更轻,但隔离边界也更依赖内核配置。

镜像由只读层组成,容器在镜像之上增加可写层。Dockerfile 的每条指令通常形成一层,构建缓存会复用未变化的层。

FROM node:22-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

先把依赖文件复制并安装,再复制源码,可以更好利用缓存。生产镜像应尽量:

  • 使用明确版本的基础镜像,而不是永远 latest。
  • 安装锁文件中的依赖。
  • 使用非 root 用户运行。
  • 清理构建工具和缓存。
  • 只复制运行所需文件。
  • 记录镜像 digest,确保部署的产物可追溯。

容器内的 localhost 指容器自身,不是宿主机。容器之间通常通过同一网络中的服务名访问。端口映射把宿主机端口转发到容器端口,但“容器没起来”“端口没映射”“应用只监听回环地址”是不同问题。

容器编排中的服务发现通常使用 DNS 名称,例如 db:5432。服务名解析失败时,先检查网络、DNS 和服务是否注册。

容器可写层随容器删除而消失。持久数据应放在:

  • 数据卷。
  • 宿主机绑定目录。
  • 对象存储。
  • 托管数据库。

不要把数据库文件放在容器临时层,也不要在生产依赖“容器重启后数据还在”。

Compose 适合本地开发和单机多服务编排:

services:
api:
build: .
ports: ["3000:3000"]
environment:
DATABASE_URL: postgres://app:secret@db:5432/app
depends_on:
- db
db:
image: postgres:17
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:

depends_on 只保证启动顺序,不保证数据库已经接受连接。应用需要重试连接或健康检查。Compose 不是生产高可用平台,多实例调度、滚动发布和故障转移要交给编排系统或托管服务。

Kubernetes 把“运行什么、需要多少副本、怎样访问”声明为对象,由控制平面持续把实际状态调整到期望状态。

对象 作用
Pod 最小部署单元,包含一个或多个容器
Deployment 管理无状态副本和滚动更新
Service 为一组 Pod 提供稳定访问入口
Ingress/Gateway 把外部 HTTP 路由到服务
ConfigMap 非敏感配置
Secret 敏感配置的封装,仍需配合加密与权限
StatefulSet 有稳定身份和存储需求的实例
Job/CronJob 一次性或定时任务

控制平面包括 API Server、调度器、控制器和状态存储。调度器选择节点,控制器比较期望状态与实际状态,etcd 保存集群状态。工作节点运行容器运行时和 kubelet。

集群失去多数控制平面节点时,已有 Pod 可能继续运行,但无法可靠调度和更新。托管 Kubernetes 会替你管理部分控制平面,但工作节点、网络、存储和应用配置仍由你负责。

resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"

requests 用于调度和保障资源,limits 用于限制上限。内存超过 limit 可能被杀,CPU 超过 limit 会被限流。设得太低会频繁重启,太高会浪费容量。需要根据实际指标调整。

  • liveness:进程卡死时重启。
  • readiness:准备好接收流量时加入 Service。
  • startup:慢启动应用在初始化阶段避免被误杀。

健康检查只应验证“本实例能否工作”,不要把它做成完整业务链路监控。依赖数据库短暂波动时,把所有实例同时标记不健康会造成更大故障。

Deployment 默认支持滚动更新。关键参数:

  • 最大不可用副本数。
  • 最大超额副本数。
  • 新版本就绪后多久接收流量。
  • 失败后是否自动回滚。

发布前应保证数据库迁移向后兼容,因为新旧 Pod 会短暂并存。

持续集成(CI)在代码变化后自动构建和验证。持续交付/部署(CD)把通过验证的产物送到环境。

拉取代码
→ 格式/静态检查
→ 单元测试
→ 构建产物/镜像
→ 扫描依赖与镜像
→ 部署测试环境
→ 集成/端到端测试
→ 生成变更记录
→ 人工或自动审批
→ 部署生产
→ 健康检查/验证

每个阶段应输出可定位的结果。只显示“pipeline failed”而不保留日志、测试报告和失败产物,会拖慢排查。

4.2 构建一次,部署同一个产物

跳转到“4.2 构建一次,部署同一个产物”

开发、测试、生产应尽量使用同一个构建产物,只改变配置和密钥。若每个环境重新构建,代码、依赖和构建工具差异会让“测试通过”失去意义。

镜像或产物应带版本、提交哈希和构建时间。部署时记录实际 digest,而不是只看可变标签。

部署顺序要明确:

  1. 先执行向后兼容的 schema 迁移。
  2. 再部署兼容新旧 schema 的应用。
  3. 回填或双写。
  4. 切换读取。
  5. 最后移除旧字段。

回滚应用不等于回滚数据库。删除列和改变类型要格外谨慎,通常需要多阶段迁移。

CI/CD 中的密钥应存放在受控密钥管理系统,按环境和用途隔离。日志中不能打印 token;构建产物不能包含生产密钥;临时凭据应短期有效。

策略 做法 风险
滚动发布 逐步替换旧实例 新旧版本并存,需兼容
蓝绿 两套环境切换流量 资源成本高,数据仍需兼容
金丝雀 先给小比例用户 需要流量分流和指标比较
功能开关 代码上线但功能关闭 开关累积、状态组合复杂
影子流量 复制请求到新版本但不影响用户 写操作和外部副作用要隔离

无论哪种,都需要验证指标:错误率、延迟、关键业务成功率和资源使用。发布完成不等于健康,必须观察一段时间。

可回滚部署要满足:

  • 旧镜像/产物仍可用。
  • 配置和密钥有版本。
  • 数据库变更兼容旧版本。
  • 回滚不需要手工改多台机器。
  • 回滚后核心指标能验证。

“把新版本删掉再重新部署旧版本”可能太慢。蓝绿切换、流量回切或保留上一版副本能缩短恢复时间。

现象 优先检查
容器启动即退出 启动命令、配置、权限、依赖
服务重启循环 内存 limit、liveness、启动时间
外部访问 502 Service、Ingress、容器端口和应用监听
某实例无流量 readiness、标签选择器、网络策略
发布后错误上升 新旧版本、数据库迁移、配置差异
节点磁盘满 日志轮转、镜像缓存、卷容量
延迟突然升高 节点资源、依赖、连接池、网络
  • 应用是否以最小权限用户运行。
  • 数据、日志和临时文件是否存储在正确位置。
  • 镜像是否锁定版本,是否只包含运行所需内容。
  • 容器是否监听正确地址和端口。
  • Kubernetes 的 requests/limits、probe 和副本数是否有依据。
  • 配置与密钥是否分离,是否支持版本和审计。
  • CI 是否包含检查、测试、构建、扫描和产物记录。
  • 部署是否构建一次并复用同一产物。
  • 数据库迁移是否兼容滚动发布与回滚。
  • 是否有灰度、验证指标和明确回滚路径。