Docker面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 06月6日 20:23

Docker 容器文件系统怎么工作?分层和 Copy-on-Write 机制

容器里写了一个文件,它到底存在哪里?删掉容器后文件去哪了?为什么镜像层是只读的但容器可以写入?理解 Docker 文件系统的工作原理,这些问题就都清楚了。从镜像到容器:分层文件系统Docker 镜像不是一个大文件——它是多层只读文件系统的堆叠。每个 Dockerfile 指令产生一层:FROM ubuntu:22.04 # 层 1:操作系统基础(~70MB)RUN apt-get update # 层 2:包索引更新RUN apt-get install -y python3 # 层 3:Python 运行时COPY . /app # 层 4:应用代码CMD ["python3", "/app/main.py"] # 不产生新层(只是元数据)┌────────────────────────┐│ 可写层(容器运行时添加) │ ← 容器启动后才有├────────────────────────┤│ 层 4:应用代码 │ ← COPY 产生├────────────────────────┤│ 层 3:Python 运行时 │ ← RUN 产生├────────────────────────┤│ 层 2:包索引 │ ← RUN 产生├────────────────────────┤│ 层 1:Ubuntu 基础 │ ← FROM 产生└────────────────────────┘关键:每个只读层都是不可变的。删除了上一层的文件,并不会真的删除——只是在新层里标记为"已删除"(whiteout 文件)。Copy-on-Write:容器写入的核心机制容器启动时,Docker 在所有只读层上面加一层可写层。容器内的写操作都走 CoW:修改已有文件1. 容器要修改 /etc/config.yml(存在于镜像层 2)2. Docker 从层 2 把 config.yml 复制到可写层3. 修改发生在可写层的副本上4. 后续读取这个文件时,可写层的版本"遮盖"了镜像层的原始版本新建文件直接写在可写层,不涉及复制。删除文件在可写层创建一个 whiteout 文件(标记为已删除),不真的从只读层删除。overlay2 的实现overlay2 是 Docker 默认的存储驱动,基于 Linux 内核的 OverlayFS:容器看到的是 merged 视图:┌──────────────┐│ merged │ = upperdir + lowerdir 合并后的视图├──────────────┤│ upperdir │ = 可写层(容器修改的文件)├──────────────┤│ lowerdir │ = 镜像的所有只读层└──────────────┘在宿主机上的实际位置# Docker 的存储根目录ls /var/lib/docker/overlay2/# 每个镜像层一个目录/var/lib/docker/overlay2/<layer-id>/ diff/ # 这一层的文件内容 link # 指向层的短链接 lower # 指向下层层的链接 merged/ # 合并视图(容器运行时才出现) work/ # OverlayFS 工作目录查看容器的 overlay2 层# 找到容器的 overlay2 路径docker inspect myapp --format '{{.GraphDriver.Data.MergedDir}}'# /var/lib/docker/overlay2/abc123/merged# 查看可写层的内容ls /var/lib/docker/overlay2/abc123/diff/diff/ 目录里就是容器内所有修改过的文件——和 docker diff 命令看到的一致。为什么容器删除后数据会丢1. docker run → 创建可写层 + 启动容器2. 容器运行中 → 数据写在可写层3. docker rm → 删除可写层 + 容器元数据可写层随容器一起删除,里面的数据也消失了。这就是为什么需要 Volume:services: postgres: image: postgres:16 volumes: - pg_data:/var/lib/postgresql/data # 数据存在 Volume,不在可写层Volume 的数据存在 /var/lib/docker/volumes/pg_data/,容器删除不影响它。镜像层的共享和节省空间多个镜像共享相同的基础层:镜像 A(Node.js 应用) 镜像 B(Python 应用)┌──────────────┐ ┌──────────────┐│ 层 3:App A │ │ 层 3:App B │ ← 不同├──────────────┤ ├──────────────┤│ 层 2:Node.js│ │ 层 2:Python │ ← 不同├──────────────┤ ├──────────────┤│ 层 1:Ubuntu │ │ 层 1:Ubuntu │ ← 共享!只存一份└──────────────┘ └──────────────┘# 查看 Docker 磁盘占用docker system df -v# SHARED SIZE 列显示被多个镜像共享的大小# UNIQUE SIZE 列显示该镜像独有的层大小镜像构建优化:减少层和大小合并 RUN 指令# 差:每条 RUN 一层RUN apt-get updateRUN apt-get install -y python3RUN apt-get install -y pip# 好:合并成一层RUN apt-get update && \ apt-get install -y python3 pip && \ rm -rf /var/lib/apt/lists/*合并的好处:层数减少rm -rf /var/lib/apt/lists/ 在同一层执行——删除操作真的释放了空间,而不是在下一层标记为已删除多阶段构建FROM node:20 AS builderWORKDIR /appCOPY . .RUN npm ci && npm run buildFROM node:20-alpineCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesCMD ["node", "dist/index.js"]最终镜像只有 dist/ 和 node_modules/——没有源码、没有构建工具,体积缩小 60-80%。.dockerignore 减少构建上下文# .dockerignore.gitnode_modules.env*.mdtest/.dockerignore 排除的文件不会进入构建上下文——既加快构建,又防止敏感文件进镜像。常见文件系统问题镜像越来越大# 查看每层大小docker history myapp:latest# 找出最大的层docker history --format "{{.Size}}\t{{.CreatedBy}}" myapp:latest常见的膨胀原因:apt-get update 的缓存、npm 缓存、临时文件。在同一层里清理掉。容器写入慢可写层写大量小文件时性能差——特别是 Mac/Windows 上 Docker Desktop 的文件共享。解决方案:大量写入用 Volume。inode 耗尽大量小文件会消耗 inode。df -i 查看剩余 inode。overlay2 比 overlay 的 inode 使用更少——这也是推荐 overlay2 的原因之一。
服务端阅读 06月6日 20:23

Docker 容器出问题了怎么排查?分层排查流程

容器启动失败、运行中崩溃、网络不通、数据丢失——Docker 故障排查有一套固定流程,按层级从外到内逐步缩小范围。排查流程总览容器状态异常?├── 容器没启动 → 查 docker logs├── 容器运行但行为异常 → 查 docker logs + docker exec├── 容器网络不通 → 查 docker network + 端口映射├── 容器数据问题 → 查 Volume 挂载└── 宿主机资源不足 → 查 docker stats + 系统资源第一步:确认容器状态# 查看所有容器(包括停止的)docker ps -a# 关注 STATUS 列# Up 2 hours → 正常运行# Exited (0) 5 mins → 正常退出# Exited (1) 5 mins → 错误退出# Exited (137) 1 min → 被 SIGKILL 杀掉(通常是 OOM)# Restarting → 不断重启(崩溃循环)退出码含义:| 退出码 | 含义 ||--------|------|| 0 | 正常退出 || 1 | 应用错误 || 137 | OOM Killed(内存不足) || 139 | Segmentation Fault || 143 | SIGTERM(正常停止) |第二步:查日志docker logs# 查看容器日志docker logs myapp# 实时跟踪docker logs -f myapp# 最近 100 行docker logs --tail 100 myapp# 带时间戳docker logs -t myapp# 查看某个时间段的日志docker logs --since "2024-01-01T00:00:00" --until "2024-01-01T12:00:00" myapp容器已经退出了?# 已退出的容器仍然可以查日志docker logs myapp # 只要容器没被 docker rm 删掉# 如果容器被删了,日志文件还在(json-file 驱动)ls /var/lib/docker/containers/<container-id>/# <container-id>-json.log日志里什么都没有?应用可能把日志写到了文件而不是 stdout/stderr。Docker 只捕获标准输出/错误流:# 进入容器查看日志文件docker exec -it myapp shcat /app/logs/app.log最佳实践:让应用把日志输出到 stdout/stderr,这样 docker logs 直接能看到。不要写到容器内的文件——容器删除后日志也丢了。第三步:进入容器排查# 进入容器执行命令docker exec -it myapp sh# 如果容器没有 sh,用 bashdocker exec -it myapp bash# 如果都没有(alpine 镜像可能只有 sh)docker exec -it myapp /bin/sh# 不进入容器,直接执行命令docker exec myapp ps auxdocker exec myapp cat /etc/config.yml容器里能做的事:ps aux 看进程top 看 CPU/内存cat /etc/hosts 看网络配置curl localhost:3000 测试内部端口env 看环境变量容器一启动就退出了怎么办docker exec 需要容器在运行状态。容器一启动就退出的情况,用覆盖入口点的方式:# 覆盖 CMD,保持容器运行docker run -it --entrypoint sh myapp:latest# 或者在 Dockerfile 末尾临时加# CMD ["sleep", "3600"]进入容器后手动执行原来的启动命令,观察报错。第四步:排查网络问题容器端口没暴露# 检查端口映射docker port myapp# 3000/tcp -> 0.0.0.0:3000# 如果没有输出,说明没做端口映射# 重新启动时加 -pdocker run -d -p 3000:3000 myapp容器间网络不通# 查看容器的网络docker inspect myapp --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'# 查看所有 Docker 网络docker network ls# 同一网络的容器可以互相通过容器名访问# 不同网络的容器互相隔离# 测试容器间连通性docker exec myapp ping redisdocker exec myapp curl http://api-service:8080/healthDNS 解析失败# 查看容器的 DNS 配置docker exec myapp cat /etc/resolv.conf# 自定义 DNSdocker run --dns 8.8.8.8 myapp第五步:排查 Volume 问题文件没有出现在容器内# 检查 Volume 挂载docker inspect myapp --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'# /host/path -> /container/path常见原因:宿主机路径写错了(相对路径问题)文件权限不匹配(容器内用户没读权限)SELinux 阻止访问(加 :z 或 :Z 后缀)# SELinux 环境docker run -v /host/path:/container/path:z myapp # 多容器共享docker run -v /host/path:/container/path:Z myapp # 单容器专用容器内修改没有反映到宿主机检查是不是挂载方向反了,或者挂载的是文件而不是目录:# 错误:挂载不存在的文件,Docker 会创建一个目录docker run -v ./config.yml:/app/config.yml myapp# 如果 ./config.yml 不存在,Docker 会创建 /app/config.yml 目录# 正确:确保文件先存在touch ./config.ymldocker run -v ./config.yml:/app/config.yml myapp常见故障速查| 症状 | 排查命令 | 常见原因 ||------|---------|---------|| 容器启动就退出 | docker logs | CMD 执行失败、配置错误 || 容器被杀 | docker inspect --format '{{.State.OOMKilled}}' | 内存不足 || 端口访问不到 | docker port + curl | 没映射端口、防火墙 || 容器间不通 | docker exec ping | 不在同一网络 || 磁盘满 | df -h + docker system df | 日志太多、镜像太多 || 构建慢 | docker build --progress=plain | 没用缓存、上下文太大 || 权限错误 | docker exec ls -la | UID 不匹配、SELinux || 容器不断重启 | docker logs --tail 50 | 启动脚本报错、依赖服务未就绪 |调试技巧用 debug 镜像替换生产镜像可能是 distroless 或 alpine,没有 curl、ping 等工具。临时用 debug 镜像排查:# 把镜像临时换成有工具的版本docker run -d --name myapp-debug \ --entrypoint sh \ -p 3000:3000 \ myapp:latest -c "sleep 3600"docker diff 看文件变更# 查看容器内哪些文件被修改了docker diff myapp# A /app/newfile.txt → Added# C /etc/config.yml → Changed# D /tmp/old.log → Deleteddocker events 实时监控# 监控 Docker 事件(容器启停、OOM、健康检查等)docker events --filter container=myapp
服务端阅读 06月6日 20:23

Docker 容器怎么接入 CI/CD?GitHub Actions 和 GitLab CI 集成

代码提交后自动构建镜像、跑测试、部署——这就是 CI/CD 和 Docker 的结合。Docker 让构建环境一致、部署产物标准化,CI/CD 让这一切自动运行。这篇讲 Docker 在主流 CI/CD 平台中的集成方式。Docker 在 CI/CD 中的三个角色构建环境:CI Runner 本身跑在 Docker 容器里,保证每次构建环境一致构建产物:docker build 产出镜像,推到 Registry,部署时直接拉镜像部署目标:生产环境拉镜像启动容器,不需要在服务器上装运行时GitHub Actions + Docker基本构建和推送# .github/workflows/deploy.ymlname: Build and Push Docker Imageon: push: branches: [main]jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: | myorg/myapp:latest myorg/myapp:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=maxcache-from: type=gha 用 GitHub Actions 的缓存加速构建——没有变化的层直接复用,不用重新构建。首次构建可能要 5 分钟,后续只要 1-2 分钟。多阶段构建减小镜像# 构建阶段FROM node:20-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run build# 运行阶段——只有产物,没有源码和 devDependenciesFROM node:20-alpineWORKDIR /appCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesCMD ["node", "dist/index.js"]多阶段构建让最终镜像只有运行时需要的文件——从 1GB+ 缩小到 200MB 以下。GitLab CI + DockerDocker-in-Docker 构建# .gitlab-ci.ymlbuild: image: docker:24 services: - docker:24-dind variables: DOCKER_TLS_CERTDIR: "/certs" before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHAdocker:24-dind 服务启动一个 Docker 守护进程,让 CI 容器里可以执行 docker build。注意要加 DOCKER_TLS_CERTDIR 确保通信安全。使用 GitLab Container RegistryGitLab 自带 Container Registry,不用额外配 Docker Hub:script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA$CI_REGISTRY_IMAGE 是 GitLab 预设变量,指向当前项目的 Registry 地址。镜像标签策略# 不推荐:只用 latestmyapp:latest# 推荐:语义化版本 + Git SHA 双标签myapp:1.2.3myapp:sha-abc1234# 回滚时指定版本docker pull myapp:1.2.2| 标签 | 用途 | 示例 ||------|------|------|| latest | 默认最新版 | 开发环境用 || 语义化版本 | 生产部署 | 1.2.3 || Git SHA | 精确追溯 | sha-abc1234 || 分支名 | 预览环境 | main, staging |生产环境永远用精确版本号或 SHA,不用 latest——latest 指向的镜像随时可能变,出了问题无法回滚。部署阶段SSH 部署到服务器# GitHub Actions- name: Deploy to server uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SERVER_HOST }} username: deploy key: ${{ secrets.SSH_PRIVATE_KEY }} script: | docker pull myorg/myapp:${{ github.sha }} docker stop myapp || true docker rm myapp || true docker run -d --name myapp \ --restart unless-stopped \ -p 3000:3000 \ myorg/myapp:${{ github.sha }}docker-compose 部署服务器上放一个 docker-compose.yml,CI 只需要触发 docker compose pull && docker compose up -d:# 服务器上的 docker-compose.ymlservices: app: image: myorg/myapp:latest ports: - "3000:3000" restart: unless-stopped# CI 部署脚本ssh deploy@server "cd /app && docker compose pull && docker compose up -d"零停机部署上面的方式会短暂中断服务。用双容器切换实现零停机:# 1. 启动新容器在不同端口docker run -d --name myapp-new -p 3001:3000 myorg/myapp:new-version# 2. 健康检查until curl -f http://localhost:3001/health; do sleep 1; done# 3. 切换 Nginx 上游# 更新 nginx upstream 指向 3001docker exec nginx nginx -s reload# 4. 停掉旧容器docker stop myapp-old && docker rm myapp-old# 5. 重命名新容器docker rename myapp-new myapp-old安全最佳实践不要用 root 跑容器:Dockerfile 里加 USER app扫描镜像漏洞:docker scout cves myapp:latest 或 Trivy最小基础镜像:用 alpine 或 distroless 代替 ubuntuSecret 不进镜像:数据库密码等通过环境变量或 Secret 注入,不写进 Dockerfile.dockerignore 排除无关文件:.git、node_modules、.env 不应该进构建上下文# .dockerignore.gitnode_modules.env*.md.dockerignore
服务端阅读 06月6日 20:23

Docker 存储驱动该选哪个?overlay2 和其他驱动的区别

Docker 镜像的每一层怎么存、容器怎么在只读层上写入、不同存储驱动有什么性能差异——理解存储驱动的工作原理,能帮你解决容器写入慢、镜像占磁盘、数据丢失这些问题。容器文件系统的分层结构Docker 镜像由多个只读层组成,容器启动时在最上面加一层可写层:┌─────────────────────┐│ 可写层(容器层) │ ← docker run 产生的可写层├─────────────────────┤│ 镜像层 3(App) │ ← COPY / RUN 指令产生的层├─────────────────────┤│ 镜像层 2(Runtime)│├─────────────────────┤│ 镜像层 1(OS 基础) │└─────────────────────┘只读层:镜像的每一层都是只读的,多个容器可以共享同一组只读层可写层:容器的所有修改(新建文件、修改配置、写日志)都写在这层容器删除后,可写层一起消失——这就是为什么容器内写的数据会丢Copy-on-Write 机制容器修改镜像中的文件时,不是直接改只读层——而是把文件复制到可写层再修改:# 镜像里有个 /etc/config.yml# 容器内修改它:echo "new value" >> /etc/config.yml# 发生了什么:# 1. 从只读层复制 config.yml 到可写层# 2. 在可写层修改# 3. 后续读这个文件时读可写层的版本(遮盖了只读层的原始文件)CoW 的性能影响:第一次修改大文件时会比较慢(需要完整复制)。频繁修改大文件(比如数据库的数据文件)不适合放在容器可写层——应该用 Volume。主流存储驱动对比| 驱动 | 文件系统 | 适用场景 | 性能 ||------|---------|---------|------|| overlay2 | OverlayFS | 默认推荐,所有 Linux 发行版 | 好 || devicemapper | devicemapper | 老系统、RHEL/CentOS 6 | 一般 || btrfs | Btrfs | 需要快照、子卷管理 | 写入好,读取一般 || zfs | ZFS | 需要数据完整性校验 | 功能强但内存需求大 |overlay2:默认且最优overlay2 是当前 Docker 的默认存储驱动,也是性能最好的:# 查看当前存储驱动docker info --format '{{.Driver}}'# overlay2overlay2 的工作方式:lowerdir(只读层)= 镜像的所有层upperdir(可写层)= 容器的修改merged = 合并视图(容器看到的文件系统)优势:只有一层 lowerdir 目录,不像老版 overlay 有多层,inode 消耗少页缓存共享——多个容器读同一个文件只占一份内存build 性能好——Docker Build 时层操作都是 O(1)99% 的场景用 overlay2 就对了,不需要换。devicemapper:老系统的选择RHEL/CentOS 6 时代内核不支持 OverlayFS,只能用 devicemapper。有两种模式:loopback(默认):用文件模拟块设备,性能差,生产环境不能用direct-lvm:直接用 LVM 块设备,性能可以// /etc/docker/daemon.json - direct-lvm 配置{ "storage-driver": "devicemapper", "storage-opts": [ "dm.directlvm_device=/dev/sdb", "dm.thinp_percent=95", "dm.thinp_metapercent=1" ]}现在大部分系统已经支持 overlay2,不需要再折腾 devicemapper。存储驱动和磁盘占用的关系每个存储驱动管理镜像层的方式不同,磁盘占用差异很大:# 查看 Docker 占用的磁盘docker system df# 详细信息docker system df -vTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 15 5 4.2GB 2.1GB (50%)Containers 8 3 120MB 80MB (66%)Local Volumes 5 3 800MB 200MB (25%)Build Cache 50 0 1.5GB 1.5GB (100%)RECLAIMABLE 是可以回收的空间。清理命令:# 清理悬空镜像(没有标签的)docker image prune# 清理所有未使用的镜像docker image prune -a# 清理构建缓存docker builder prune# 一键全清docker system prune -aVolume:数据持久化的正确方式容器可写层的数据会随容器删除而丢失。需要持久化的数据(数据库、日志、配置)用 Volume:services: postgres: image: postgres:16 volumes: - pg_data:/var/lib/postgresql/data # 命名 Volume - ./init.sql:/docker-entrypoint-initdb.d/init.sql # 绑定挂载 app: image: myapp:latest volumes: - ./config:/app/config:ro # 只读绑定挂载 - app_logs:/app/logs # 命名 Volumevolumes: pg_data: app_logs:命名 Volume vs 绑定挂载| | 命名 Volume | 绑定挂载 ||---|---|---|| 存储位置 | Docker 管理(/var/lib/docker/volumes/) | 宿主机任意目录 || 性能 | 好(Docker 优化) | Mac/Win 上可能慢 || 可移植性 | 好 | 依赖宿主机路径 || 备份 | docker volume inspect 找路径 | 直接访问宿主机目录 || 适用场景 | 数据库、应用数据 | 配置文件、代码挂载 |存储驱动选择决策| 你的情况 | 推荐驱动 ||---------|---------|| Linux 内核 4.0+ | overlay2(默认) || RHEL/CentOS 7+ | overlay2 || 需要 ZFS 快照和校验 | zfs || 需要 Btrfs 子卷管理 | btrfs || 老系统无法升级内核 | devicemapper (direct-lvm) |不确定就用 overlay2——它已经是默认选项了。除非有明确的特殊需求,否则不需要改存储驱动。
服务端阅读 06月6日 20:23

Docker 容器资源怎么监控?从 docker stats 到 Prometheus 的完整方案

容器跑着跑着内存爆了、CPU 拉满了、磁盘写满了——这些问题的根源都能通过资源监控提前发现。Docker 提供了从命令行到 API 多层级的资源监控手段,这篇按从简单到复杂的顺序讲清楚。docker stats:最快上手# 查看所有运行中容器的资源使用docker stats# 只看特定容器docker stats myapp# 只输出一次(不刷新)docker stats --no-stream# 只看特定指标docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"输出示例:NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/Omyapp 12.5% 256MiB / 4GiB 6.25% 1.2MB / 340kB 45MB / 0Bredis 0.3% 8MiB / 512MiB 1.56% 56kB / 32kB 0B / 0BMEM USAGE / LIMIT 最关键——如果 Usage 接近 Limit,说明容器快 OOM 了。BLOCK I/O 高说明磁盘读写频繁,可能是日志写入太多或数据库查询没走缓存。局限:docker stats 只能看实时数据,没有历史趋势。要看趋势得上 Prometheus + Grafana。设置资源限制:监控的前提监控资源使用的目的是发现问题,而限制资源是防止问题蔓延:# docker-compose.ymlservices: app: image: myapp:latest deploy: resources: limits: cpus: '2.0' # 最多用 2 核 memory: 4G # 内存上限 4GB reservations: cpus: '0.5' # 保底 0.5 核 memory: 1G # 保底 1GB# 命令行方式docker run -d --name myapp \ --cpus=2.0 \ --memory=4g \ --memory-swap=4g \ # 禁止 swap myapp:latest--memory-swap=4g 等于 --memory 的值意味着容器不能用 swap——全部用物理内存。OOM 时容器会被直接杀掉而不是被 swap 拖慢。CPU 限制的两种方式# 1. --cpus:限制 CPU 时间占比(推荐)docker run --cpus=1.5 myapp # 最多用 1.5 核# 2. --cpu-period + --cpu-quota:更精细的控制docker run --cpu-period=100000 --cpu-quota=50000 myapp# quota/period = 50000/100000 = 0.5 核日常用 --cpus 就够了。--cpu-period/--cpu-quota 只在需要非整数的精确控制时用。cgroups:底层数据源Docker 的资源限制和监控都基于 Linux cgroups。直接读 cgroups 文件可以拿到最原始的数据:# 容器的内存使用(字节)cat /sys/fs/cgroup/memory/docker/<container-id>/memory.usage_in_bytes# 容器的 CPU 使用(纳秒)cat /sys/fs/cgroup/cpu/docker/<container-id>/cpuacct.usage# 内存限制cat /sys/fs/cgroup/memory/docker/<container-id>/memory.limit_in_bytes一般不需要直接读 cgroups——docker stats 和 Prometheus 已经做了封装。但如果容器内的监控工具和宿主机的 docker stats 数据对不上,可能是 cgroups 版本差异(cgroups v1 vs v2)导致的。Docker API:程序化监控# 获取容器资源使用统计(JSON 格式)curl --unix-socket /var/run/docker.sock \ http://localhost/containers/<container-id>/stats?stream=false返回的 JSON 包含 CPU、内存、网络、磁盘 IO 的详细数据。适合自建监控脚本:import dockerclient = docker.from_env()for container in client.containers.list(): stats = container.stats(stream=False) cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - \ stats['precpu_stats']['cpu_usage']['total_usage'] system_delta = stats['cpu_stats']['system_cpu_usage'] - \ stats['precpu_stats']['system_cpu_usage'] cpu_percent = (cpu_delta / system_delta) * 100 if system_delta > 0 else 0 mem_usage = stats['memory_stats']['usage'] / 1024 / 1024 mem_limit = stats['memory_stats']['limit'] / 1024 / 1024 print(f"{container.name}: CPU={cpu_percent:.1f}%, MEM={mem_usage:.0f}/{mem_limit:.0f}MB")cAdvisor + Prometheus:生产级监控docker stats 和 API 只适合临时查看。需要历史趋势和告警时,用 cAdvisor 采集 + Prometheus 存储 + Grafana 展示:services: cadvisor: image: gcr.io/cadvisor/cadvisor:latest volumes: - /:/rootfs:ro - /var/run:/var/run:ro - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro ports: - "8080:8080" prometheus: image: prom/prometheus:latest volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml ports: - "9090:9090"cAdvisor 自动采集所有容器的 CPU、内存、网络、磁盘指标,Prometheus 每 15 秒拉取一次。Grafana 里导入 Dashboard ID 11600 即可看到完整的容器资源监控面板。OOM 处理:内存爆了怎么办容器因内存不足被杀时,docker inspect 会记录原因:docker inspect <container-id> --format '{{.State.OOMKilled}}'# true 说明是 OOM 杀的处理步骤:查看内存限制是否太小——docker stats 看 MEM USAGE 是否经常顶到 LIMIT分析内存泄漏——进入容器 docker exec -it <id> sh,用 top 或 ps aux --sort=-%mem 找内存大户临时解决:加大内存限制 docker update --memory=8g <container-id>根本解决:修复应用的内存泄漏监控方案选择| 场景 | 方案 | 成本 ||------|------|------|| 临时排查 | docker stats | 零成本 || 自建脚本监控 | Docker API + Python | 低 || 小团队日常监控 | cAdvisor + Prometheus + Grafana | 中 || 生产环境 | 完整监控栈 + Alertmanager | 高 |起步建议:先用 docker stats 日常看一眼,发现问题了再搭 Prometheus 长期监控。
服务端阅读 06月5日 22:29

Docker Desktop 怎么用?安装配置和常见问题

Docker Desktop 是在 Mac 和 Windows 上用 Docker 最省事的方式——装一个应用就拥有完整的 Docker 环境,不需要折腾虚拟机或 Linux 双系统。但它不只是个安装包,里面的 WSL2 集成、Kubernetes 支持、资源管理有些门道值得了解。Docker Desktop 里装了什么Docker Desktop 不是 Docker Engine 的 GUI 包装——它是一个完整的开发环境:| 组件 | 作用 ||------|------|| Docker Engine | 容器运行时 || Docker CLI | 命令行工具 || Docker Compose | 多容器编排 || Docker BuildKit | 高性能构建引擎 || Kubernetes (可选) | 单节点 K8s 集群 || Docker Scout | 镜像漏洞扫描 || Docker Extensions | 扩展市场 |Mac 上 Docker Desktop 通过一个轻量级 Linux 虚拟机运行 Docker Engine(因为 Docker 本质上需要 Linux 内核)。Windows 上通过 WSL2 运行。安装后的关键配置资源分配默认配置经常不够用——4GB 内存跑不了几个容器:Settings → Resources CPUs: 4-6(建议宿主机的 50%) Memory: 8-12GB(建议宿主机的 50%) Swap: 2GB Disk image size: 60GB+调完后 Docker Desktop 会重启虚拟机。分配太多会导致宿主机卡顿,太少容器 OOM。建议 CPU 和内存各分一半给 Docker。WSL2 集成(Windows)Docker Desktop 在 Windows 上跑在 WSL2 里。开启后可以在 WSL2 的 Linux 发行版中直接使用 docker 命令:Settings → Resources → WSL Integration ✅ Enable integration with my default WSL distro ✅ Ubuntu (或其他发行版)这样在 Windows Terminal 的 Ubuntu 标签页里直接 docker run,不需要额外安装 Docker。文件共享性能Mac 上 Docker 挂载目录特别慢——因为文件要在 macOS 和 Linux 虚拟机之间同步。VirtioFS 是 Docker Desktop 4.x 以后的新方案,比之前的 gRPC FUSE 快很多:Settings → General ✅ Choose file sharing implementation for your containers: VirtioFS另外 node_modules 这种大量小文件的目录不要挂载——用匿名 volume 代替:services: app: volumes: - .:/app # 代码目录挂载 - /app/node_modules # node_modules 用容器内的,不走文件共享日常使用的核心功能镜像管理Docker Desktop → Images可以搜索、拉取、删除镜像,查看镜像层信息。比命令行更直观——特别是看哪个镜像占了多少磁盘。容器管理Docker Desktop → Containers启动、停止、删除容器,查看日志,进入容器终端。小技巧:点击容器的端口号可以直接在浏览器打开。Volume 管理Docker Desktop → Volumes查看所有 Docker Volume 占用的磁盘空间。容器删了 Volume 不会自动删——时间久了会积累大量废弃数据。定期清理:docker volume prune # 删除所有未被容器引用的 volume构建缓存清理docker builder prune # 清理构建缓存docker system prune -a # 一键清理所有未使用的资源(镜像、容器、网络、缓存)Docker Desktop 的 Troubleshoot 页面也有 "Clean / Purge data" 按钮——重置整个 Docker 环境。Kubernetes 支持Docker Desktop 内置了单节点 Kubernetes 集群,一键开启:Settings → Kubernetes ✅ Enable Kubernetes开启后 kubectl 直接可用:kubectl get nodes# NAME STATUS ROLES AGE VERSION# docker-desktop Ready control-plane 1m v1.28.0适合本地开发测试 K8s manifest,不需要装 minikube 或 kind。注意:开启 K8s 会额外占用 2-3GB 内存。不用时建议关掉。Docker ExtensionsDocker Desktop 支持扩展,常用的几个:| 扩展 | 功能 ||------|------|| Disk Usage | 可视化磁盘占用分析 || Trivy | 镜像安全扫描 || DDEV | 本地开发环境管理 || Tilt | 实时开发工作流 |安装方式:Extensions → Browse → InstallDocker Desktop 的替代方案Docker Desktop 对个人免费,但大企业(250+ 员工或 1000 万+ 美元年收入)需要付费订阅。如果不想付费:| 替代方案 | 平台 | 说明 ||---------|------|------|| OrbStack | Mac | 比 Docker Desktop 快 3-5 倍启动,内存占用少 || Rancher Desktop | Mac/Win/Linux | 开源免费,支持 containerd 和 dockerd || Colima | Mac | 命令行工具,轻量,基于 Lima || Podman Desktop | Mac/Win/Linux | Red Hat 出品,无守护进程 |推荐:Mac 用户优先试 OrbStack——启动快、内存省、文件共享性能好,个人免费。常见问题Docker Desktop 启动慢Mac 上首次启动需要 30-60 秒。加快方法:不要关 Docker Desktop,用 docker stop 停容器即可。Docker Desktop 自身在后台几乎不占 CPU。磁盘空间持续增长Docker 的虚拟磁盘(Docker.raw / data.vhdx)只会增大不会自动缩小。即使删了镜像,虚拟磁盘文件也不会缩小。解决:# Mac:压缩虚拟磁盘docker system prune -a# 然后重启 Docker Desktop → Troubleshoot → Clean / Purge data容器网络访问宿主机服务容器里访问宿主机(比如宿主机上的数据库):# 从容器内访问宿主机curl http://host.docker.internal:5432host.docker.internal 是 Docker Desktop 提供的特殊 DNS 名,自动解析为宿主机 IP。
服务端阅读 06月5日 22:29

Docker 反向代理该用 Nginx 还是 Traefik?部署和自动发现对比

Docker 部署反向代理的核心问题是:容器 IP 每次重启都会变,手动配 upstream 不现实。Nginx 需要手动更新配置,Traefik 能自动发现容器。选哪个取决于你的场景。Nginx:手动配置但性能最强基本反向代理# docker-compose.ymlservices: nginx: image: nginx:1.25 ports: - "80:80" - "443:443" volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./certs:/etc/nginx/certs:ro networks: - frontend app1: image: myapp:latest networks: - frontend app2: image: another-app:latest networks: - frontendnetworks: frontend:# nginx/conf.d/default.confupstream app1 { server app1:3000;}upstream app2 { server app2:8080;}server { listen 80; server_name app1.example.com; location / { proxy_pass http://app1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }}server { listen 80; server_name app2.example.com; location / { proxy_pass http://app2; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }}Docker Compose 的服务名(app1、app2)会自动解析为容器 IP——不需要硬编码 IP。但容器重启后如果 upstream 数量变了(比如扩容),Nginx 不会自动感知。SSL 配置(Let's Encrypt)手动获取证书:certbot certonly --standalone -d example.comserver { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem; location / { proxy_pass http://app1; }}问题:证书 90 天过期,需要 cron 定时续期 + docker exec nginx nginx -s reload。Nginx 什么时候合适流量非常大,需要极致性能配置不频繁变动团队熟悉 NginxTraefik:自动发现的反向代理Traefik 的核心卖点:容器启动/停止时自动更新路由,不需要改配置文件或重启代理。基本配置services: traefik: image: traefik:v2.10 command: - "--api.insecure=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" ports: - "80:80" - "443:443" - "8080:8080" # Dashboard volumes: - /var/run/docker.sock:/var/run/docker.sock:ro app1: image: myapp:latest labels: - "traefik.enable=true" - "traefik.http.routers.app1.rule=Host(`app1.example.com`)" - "traefik.http.routers.app1.entrypoints=web" - "traefik.http.services.app1.loadbalancer.server.port=3000"关键点:--providers.docker=true 启用 Docker Provider,自动监听容器事件exposedbydefault=false 只代理有 traefik.enable=true 标签的容器容器的路由规则通过 labels 配置,不需要单独的配置文件自动 SSL(Let's Encrypt)services: traefik: command: - "--certificatesresolvers.le.acme.email=you@example.com" - "--certificatesresolvers.le.acme.storage=/acme.json" - "--certificatesresolvers.le.acme.tlschallenge=true" volumes: - ./acme.json:/acme.json app1: labels: - "traefik.http.routers.app1.tls=true" - "traefik.http.routers.app1.tls.certresolver=le"Traefik 自动申请和续期证书——不需要 cron,不需要手动 reload。这是 Traefik 相比 Nginx 最大的优势。负载均衡同一个服务多个实例,Traefik 自动负载均衡: app1: deploy: replicas: 3 labels: - "traefik.http.services.app1.loadbalancer.server.port=3000"3 个副本自动分摊流量,扩容缩容 Traefik 实时感知。Caddy:最简配置Caddy 的卖点是一个 Caddyfile 搞定反向代理 + 自动 HTTPS:services: caddy: image: caddy:2 ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/datavolumes: caddy_data:# Caddyfileapp1.example.com { reverse_proxy app1:3000}app2.example.com { reverse_proxy app2:8080}就这样——Caddy 自动申请 Let's Encrypt 证书、自动续期、自动 HTTP→HTTPS 重定向。比 Nginx 少 90% 的配置。局限:不如 Nginx 灵活(复杂的 rewrite/条件判断不好写),不如 Traefik 自动化(不自动发现容器),适合简单的反向代理场景。选择决策| 场景 | 推荐 | 原因 ||------|------|------|| 简单反向代理 + HTTPS | Caddy | 配置最少,自动 HTTPS || 容器频繁变动 | Traefik | 自动发现、自动配置 || 流量极大 | Nginx | 性能最强 || 需要 Let's Encrypt | Traefik 或 Caddy | Nginx 需要手动续期 || 已有 Nginx 运维经验 | Nginx | 熟悉的工具不容易出问题 || Docker Compose 项目 | Traefik | labels 配置和 compose 一体 || Kubernetes 环境 | Nginx Ingress | K8s 生态标配 |起步建议:Docker Compose 项目用 Traefik,省心省力。需要极致性能或复杂路由时切换 Nginx。
服务端阅读 06月5日 22:29

Docker 容器怎么监控?Prometheus + Grafana 完整方案

容器出问题了,docker stats 只能看到 CPU 和内存——磁盘 IO、网络、进程状态全不知道。完整的监控系统需要指标采集、存储、可视化、告警四层。Docker 监控的四层架构容器 → 采集器 → 存储后端 → 可视化 cAdvisor Prometheus Grafana Node Loki Exporter采集层:从容器和宿主机收集指标数据存储层:时序数据库存指标,日志库存日志可视化层:Dashboard 展示趋势、图表告警层:超过阈值自动通知指标采集:cAdvisor + Node ExportercAdvisor——容器指标Google 出品,自动发现所有容器,采集 CPU、内存、网络、磁盘 IO:services: cadvisor: image: gcr.io/cadvisor/cadvisor:latest ports: - "8080:8080" volumes: - /:/rootfs:ro - /var/run:/var/run:ro - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:rocAdvisor 自带一个简单的 Web UI(http://localhost:8080),可以看每个容器的实时指标。Node Exporter——宿主机指标cAdvisor 只管容器,宿主机本身的 CPU、内存、磁盘、网络由 Node Exporter 采集:services: node-exporter: image: prom/node-exporter:latest ports: - "9100:9100" volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro command: - '--path.procfs=/host/proc' - '--path.sysfs=/host/sys' - '--path.rootfs=/rootfs'Prometheus——指标存储和查询Prometheus 定时从 cAdvisor 和 Node Exporter 拉取指标,存到自己的时序数据库:services: prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - prometheus_data:/prometheusvolumes: prometheus_data:# prometheus/prometheus.ymlglobal: scrape_interval: 15sscrape_configs: - job_name: 'cadvisor' static_configs: - targets: ['cadvisor:8080'] - job_name: 'node' static_configs: - targets: ['node-exporter:9100'] - job_name: 'app' static_configs: - targets: ['app:8080']scrape_interval: 15s 表示每 15 秒采集一次。容器数量多时可以调大到 30s-60s 减少负载。常用 PromQL 查询# 所有容器的 CPU 使用率rate(container_cpu_usage_seconds_total{name!=""}[5m]) * 100# 容器内存使用量(MB)container_memory_usage_bytes{name!=""} / 1024 / 1024# 宿主机磁盘使用率(1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100# 容器网络流入速率rate(container_network_receive_bytes_total[5m])Grafana——可视化 DashboardPrometheus 的 UI 只适合临时查询。正式的监控面板用 Grafana:services: grafana: image: grafana/grafana:10.3.0 ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin volumes: - grafana_data:/var/lib/grafana配置步骤:打开 http://localhost:3000(默认 admin/admin)添加数据源 → Prometheus → URL 填 http://prometheus:9090导入 Dashboard:推荐 ID 11600(Docker 监控)和 1860(Node Exporter 全量)导入方式:Dashboard → Import → 输入 ID → Load → 选择 Prometheus 数据源告警:AlertmanagerPrometheus 本身只负责判断规则,告警通知由 Alertmanager 发送:# prometheus/alert_rules.ymlgroups: - name: docker_alerts rules: - alert: ContainerCpuHigh expr: rate(container_cpu_usage_seconds_total{name!=""}[5m]) * 100 > 80 for: 5m labels: severity: warning annotations: summary: "容器 {{ $labels.name }} CPU 超过 80%" - alert: ContainerMemoryHigh expr: container_memory_usage_bytes / container_spec_memory_limit_bytes * 100 > 90 for: 5m labels: severity: critical - alert: DiskSpaceLow expr: (1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 > 85 for: 10mfor: 5m 表示持续 5 分钟才告警——避免短暂波动误报。完整的 docker-compose 监控栈version: "3.8"services: prometheus: image: prom/prometheus:latest volumes: - ./prometheus:/etc/prometheus - prometheus_data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.retention.time=30d' grafana: image: grafana/grafana:10.3.0 ports: - "3000:3000" volumes: - grafana_data:/var/lib/grafana cadvisor: image: gcr.io/cadvisor/cadvisor:latest volumes: - /:/rootfs:ro - /var/run:/var/run:ro - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro node-exporter: image: prom/node-exporter:latest volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro command: - '--path.procfs=/host/proc' - '--path.sysfs=/host/sys' alertmanager: image: prom/alertmanager:latest volumes: - ./alertmanager:/etc/alertmanagervolumes: prometheus_data: grafana_data:这套组合约需 2-3GB 内存,覆盖了指标采集、存储、可视化、告警四层。监控方案选择| 规模 | 推荐方案 | 内存需求 ||------|---------|---------|| 单机开发 | docker stats + cAdvisor Web | < 500MB || 小团队(<20 容器) | Prometheus + Grafana + cAdvisor | 1-2GB || 中等规模 | 完整监控栈 + Alertmanager | 2-4GB || 大规模/生产 | Kubernetes + Prometheus Operator | 按需扩展 |起步建议:先跑 cAdvisor + Prometheus + Grafana 三件套,够用了再加告警。
服务端阅读 06月5日 22:29

Docker 容器日志怎么聚合?ELK、Loki 和 EFK 怎么选

容器一多,日志分散在各处——docker logs 只能看单个容器的输出,排查问题要一个一个翻。日志聚合把所有容器的日志集中到一个地方,搜索、过滤、告警一站搞定。Docker 日志驱动:日志的入口Docker 支持多种日志驱动,决定容器日志的去向:# 查看当前日志驱动docker info --format '{{.LoggingDriver}}'# 默认是 json-file# docker-compose.yml 全局配置services: app: logging: driver: json-file options: max-size: "10m" # 单个日志文件最大 10MB max-file: "3" # 最多保留 3 个文件json-file 默认不限制大小——跑久了磁盘会被日志撑满。务必配 max-size 和 max-file。其他日志驱动| 驱动 | 去向 | 适用场景 ||------|------|---------|| json-file | 本地文件 | 默认,简单场景 || local | 本地文件(压缩) | 省磁盘 || journald | systemd journal | CentOS/Ubuntu 系统日志 || fluentd | Fluentd | 接入日志聚合栈 || syslog | syslog 服务 | 传统运维 |生产环境推荐用 fluentd 或 local 驱动——前者直接接入日志聚合,后者比 json-file 省磁盘。ELK Stack:经典方案Elasticsearch + Logstash + Kibana,功能最全但最重:# docker-compose.ymlservices: elasticsearch: image: elasticsearch:8.12.0 environment: - discovery.type=single-node - xpack.security.enabled=false ports: - "9200:9200" volumes: - es_data:/usr/share/elasticsearch/data logstash: image: logstash:8.12.0 volumes: - ./logstash/pipeline:/usr/share/logstash/pipeline ports: - "5044:5044" kibana: image: kibana:8.12.0 ports: - "5601:5601" environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 app: image: myapp:latest logging: driver: fluentd options: fluentd-address: localhost:24224 tag: myappvolumes: es_data:问题:ELK 最少需要 4GB 内存才能跑起来。小团队或开发环境用太重了。EFK Stack:轻量替代用 Fluentd 替代 Logstash,更省资源:services: fluentd: image: fluent/fluentd:v1.16 volumes: - ./fluentd/conf:/fluentd/etc ports: - "24224:24224" environment: - FLUENTD_CONF=fluent.conf# fluentd/conf/fluent.conf<source> @type forward port 24224</source><match **> @type elasticsearch host elasticsearch port 9200 logstash_format true logstash_prefix fluentd <buffer> @type file path /var/log/fluentd/buffer flush_interval 5s </buffer></match>容器配置 logging.driver: fluentd 后,所有 stdout/stderr 输出自动发到 Fluentd,Fluentd 转存到 Elasticsearch。Grafana Loki:最轻量的选择Loki 只索引标签不索引日志内容,存储成本比 Elasticsearch 低 10 倍以上:# docker-compose.ymlservices: loki: image: grafana/loki:2.9.0 ports: - "3100:3100" command: -config.file=/etc/loki/local-config.yaml promtail: image: grafana/promtail:2.9.0 volumes: - /var/log:/var/log - /var/lib/docker/containers:/var/lib/docker/containers:ro - ./promtail/config.yml:/etc/promtail/config.yml command: -config.file=/etc/promtail/config.yml grafana: image: grafana/grafana:10.3.0 ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=adminPromtail 从 Docker 容器日志目录读取日志,推送到 Loki。Grafana 查询和可视化。Loki 的查询语法(LogQL)比 Kibana 的 KQL 简单:{container_name="myapp"} |= "error" | json | line_format "{{.message}}"意思是:从 myapp 容器日志中过滤包含 "error" 的行,解析 JSON,只显示 message 字段。日志结构化:让聚合更有效非结构化日志在聚合平台里只能做全文搜索。结构化日志可以按字段过滤、聚合统计:# Python 结构化日志(JSON 格式)import loggingimport jsonclass JSONFormatter(logging.Formatter): def format(self, record): return json.dumps({ "timestamp": self.formatTime(record), "level": record.levelname, "message": record.getMessage(), "service": "user-service", "trace_id": getattr(record, "trace_id", None), })// Node.js 用 pino 直接输出 JSONconst logger = require('pino')({ level: 'info', formatters: { level: (label) => ({ level: label }) },})logger.info({ userId: 123, action: 'login' }, 'User logged in')选择决策| 场景 | 推荐方案 | 理由 ||------|---------|------|| 开发/测试 | docker logs + json-file | 够用 || 小团队(<10 服务) | Grafana Loki + Promtail | 轻量,1GB 内存够 || 中大型团队 | EFK Stack | 功能全、社区大 || 需要全文搜索 | ELK Stack | Elasticsearch 全文检索最强 || 已有 Grafana | 直接加 Loki | 不用额外装 Kibana || 日志量巨大 | Loki(只索引标签) | 存储成本最低 |起步建议:先用 Loki + Grafana,轻量够用。等日志量大到需要全文搜索时再迁移到 ELK。
服务端阅读 06月5日 22:29

Docker 容器配置怎么管理?环境变量、挂载和配置中心怎么选

Docker 容器的配置管理不是把配置写死在镜像里——那样每次改配置都得重新构建。正确做法是配置与镜像分离,容器启动时注入配置,运行时能动态更新。这篇讲清楚 Docker 环境下四种配置管理方式的适用场景和实现方法。环境变量:最简单的配置注入适合少量、扁平的配置项(数据库地址、端口、开关):# docker-compose.ymlservices: app: image: myapp:latest environment: - DB_HOST=postgres - DB_PORT=5432 - LOG_LEVEL=info - FEATURE_FLAG=true或者用 .env 文件:# .envDB_HOST=postgresDB_PORT=5432LOG_LEVEL=infoservices: app: env_file: .env优势:简单、Docker 原生支持、docker-compose 直接读取局限:只有字符串值、不能表示嵌套结构、修改需要重启容器敏感信息别放环境变量环境变量会被 docker inspect 暴露,也会出现在进程列表里。密码、密钥等用 Docker Secret 或文件挂载:# Docker Swarm Secretecho "my_password" | docker secret create db_password -services: app: secrets: - db_passwordsecrets: db_password: external: true配置文件挂载:复杂配置的首选当配置是结构化的(YAML、JSON、TOML),用 Volume 挂载比环境变量更合适:services: app: image: myapp:latest volumes: - ./config/app.yml:/app/config/app.yml:ro - ./config/nginx.conf:/etc/nginx/conf.d/default.conf:ro:ro 表示只读挂载——容器不能修改配置文件,只能读取。防止容器内进程意外修改配置。只挂载需要的文件,别挂载整个目录# 好:只挂载需要的文件volumes: - ./config/app.yml:/app/config/app.yml:ro# 差:挂载整个目录,可能暴露无关文件volumes: - ./config:/app/config:ro配置热更新:不重启容器更新配置挂载的文件修改后,容器内立即可见——但应用是否自动重新加载取决于应用本身:Nginx:docker exec nginx nginx -s reloadSpring Boot:配合 Spring Cloud Config 自动刷新Node.js:用 chokidar 监听文件变化如果应用不支持热加载,可以配合 inotifywait 检测文件变化后发送信号:#!/bin/bashinotifywait -m -e modify /app/config/app.yml | while read event; do kill -SIGHUP 1 # 发送 HUP 信号给主进程done配置中心:分布式系统的统一配置多服务、多实例的场景,配置文件挂载管理成本太高——改一个配置要同步到所有机器。配置中心解决的就是这个问题。Consul + Consul-Template# docker-compose.ymlservices: consul: image: consul:1.15 ports: - "8500:8500" command: agent -dev -client=0.0.0.0 app: image: myapp:latest volumes: - ./templates:/templates command: > consul-template -consul-addr=consul:8500 -template="/templates/app.ctmpl:/app/config/app.yml:docker restart app"consul-template 监听 Consul KV 变化,自动重新生成配置文件并触发容器重启。Etcd + Confd和 Consul-Template 类似的模式,Etcd 做存储,Confd 做模板渲染:# 写入配置etcdctl set /myapp/db_host "postgres.prod"# confd 读取 etcd 并渲染模板confd -onetime -backend etcd -node http://etcd:2379Spring Cloud Config ServerJava 生态的标准方案:services: config-server: image: springcloud/configserver ports: - "8888:8888" environment: - SPRING_CLOUD_CONFIG_SERVER_GIT_URI=https://github.com/org/config-repo app: image: my-spring-app environment: - SPRING_CLOUD_CONFIG_URI=http://config-server:8888配置存在 Git 仓库里,有版本历史。应用启动时从 Config Server 拉取配置,配合 /actuator/refresh 端点实现热更新。Kubernetes ConfigMap 和 Secret如果跑在 K8s 上,环境变量和文件挂载都由 ConfigMap/Secret 管理:# ConfigMapapiVersion: v1kind: ConfigMapmetadata: name: app-configdata: DB_HOST: postgres app.yml: | server: port: 8080 logging: level: info---# Pod 使用 ConfigMapapiVersion: v1kind: Podspec: containers: - name: app envFrom: - configMapRef: name: app-config volumeMounts: - name: config mountPath: /app/config volumes: - name: config configMap: name: app-configSecret 和 ConfigMap 用法一样,但值是 base64 编码的,且访问可以加 RBAC 控制:kubectl create secret generic db-credentials \ --from-literal=username=admin \ --from-literal=password=s3cretConfigMap/Secret 更新后 Pod 不会自动重启——需要手动 kubectl rollout restart 或用 Reloader 之类的工具自动触发。选择决策| 场景 | 推荐方案 | 原因 ||------|---------|------|| 单容器、少量配置 | 环境变量 + .env | 最简单 || 单容器、复杂配置 | Volume 挂载配置文件 | 支持结构化配置 || 多容器、同一台主机 | docker-compose + 挂载 | compose 管理方便 || 多主机、多服务 | Consul/Etcd 配置中心 | 统一管理、动态更新 || Kubernetes 环境 | ConfigMap + Secret | K8s 原生方案 || 敏感信息 | Docker Secret / K8s Secret | 不暴露在环境变量里 |原则:配置与代码分离,敏感信息加密,变更可追溯。能用简单的就不用复杂的——别为了用 Consul 而用 Consul。
服务端阅读 06月3日 00:04

Docker Compose 怎么用?多容器编排和常用命令速查

Docker Compose 用一个 YAML 文件定义和运行多容器应用。一条命令启动所有服务,不用逐个 docker run。docker-compose.yml 基本结构关键配置:build: . — 用当前目录的 Dockerfile 构建镜像ports: 宿主机端口:容器端口environment: 环境变量(同一网络内用服务名互访,如 DB_HOST: db)depends_on: 启动依赖(只控制启动顺序,不等服务就绪)volumes: 数据持久化(.:/app 挂载源码方便开发热更新)常用命令开发 vs 生产开发时加 volume 挂载源码,代码修改实时生效:生产时去掉 volume 挂载,用构建好的镜像:用多个 compose 文件覆盖配置:常见问题端口冲突:某个端口已被占用。改宿主机端口(3001:3000)或停掉占用进程。容器启动后立即退出:docker compose logs web 查看日志定位原因。Volume 数据残留:docker compose down 不删除 Volume。要清空数据用 docker compose down -v。
服务端阅读 06月3日 00:04

Docker 网络模式有哪些?bridge、host 和 overlay 怎么选?

Docker 有四种网络模式:bridge(默认)、host、none、overlay。日常开发用 bridge,性能敏感用 host,集群通信用 overlay。bridge:默认模式每个容器有独立网络栈,通过虚拟网桥(docker0)和宿主机通信。容器之间用服务名互访(同网络内),外部通过端口映射访问容器。bridge 模式的端口映射(-p 8080:80)有一层 NAT 转换,理论上比 host 慢一点,但隔离性好。自定义网络让容器间可以通过服务名通信:host:直接用宿主机网络容器不隔离网络,直接用宿主机的网络栈。没有 NAT、没有端口映射,性能最好。host 模式的限制:端口冲突——多个容器不能监听同一个端口没有网络隔离——容器能看到宿主机所有网络接口macOS/Windows 上不支持 host 模式(只有 Linux 支持)适合:网络密集型应用(代理、负载均衡)、需要极低延迟的场景。none:无网络容器没有网络接口,只有 loopback。适合纯计算任务(批处理、数据处理),不需要网络的场景。overlay:跨主机网络Docker Swarm 模式下,overlay 网络让不同主机上的容器互相通信:Kubernetes 有自己的网络方案(Flannel、Calico),不用 Docker overlay。怎么选开发/通用场景:bridge + 自定义网络性能优先(Linux):host纯计算/安全隔离:noneSwarm 集群:overlay生产环境:K8s 管网络,不需要选 Docker 网络模式
服务端阅读 06月3日 00:04

Docker 镜像和 Dockerfile 是什么?从零构建镜像详解

Docker 镜像是容器的模板——只读的文件包,包含运行应用所需的一切(代码、运行时、依赖、配置)。Dockerfile 是构建镜像的脚本——一行一个指令,告诉 Docker 怎么组装镜像。镜像是什么镜像是一个分层的文件系统。每个指令(RUN、COPY、ADD)创建一层,层层叠加。好处:多个镜像共享相同的基础层,节省磁盘和拉取时间。镜像本身不可变。运行容器时,Docker 在镜像顶部加一个可写层——容器修改文件只影响这个可写层,不修改镜像。Dockerfile 基本结构逐行解读:FROM:基础镜像,你的镜像在此之上构建WORKDIR:设置工作目录,后续指令都在这个目录下执行COPY:拷贝文件到镜像里RUN:构建时执行命令(安装依赖等)EXPOSE:声明端口(文档作用,不实际映射)CMD:容器启动时执行的命令构建镜像-t myapp:v1 给镜像打标签,. 表示 Dockerfile 在当前目录。Dockerfile 优化技巧1. 利用缓存:Docker 按层缓存。package.json 没变时 npm ci 用缓存,不用重新安装。所以先 COPY package.json 再 COPY 源码——源码变了不影响依赖缓存。2. .dockerignore:排除不需要的文件:不加 .dockerignore 会把 node_modules 也 COPY 进去,既慢又大。3. 多阶段构建:编译和运行分开,运行镜像不需要编译工具。详见多阶段构建专题。镜像标签管理不要只用 latest 标签——无法回滚。用版本号(v1.2.3)或 git commit hash 标记每个镜像。
服务端阅读 06月3日 00:04

Docker 和虚拟机有什么区别?该用哪个?

Docker 和虚拟机都是隔离运行环境的技术,但原理完全不同:虚拟机虚拟整套硬件(含操作系统),Docker 共享宿主机内核只隔离进程。结果:Docker 启动快 10 倍、内存省 5 倍,但隔离性不如虚拟机。核心区别| 维度 | Docker 容器 | 虚拟机 ||------|------------|--------|| 虚拟层级 | 进程级隔离(共享内核) | 硬件级虚拟化(独立内核) || 启动时间 | 秒级 | 分钟级 || 内存占用 | MB 级 | GB 级 || 镜像大小 | MB-Tens of MB | GB 级 || 性能损耗 | 接近原生 | 5-15% || 隔离性 | 弱(共享内核) | 强(独立内核) || 密度 | 单机跑几十个 | 单机跑几个 |为什么 Docker 这么轻虚拟机需要给每个实例装一套完整的操作系统(Linux 内核 + 用户空间),至少 1GB 内存。Docker 容器只是宿主机上的一个进程——用 Linux namespace 隔离进程、网络、文件系统,用 cgroup 限制资源。所有容器共享同一个内核,不需要重复运行操作系统。打个比方:虚拟机是每家自建一栋房子(独立地基、管道),Docker 是同一栋楼里的不同公寓(共享地基和管道,各自有门锁)。Docker 做不到什么因为共享内核,容器不能:运行不同内核版本的系统(Linux 容器不能跑在 Windows 上,反之亦然)完全隔离内核漏洞(一个容器的内核漏洞可能影响宿主机)运行需要特定硬件驱动的应用虚拟机可以——它有独立的内核,可以跑 Windows on Linux、Linux on macOS。什么时候用虚拟机需要运行不同操作系统(Windows + Linux 混合环境)安全要求极高(金融、政务),需要内核级隔离需要直接访问硬件(GPU 直通、特定网卡)合规要求规定必须用虚拟机什么时候用 Docker微服务部署、CI/CD 流水线开发环境统一(所有人跑相同的容器)快速扩缩容90% 的后端应用场景混合使用Docker 跑在虚拟机里是常见架构——云厂商的虚拟机(EC2/ECS)上跑 Docker 容器。虚拟机提供硬件级隔离(多租户安全),Docker 提供应用级隔离(部署灵活性)。
服务端阅读 06月3日 00:02

Docker 健康检查怎么配?HEALTHCHECK 和 Compose 健康检查详解

Docker 健康检查自动检测容器内应用是否正常——容器运行中不代表应用可用。MySQL 可能在做崩溃恢复,Nginx 可能配置错误 502,但容器状态都是 Up。Dockerfile 里配置参数:--interval=30s:每 30 秒检查一次--timeout=5s:5 秒内没响应算失败--retries=3:连续 3 次失败才标记为 unhealthy健康状态:starting(启动中)→ healthy(健康)→ unhealthy(不健康)docker-compose.yml 里配置start_period 很重要——应用启动需要时间(Spring Boot 可能要 30 秒),启动期间的健康检查失败不应该算数。不同应用的健康检查命令depends_on 配合健康检查Compose 的 depends_on 默认只等容器启动,不等应用就绪:condition: servicehealthy 确保 db 真正可用后 web 才启动。比 dependson: db(只等容器启动)更可靠。没有 curl 的镜像Alpine 镜像没有 curl。用 wget 替代:或者安装 curl:RUN apk add --no-cache curl
服务端阅读 06月3日 00:02

Docker 多阶段构建怎么用?减小镜像体积的最佳方法

多阶段构建让 Dockerfile 分多个阶段——编译阶段用完整环境构建产物,运行阶段只拷贝最终产物。结果:镜像从 1GB+ 缩小到 50MB 以下。问题:单阶段构建镜像太大node:20 镜像 1.1GB,加上 node_modules 几百 MB,最终镜像可能 1.5GB。但运行时只需要 dist/ 目录和 node 生产依赖。多阶段构建--from=builder 从第一阶段拷贝指定目录。node:20-slim 只有 200MB,最终镜像约 300MB——比单阶段小 5 倍。Go 应用:极致压缩Go 编译出单个二进制文件,运行时不需要 Go 环境:scratch 是空镜像——里面只有你拷贝的二进制文件。最终镜像可能只有 10-20MB。前端应用:Nginx 托管静态文件前端只需要构建后的 HTML/CSS/JS,不需要 node_modules。nginx:alpine 只有 25MB。COPY --from 的其他用法不限于同一 Dockerfile 的阶段,可以从其他镜像拷贝:从 Caddy 官方镜像里只拷贝二进制文件,不用自己安装。关键要点每个 FROM 开始一个新阶段,只有最后一个阶段的产物进入最终镜像用 AS 命名阶段,COPY --from=名称 引用运行阶段尽量用 slim/alpine 变体不要把源码、编译工具、dev 依赖带进运行镜像
服务端阅读 06月2日 23:47

Docker 容器数据怎么备份和恢复?Volume 备份和数据库导出实战

Docker 容器的数据在 Volume 里——备份 Volume 就是备份数据。两种方式:直接备份 Volume 文件,或从容器内导出。方法一:备份 Volume 目录简单粗暴,但要求停掉容器或确保数据一致性(数据库正在写入时备份可能损坏)。方法二:用临时容器备份不停容器,用 --volumes-from 挂载同一个 Volume:临时容器挂载 pg_data(只读)和宿主机 /backup 目录,把数据打包到宿主机。数据库导出(推荐)数据库不适合直接拷文件——文件可能处于不一致状态。用数据库的导出工具:在容器里执行:导出的是 SQL 文本,保证逻辑一致性,可以跨版本恢复。恢复数据库恢复用 psql/mysql 命令,不用拷文件。自动化备份备份文件要推到远程存储(S3、OSS),不要只存在本机——本机挂了备份也没了。Docker Volume 备份 vs 数据库导出Volume 备份:快,但不保证一致性,适合非数据库文件数据库导出:慢,但保证一致性,适合数据库两者配合:Volume 备份应用配置/上传文件,数据库导出业务数据
服务端阅读 06月2日 23:47

Docker 容器怎么监控?Prometheus + Grafana 和告警配置实战

Docker 监控分两层:容器级(CPU/内存/网络)和应用级(QPS/延迟/错误率)。容器级用 cAdvisor + Prometheus,应用级用代码埋点 + Prometheus。统一在 Grafana 看板和告警。最快上手:docker stats只适合临时查看,没有历史数据、没有告警、没有可视化。Prometheus + cAdvisor:容器级监控cAdvisor 采集容器的 CPU、内存、网络、磁盘 IO 指标,Prometheus 存储和查询,Grafana 可视化。cAdvisor 暴露 /metrics 端点,Prometheus 定时拉取。Grafana 导入 Docker dashboard 模板(ID 893)即可看到容器资源看板。关键监控指标| 指标 | 含义 | 告警阈值 ||------|------|----------|| containercpuusagesecondstotal | CPU 使用率 | > 80% 持续 5 分钟 || containermemoryusagebytes | 内存使用 | > 90% 限制值 || containernetworkreceivebytestotal | 网络接收 | 异常突增 || containeroom_events | OOM 次数 | > 0 立即告警 |OOM 事件是最严重的——容器被杀意味着应用中断,必须立即处理。告警配置Prometheus Alertmanager 配置告警规则和通知渠道:通知渠道支持邮件、Slack、钉钉、企业微信。日志监控监控 + 日志配合:告警触发后用 docker logs 查看对应容器的日志,定位问题。如果用了 Loki,直接在 Grafana 里查日志,不用跳到终端。
服务端阅读 06月2日 23:47

Docker 编排工具怎么选?Docker Compose、Swarm 和 Kubernetes 对比

Docker 编排工具解决的是多容器管理问题——手动 docker run 管几个容器还行,几十个就力不从心了。三个主流方案:Compose(开发)、Swarm(小团队)、Kubernetes(生产)。Docker Compose:开发环境首选一条 docker compose up -d 启动所有服务。适合本地开发、CI 测试、小型项目部署。局限:单机运行,不支持自动扩缩容,没有滚动更新,没有服务发现。服务挂了需要手动重启。Docker Swarm:轻量级集群Swarm 内置在 Docker 里,不需要额外安装。支持多节点集群、滚动更新、服务发现、内置负载均衡。局限:功能比 K8s 少很多——没有自动扩缩容(HPA)、没有自定义调度、没有 CRD 扩展。社区在萎缩,新项目不建议选 Swarm。Kubernetes:生产标准K8s 是容器编排的事实标准。功能完整:自动扩缩容、滚动更新、服务发现、配置管理、密钥管理、持久卷、网络策略、审计日志。K8s 的代价:学习曲线陡、运维复杂、需要专门的平台团队。小项目用 K8s 是杀鸡用牛刀。怎么选1-5 个服务:Docker Compose,简单够用5-20 个服务,单集群:Swarm 或 K8s(建议直接 K8s,Swarm 没有未来)20+ 个服务,多环境:Kubernetes云上部署:直接用云厂商的 K8s 托管服务(EKS/GKE/AKS),别自己搭一句话:开发用 Compose,生产用 K8s。Swarm 跳过。