标签

Docker

Docker 是一个开源的容器化平台,允许开发者打包应用及其全部依赖到一个可移植的容器中,然后这个容器可以在任何机器上运行,确保应用在不同环境之间运行的一致性。Docker 使用了 Linux 容器(LXC)的技术,但进行了扩展,使其更为易用、功能更全面。

Docker
查看更多相关内容
服务端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 够用且省心。
服务端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 网络模式有哪些?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 提供应用级隔离(部署灵活性)。