部署与运维

服务器运维笔记:Docker 容器化部署与代理配置

本文是服务器运维系列第七篇,聚焦 Docker 容器化部署的方方面面:从基础原理到生产级实战,涵盖安装、编排、网络、存储、安全与监控,并单独讲清 daemon 与容器两个层级的代理配置,适合作为随手翻阅的运维手册。

本文是服务器运维系列第七篇,聚焦 Docker 容器化部署的方方面面:从基础原理到生产级实战,涵盖安装、编排、网络、存储、安全与监控,并单独讲清 daemon 与容器两个层级的代理配置,适合作为随手翻阅的运维手册。


Docker 基础

架构原理

Docker 采用 Client-Server 架构,核心组件如下:

  • Docker Client:命令行工具 docker,通过 REST API 与 Daemon 通信
  • Docker Daemon(dockerd):后台守护进程,负责构建、运行、分发容器
  • containerd:容器运行时,管理容器生命周期
  • runc:底层 OCI 运行时,真正调用 Linux 内核的 namespace 和 cgroup
plaintext18 行
┌──────────┐     REST API     ┌──────────────┐
│  docker  │ ──────────────── │   dockerd     │
│  (CLI)   │                  │  (Daemon)     │
└──────────┘                  └──────┬───────┘
                                     │ gRPC
                              ┌──────▼───────┐
                              │  containerd  │
                              └──────┬───────┘
                                     │
                              ┌──────▼───────┐
                              │    runc      │
                              └──────┬───────┘
                                     │
                              ┌──────▼───────┐
                              │ Linux Kernel │
                              │ namespace    │
                              │ cgroup       │
                              └──────────────┘

镜像 vs 容器

概念 说明
镜像(Image) 只读模板,由多个层(layer)叠加而成,类似 Git 的 commit 历史
容器(Container) 镜像的运行实例,在只读层之上添加可写层(Copy-on-Write)
层(Layer) RUN、COPY 等文件系统变更形成层,ENV 等指令主要修改镜像配置;构建方式会影响最终层结构

类比理解:镜像是“菜谱”,容器是“做出来的菜”,同一个菜谱可以做出无数份菜。

仓库(Registry)

  • Docker Hub:官方公共仓库,docker pull nginx 默认从这里拉取

  • 私有仓库:Harbor、阿里云 ACR、腾讯云 TCR 等

  • 仓库地址格式:<registry>/<namespace>/<repository>:<tag>

    • 例:harbor.example.com/prod/nginx:1.25-alpine

安装部署

Ubuntu 安装(Debian 使用独立软件源)

plaintext23 行
# 卸载旧版本
sudo apt remove docker docker-engine docker.io containerd runc

# 安装依赖
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release

# 添加 Docker 官方 GPG Key
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# 添加仓库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装 Docker Engine
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 验证安装
sudo docker run hello-world

CentOS / RHEL 安装

plaintext17 行
# 卸载旧版本
sudo yum remove docker docker-client docker-client-latest docker-common \
  docker-latest docker-latest-logrotate docker-logrotate docker-engine

# 安装 yum-utils
sudo yum install -y yum-utils

# 添加仓库(国内建议用阿里云镜像)
sudo yum-config-manager --add-repo \
  https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 安装 Docker Engine
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 启动并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker

国内镜像源配置

国内拉取 Docker Hub 镜像速度慢,配置镜像加速器:

plaintext12 行
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com",
    "https://hub-mirror.c.163.com",
    "https://mirror.aliyuncs.com"
  ]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

验证加速器是否生效:

plaintext1 行
docker info | grep -A 5 "Registry Mirrors"

国内可用的镜像源经常变更或失效,建议多配几个做备份,失败时 daemon 会自动切换到下一个可用的地址。

daemon.json 生产优化

plaintext18 行
{
  "registry-mirrors": ["https://mirror.ccs.tencentyun.com"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "default-address-pools": [
    {"base": "172.20.0.0/16", "size": 24}
  ],
  "live-restore": true,
  "max-concurrent-downloads": 10,
  "max-concurrent-uploads": 5,
  "default-ulimits": {
    "nofile": { "Name": "nofile", "Hard": 65535, "Soft": 65535 }
  }
}

关键参数说明:

  • live-restore:Daemon 重启时保持容器运行,避免业务中断
  • log-opts:限制日志文件大小,防止磁盘被撑爆
  • default-address-pools:自定义容器网络地址池,避免与宿主机网段冲突
  • max-concurrent-downloads:并行拉取镜像层数,提升效率

Dockerfile 编写

基础语法

plaintext26 行
# 基础镜像
FROM node:24-alpine

# 维护者信息
LABEL maintainer="ops@example.com"

# 设置工作目录
WORKDIR /app

# 复制依赖文件(利用层缓存)
COPY package.json package-lock.json ./

# 安装依赖
RUN npm ci --only=production

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 3000

# 设置环境变量
ENV NODE_ENV=production

# 启动命令
CMD ["node", "server.js"]

多阶段构建

多阶段构建可以显著减小最终镜像体积,编译工具不会出现在生产镜像中。

plaintext19 行
# ===== 第一阶段:构建 =====
FROM golang:1.21-alpine AS builder

WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o app .

# ===== 第二阶段:运行 =====
FROM alpine:3.19

RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY --from=builder /build/app .

EXPOSE 8080
CMD ["./app"]

效果对比:

  • 单阶段(golang:1.21):~800MB
  • 多阶段(alpine:3.19):~15MB

层缓存优化

核心原则:把变化频率低的指令放前面,变化频率高的放后面。

plaintext8 行
#  正确做法:先复制依赖描述文件,再复制源码
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

#  错误做法:每次代码改动都要重新安装依赖
COPY . .
RUN npm ci

进阶技巧:

plaintext7 行
# 使用 BuildKit 的缓存挂载,避免重复下载
RUN --mount=type=cache,target=/root/.npm npm ci --only=production

# 使用缓存挂载加速 apt
RUN --mount=type=cache,target=/var/cache/apt \
    --mount=type=cache,target=/var/lib/apt/lists \
    apt-get update && apt-get install -y curl

安全最佳实践

plaintext20 行
# 使用最小基础镜像
FROM alpine:3.19

# 不以 root 运行
RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -s /bin/sh -D appuser

# 安装依赖后清理缓存
RUN apk --no-cache add curl ca-certificates && \
    rm -rf /tmp/* /var/cache/apk/*

# 设置只读文件系统(配合 --read-only 运行)
WORKDIR /app
COPY --chown=appuser:appgroup . .

# 切换到非 root 用户
USER appuser

# 使用 exec 格式,信号正确传递
CMD ["./app"]

.dockerignore

.dockerignore 文件排除不需要的文件,减小构建上下文大小:

plaintext18 行
# .dockerignore
.git
.gitignore
node_modules
npm-debug.log
Dockerfile
docker-compose*.yml
.env
.env.*
*.md
.vscode
.idea
coverage
test
tests
__pycache__
*.pyc
.dockerignore

Docker Compose

核心语法

Docker Compose 使用 YAML 文件定义和运行多容器应用。

yaml84 行
# docker-compose.yml

services:
  web:
    build:
      context: ./web
      dockerfile: Dockerfile
    ports:
      - "8080:80"
    environment:
      - NODE_ENV=production
    depends_on:
      api:
        condition: service_healthy
    networks:
      - frontend
    restart: unless-stopped

  api:
    image: myapp/api:latest
    ports:
      - "3000:3000"
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - frontend
      - backend
    restart: unless-stopped

  db:
    image: mysql:8.0
    volumes:
      - mysql_data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
      MYSQL_DATABASE: myapp
    secrets:
      - db_root_password
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    networks:
      - backend
    restart: unless-stopped

volumes:
  mysql_data:
    driver: local
  redis_data:
    driver: local

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # 不对外暴露

secrets:
  db_root_password:
    file: ./secrets/db_root_password.txt

环境变量管理

三种方式,优先级从高到低:

yaml13 行
services:
  api:
    # 方式1:直接定义(优先级最高)
    environment:
      NODE_ENV: production
      LOG_LEVEL: info

    # 方式2:从文件加载
    env_file:
      - .env
      - .env.production

    # 方式3:引用宿主机变量(在 .env 中定义 COMPOSE_PROJECT_NAME 等)

.env 文件示例:

plaintext7 行
# .env
DB_HOST=db
DB_PORT=3306
DB_NAME=myapp
DB_USER=admin
DB_PASSWORD=secret123
REDIS_PASSWORD=redis_secret

依赖管理

depends_on 支持条件等待:

yaml16 行
services:
  api:
    depends_on:
      db:
        condition: service_healthy    # 等待健康检查通过
      redis:
        condition: service_started    # 等待容器启动
      migration:
        condition: service_completed_successfully  # 等待任务完成

  migration:
    image: myapp/migrate:latest
    command: ["./migrate", "up"]
    depends_on:
      db:
        condition: service_healthy

常用 Compose 命令

plaintext27 行
# 启动所有服务(后台)
docker compose up -d

# 查看服务状态
docker compose ps

# 查看日志
docker compose logs -f api
docker compose logs --tail=100

# 重新构建并启动
docker compose up -d --build

# 停止并移除容器、网络
docker compose down

# 停止并移除,同时删除数据卷
docker compose down -v

# 扩缩容
docker compose up -d --scale api=3

# 进入容器
docker compose exec api sh

# 只启动指定服务及其依赖
docker compose up -d api

镜像管理

构建镜像

plaintext14 行
# 基本构建
docker build -t myapp:1.0 .

# 指定 Dockerfile
docker build -f Dockerfile.prod -t myapp:1.0-prod .

# 构建时传入参数
docker build --build-arg VERSION=1.0 --build-arg ENV=prod -t myapp:1.0 .

# 使用 BuildKit(更快,功能更多)
DOCKER_BUILDKIT=1 docker build -t myapp:1.0 .

# 多平台构建
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:1.0 .

标签策略

推荐的标签命名规范:

plaintext11 行
# 语义化版本
docker tag myapp:latest harbor.example.com/prod/myapp:1.2.3
docker tag myapp:latest harbor.example.com/prod/myapp:1.2
docker tag myapp:latest harbor.example.com/prod/myapp:1

# Git commit hash(方便追溯)
docker tag myapp:latest harbor.example.com/prod/myapp:abc1234

# 环境标签
docker tag myapp:1.2.3 harbor.example.com/prod/myapp:stable
docker tag myapp:1.2.3-rc1 harbor.example.com/prod/myapp:canary

推拉镜像

plaintext15 行
# 登录私有仓库
docker login harbor.example.com -u admin -p <password>

# 打标签并推送
docker tag nginx:1.25 harbor.example.com/prod/nginx:1.25
docker push harbor.example.com/prod/nginx:1.25

# 拉取镜像
docker pull harbor.example.com/prod/nginx:1.25

# 导出镜像为文件(离线传输)
docker save -o nginx-1.25.tar harbor.example.com/prod/nginx:1.25

# 从文件导入镜像
docker load -i nginx-1.25.tar

镜像清理

plaintext17 行
# 查看磁盘使用情况
docker system df

# 清理悬空镜像(dangling)
docker image prune

# 清理所有未使用的镜像
docker image prune -a

# 清理所有未使用的资源(容器、网络、镜像、构建缓存)
docker system prune -a --volumes

# 清理构建缓存
docker builder prune

# 删除指定镜像
docker rmi harbor.example.com/prod/nginx:1.25

私有仓库 Harbor

Harbor 是企业级 Docker 镜像仓库,安装步骤:

plaintext7 行
# 下载 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

harbor.yml 关键配置:

plaintext11 行
hostname: harbor.example.com
http:
  port: 80
https:
  port: 443
  certificate: /data/cert/server.crt
  private_key: /data/cert/server.key
harbor_admin_password: Harbor12345
database:
  password: root123
data_volume: /data/harbor

安装并启动:

plaintext2 行
sudo ./install.sh --with-trivy  # 启用漏洞扫描
docker compose ps                # 验证所有组件运行正常

网络

网络模式

模式 说明 适用场景
bridge 默认模式,容器通过虚拟网桥通信 大多数场景
host 容器共享宿主机网络栈 高性能网络需求
none 无网络 安全隔离场景
overlay 跨主机容器通信 Docker Swarm 集群
macvlan 容器拥有独立 MAC 地址 需要容器直接接入物理网络

自定义网络

plaintext21 行
# 创建自定义 bridge 网络
docker network create --driver bridge \
  --subnet 172.20.0.0/16 \
  --gateway 172.20.0.1 \
  mynetwork

# 查看网络详情
docker network inspect mynetwork

# 将容器加入网络
docker run -d --name web --network mynetwork nginx

# 运行时连接到多个网络
docker network connect mynetwork existing_container
docker network connect another_network existing_container

# 断开网络
docker network disconnect mynetwork existing_container

# 清理未使用的网络
docker network prune

容器互联

在同一自定义网络中,容器可以通过 服务名 相互访问:

yaml11 行
# docker-compose.yml
services:
  api:
    networks:
      - backend
  db:
    networks:
      - backend

networks:
  backend:

在 api 容器中,可以直接用 db 作为主机名连接数据库:

plaintext2 行
# 在 api 容器内
mysql -h db -u root -p  # "db" 自动解析为 db 容器的 IP

端口映射

plaintext14 行
# 映射单个端口
docker run -d -p 8080:80 nginx

# 映射多个端口
docker run -d -p 8080:80 -p 8443:443 nginx

# 指定绑定地址(仅监听本机)
docker run -d -p 127.0.0.1:8080:80 nginx

# 随机端口映射
docker run -d -P nginx

# 查看端口映射
docker port <container_name>

Docker 代理配置

Docker 走代理要分清三个层级:docker pull 拉镜像由 Docker daemon 决定,容器内程序由进程的环境变量决定,Compose 容器访问宿主机代理则受网络段影响。三个层级的配置位置不同,混在一起排查会很费时。

三个层级总览

层级 配置位置 影响
Daemon /etc/systemd/system/docker.service.d/proxy.conf docker pull / docker push
容器构建(build) ~/.docker/config.json 或 --build-arg docker build 时的网络请求
容器运行(run) -e HTTP_PROXY=... 或 environment: 容器进程的 HTTP 客户端

场景一:让 docker pull 走代理

国内服务器拉 Docker Hub 经常超时,与其改镜像源,不如让 daemon 走代理。创建 systemd drop-in:

plaintext2 行
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo vim /etc/systemd/system/docker.service.d/proxy.conf

内容:

plaintext4 行
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,::1,*.aliyun.com,*.tencentyun.com"

NO_PROXY 把国内常用的镜像域名加进去,避免走代理反而慢。随后重载 systemd 并重启 Docker:

plaintext5 行
sudo systemctl daemon-reload
sudo systemctl restart docker

# 验证代理变量已注入
sudo systemctl show docker | grep -i proxy

取消代理时删除 proxy.conf,再执行 sudo systemctl daemon-reload 和 sudo systemctl restart docker。这个配置只影响 docker pull / docker push,不影响容器内部进程。

场景二:让容器内程序走代理

代理在宿主机、程序在容器里,最直接的方式是给容器传环境变量。注意要用宿主机的 bridge 网关地址 172.17.0.1,不要写 127.0.0.1,那指向的是容器自己。

plaintext5 行
docker run -d \
  -e HTTP_PROXY=http://172.17.0.1:7890 \
  -e HTTPS_PROXY=http://172.17.0.1:7890 \
  -e NO_PROXY=localhost,127.0.0.1,::1 \
  myimage

Compose 里的等价写法:

plaintext7 行
services:
  myapp:
    image: myimage
    environment:
      HTTP_PROXY: http://172.17.0.1:7890
      HTTPS_PROXY: http://172.17.0.1:7890
      NO_PROXY: localhost,127.0.0.1,::1

如果希望 docker build 和 docker run 都自动生效,可以写进 ~/.docker/config.json:

plaintext9 行
{
  "proxies": {
    "default": {
      "httpProxy": "http://172.17.0.1:7890",
      "httpsProxy": "http://172.17.0.1:7890",
      "noProxy": "localhost,127.0.0.1,::1"
    }
  }
}

程序必须读取 HTTP_PROXY 环境变量才会走代理。curl、wget、Python 的 requests、Java 都遵守;但部分 Go 编写的 HTTP 客户端和部分 Node 库不读这个变量,只能改程序代码。

场景三:Compose 容器访问宿主机代理

Compose 里 host.docker.internal 解析到默认 bridge 网关,与 Compose 自己创建的项目网络不在同一网段,直接使用常常不通。先查看 Docker 上所有网络网关:

plaintext2 行
docker network inspect $(docker network ls -q) \
  --format '{{.Name}}: Subnet={{(index .IPAM.Config 0).Subnet}} Gateway={{(index .IPAM.Config 0).Gateway}}'

正确做法是手动指定 Compose 网段和网关,并把 host.docker.internal 固定到该网关:

plaintext15 行
services:
  myapp:
    image: myimage
    extra_hosts:
      - "host.docker.internal:172.30.0.1"
    environment:
      HTTP_PROXY: http://host.docker.internal:7890
      HTTPS_PROXY: http://host.docker.internal:7890

networks:
  default:
    ipam:
      config:
        - subnet: 172.30.0.0/16
          gateway: 172.30.0.1

挑一个没被占用的网段,如 172.30.0.0/16,extra_hosts 里写死这个网关 IP,不要用 host-gateway 魔法值。宿主机的防火墙要放行该网段,否则请求到不了代理:

plaintext2 行
sudo ufw allow from 172.30.0.0/16
sudo ufw reload

或者在代理软件层面允许局域网访问,例如 mihomo 的 allow-lan: true 配合 bind-address: "*"。

验证容器内能否解析并连通:

plaintext4 行
docker exec -it myapp sh
getent ahosts host.docker.internal
# 应显示 172.30.0.1
nc -zv host.docker.internal 7890

environment 环境变量与应用配置里的代理

两个层级的代理需要分别配置,作用范围不同:

配置位置 影响范围
environment 的 HTTP_PROXY 进程级,影响通用 HTTP 库(curl、Python requests、Java HttpClient 等)
应用配置文件里的 proxy 业务级,应用特定 API 走的代理

举例:某个 AI 网关程序,environment: HTTP_PROXY=... 控制下载管理面板、访问 GitHub API 等框架请求;config.yaml 里的 proxy 控制转发到 OpenAI 的业务请求,二者不能互相覆盖。

用 host 网络模式简化

不想折腾网络段时,可以让容器与宿主机共享网络栈:

plaintext1 行
docker run -d --network host myimage
plaintext7 行
services:
  myapp:
    image: myimage
    network_mode: host
    environment:
      HTTP_PROXY: http://127.0.0.1:7890
      HTTPS_PROXY: http://127.0.0.1:7890

host 模式下 127.0.0.1 就是宿主机的回环地址:不用映射端口(-p 失效)、无 NAT 性能最好。代价是失去网络隔离(容器能看到宿主机所有端口)、多个容器监听同一端口会冲突、也没有 Compose 的容器名 DNS。适合单机部署、性能敏感、不需要多容器互联的场景。

调试代理是否生效

按层级排查:

plaintext17 行
# daemon 代理是否注入
sudo systemctl show docker | grep -i proxy

# 镜像能否通过代理拉取
docker pull hello-world

# 容器能否访问宿主机代理
docker run --rm -it alpine sh
apk add curl
curl -v http://172.17.0.1:7890

# 容器内程序是否读到代理变量
docker exec myapp env | grep -i proxy

# 程序请求是否真的走代理
docker exec myapp curl -v https://www.google.com
# 如果走代理,会看到 Proxy-Connection 之类的请求头

代理故障速查

现象 原因 解决
docker pull 慢 daemon 没配代理 配 /etc/systemd/system/docker.service.d/proxy.conf
配了 daemon 代理容器仍慢 容器不读 daemon 代理 容器单独设置 -e HTTP_PROXY
容器内 curl 走代理失败 ufw 挡了 Docker 网段 ufw allow from <Docker 网段>
Compose 的 host.docker.internal 不通 跨网段问题 手动指定网段和网关
环境变量配了程序仍不走代理 程序不读 HTTP_PROXY 在程序代码里改

代理实用经验

  • 能用宿主机代理就别在容器里再装代理软件,多套一层全是麻烦
  • 172.17.0.1 只在默认 bridge 网络下可用,Compose 自定义网络要算自己的网关
  • 代理和防火墙是组合拳:代理通了但 ufw 挡了网段,照样不通
  • docker exec ... env 是排查环境变量是否生效的第一招
  • daemon 代理与容器代理分别管不同的事,可以混用

数据卷

Volume vs Bind Mount

特性 Volume(数据卷) Bind Mount(绑定挂载)
管理方式 Docker 管理 用户手动管理
存储位置 /var/lib/docker/volumes/ 宿主机任意路径
可移植性 高 低
备份 方便(docker volume) 直接操作文件系统
推荐场景 数据库数据、应用数据 配置文件、开发环境代码

数据持久化

plaintext16 行
# 创建命名数据卷
docker volume create mysql_data

# 使用数据卷运行容器
docker run -d --name db \
  -v mysql_data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  mysql:8.0

# 使用 bind mount
docker run -d --name web \
  -v /opt/nginx/conf:/etc/nginx/conf.d:ro \
  -v /opt/nginx/html:/usr/share/nginx/html \
  nginx

# Docker Compose 中使用
yaml14 行
services:
  db:
    volumes:
      - mysql_data:/var/lib/mysql           # 命名卷
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro  # 绑定挂载
      - /etc/localtime:/etc/localtime:ro    # 同步时区

volumes:
  mysql_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/mysql  # 指定宿主机存储路径

数据备份与恢复

plaintext17 行
# 备份数据卷
docker run --rm \
  -v mysql_data:/source:ro \
  -v /opt/backup:/backup \
  alpine tar czf /backup/mysql_data_$(date +%Y%m%d).tar.gz -C /source .

# 恢复数据卷
docker run --rm \
  -v mysql_data:/target \
  -v /opt/backup:/backup:ro \
  alpine sh -c "tar xzf /backup/mysql_data_20240101.tar.gz -C /target"

# 直接备份 MySQL(推荐)
docker exec db mysqldump -u root -p --all-databases > /opt/backup/all_databases_$(date +%Y%m%d).sql

# 恢复 MySQL
docker exec -i db mysql -u root -p < /opt/backup/all_databases_20240101.sql

资源限制

CPU 限制

plaintext10 行
# 限制使用 1.5 个 CPU 核心
docker run -d --cpus="1.5" myapp

# 设置 CPU 份额(相对权重,默认 1024)
docker run -d --cpu-shares=512 myapp

# 绑定到特定 CPU 核心
docker run -d --cpuset-cpus="0,1" myapp

# Docker Compose 中配置
yaml8 行
services:
  api:
    deploy:
      resources:
        limits:
          cpus: "1.5"
        reservations:
          cpus: "0.5"

内存限制

plaintext10 行
# 限制内存 512MB,swap 最大 1GB
docker run -d --memory=512m --memory-swap=1g myapp

# 仅限制内存(不限 swap)
docker run -d --memory=512m myapp

# 内存软限制
docker run -d --memory=512m --memory-reservation=256m myapp

# Docker Compose 中配置
yaml8 行
services:
  api:
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M

IO 限制

plaintext8 行
# 限制磁盘写入速度(10MB/s)
docker run -d --device-write-bps /dev/sda:10mb myapp

# 限制磁盘读取速度
docker run -d --device-read-bps /dev/sda:10mb myapp

# 限制 IOPS
docker run -d --device-write-iops /dev/sda:1000 myapp

OOM 处理

plaintext8 行
# 禁止 OOM Killer 杀死该容器(慎用)
docker run -d --oom-kill-disable --memory=512m myapp

# 设置 OOM 优先级调整
docker run -d --oom-score-adj=-500 myapp

# 查看容器是否因 OOM 被杀死
docker inspect <container> --format='{{.State.OOMKilled}}'

cgroup 概念

Docker 使用 Linux cgroup(Control Groups)实现资源限制:

  • cgroup v1:每个资源控制器独立挂载(cpu、memory、blkio 等)
  • cgroup v2:统一层级结构,功能更强大(支持 PSI、io.latency 等)

查看当前系统的 cgroup 版本:

plaintext6 行
# 查看 cgroup 版本
stat -fc %T /sys/fs/cgroup/
# 输出 "cgroup2fs" 表示 v2,"tmpfs" 表示 v1

# 查看容器的 cgroup 配置
docker inspect <container> --format='{{.HostConfig.CgroupParent}}'

日志与监控

日志驱动

Docker 支持多种日志驱动:

plaintext6 行
# 查看容器日志
docker logs -f --tail=100 <container>

# 查看日志文件大小
docker inspect <container> --format='{{.LogPath}}'
ls -lh /var/lib/docker/containers/<id>/<id>-json.log

配置全局日志驱动(daemon.json):

plaintext9 行
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3",
    "labels": "service,env",
    "tag": "{{.Name}}/{{.ID}}"
  }
}

常用日志驱动对比:

驱动 说明 适用场景
json-file 默认,输出到 JSON 文件 开发/测试
syslog 发送到 syslog 使用 syslog 的环境
fluentd 发送到 Fluentd 日志聚合
awslogs 发送到 CloudWatch AWS 环境
gelf 发送到 Graylog 集中日志

容器监控 cAdvisor

cAdvisor 是 Google 开源的容器资源监控工具:

yaml14 行
# docker-compose.yml
services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.47.0
    privileged: true
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    ports:
      - "8080:8080"
    restart: unless-stopped

配合 Prometheus + Grafana 实现完整监控栈:

plaintext16 行
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    volumes:
      - grafana_data:/var/lib/grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123

健康检查

plaintext3 行
# Dockerfile 中定义
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1

Compose 中定义:

yaml15 行
services:
  api:
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  db:
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

查看健康状态:

plaintext2 行
docker inspect --format='{{.State.Health.Status}}' <container>
# healthy / unhealthy / starting

安全

非 root 运行

plaintext5 行
# Dockerfile 中创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser
WORKDIR /app
COPY --chown=appuser:appuser . .
USER appuser

运行时指定用户:

plaintext1 行
docker run --user 1001:1001 myapp

只读文件系统

plaintext4 行
# 只读文件系统 + 临时可写目录
docker run --read-only --tmpfs /tmp:rw,noexec,nosuid myapp

# Docker Compose
yaml6 行
services:
  api:
    read_only: true
    tmpfs:
      - /tmp:rw,noexec,nosuid
      - /var/run:rw,noexec,nosuid

能力限制(Capabilities)

plaintext8 行
# 移除所有能力,只添加需要的
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp

# 常见能力说明
# NET_BIND_SERVICE  — 绑定 1024 以下端口
# SYS_PTRACE       — 调试进程
# NET_ADMIN         — 网络管理(修改路由表等)
# NET_RAW           — 使用 raw socket(ping 等)

镜像扫描 Trivy

plaintext17 行
# 安装 Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# 扫描镜像漏洞
trivy image nginx:1.25

# 只显示高危和严重漏洞
trivy image --severity HIGH,CRITICAL nginx:1.25

# 扫描并输出 JSON 报告
trivy image --format json --output report.json nginx:1.25

# 扫描 Dockerfile(配置问题)
trivy config Dockerfile

# 扫描文件系统
trivy fs --security-checks vuln,config .

CI/CD 集成示例(GitLab CI):

plaintext4 行
image_scan:
  stage: test
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

安全加固清单

plaintext18 行
# 1. 不使用 --privileged(除非绝对必要)
# 2. 只读文件系统
docker run --read-only --tmpfs /tmp myapp

# 3. 限制能力
docker run --cap-drop=ALL myapp

# 4. 禁止提权
docker run --security-opt=no-new-privileges myapp

# 5. 使用 seccomp 配置文件
docker run --security-opt seccomp=custom-seccomp.json myapp

# 6. 限制 PID 数量
docker run --pids-limit=100 myapp

# 7. 设置资源限制
docker run --memory=512m --cpus="1.0" myapp

常用运维命令速查表

容器生命周期

plaintext7 行
docker run -d --name myapp -p 8080:80 nginx    # 后台运行
docker start myapp                                # 启动
docker stop myapp                                 # 优雅停止(SIGTERM + SIGKILL)
docker restart myapp                              # 重启
docker rm myapp                                   # 删除容器
docker rm -f myapp                                # 强制删除
docker update --restart=always myapp              # 更新重启策略

容器信息

plaintext8 行
docker ps                                        # 运行中容器
docker ps -a                                     # 所有容器
docker inspect myapp                             # 详细信息
docker stats                                     # 实时资源使用
docker top myapp                                 # 容器内进程
docker diff myapp                                # 文件系统变更
docker port myapp                                # 端口映射
docker logs -f --tail=100 myapp                  # 查看日志

容器操作

plaintext5 行
docker exec -it myapp sh                         # 进入容器
docker exec -it myapp bash                       # 进入容器(bash)
docker cp myapp:/app/config.yaml ./config.yaml   # 从容器复制文件
docker cp ./file.txt myapp:/app/                 # 复制文件到容器
docker commit myapp myapp:snapshot                # 从容器创建镜像

镜像操作

plaintext9 行
docker images                                    # 列出镜像
docker pull nginx:1.25                           # 拉取镜像
docker push harbor.example.com/prod/nginx:1.25   # 推送镜像
docker build -t myapp:1.0 .                      # 构建镜像
docker tag myapp:1.0 myapp:latest                # 打标签
docker rmi myapp:1.0                             # 删除镜像
docker image prune -a                            # 清理未使用镜像
docker save -o myapp.tar myapp:1.0               # 导出镜像
docker load -i myapp.tar                         # 导入镜像

网络操作

plaintext6 行
docker network ls                                # 列出网络
docker network create mynet                      # 创建网络
docker network inspect mynet                     # 网络详情
docker network connect mynet myapp               # 连接网络
docker network disconnect mynet myapp            # 断开网络
docker network prune                             # 清理未使用网络

数据卷操作

plaintext5 行
docker volume ls                                 # 列出数据卷
docker volume create mydata                      # 创建数据卷
docker volume inspect mydata                     # 数据卷详情
docker volume rm mydata                          # 删除数据卷
docker volume prune                              # 清理未使用数据卷

系统操作

plaintext5 行
docker info                                      # 系统信息
docker version                                   # 版本信息
docker system df                                 # 磁盘使用
docker system prune -a --volumes                 # 全面清理
docker system events                             # 实时事件

结语

Docker 容器化部署是一项系统工程,涉及镜像构建、网络配置、存储管理、安全加固和监控运维等多个维度。掌握这些核心知识后,建议在实际项目中逐步实践,从单容器部署到多服务编排,从开发环境到生产环境,不断积累经验。

小贴士:生产环境部署前,务必做好镜像漏洞扫描、资源限制配置和日志收集方案。容器共享宿主机内核,隔离和资源限制需要分别配置。

镜像、容器状态与数据恢复

镜像固定软件,容器持有运行状态,挂载卷保存独立数据。重新创建容器不会自动恢复被删除或损坏的卷。交付时记录镜像 digest、卷路径、环境配置与恢复命令,并通过恢复到新卷验证备份。

Docker 发布端口会创建网络转发规则,主机 UFW 规则可能无法按预期覆盖这条路径。按所用防火墙后端核对规则,并从外部验证暴露面。Docker 网络文档解释了这一交互。

← 返回文章