服务端6月6日 20:29
Docker Swarm 怎么用?集群部署和运维命令Docker Swarm 是 Docker 自带的容器编排工具——不需要额外安装,几条命令就能把多台服务器组成集群,部署高可用服务。和 Kubernetes 比,Swarm 简单得多,适合中小规模部署。
## Swarm vs Kubernetes:怎么选
| 维度 | Docker Swarm | Kubernetes |
|------|-------------|------------|
| 复杂度 | 低(几条命令) | 高(概念多、配置复杂) |
| 学习成本 | 半天 | 几周 |
| 适用规模 | 3-50 节点 | 10-10000+ 节点 |
| 自动扩缩容 | 手动 | 自动(HPA) |
| 自愈能力 | 有(重启失败容器) | 强(多种控制器) |
| 生态 | Docker 原生 | CNCF 全家桶 |
**选 Swarm 的情况**:团队小、服务器少、不想花时间学 K8s
**选 K8s 的情况**:大规模部署、需要自动扩缩容、团队有 K8s 经验
## 初始化集群
### Manager 节点
```bash
# 在第一台服务器上初始化
docker swarm init --advertise-addr 192.168.1.10
# 输出加入命令
# docker swarm join --token SWMTKN-xxx 192.168.1.10:2377
```
### Worker 节点
```bash
# 在其他服务器上执行加入命令
docker swarm join --token SWMTKN-xxx 192.168.1.10:2377
```
### 查看集群状态
```bash
# 查看所有节点
docker node ls
# 输出
# ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
# abc123 * node1 Ready Active Leader
# def456 node2 Ready Active
# ghi789 node3 Ready Active
```
Manager 节点负责调度和管理,Worker 节点只跑容器。Manager 也会跑容器——小集群不需要单独的管理节点。
### 节点管理
```bash
# 标记节点角色(让某些任务只跑在特定节点)
docker node update --label-add role=backend node2
docker node update --label-add role=database node3
# 排空节点(维护时把容器迁移走)
docker node update --availability drain node2
# 恢复节点
docker node update --availability active node2
```
## 部署服务
### 基本部署
```bash
# 部署一个服务,3 个副本
docker service create \
--name myapp \
--replicas 3 \
--publish 3000:3000 \
myapp:latest
```
Swarm 自动把 3 个副本分散到不同节点,内置负载均衡——访问任意节点的 3000 端口都会被路由到健康的副本。
### 使用 docker-compose 部署
```yaml
# docker-compose.yml
services:
api:
image: myapp:latest
ports:
- "3000:3000"
deploy:
replicas: 3
update_config:
parallelism: 1 # 每次更新 1 个副本
delay: 10s # 间隔 10 秒
failure_action: rollback # 失败自动回滚
rollback_config:
parallelism: 0 # 回滚时一次全部替换
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
resources:
limits:
cpus: '1.0'
memory: 2G
redis:
image: redis:7
deploy:
replicas: 1
placement:
constraints:
- node.role == manager # 只跑在 manager 节点
postgres:
image: postgres:16
volumes:
- pg_data:/var/lib/postgresql/data
deploy:
replicas: 1
placement:
constraints:
- node.labels.role == database # 只跑在标记了 database 的节点
volumes:
pg_data:
```
```bash
# 部署
docker stack deploy -c docker-compose.yml myapp
# 查看服务
docker service ls
# 查看某个服务的副本
docker service ps myapp_api
```
## 滚动更新
```bash
# 更新镜像版本
docker service update --image myapp:v2.0 myapp_api
# 查看更新进度
docker service ps myapp_api
```
`update_config` 控制更新策略:
- `parallelism: 1` 每次只更新 1 个副本
- `delay: 10s` 每个副本更新后等 10 秒再更新下一个
- `failure_action: rollback` 新版本启动失败时自动回滚到旧版本
### 手动回滚
```bash
# 回滚到上一版本
docker service rollback myapp_api
```
## Overlay 网络:跨主机通信
Swarm 模式下的 Overlay 网络让不同主机上的容器直接通信:
```bash
# 创建 Overlay 网络
docker network create -d overlay my-net
```
```yaml
services:
api:
networks:
- my-net
redis:
networks:
- my-net
networks:
my-net:
external: true
```
api 容器在 node1,redis 在 node2——通过 `redis:6379` 直接访问,和单机体验一样。
## 配置和敏感信息
### Config(非敏感配置)
```bash
# 创建配置
echo "server.port=8080" | docker config create app_config -
```
```yaml
services:
api:
configs:
- source: app_config
target: /app/config.properties
configs:
app_config:
external: true
```
### Secret(敏感信息)
```bash
# 创建 Secret
echo "db_password_123" | docker secret create db_password -
```
```yaml
services:
api:
secrets:
- db_password
secrets:
db_password:
external: true
```
Secret 在容器内挂载为 `/run/secrets/db_password`,只有容器内可读,不会出现在 `docker inspect` 里。
## 常用运维命令
```bash
# 查看服务日志
docker service logs myapp_api
# 扩缩容
docker service scale myapp_api=5
# 查看服务详情
docker service inspect myapp_api
# 删除服务
docker service rm myapp_api
# 删除整个 Stack
docker stack rm myapp
# 查看集群事件
docker events --filter type=service
```
## 什么时候该从 Swarm 迁移到 K8s
- 需要自动扩缩容(HPA)
- 需要 CronJob(定时任务)
- 需要 Ingress 控制器(7 层路由)
- 需要 Pod 级别的健康检查
- 集群超过 50 个节点
在以上需求出现之前,Swarm 够用且省心。
标签
Docker
Docker 是一个开源的容器化平台,允许开发者打包应用及其全部依赖到一个可移植的容器中,然后这个容器可以在任何机器上运行,确保应用在不同环境之间运行的一致性。Docker 使用了 Linux 容器(LXC)的技术,但进行了扩展,使其更为易用、功能更全面。

服务端6月6日 20:29
Docker 怎么限制容器资源?CPU、内存和磁盘 IO 配置不限制容器资源,一个失控的容器就能吃光宿主机内存,拖垮同一台机器上的所有服务。Docker 提供了 CPU、内存、磁盘 IO 的精细限制手段,核心是 `docker run` 的资源参数和 docker-compose 的 `deploy.resources` 配置。
## 内存限制:最常用也最重要
```bash
# 限制最大内存 4GB
docker run -d --name myapp --memory=4g myapp:latest
# 限制内存 + 禁用 swap
docker run -d --name myapp --memory=4g --memory-swap=4g myapp:latest
```
`--memory-swap=4g` 等于 `--memory` 的值意味着容器不能用 swap。如果 `--memory-swap` 比 `--memory` 大,差值就是允许的 swap 大小。
### docker-compose 配置
```yaml
services:
app:
image: myapp:latest
deploy:
resources:
limits:
memory: 4G # 硬上限,超过会被 OOM Kill
reservations:
memory: 1G # 软保底,调度时保证至少 1G
```
### OOM 时会发生什么
容器内存超过 `limits.memory` 时,内核 OOM Killer 杀掉容器里内存占用最大的进程:
```bash
# 检查容器是否被 OOM 杀掉
docker inspect myapp --format '{{.State.OOMKilled}}'
# true = 被 OOM 杀了
# 查看 OOM 事件
dmesg | grep -i oom
```
### OOM 优先级调整
多个容器抢内存时,可以设 OOM 优先级:
```bash
# 不容易被 OOM Kill(-1000 到 1000,越小越不容易被杀)
docker run -d --name important-app --oom-score-adj=-500 myapp
# 容易被 OOM Kill(优先杀这个)
docker run -d --name cache-app --oom-score-adj=500 redis
```
核心服务(数据库、API 网关)设低值,缓存类服务(Redis、CDN)设高值。
## CPU 限制
### 限制 CPU 核数
```bash
# 最多用 2 核
docker run -d --cpus=2.0 myapp
# 最多用 0.5 核
docker run -d --cpus=0.5 myapp
```
```yaml
services:
app:
deploy:
resources:
limits:
cpus: '2.0'
reservations:
cpus: '0.5'
```
`--cpus=2.0` 不是绑核——容器可以在任意 2 个核心上运行,只是总使用时间不超过 200%。绑核用 `--cpuset-cpus`:
```bash
# 只在第 0 和第 2 个核心上运行
docker run -d --cpuset-cpus=0,2 myapp
# 只在第 1-3 个核心上运行
docker run -d --cpuset-cpus=1-3 myapp
```
绑核适合对 CPU 缓存一致性敏感的应用(如高性能计算),一般 Web 服务不需要。
### CPU 权重
多个容器抢 CPU 时,按权重分配:
```bash
# 默认权重 1024
# 高权重 = 抢到更多 CPU 时间
docker run -d --cpu-shares=2048 high-priority-app
docker run -d --cpu-shares=512 low-priority-app
```
注意:`--cpu-shares` 只在 CPU 资源紧张时生效。CPU 空闲时低权重容器也能用满 CPU。
## 磁盘 IO 限制
限制容器读写磁盘的速率,防止一个容器把磁盘 IO 吃光:
```bash
# 限制写速率 10MB/s
docker run -d \
--device-write-bps /dev/sda:10mb \
myapp
# 限制读速率 20MB/s
docker run -d \
--device-read-bps /dev/sda:20mb \
myapp
# 限制 IOPS(每秒 IO 操作数)
docker run -d \
--device-write-iops /dev/sda:1000 \
myapp
```
磁盘 IO 限制在 docker-compose 里不支持——需要 `docker run` 方式启动。
## PIDs 限制:防止进程炸弹
限制容器内的进程数,防止 fork 炸弹:
```bash
# 最多 100 个进程
docker run -d --pids-limit=100 myapp
```
```yaml
services:
app:
deploy:
resources:
limits:
pids: 100
```
没有这个限制,一个容器可以 fork 出几千个进程,耗尽宿主机的 PID 表。
## 运行时修改资源限制
不需要重建容器就能调整限制:
```bash
# 动态调整内存限制
docker update --memory=8g myapp
# 动态调整 CPU 限制
docker update --cpus=4.0 myapp
# 同时调整多个
docker update --memory=8g --cpus=4.0 myapp
```
注意:`docker update` 不能修改 `--pids-limit` 和 `--cpuset-cpus`——这些需要重建容器。
## 资源限制最佳实践
| 容器类型 | 内存 | CPU | 其他 |
|---------|------|-----|------|
| Web API | 2-4G | 1-2 核 | pids-limit: 200 |
| 数据库 | 4-16G | 2-4 核 | 绑核、禁 swap |
| 缓存 Redis | 2-8G | 1-2 核 | 禁 swap |
| 日志采集 | 512M-1G | 0.5 核 | 限制磁盘 IO |
| 后台任务 | 1-2G | 0.5-1 核 | pids-limit: 50 |
**原则**:
- 所有生产容器都设内存限制,防止单个容器拖垮宿主机
- 数据库容器禁 swap(`--memory-swap` 等于 `--memory`)
- 核心服务设低 OOM 优先级
- 不确定资源需求时先设宽松限制,用 `docker stats` 观察实际用量再收紧
服务端6月6日 20:29
Docker 私有仓库怎么搭建?Registry 和 Harbor 选型Docker Hub 是公开的,你的私有镜像不想让外人看到。企业内部需要一个私有 Registry 存放自己的镜像,CI/CD 推送镜像到私有仓库,生产服务器从私有仓库拉取。
## 最简方案:Docker 官方 Registry
Docker 官方提供了一个极简的 Registry 镜像,几分钟就能跑起来:
```bash
# 启动私有仓库
docker run -d -p 5000:5000 --name registry registry:2
```
```bash
# 推送镜像
docker tag myapp:latest localhost:5000/myapp:latest
docker push localhost:5000/myapp:latest
# 拉取镜像
docker pull localhost:5000/myapp:latest
```
这就够了——一个能推能拉的私有仓库。但生产环境需要持久化存储、认证、TLS。
### 持久化存储
```yaml
services:
registry:
image: registry:2
ports:
- "5000:5000"
volumes:
- registry_data:/var/lib/registry
volumes:
registry_data:
```
默认镜像存在 `/var/lib/registry`,用 Volume 持久化防止容器重启后镜像丢失。
### 启用 TLS
HTTP 模式下 Docker 客户端会拒绝推送(安全限制)。加 TLS:
```yaml
services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
REGISTRY_HTTP_TLS_KEY: /certs/domain.key
volumes:
- registry_data:/var/lib/registry
- ./certs:/certs:ro
```
```bash
# 自签名证书(测试用)
mkdir certs
openssl req -newkey rsa:4096 -nodes -sha256 \
-keyout certs/domain.key \
-x509 -days 365 \
-out certs/domain.crt \
-subj "/CN=registry.example.com"
```
所有拉取镜像的机器都要信任这个证书:
```bash
# 把证书复制到 Docker 信任目录
sudo mkdir -p /etc/docker/certs.d/registry.example.com:5000
sudo cp certs/domain.crt /etc/docker/certs.d/registry.example.com:5000/ca.crt
sudo systemctl restart docker
```
### 基本认证
```bash
# 创建用户密码文件
mkdir auth
docker run --entrypoint htpasswd httpd:2 -Bbn admin password123 > auth/htpasswd
```
```yaml
services:
registry:
image: registry:2
environment:
REGISTRY_AUTH: htpassd
REGISTRY_AUTH_HTPASSWD_REALM: "Registry Realm"
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
volumes:
- ./auth:/auth:ro
```
```bash
# 登录后才能推送
docker login registry.example.com:5000
# Username: admin
# Password: password123
```
## Harbor:企业级私有仓库
Docker 官方 Registry 功能太简陋——没有 Web 界面、没有镜像扫描、没有 RBAC。Harbor 是 VMware 开源的企业级 Registry,补全了这些能力。
### Docker Compose 部署 Harbor
```bash
# 下载 Harbor
wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz
tar xzf harbor-offline-installer-v2.10.0.tgz
cd harbor
# 编辑配置
cp harbor.yml.tmpl harbor.yml
```
```yaml
# harbor.yml 关键配置
hostname: registry.example.com
http:
port: 80
https:
port: 443
certificate: /certs/domain.crt
private_key: /certs/domain.key
harbor_admin_password: Harbor12345
data_volume: /data/harbor
```
```bash
# 安装
./install.sh
# 访问 Web 界面
# https://registry.example.com
# 用户名: admin 密码: Harbor12345
```
### Harbor 的企业级功能
| 功能 | 说明 |
|------|------|
| Web 管理界面 | 浏览镜像、标签、层信息 |
| RBAC | 项目级权限控制(只读/开发/管理员) |
| 镜像扫描 | Trivy 集成,自动扫描漏洞 |
| 镜像签名 | Docker Content Trust,防止篡改 |
| 垃圾回收 | 清理无引用的镜像层,回收磁盘 |
| 复制规则 | 跨 Registry 同步镜像 |
| 审计日志 | 记录所有推送/拉取操作 |
### 项目和权限
Harbor 用"项目"组织镜像,类似 GitHub 的仓库:
```
项目: frontend
├── api-gateway:v1.2.3
├── web-app:v2.0.0
└── auth-service:v1.0.0
项目: backend
├── user-service:v3.1.0
└── order-service:v2.5.0
```
每个项目可以设不同的成员和权限——前端团队只能推拉 frontend 项目,后端团队只能推拉 backend 项目。
## 云厂商托管 Registry
不想自己运维 Registry,用云厂商的托管服务:
| 云厂商 | 服务名 | 特点 |
|--------|--------|------|
| AWS | ECR | 与 IAM 集成,按存储计费 |
| GCP | Artifact Registry | 与 GCP IAM 集成 |
| Azure | ACR | 与 Azure AD 集成 |
| 阿里云 | 容器镜像服务 | 国内访问快 |
```bash
# AWS ECR 示例
aws ecr get-login-password | docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com
docker tag myapp:latest 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:latest
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:latest
```
托管服务的优势:不需要维护服务器、自动 TLS、自动漏洞扫描。劣势:网络延迟(国内拉海外镜像慢)、费用随存储量增长。
## 选择决策
| 场景 | 推荐方案 |
|------|---------|
| 个人/小团队测试 | Docker 官方 Registry |
| 团队 5-20 人 | Harbor(Docker Compose 部署) |
| 企业生产环境 | Harbor(高可用部署) |
| 全部在云上 | 云厂商托管 Registry |
| 国内访问为主 | 阿里云容器镜像服务 |
**起步建议**:先用官方 Registry 跑起来,等需要 Web 界面和权限管理时迁移到 Harbor。
服务端6月6日 20:29
Docker 容器日志怎么管理?轮转、结构化和聚合方案容器日志管理不只是 `docker logs` ——那只能看单个容器的标准输出。生产环境需要日志轮转防止磁盘撑满、日志聚合实现集中查询、结构化日志方便检索。这篇从本地管理到集中式方案逐步展开。
## docker logs 的局限
```bash
docker logs myapp # 查看日志
docker logs -f myapp # 实时跟踪
docker logs --tail 100 myapp # 最近 100 行
```
问题:
- 容器删了日志就没了
- 多容器没法一起搜
- 没有日志轮转,磁盘会被撑满
- 没有结构化字段,搜索靠 grep
## 本地日志轮转:防止磁盘撑满
### json-file 驱动的轮转配置
```yaml
services:
app:
image: myapp:latest
logging:
driver: json-file
options:
max-size: "10m" # 单个日志文件最大 10MB
max-file: "3" # 最多保留 3 个文件
```
这样每个容器最多占 30MB 日志(10MB × 3 个文件)。超过 10MB 自动轮转,超过 3 个文件自动删除最老的。
### local 驱动:更省磁盘
```yaml
services:
app:
logging:
driver: local
options:
max-size: "10m"
max-file: "5"
```
`local` 驱动用压缩存储,同样内容比 json-file 省 50% 空间。而且日志格式更易读。
### 全局配置
不想每个容器都写 logging 配置?在 `daemon.json` 里设全局默认:
```json
// /etc/docker/daemon.json
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
```
重启 Docker 后所有容器都用这个配置。**强烈建议加上**——我见过太多服务器因为 Docker 日志占满磁盘而崩溃。
## 结构化日志:让检索更高效
非结构化日志只能全文搜索。结构化日志可以按字段过滤:
```python
# Python - 用 structlog 输出 JSON
import structlog
logger = structlog.get_logger()
logger.info("user_login", user_id=123, ip="1.2.3.4")
```
```javascript
// Node.js - 用 pino 输出 JSON
const pino = require('pino')()
pino.info({ userId: 123, action: 'login' }, 'User logged in')
```
输出示例:
```json
{"level":"info","time":1704067200,"userId":123,"action":"login","msg":"User logged in"}
```
在日志聚合平台里可以按 `userId=123` 或 `action=login` 精确过滤,不用全文搜索。
## 日志级别管理
```bash
# 动态调整日志级别(不需要重启容器)
# Spring Boot
curl -X POST http://localhost:8080/actuator/loggers/com.example \
-d '{"configuredLevel": "DEBUG"}'
# Node.js(需要应用支持)
# 通过环境变量控制
LOG_LEVEL=debug node app.js
```
生产环境默认 INFO 级别,排查问题时临时切 DEBUG,不需要重新部署。
## 日志聚合:集中式管理
### 轻量方案:Grafana Loki
Loki 只索引标签不索引正文,存储成本是 ELK 的 1/10:
```yaml
services:
loki:
image: grafana/loki:2.9.0
ports:
- "3100:3100"
promtail:
image: grafana/promtail:2.9.0
volumes:
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- ./promtail.yml:/etc/promtail/config.yml
command: -config.file=/etc/promtail/config.yml
grafana:
image: grafana/grafana:10.3.0
ports:
- "3000:3000"
```
Promtail 自动从 Docker 容器目录读取日志,推送到 Loki。Grafana 查询:
```logql
{container_name="myapp"} |= "error" | json | level="error"
```
### 重量方案:EFK Stack
需要全文搜索、复杂聚合时用 EFK(Elasticsearch + Fluentd + Kibana):
```yaml
services:
elasticsearch:
image: elasticsearch:8.12.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
volumes:
- es_data:/usr/share/elasticsearch/data
fluentd:
image: fluent/fluentd:v1.16
volumes:
- ./fluentd/conf:/fluentd/etc
ports:
- "24224:24224"
kibana:
image: kibana:8.12.0
ports:
- "5601:5601"
```
容器配置 Fluentd 日志驱动:
```yaml
services:
app:
logging:
driver: fluentd
options:
fluentd-address: localhost:24224
tag: myapp
```
EFK 最少需要 4GB 内存。团队小于 10 人用 Loki 就够了。
## 日志管理最佳实践
| 检查项 | 建议 |
|--------|------|
| 日志轮转 | 全局配 `max-size: 10m, max-file: 3` |
| 日志级别 | 生产用 INFO,排查切 DEBUG |
| 结构化 | 用 JSON 格式输出 |
| 敏感信息 | 不在日志里打印密码、token |
| 聚合 | 小团队用 Loki,大团队用 EFK |
| 持久化 | 关键日志用 Volume 存储,不依赖容器可写层 |
| 监控 | 对 ERROR 日志设置告警 |
**起步建议**:先配好本地日志轮转(10 分钟的事),再按需加 Loki 聚合。
服务端6月6日 20:29
Docker 容器间怎么通信?同一项目、跨项目和跨主机方案两个容器要互相访问,怎么连通?同一个 compose 项目的容器用服务名直接访问,不同项目的容器需要共享网络,跨主机通信就得用 Overlay 网络。这篇按场景从简单到复杂讲清楚。
## 同一 docker-compose 项目:默认网络
Docker Compose 自动为每个项目创建一个网络,项目内的容器可以互相用服务名访问:
```yaml
# docker-compose.yml
services:
api:
image: myapp-api
ports:
- "3000:3000"
redis:
image: redis:7
postgres:
image: postgres:16
```
```bash
# api 容器内直接用服务名访问
curl http://redis:6379 # 访问 Redis
curl http://postgres:5432 # 访问 PostgreSQL
```
不需要 IP,不需要 `--link`——Docker 内置 DNS 自动把服务名解析为容器 IP。
### 自定义网络名
默认网络名是 `项目名_default`。如果要自定义:
```yaml
services:
api:
networks:
- frontend
- backend
redis:
networks:
- backend
networks:
frontend:
backend:
```
api 同时在 frontend 和 backend 两个网络里——可以访问两边。redis 只在 backend 里——frontend 网络的容器访问不到 redis,实现网络隔离。
## 不同 docker-compose 项目:外部网络
两个独立的 compose 项目需要通信时,共享一个外部网络:
```bash
# 创建共享网络
docker network create shared-net
```
```yaml
# 项目 A: docker-compose.yml
services:
api:
networks:
- shared-net
networks:
shared-net:
external: true
```
```yaml
# 项目 B: docker-compose.yml
services:
worker:
networks:
- shared-net
networks:
shared-net:
external: true
```
项目 A 的 api 和项目 B 的 worker 通过服务名互相访问。
## 容器访问宿主机
容器里需要访问宿主机上的服务(比如宿主机上的 MySQL):
```bash
# 专用 DNS 名
curl http://host.docker.internal:3306
```
`host.docker.internal` 是 Docker Desktop 提供的特殊 DNS,自动解析为宿主机 IP。Linux 上需要手动添加:
```yaml
services:
api:
extra_hosts:
- "host.docker.internal:host-gateway"
```
## 容器间直接用 IP
不推荐但有时需要:
```bash
# 查看容器 IP
docker inspect myapp --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
# 查看所有容器的 IP
docker network inspect bridge --format '{{range .Containers}}{{.Name}}: {{.IPv4Address}}{{end}}'
```
容器 IP 每次重启可能变化——硬编码 IP 是反模式,应该用服务名或 DNS。
## 端口映射:容器对外暴露服务
```yaml
services:
api:
ports:
- "3000:3000" # 宿主机 3000 → 容器 3000
- "8080:80" # 宿主机 8080 → 容器 80
- "127.0.0.1:3306:3306" # 只允许本机访问
```
`127.0.0.1:3306:3306` 这种写法限制了只监听 loopback 接口——外部无法访问,只有宿主机本身可以连。适合数据库等不需要对外暴露的服务。
### 端口冲突排查
```bash
# 查看宿主机端口占用
lsof -i :3000
# 或
ss -tlnp | grep 3000
# Docker 占用的端口
docker port myapp
```
## 网络模式选择
| 模式 | 说明 | 适用场景 |
|------|------|---------|
| bridge(默认) | 容器有独立 IP,通过 NAT 访问外部 | 大部分场景 |
| host | 容器直接用宿主机网络栈,无隔离 | 需要极致网络性能 |
| none | 无网络 | 离线计算任务 |
| overlay | 跨主机容器通信 | Docker Swarm / 多主机 |
### host 模式
```yaml
services:
api:
network_mode: host
```
host 模式下容器没有独立 IP,直接用宿主机的端口和网络。好处是没有 NAT 性能损耗,坏处是端口冲突风险高(容器和宿主机共享端口空间)。
**不要在生产环境用 host 模式**——失去了网络隔离,一个容器被攻破等于宿主机被攻破。
## 跨主机通信:Overlay 网络
Docker Swarm 多主机环境下,不同主机上的容器需要通信:
```bash
# 创建 Overlay 网络
docker network create -d overlay my-overlay
# 在 Overlay 网络上启动服务
docker service create --network my-overlay --name api myapp
docker service create --network my-overlay --name worker myworker
```
Overlay 网络底层用 VXLAN 隧道——api 和 worker 即使跑在不同的物理机上,也能通过服务名直接通信,和单机体验一致。
## DNS 排查
容器间访问不通时,先排查 DNS:
```bash
# 进入容器测试 DNS 解析
docker exec myapp nslookup redis
docker exec myapp ping redis
# 查看 DNS 配置
docker exec myapp cat /etc/resolv.conf
# 临时指定 DNS
docker run --dns 8.8.8.8 myapp
```
常见问题:自定义了 `docker-compose.yml` 的 `networks` 但忘了在服务里引用,或者两个服务不在同一个网络里。
服务端6月6日 20:23
Docker 容器文件系统怎么工作?分层和 Copy-on-Write 机制容器里写了一个文件,它到底存在哪里?删掉容器后文件去哪了?为什么镜像层是只读的但容器可以写入?理解 Docker 文件系统的工作原理,这些问题就都清楚了。
## 从镜像到容器:分层文件系统
Docker 镜像不是一个大文件——它是多层只读文件系统的堆叠。每个 Dockerfile 指令产生一层:
```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 │ = 镜像的所有只读层
└──────────────┘
```
### 在宿主机上的实际位置
```bash
# Docker 的存储根目录
ls /var/lib/docker/overlay2/
# 每个镜像层一个目录
/var/lib/docker/overlay2/<layer-id>/
diff/ # 这一层的文件内容
link # 指向层的短链接
lower # 指向下层层的链接
merged/ # 合并视图(容器运行时才出现)
work/ # OverlayFS 工作目录
```
### 查看容器的 overlay2 层
```bash
# 找到容器的 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:
```yaml
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 │ ← 共享!只存一份
└──────────────┘ └──────────────┘
```
```bash
# 查看 Docker 磁盘占用
docker system df -v
# SHARED SIZE 列显示被多个镜像共享的大小
# UNIQUE SIZE 列显示该镜像独有的层大小
```
## 镜像构建优化:减少层和大小
### 合并 RUN 指令
```dockerfile
# 差:每条 RUN 一层
RUN apt-get update
RUN apt-get install -y python3
RUN apt-get install -y pip
# 好:合并成一层
RUN apt-get update && \
apt-get install -y python3 pip && \
rm -rf /var/lib/apt/lists/*
```
合并的好处:
1. 层数减少
2. `rm -rf /var/lib/apt/lists/` 在同一层执行——删除操作真的释放了空间,而不是在下一层标记为已删除
### 多阶段构建
```dockerfile
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
FROM node:20-alpine
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
```
最终镜像只有 `dist/` 和 `node_modules/`——没有源码、没有构建工具,体积缩小 60-80%。
### .dockerignore 减少构建上下文
```
# .dockerignore
.git
node_modules
.env
*.md
test/
```
`.dockerignore` 排除的文件不会进入构建上下文——既加快构建,又防止敏感文件进镜像。
## 常见文件系统问题
### 镜像越来越大
```bash
# 查看每层大小
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 的原因之一。
服务端6月6日 20:23
Docker 容器出问题了怎么排查?分层排查流程容器启动失败、运行中崩溃、网络不通、数据丢失——Docker 故障排查有一套固定流程,按层级从外到内逐步缩小范围。
## 排查流程总览
```
容器状态异常?
├── 容器没启动 → 查 docker logs
├── 容器运行但行为异常 → 查 docker logs + docker exec
├── 容器网络不通 → 查 docker network + 端口映射
├── 容器数据问题 → 查 Volume 挂载
└── 宿主机资源不足 → 查 docker stats + 系统资源
```
## 第一步:确认容器状态
```bash
# 查看所有容器(包括停止的)
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
```bash
# 查看容器日志
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
```
### 容器已经退出了?
```bash
# 已退出的容器仍然可以查日志
docker logs myapp # 只要容器没被 docker rm 删掉
# 如果容器被删了,日志文件还在(json-file 驱动)
ls /var/lib/docker/containers/<container-id>/
# <container-id>-json.log
```
### 日志里什么都没有?
应用可能把日志写到了文件而不是 stdout/stderr。Docker 只捕获标准输出/错误流:
```bash
# 进入容器查看日志文件
docker exec -it myapp sh
cat /app/logs/app.log
```
**最佳实践**:让应用把日志输出到 stdout/stderr,这样 `docker logs` 直接能看到。不要写到容器内的文件——容器删除后日志也丢了。
## 第三步:进入容器排查
```bash
# 进入容器执行命令
docker exec -it myapp sh
# 如果容器没有 sh,用 bash
docker exec -it myapp bash
# 如果都没有(alpine 镜像可能只有 sh)
docker exec -it myapp /bin/sh
# 不进入容器,直接执行命令
docker exec myapp ps aux
docker exec myapp cat /etc/config.yml
```
容器里能做的事:
- `ps aux` 看进程
- `top` 看 CPU/内存
- `cat /etc/hosts` 看网络配置
- `curl localhost:3000` 测试内部端口
- `env` 看环境变量
### 容器一启动就退出了怎么办
`docker exec` 需要容器在运行状态。容器一启动就退出的情况,用覆盖入口点的方式:
```bash
# 覆盖 CMD,保持容器运行
docker run -it --entrypoint sh myapp:latest
# 或者在 Dockerfile 末尾临时加
# CMD ["sleep", "3600"]
```
进入容器后手动执行原来的启动命令,观察报错。
## 第四步:排查网络问题
### 容器端口没暴露
```bash
# 检查端口映射
docker port myapp
# 3000/tcp -> 0.0.0.0:3000
# 如果没有输出,说明没做端口映射
# 重新启动时加 -p
docker run -d -p 3000:3000 myapp
```
### 容器间网络不通
```bash
# 查看容器的网络
docker inspect myapp --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
# 查看所有 Docker 网络
docker network ls
# 同一网络的容器可以互相通过容器名访问
# 不同网络的容器互相隔离
```
```bash
# 测试容器间连通性
docker exec myapp ping redis
docker exec myapp curl http://api-service:8080/health
```
### DNS 解析失败
```bash
# 查看容器的 DNS 配置
docker exec myapp cat /etc/resolv.conf
# 自定义 DNS
docker run --dns 8.8.8.8 myapp
```
## 第五步:排查 Volume 问题
### 文件没有出现在容器内
```bash
# 检查 Volume 挂载
docker inspect myapp --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'
# /host/path -> /container/path
```
常见原因:
- 宿主机路径写错了(相对路径问题)
- 文件权限不匹配(容器内用户没读权限)
- SELinux 阻止访问(加 `:z` 或 `:Z` 后缀)
```bash
# SELinux 环境
docker run -v /host/path:/container/path:z myapp # 多容器共享
docker run -v /host/path:/container/path:Z myapp # 单容器专用
```
### 容器内修改没有反映到宿主机
检查是不是挂载方向反了,或者挂载的是文件而不是目录:
```bash
# 错误:挂载不存在的文件,Docker 会创建一个目录
docker run -v ./config.yml:/app/config.yml myapp
# 如果 ./config.yml 不存在,Docker 会创建 /app/config.yml 目录
# 正确:确保文件先存在
touch ./config.yml
docker 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 镜像排查:
```bash
# 把镜像临时换成有工具的版本
docker run -d --name myapp-debug \
--entrypoint sh \
-p 3000:3000 \
myapp:latest -c "sleep 3600"
```
### docker diff 看文件变更
```bash
# 查看容器内哪些文件被修改了
docker diff myapp
# A /app/newfile.txt → Added
# C /etc/config.yml → Changed
# D /tmp/old.log → Deleted
```
### docker events 实时监控
```bash
# 监控 Docker 事件(容器启停、OOM、健康检查等)
docker events --filter container=myapp
```
服务端6月6日 20:23
Docker 容器怎么接入 CI/CD?GitHub Actions 和 GitLab CI 集成代码提交后自动构建镜像、跑测试、部署——这就是 CI/CD 和 Docker 的结合。Docker 让构建环境一致、部署产物标准化,CI/CD 让这一切自动运行。这篇讲 Docker 在主流 CI/CD 平台中的集成方式。
## Docker 在 CI/CD 中的三个角色
1. **构建环境**:CI Runner 本身跑在 Docker 容器里,保证每次构建环境一致
2. **构建产物**:`docker build` 产出镜像,推到 Registry,部署时直接拉镜像
3. **部署目标**:生产环境拉镜像启动容器,不需要在服务器上装运行时
## GitHub Actions + Docker
### 基本构建和推送
```yaml
# .github/workflows/deploy.yml
name: Build and Push Docker Image
on:
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=max
```
`cache-from: type=gha` 用 GitHub Actions 的缓存加速构建——没有变化的层直接复用,不用重新构建。首次构建可能要 5 分钟,后续只要 1-2 分钟。
### 多阶段构建减小镜像
```dockerfile
# 构建阶段
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 运行阶段——只有产物,没有源码和 devDependencies
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
```
多阶段构建让最终镜像只有运行时需要的文件——从 1GB+ 缩小到 200MB 以下。
## GitLab CI + Docker
### Docker-in-Docker 构建
```yaml
# .gitlab-ci.yml
build:
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_SHA
```
`docker:24-dind` 服务启动一个 Docker 守护进程,让 CI 容器里可以执行 `docker build`。注意要加 `DOCKER_TLS_CERTDIR` 确保通信安全。
### 使用 GitLab Container Registry
GitLab 自带 Container Registry,不用额外配 Docker Hub:
```yaml
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
```
`$CI_REGISTRY_IMAGE` 是 GitLab 预设变量,指向当前项目的 Registry 地址。
## 镜像标签策略
```bash
# 不推荐:只用 latest
myapp:latest
# 推荐:语义化版本 + Git SHA 双标签
myapp:1.2.3
myapp:sha-abc1234
# 回滚时指定版本
docker pull myapp:1.2.2
```
| 标签 | 用途 | 示例 |
|------|------|------|
| `latest` | 默认最新版 | 开发环境用 |
| 语义化版本 | 生产部署 | `1.2.3` |
| Git SHA | 精确追溯 | `sha-abc1234` |
| 分支名 | 预览环境 | `main`, `staging` |
**生产环境永远用精确版本号或 SHA,不用 `latest`**——`latest` 指向的镜像随时可能变,出了问题无法回滚。
## 部署阶段
### SSH 部署到服务器
```yaml
# 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`:
```yaml
# 服务器上的 docker-compose.yml
services:
app:
image: myorg/myapp:latest
ports:
- "3000:3000"
restart: unless-stopped
```
```bash
# CI 部署脚本
ssh deploy@server "cd /app && docker compose pull && docker compose up -d"
```
### 零停机部署
上面的方式会短暂中断服务。用双容器切换实现零停机:
```bash
# 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 指向 3001
docker 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` 代替 `ubuntu`
- **Secret 不进镜像**:数据库密码等通过环境变量或 Secret 注入,不写进 Dockerfile
- **`.dockerignore` 排除无关文件**:`.git`、`node_modules`、`.env` 不应该进构建上下文
```
# .dockerignore
.git
node_modules
.env
*.md
.dockerignore
```
服务端6月6日 20:23
Docker 存储驱动该选哪个?overlay2 和其他驱动的区别Docker 镜像的每一层怎么存、容器怎么在只读层上写入、不同存储驱动有什么性能差异——理解存储驱动的工作原理,能帮你解决容器写入慢、镜像占磁盘、数据丢失这些问题。
## 容器文件系统的分层结构
Docker 镜像由多个只读层组成,容器启动时在最上面加一层可写层:
```
┌─────────────────────┐
│ 可写层(容器层) │ ← docker run 产生的可写层
├─────────────────────┤
│ 镜像层 3(App) │ ← COPY / RUN 指令产生的层
├─────────────────────┤
│ 镜像层 2(Runtime)│
├─────────────────────┤
│ 镜像层 1(OS 基础) │
└─────────────────────┘
```
- **只读层**:镜像的每一层都是只读的,多个容器可以共享同一组只读层
- **可写层**:容器的所有修改(新建文件、修改配置、写日志)都写在这层
- 容器删除后,可写层一起消失——这就是为什么容器内写的数据会丢
### Copy-on-Write 机制
容器修改镜像中的文件时,不是直接改只读层——而是把文件复制到可写层再修改:
```bash
# 镜像里有个 /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 的默认存储驱动,也是性能最好的:
```bash
# 查看当前存储驱动
docker info --format '{{.Driver}}'
# overlay2
```
overlay2 的工作方式:
```
lowerdir(只读层)= 镜像的所有层
upperdir(可写层)= 容器的修改
merged = 合并视图(容器看到的文件系统)
```
优势:
- 只有一层 lowerdir 目录,不像老版 overlay 有多层,inode 消耗少
- 页缓存共享——多个容器读同一个文件只占一份内存
- build 性能好——Docker Build 时层操作都是 O(1)
**99% 的场景用 overlay2 就对了**,不需要换。
### devicemapper:老系统的选择
RHEL/CentOS 6 时代内核不支持 OverlayFS,只能用 devicemapper。有两种模式:
- **loopback**(默认):用文件模拟块设备,**性能差,生产环境不能用**
- **direct-lvm**:直接用 LVM 块设备,性能可以
```json
// /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。
## 存储驱动和磁盘占用的关系
每个存储驱动管理镜像层的方式不同,磁盘占用差异很大:
```bash
# 查看 Docker 占用的磁盘
docker system df
# 详细信息
docker system df -v
```
```
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 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** 是可以回收的空间。清理命令:
```bash
# 清理悬空镜像(没有标签的)
docker image prune
# 清理所有未使用的镜像
docker image prune -a
# 清理构建缓存
docker builder prune
# 一键全清
docker system prune -a
```
## Volume:数据持久化的正确方式
容器可写层的数据会随容器删除而丢失。需要持久化的数据(数据库、日志、配置)用 Volume:
```yaml
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 # 命名 Volume
volumes:
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**——它已经是默认选项了。除非有明确的特殊需求,否则不需要改存储驱动。
服务端6月6日 20:23
Docker 容器资源怎么监控?从 docker stats 到 Prometheus 的完整方案容器跑着跑着内存爆了、CPU 拉满了、磁盘写满了——这些问题的根源都能通过资源监控提前发现。Docker 提供了从命令行到 API 多层级的资源监控手段,这篇按从简单到复杂的顺序讲清楚。
## docker stats:最快上手
```bash
# 查看所有运行中容器的资源使用
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/O
myapp 12.5% 256MiB / 4GiB 6.25% 1.2MB / 340kB 45MB / 0B
redis 0.3% 8MiB / 512MiB 1.56% 56kB / 32kB 0B / 0B
```
`MEM USAGE / LIMIT` 最关键——如果 Usage 接近 Limit,说明容器快 OOM 了。`BLOCK I/O` 高说明磁盘读写频繁,可能是日志写入太多或数据库查询没走缓存。
**局限**:`docker stats` 只能看实时数据,没有历史趋势。要看趋势得上 Prometheus + Grafana。
## 设置资源限制:监控的前提
监控资源使用的目的是发现问题,而限制资源是防止问题蔓延:
```yaml
# docker-compose.yml
services:
app:
image: myapp:latest
deploy:
resources:
limits:
cpus: '2.0' # 最多用 2 核
memory: 4G # 内存上限 4GB
reservations:
cpus: '0.5' # 保底 0.5 核
memory: 1G # 保底 1GB
```
```bash
# 命令行方式
docker run -d --name myapp \
--cpus=2.0 \
--memory=4g \
--memory-swap=4g \ # 禁止 swap
myapp:latest
```
`--memory-swap=4g` 等于 `--memory` 的值意味着容器不能用 swap——全部用物理内存。OOM 时容器会被直接杀掉而不是被 swap 拖慢。
### CPU 限制的两种方式
```bash
# 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 文件可以拿到最原始的数据:
```bash
# 容器的内存使用(字节)
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:程序化监控
```bash
# 获取容器资源使用统计(JSON 格式)
curl --unix-socket /var/run/docker.sock \
http://localhost/containers/<container-id>/stats?stream=false
```
返回的 JSON 包含 CPU、内存、网络、磁盘 IO 的详细数据。适合自建监控脚本:
```python
import docker
client = 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 展示:
```yaml
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` 会记录原因:
```bash
docker inspect <container-id> --format '{{.State.OOMKilled}}'
# true 说明是 OOM 杀的
```
处理步骤:
1. 查看内存限制是否太小——`docker stats` 看 MEM USAGE 是否经常顶到 LIMIT
2. 分析内存泄漏——进入容器 `docker exec -it <id> sh`,用 `top` 或 `ps aux --sort=-%mem` 找内存大户
3. 临时解决:加大内存限制 `docker update --memory=8g <container-id>`
4. 根本解决:修复应用的内存泄漏
## 监控方案选择
| 场景 | 方案 | 成本 |
|------|------|------|
| 临时排查 | `docker stats` | 零成本 |
| 自建脚本监控 | Docker API + Python | 低 |
| 小团队日常监控 | cAdvisor + Prometheus + Grafana | 中 |
| 生产环境 | 完整监控栈 + Alertmanager | 高 |
**起步建议**:先用 `docker stats` 日常看一眼,发现问题了再搭 Prometheus 长期监控。
服务端6月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 代替:
```yaml
services:
app:
volumes:
- .:/app # 代码目录挂载
- /app/node_modules # node_modules 用容器内的,不走文件共享
```
## 日常使用的核心功能
### 镜像管理
```
Docker Desktop → Images
```
可以搜索、拉取、删除镜像,查看镜像层信息。比命令行更直观——特别是看哪个镜像占了多少磁盘。
### 容器管理
```
Docker Desktop → Containers
```
启动、停止、删除容器,查看日志,进入容器终端。小技巧:点击容器的端口号可以直接在浏览器打开。
### Volume 管理
```
Docker Desktop → Volumes
```
查看所有 Docker Volume 占用的磁盘空间。容器删了 Volume 不会自动删——时间久了会积累大量废弃数据。定期清理:
```bash
docker volume prune # 删除所有未被容器引用的 volume
```
### 构建缓存清理
```bash
docker builder prune # 清理构建缓存
docker system prune -a # 一键清理所有未使用的资源(镜像、容器、网络、缓存)
```
Docker Desktop 的 Troubleshoot 页面也有 "Clean / Purge data" 按钮——重置整个 Docker 环境。
## Kubernetes 支持
Docker Desktop 内置了单节点 Kubernetes 集群,一键开启:
```
Settings → Kubernetes
✅ Enable Kubernetes
```
开启后 `kubectl` 直接可用:
```bash
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 Extensions
Docker Desktop 支持扩展,常用的几个:
| 扩展 | 功能 |
|------|------|
| Disk Usage | 可视化磁盘占用分析 |
| Trivy | 镜像安全扫描 |
| DDEV | 本地开发环境管理 |
| Tilt | 实时开发工作流 |
安装方式:Extensions → Browse → Install
## Docker 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`)只会增大不会自动缩小。即使删了镜像,虚拟磁盘文件也不会缩小。解决:
```bash
# Mac:压缩虚拟磁盘
docker system prune -a
# 然后重启 Docker Desktop → Troubleshoot → Clean / Purge data
```
### 容器网络访问宿主机服务
容器里访问宿主机(比如宿主机上的数据库):
```bash
# 从容器内访问宿主机
curl http://host.docker.internal:5432
```
`host.docker.internal` 是 Docker Desktop 提供的特殊 DNS 名,自动解析为宿主机 IP。
服务端6月5日 22:29
Docker 反向代理该用 Nginx 还是 Traefik?部署和自动发现对比Docker 部署反向代理的核心问题是:容器 IP 每次重启都会变,手动配 upstream 不现实。Nginx 需要手动更新配置,Traefik 能自动发现容器。选哪个取决于你的场景。
## Nginx:手动配置但性能最强
### 基本反向代理
```yaml
# docker-compose.yml
services:
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:
- frontend
networks:
frontend:
```
```nginx
# nginx/conf.d/default.conf
upstream 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)
手动获取证书:
```bash
certbot certonly --standalone -d example.com
```
```nginx
server {
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 什么时候合适
- 流量非常大,需要极致性能
- 配置不频繁变动
- 团队熟悉 Nginx
## Traefik:自动发现的反向代理
Traefik 的核心卖点:容器启动/停止时自动更新路由,不需要改配置文件或重启代理。
### 基本配置
```yaml
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)
```yaml
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 自动负载均衡:
```yaml
app1:
deploy:
replicas: 3
labels:
- "traefik.http.services.app1.loadbalancer.server.port=3000"
```
3 个副本自动分摊流量,扩容缩容 Traefik 实时感知。
## Caddy:最简配置
Caddy 的卖点是一个 Caddyfile 搞定反向代理 + 自动 HTTPS:
```yaml
services:
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
volumes:
caddy_data:
```
```
# Caddyfile
app1.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。
服务端6月5日 22:29
Docker 容器怎么监控?Prometheus + Grafana 完整方案容器出问题了,`docker stats` 只能看到 CPU 和内存——磁盘 IO、网络、进程状态全不知道。完整的监控系统需要指标采集、存储、可视化、告警四层。
## Docker 监控的四层架构
```
容器 → 采集器 → 存储后端 → 可视化
cAdvisor Prometheus Grafana
Node Loki
Exporter
```
- **采集层**:从容器和宿主机收集指标数据
- **存储层**:时序数据库存指标,日志库存日志
- **可视化层**:Dashboard 展示趋势、图表
- **告警层**:超过阈值自动通知
## 指标采集:cAdvisor + Node Exporter
### cAdvisor——容器指标
Google 出品,自动发现所有容器,采集 CPU、内存、网络、磁盘 IO:
```yaml
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:ro
```
cAdvisor 自带一个简单的 Web UI(`http://localhost:8080`),可以看每个容器的实时指标。
### Node Exporter——宿主机指标
cAdvisor 只管容器,宿主机本身的 CPU、内存、磁盘、网络由 Node Exporter 采集:
```yaml
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 拉取指标,存到自己的时序数据库:
```yaml
services:
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
volumes:
prometheus_data:
```
```yaml
# prometheus/prometheus.yml
global:
scrape_interval: 15s
scrape_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 查询
```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——可视化 Dashboard
Prometheus 的 UI 只适合临时查询。正式的监控面板用 Grafana:
```yaml
services:
grafana:
image: grafana/grafana:10.3.0
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
```
配置步骤:
1. 打开 `http://localhost:3000`(默认 admin/admin)
2. 添加数据源 → Prometheus → URL 填 `http://prometheus:9090`
3. 导入 Dashboard:推荐 ID **11600**(Docker 监控)和 **1860**(Node Exporter 全量)
导入方式:Dashboard → Import → 输入 ID → Load → 选择 Prometheus 数据源
## 告警:Alertmanager
Prometheus 本身只负责判断规则,告警通知由 Alertmanager 发送:
```yaml
# prometheus/alert_rules.yml
groups:
- 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: 10m
```
`for: 5m` 表示持续 5 分钟才告警——避免短暂波动误报。
## 完整的 docker-compose 监控栈
```yaml
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/alertmanager
volumes:
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 三件套,够用了再加告警。
服务端6月5日 22:29
Docker 容器日志怎么聚合?ELK、Loki 和 EFK 怎么选容器一多,日志分散在各处——`docker logs` 只能看单个容器的输出,排查问题要一个一个翻。日志聚合把所有容器的日志集中到一个地方,搜索、过滤、告警一站搞定。
## Docker 日志驱动:日志的入口
Docker 支持多种日志驱动,决定容器日志的去向:
```bash
# 查看当前日志驱动
docker info --format '{{.LoggingDriver}}'
# 默认是 json-file
```
```yaml
# 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,功能最全但最重:
```yaml
# docker-compose.yml
services:
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: myapp
volumes:
es_data:
```
**问题**:ELK 最少需要 4GB 内存才能跑起来。小团队或开发环境用太重了。
## EFK Stack:轻量替代
用 Fluentd 替代 Logstash,更省资源:
```yaml
services:
fluentd:
image: fluent/fluentd:v1.16
volumes:
- ./fluentd/conf:/fluentd/etc
ports:
- "24224:24224"
environment:
- FLUENTD_CONF=fluent.conf
```
```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 倍以上:
```yaml
# docker-compose.yml
services:
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=admin
```
Promtail 从 Docker 容器日志目录读取日志,推送到 Loki。Grafana 查询和可视化。
**Loki 的查询语法**(LogQL)比 Kibana 的 KQL 简单:
```logql
{container_name="myapp"} |= "error" | json | line_format "{{.message}}"
```
意思是:从 myapp 容器日志中过滤包含 "error" 的行,解析 JSON,只显示 message 字段。
## 日志结构化:让聚合更有效
非结构化日志在聚合平台里只能做全文搜索。结构化日志可以按字段过滤、聚合统计:
```python
# Python 结构化日志(JSON 格式)
import logging
import json
class 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),
})
```
```javascript
// Node.js 用 pino 直接输出 JSON
const 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。
服务端6月5日 22:29
Docker 容器配置怎么管理?环境变量、挂载和配置中心怎么选Docker 容器的配置管理不是把配置写死在镜像里——那样每次改配置都得重新构建。正确做法是配置与镜像分离,容器启动时注入配置,运行时能动态更新。这篇讲清楚 Docker 环境下四种配置管理方式的适用场景和实现方法。
## 环境变量:最简单的配置注入
适合少量、扁平的配置项(数据库地址、端口、开关):
```yaml
# docker-compose.yml
services:
app:
image: myapp:latest
environment:
- DB_HOST=postgres
- DB_PORT=5432
- LOG_LEVEL=info
- FEATURE_FLAG=true
```
或者用 `.env` 文件:
```bash
# .env
DB_HOST=postgres
DB_PORT=5432
LOG_LEVEL=info
```
```yaml
services:
app:
env_file: .env
```
**优势**:简单、Docker 原生支持、docker-compose 直接读取
**局限**:只有字符串值、不能表示嵌套结构、修改需要重启容器
### 敏感信息别放环境变量
环境变量会被 `docker inspect` 暴露,也会出现在进程列表里。密码、密钥等用 Docker Secret 或文件挂载:
```bash
# Docker Swarm Secret
echo "my_password" | docker secret create db_password -
```
```yaml
services:
app:
secrets:
- db_password
secrets:
db_password:
external: true
```
## 配置文件挂载:复杂配置的首选
当配置是结构化的(YAML、JSON、TOML),用 Volume 挂载比环境变量更合适:
```yaml
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` 表示只读挂载——容器不能修改配置文件,只能读取。防止容器内进程意外修改配置。
### 只挂载需要的文件,别挂载整个目录
```yaml
# 好:只挂载需要的文件
volumes:
- ./config/app.yml:/app/config/app.yml:ro
# 差:挂载整个目录,可能暴露无关文件
volumes:
- ./config:/app/config:ro
```
### 配置热更新:不重启容器更新配置
挂载的文件修改后,容器内立即可见——但应用是否自动重新加载取决于应用本身:
- **Nginx**:`docker exec nginx nginx -s reload`
- **Spring Boot**:配合 Spring Cloud Config 自动刷新
- **Node.js**:用 `chokidar` 监听文件变化
如果应用不支持热加载,可以配合 `inotifywait` 检测文件变化后发送信号:
```bash
#!/bin/bash
inotifywait -m -e modify /app/config/app.yml | while read event; do
kill -SIGHUP 1 # 发送 HUP 信号给主进程
done
```
## 配置中心:分布式系统的统一配置
多服务、多实例的场景,配置文件挂载管理成本太高——改一个配置要同步到所有机器。配置中心解决的就是这个问题。
### Consul + Consul-Template
```yaml
# docker-compose.yml
services:
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 做模板渲染:
```bash
# 写入配置
etcdctl set /myapp/db_host "postgres.prod"
# confd 读取 etcd 并渲染模板
confd -onetime -backend etcd -node http://etcd:2379
```
### Spring Cloud Config Server
Java 生态的标准方案:
```yaml
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 管理:
```yaml
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DB_HOST: postgres
app.yml: |
server:
port: 8080
logging:
level: info
---
# Pod 使用 ConfigMap
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config
volumeMounts:
- name: config
mountPath: /app/config
volumes:
- name: config
configMap:
name: app-config
```
Secret 和 ConfigMap 用法一样,但值是 base64 编码的,且访问可以加 RBAC 控制:
```bash
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=s3cret
```
**ConfigMap/Secret 更新后 Pod 不会自动重启**——需要手动 `kubectl rollout restart` 或用 Reloader 之类的工具自动触发。
## 选择决策
| 场景 | 推荐方案 | 原因 |
|------|---------|------|
| 单容器、少量配置 | 环境变量 + .env | 最简单 |
| 单容器、复杂配置 | Volume 挂载配置文件 | 支持结构化配置 |
| 多容器、同一台主机 | docker-compose + 挂载 | compose 管理方便 |
| 多主机、多服务 | Consul/Etcd 配置中心 | 统一管理、动态更新 |
| Kubernetes 环境 | ConfigMap + Secret | K8s 原生方案 |
| 敏感信息 | Docker Secret / K8s Secret | 不暴露在环境变量里 |
**原则**:配置与代码分离,敏感信息加密,变更可追溯。能用简单的就不用复杂的——别为了用 Consul 而用 Consul。
服务端6月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。服务端6月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
- 纯计算/安全隔离:none
- Swarm 集群:overlay
- 生产环境:K8s 管网络,不需要选 Docker 网络模式服务端6月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 标记每个镜像。服务端6月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 提供应用级隔离(部署灵活性)。