⚠️ 提示:本站 AI 助手使用博主个人 API Token,没事别特么瞎用。
DevOps · · 约 28 分钟阅读

Docker 入门:镜像、容器、网络与日常排障

Docker 已经成为现代软件交付的标准。本文从安装、Dockerfile 最佳实践、镜像构建缓存、网络/存储模型、Compose 编排,到线上 10+ 常见报错、性能分析逐一整理。

Docker 入门:镜像、容器、网络与日常排障

Docker 把程序 + 依赖 + 运行环境打包进一个”镜像”,在任何 Linux / macOS / Windows 机器上拉下来就能跑。它解决了”我机器上能跑啊”的千年难题,也是整个云原生世界的基础。

二、核心概念速览

概念一句话说明
Image(镜像)只读的”模板”,比如 nginx:1.27-alpine,由一层层文件系统叠加组成
Container(容器)镜像”跑起来”的实例,基于写时复制(Copy-on-Write)有独立可写层 + 进程空间
Registry镜像仓库:Docker Hub、阿里云 ACR、Harbor、GitHub GHCR
Dockerfile描述如何一步步构建镜像的文本文件
Volume容器数据持久化方案,绕开 Union FS,直接写宿主机
Bind Mount把宿主机某目录直接挂到容器里
Networkbridge / host / none / container:xxx / 用户自定义 bridge
Compose用 YAML 一次性编排启动多个容器(Web + DB + Redis…)
BuildKitDocker 23.0+ 默认构建引擎,比老 builder 快、支持缓存导出、多阶段更聪明
Layer(层)Dockerfile 每条指令生成一层,完全相同的指令会命中缓存,这就是优化构建速度的根

三、安装

2.1 macOS / Windows(桌面版)

直接装 Docker Desktop:https://www.docker.com/products/docker-desktop/

  • 自带 Docker Engine + CLI + Compose v2 + K8s 单节点 + GUI

  • macOS 用 HyperKit / Apple Virtualization;Win 用 WSL2

检查:

bash docker version # 看 Client / Engine 版本 docker compose version # 确认 Compose v2 docker info | head -30

2.2 Linux(Ubuntu / Debian 生产服务器)

`bash

1) 卸载旧版本

for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt remove -y $pkg done

2) 加官方源

sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg —dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo
“deb [arch=$(dpkg —print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu
$(. /etc/os-release && echo $VERSION_CODENAME) stable” | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null

3) 安装

sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

4) 让非 root 也能跑 docker(重新登录生效)

sudo usermod -aG docker $USER

5) (可选) 国内镜像加速

sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json << ‘EOF’ { “registry-mirrors”: [ “https://docker.1ms.run”, “https://docker.m.daocloud.io” ], “log-driver”: “json-file”, “log-opts”: { “max-size”: “100m”, “max-file”: “5” }, “storage-driver”: “overlay2” } EOF sudo systemctl daemon-reload sudo systemctl enable —now docker

验证

docker run —rm hello-world `

CentOS / Rocky / RHEL 换源用 download.docker.com/linux/centos 即可,步骤类似。

四、第一个容器 & 常用命令

`bash

拉镜像

docker pull nginx:1.27-alpine

看本地镜像

docker images

启动容器(-d 后台 -p 端口映射 —name 起名 -v 挂载)

docker run -d —name my-nginx -p 8080:80
-v /data/html:/usr/share/nginx/html:ro
—restart=unless-stopped
nginx:1.27-alpine

看运行中的容器

docker ps

看所有(包括已停止)

docker ps -a

日志

docker logs my-nginx docker logs -f —tail 200 my-nginx # 实时跟 最新200行

进容器(推荐用 exec -it,别用 attach)

docker exec -it my-nginx /bin/sh # alpine 没 bash

停止 / 启动 / 删除

docker stop my-nginx docker start my-nginx docker rm my-nginx # 运行中的要加 -f docker rm -f $(docker ps -aq) # 危险:删所有容器

磁盘占用大起底

docker system df docker system prune -a # 清理停止的容器、无用镜像、虚悬镜像 docker builder prune —all # 清构建缓存 docker volume prune # 清没被使用的 volume `

五、Dockerfile 最佳实践

4.1 模板:Node.js 多阶段构建(经典案例)

`dockerfile

---------------- 阶段 1:构建 ----------------

FROM node:20-alpine AS builder

WORKDIR /app

先只拷贝依赖描述文件,充分利用缓存

COPY package*.json ./

—include=dev 确保装齐 devDependencies(用于 TS 编译/前端构建);npm 7+ 旧写法 —only=production=false 已废弃

RUN npm ci —include=dev —no-audit —no-fund

再拷贝源码(源码经常变,放后面避免每次装依赖)

COPY . .

前端构建 / 编译 TS 等

RUN npm run build

---------------- 阶段 2:运行 ----------------

FROM node:20-alpine AS runner

ENV NODE_ENV=production
PORT=3000

不要用 root 跑容器

RUN addgroup -S nodeapp && adduser -S nodeapp -G nodeapp WORKDIR /app RUN chown -R nodeapp:nodeapp /app USER nodeapp

从 builder 阶段拷贝需要的东西,dev 依赖全丢掉

COPY —from=builder —chown=nodeapp:nodeapp /app/dist ./dist COPY —from=builder —chown=nodeapp:nodeapp /app/package*.json ./ RUN npm ci —omit=dev —no-audit —no-fund

EXPOSE 3000

健康检查(可选但推荐)

HEALTHCHECK —interval=30s —timeout=5s —retries=3
CMD wget -qO- http://127.0.0.1:3000/healthz || exit 1

用 exec 形式(JSON 数组),保证信号正确传递给 node

CMD [“node”, “dist/server.js”] `

4.2 构建镜像 & 运行

bash docker build -t myapp:v1.0.0 . docker run -d -p 3000:3000 --name myapp myapp:v1.0.0

4.3 Dockerfile 优化规则

原则怎么做原因
命中缓存变更频率低的指令放上面(apt install / npm ci / pip install)一层没改,下面的缓存就都命中
多阶段构建构建工具全放 builder,最终 runner 只含运行时镜像体积从 1GB 能减到 100MB
用 slim/alpine 基础镜像node:20-alpine > node:20 > node:20-bookworm小 + 攻击面小
不要用 rootuseradd + USER xxx容器被攻破也拿不到宿主机 root
合并 RUN 指令RUN apt update && apt install -y ... && rm -rf /var/lib/apt/lists/*每层分开存,写在一个 RUN 里才会把缓存包清干净
忽略无关文件写 .dockerignore,排除 node_modules、.git、日志等构建上下文(build context)越小传得越快
明确版本node:20.17.0-alpine3.20 而不是 node:latestlatest 指不定今天是谁,构建不可复现
敏感信息不要用 ENV 传密码!用构建时 --secret 或运行时环境变量 / Secretdocker history 能看到 ENV 的明文

一个好的 .dockerignore:

.git .gitignore node_modules npm-debug.log Dockerfile .dockerignore README.md .env.* dist coverage .idea .vscode

六、网络模型

驱动说明典型场景
bridge(默认)容器通过一个网桥(docker0)出网,互相通过 IP 访问单机多容器互相访问
host容器和宿主机共享 Network Namespace,端口不冲突就行想让容器网络性能跟宿主机一样(比如压测、Nginx 大流量)
none没有网卡安全隔离(容器完全断网)
container:id跟另一个容器共享网络栈Sidecar 模式(比如 envoy 代理 app 的流量)
自定义 bridgedocker network create mynet,容器用 --net mynet 加入推荐:自带 DNS,可以用容器名直接互联

自定义网络示例:

`bash docker network create mynet -d bridge docker run -d —name db —net mynet -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0 docker run -d —name web —net mynet -p 8080:80 nginx

web 里直接 ping / 连 “db:3306” 就行,不用查 IP

`

七、持久化:Volume vs Bind Mount

方式命令适用场景
Volume(推荐)-v mydata:/var/lib/mysql数据库、独立于容器生命周期的数据。Docker 管理,portable
Bind Mount-v /srv/mysql:/var/lib/mysql宿主机上现成的目录要给容器用(开发时挂源码、配置文件)

备份 volume:

`bash

用一个临时容器把 volume 打成 tar

docker run —rm -v mydata:/data -v $(pwd):/backup alpine
tar czf /backup/mydata-$(date +%Y%m%d).tar.gz -C /data . `

八、Docker Compose

compose.yml:

`yaml services: web: build: . image: myapp:v1 ports: - “8080:3000” environment: NODE_ENV: production DB_HOST: db DB_PORT: 3306 DB_PASSWORD: ${DB_PASSWORD} # 从 .env 文件或环境变量读 depends_on: db: condition: service_healthy redis: condition: service_started restart: unless-stopped healthcheck: test: [“CMD”, “wget”, “-qO-”, “http://127.0.0.1:3000/healthz”] interval: 30s retries: 3

db: image: mysql:8.0 volumes: - dbdata:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} healthcheck: test: [“CMD”, “mysqladmin”, “ping”, “-uroot”, “-p${DB_PASSWORD}”] interval: 10s timeout: 5s retries: 10 restart: unless-stopped

redis: image: redis:7-alpine command: redis-server —appendonly yes volumes: - redisdata:/data restart: unless-stopped

volumes: dbdata: redisdata: `

运行:

`bash

启动(-d 后台)

docker compose up -d

看日志

docker compose logs -f web

扩缩容(只能扩无状态服务)

docker compose up -d —scale web=3

停 + 删容器

docker compose down

连同 volume 一起删(注意数据会丢)

docker compose down -v `

九、日常排障手册

8.1 调试工具箱

需求命令
容器起不来,到底在报什么docker ps -a 找到 ID → docker logs <ID>;看之前的日志不要只看最后
容器内进程 / 端口 / 文件docker exec -it <ID> sh + ps auxf / ss -lntp / du -sh /(镜像里没这些命令的话用 nsenter 或者 docker debug)
镜像多大,哪层肥docker images;docker history myapp:v1 --no-trunc;更推荐 dive 工具
容器网络不通1. docker inspect <ID> | jq '.[0].NetworkSettings.Networks' 看 IP/网关2. docker run --rm --net container:<ID> nicolaka/netshoot ss -lntp / ping / curl 工具容器3. 宿主机 iptables FORWARD 链是否 DROP
磁盘莫名满docker system df → docker system prune -a → 看 overlay2 哪层大 → du -sh /var/lib/docker/overlay2/* | sort -hr | head
退出码是什么意思docker inspect <ID> --format '{{.State.ExitCode}}',137=OOMKilled,143=SIGTERM,1=应用自身报错
OOM Killeddmesg | grep -i oom;docker inspect ... MemoryLimit 设小了;--memory=1g 调大或优化程序
配置文件改了容器里没生效bind mount 的文件如果宿主用 mv 覆盖(比如 sed -i),inode 会变。要么重启容器,要么 sed --in-place=... 改同一个文件
宿主机访问容器端口不通① docker port <container> 端口映射有没有;② 防火墙 iptables/firewalld;③ SELinux;④ 容器里程序是不是只绑了 127.0.0.1(要绑 0.0.0.0)

8.2 常见报错对照表

报错关键字根因 / 排查
permission denied while trying to connect to the Docker daemon socket at unix:///...当前用户不在 docker 组。sudo usermod -aG docker $USER + 重新登录(或 newgrp docker)
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?dockerd 没起来。sudo systemctl start docker;检查 daemon.json 语法错误
Error response from daemon: pull access denied for xxx, repository does not exist or may require 'docker login'私有镜像要先 docker login <registry>,或者镜像名写错了
Error: No such container: xxx容器名 / ID 写错,或者已经 rm 了。docker ps -a 看看
port is already allocated宿主机端口被占了。ss -lntp | grep <PORT> 查谁占了,或者改映射
ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref ...: "/xxx": not foundDockerfile 里 COPY xxx yyy 的 xxx 在 build context 里不存在 / 被 .dockerignore 排除了
COPY failed: file not found in build context or excluded by .dockerignore同上,检查路径 / .dockerignore
exec /bin/sh: exec format error平台架构不匹配:Mac M 系列(arm64)构建的镜像跑到 Linux x86_64 上了。用 docker buildx build --platform linux/amd64 . 跨平台构建
Temporary failure resolving 'deb.debian.org' (RUN apt update)容器内 DNS 不通。daemon.json 加 "dns": ["223.5.5.5","8.8.8.8"];或宿主机 /etc/resolv.conf 配置问题
OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "xxx": executable file not found in $PATHCMD 或 ENTRYPOINT 里写的程序文件不存在 / 路径错 / 没可执行权限
no space left on device磁盘 / inode 满。docker system prune -a --volumes;检查 overlay2 大小;docker builder prune
HealthCheck: unhealthydocker inspect <ID> 里 State.Health.Log[] 看最近一次命令真实输出和退出码
depends_on 服务起来但应用还没 readydepends_on 默认只等”容器启动”,不等”应用可用”。用 condition: service_healthy + 给依赖服务加 healthcheck

8.3 性能分析:容器卡 / 慢

  1. docker stats → 先看 CPU% / MEM 使用率 / NET I/O / DISK I/O 的实时排行

  2. 某容器居高不下 → docker top <ID> 看里面 PID

  3. 定位到进程号后,可以:

    • 宿主机 pidstat -h -u -p <PID> 1(CPU)

    • strace -c -p <PID>(系统调用)

    • perf top -p <PID>(热点函数,要符号表)

  4. 网络慢 → docker run --rm --net host nicolaka/netshoot iperf3 ... / mtr 测带宽/丢包

8.4 构建慢怎么办

  • 使用 BuildKit(默认了,但 CI 里确认 DOCKER_BUILDKIT=1)

  • RUN —mount=type=cache,target=/root/.npm:把包管理器缓存持久化

  • COPY package.json 先于 COPY .

  • Registry Mirror:国内源拉基础镜像

  • 多平台并行构建:buildx build --platform linux/amd64,linux/arm64 -t repo/image --push .

  • CI 里导出/导入缓存:--cache-from type=gha --cache-to type=gha(GitHub Actions)

十、安全加固清单

  1. 不要用 :latest,明确版本号

  2. 不要以 root 运行,Dockerfile 里 USER nonroot

  3. dockerd 只监听 Unix socket,不要绑定 tcp:2375(裸 TCP 约等于给全世界 root 权限);一定要远程管理就用 TLS:2376

  4. 敏感变量别写进 ENV / 镜像层,用:

    • 运行时 --env-file /etc/default/myapp.env(文件权限 0600)

    • Docker Secrets(Swarm)或 HashiCorp Vault / 云 KMS

  5. 开启内容信任(Notary / Cosign):export DOCKER_CONTENT_TRUST=1

  6. 定期镜像扫描:Trivy / Grype 扫 CVE:trivy image myapp:v1

  7. 镜像精简 + 只读根文件系统:--read-only + tmpfs 给需要写的路径

参考资料

问问 AI