搜 索

🐳 Docker从入门到放弃

  • 24阅读
  • 2023年09月23日
  • 0评论
首页 / 编程 / 正文

一、Docker 是什么?——一句话就能说清,但我偏要说一万字

1.1 从"在我电脑上能跑"说起

每个程序员的职业生涯中,都至少说过一次这句话:

"在我电脑上是好的啊?!"

这句话的杀伤力,堪比产品经理的"这个需求很简单"。

Docker 的出现,就是为了终结这场旷日持久的甩锅大战。它的核心思想很简单:把你的代码、依赖、配置、环境,统统打包成一个标准化的箱子(容器),扔到哪里都能跑。

1.2 虚拟机 vs 容器:一图胜千言

graph TB subgraph VM["虚拟机架构 🏋️ 重量级"] HW1["物理硬件"] HOS1["宿主机 OS"] HYP["Hypervisor
(VMware/KVM)"] subgraph VM1["虚拟机 1"] GOS1["Guest OS
完整操作系统 🐘"] LIB1["Bins/Libs"] APP1["App A"] end subgraph VM2["虚拟机 2"] GOS2["Guest OS
完整操作系统 🐘"] LIB2["Bins/Libs"] APP2["App B"] end HW1 --> HOS1 --> HYP HYP --> VM1 HYP --> VM2 end subgraph CT["容器架构 🏃 轻量级"] HW2["物理硬件"] HOS2["宿主机 OS"] DCK["Docker Engine"] subgraph C1["容器 1"] CLIB1["Bins/Libs"] CAPP1["App A"] end subgraph C2["容器 2"] CLIB2["Bins/Libs"] CAPP2["App B"] end HW2 --> HOS2 --> DCK DCK --> C1 DCK --> C2 end
对比维度虚拟机容器
启动速度分钟级 😴秒级 ⚡
资源占用GB 级MB 级
隔离性强(硬件级)较强(内核级)
性能损耗10-20%≈ 0%
心情影响等到怀疑人生快到怀疑人生

1.3 Docker 核心概念三件套

graph LR DF["📄 Dockerfile
———
造房子的图纸"] -->|docker build| IMG["📦 Image 镜像
———
造好的毛坯房"] IMG -->|docker run| CT["🏠 Container 容器
———
住进去的房子"] CT -->|docker commit| IMG2["📦 New Image
———
装修后拍照存档"] REG["☁️ Registry 仓库
———
房产中介
(Docker Hub)"] IMG -->|docker push| REG REG -->|docker pull| IMG

用人话说:

  • Dockerfile:菜谱,告诉 Docker 怎么做这道菜
  • Image:做好的预制菜,冷冻保存,随时加热
  • Container:把预制菜热好端上桌,正在被吃的那盘
  • Registry:预制菜超市(Docker Hub)

二、安装 Docker——第一个放弃的机会

2.1 各平台安装体验一览

graph TD START["我要安装 Docker!"] --> OS{"你用什么系统?"} OS -->|"macOS"| MAC["下载 Docker Desktop
拖进 Applications"] MAC --> MAC_OK["✅ 一分钟搞定"] MAC_OK --> MAC_BUT["但是...吃掉你 4GB 内存 💀"] OS -->|"Windows"| WIN{"你有 WSL2 吗?"} WIN -->|"有"| WIN_OK["Docker Desktop for Windows"] WIN -->|"没有"| WSL["先装 WSL2..."] WSL --> BIOS["先开 BIOS 虚拟化..."] BIOS --> REBOOT["重启电脑..."] REBOOT --> PRAY["祈祷不蓝屏 🙏"] PRAY --> WIN_OK WIN_OK --> WIN_BUT["但是...Hyper-V 和 VMware 打架 💀"] OS -->|"Linux"| LINUX["一行命令搞定"] LINUX --> LINUX_CMD["curl -fsSL get.docker.com | sh"] LINUX_CMD --> LINUX_OK["✅ 完美"] LINUX_OK --> LINUX_BUT["但是...你选择了 Linux 💀"]

2.2 Linux 安装(正经版)

# Ubuntu / Debian
# 1. 卸载旧版本(如果有的话)
sudo apt-get remove docker docker-engine docker.io containerd runc

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

# 3. 添加 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

# 4. 设置仓库
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

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

# 6. 验证安装
sudo docker run hello-world

# 7. 把自己加到 docker 组(告别 sudo)
sudo usermod -aG docker $USER
# 然后重新登录,或者 newgrp docker
🎉 如果你看到了 Hello from Docker!,恭喜你,你已经完成了整个 Docker 学习过程中最简单的一步。

三、Docker 基础操作——幸福的时光总是短暂的

3.1 Docker 命令全景图

graph TB subgraph "镜像操作 📦" PULL["docker pull
拉取镜像"] BUILD["docker build
构建镜像"] IMAGES["docker images
查看镜像列表"] RMI["docker rmi
删除镜像"] TAG["docker tag
给镜像打标签"] PUSH["docker push
推送到仓库"] end subgraph "容器生命周期 🏠" RUN["docker run
创建并启动"] START["docker start
启动已停止的"] STOP["docker stop
优雅停止"] KILL["docker kill
强制停止"] RM["docker rm
删除容器"] RESTART["docker restart
重启"] end subgraph "容器交互 🔧" EXEC["docker exec
进入容器"] LOGS["docker logs
查看日志"] CP["docker cp
文件复制"] INSPECT["docker inspect
查看详情"] TOP["docker top
查看进程"] STATS["docker stats
资源监控"] end subgraph "清理操作 🧹" PRUNE_C["docker container prune
清理停止的容器"] PRUNE_I["docker image prune
清理悬空镜像"] PRUNE_A["docker system prune
核弹级清理 💣"] end

3.2 最常用的命令(背下来,不亏)

# ============ 镜像操作 ============

# 搜索镜像(Docker Hub 上找找看)
docker search nginx

# 拉取镜像(不指定 tag 默认 latest)
docker pull nginx:1.25
docker pull redis:7-alpine    # alpine 版本更小巧

# 查看本地镜像
docker images
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

# 删除镜像
docker rmi nginx:1.25
docker rmi $(docker images -q -f "dangling=true")  # 删除所有悬空镜像


# ============ 容器操作 ============

# 运行容器(最基础)
docker run nginx

# 运行容器(人类能用的版本)
docker run -d \                    # 后台运行(detached)
  --name my-nginx \                # 给容器取个名字
  -p 8080:80 \                     # 端口映射:宿主机8080 -> 容器80
  -v /data/html:/usr/share/nginx/html \  # 目录挂载
  -e TZ=Asia/Dubai \               # 环境变量(迪拜时区 🇦🇪)
  --restart unless-stopped \       # 重启策略
  nginx:1.25

# 查看运行中的容器
docker ps
docker ps -a    # 包含已停止的

# 进入容器(救命用)
docker exec -it my-nginx /bin/bash
docker exec -it my-nginx sh       # 如果容器里没有 bash

# 查看日志(排错用)
docker logs my-nginx
docker logs -f my-nginx            # 实时跟踪
docker logs --tail 100 my-nginx    # 最后100行
docker logs --since 30m my-nginx   # 最近30分钟

# 容器与宿主机之间复制文件
docker cp my-nginx:/etc/nginx/nginx.conf ./nginx.conf
docker cp ./index.html my-nginx:/usr/share/nginx/html/

# 停止与删除
docker stop my-nginx
docker rm my-nginx
docker rm -f my-nginx              # 强制删除(运行中也删)

3.3 端口映射——最容易翻车的地方

graph LR subgraph "宿主机 🖥️" P1["端口 8080"] P2["端口 3306"] P3["端口 6379"] end subgraph "容器1: Nginx" CP1["端口 80"] end subgraph "容器2: MySQL" CP2["端口 3306"] end subgraph "容器3: Redis" CP3["端口 6379"] end P1 -->|"-p 8080:80"| CP1 P2 -->|"-p 3306:3306"| CP2 P3 -->|"-p 6379:6379"| CP3

💡 记忆口诀-p 宿主:容器,左边是外面,右边是里面。就像写收件地址一样——先写大的(外面),再写小的(里面)。

如果你记反了,恭喜你获得了一个花费两小时 debug 的机会。


四、Dockerfile——入门的分水岭

4.1 Dockerfile 指令速查

graph TD FROM["FROM
基础镜像
👶 我从哪来"] --> WORKDIR["WORKDIR
工作目录
📂 我在哪干活"] WORKDIR --> COPY["COPY / ADD
复制文件
📋 把东西搬进来"] COPY --> RUN["RUN
执行命令
🔨 安装依赖/编译"] RUN --> ENV["ENV
环境变量
⚙️ 配置参数"] ENV --> EXPOSE["EXPOSE
暴露端口
🚪 开个门"] EXPOSE --> CMD["CMD / ENTRYPOINT
启动命令
🚀 容器启动时干啥"] style FROM fill:#e1f5fe style CMD fill:#c8e6c9

4.2 一个 Spring Boot 项目的 Dockerfile 进化史

第一版:能跑就行(新手村)

FROM openjdk:17
COPY target/app.jar /app.jar
CMD ["java", "-jar", "/app.jar"]

镜像大小:471MB 😱

第二版:知道用 slim 了(初级玩家)

FROM openjdk:17-slim
WORKDIR /app
COPY target/app.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

镜像大小:291MB 🤔

第三版:多阶段构建(高级玩家)

# ===== 构建阶段 =====
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
# 先下载依赖(利用缓存层)
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests -B

# ===== 运行阶段 =====
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app

# 安全:创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

COPY --from=builder /build/target/*.jar app.jar

# 安全:切换到非 root 用户
USER appuser

EXPOSE 8080

# JVM 调优参数
ENTRYPOINT ["java", \
  "-XX:+UseContainerSupport", \
  "-XX:MaxRAMPercentage=75.0", \
  "-XX:+UseG1GC", \
  "-Djava.security.egd=file:/dev/./urandom", \
  "-jar", "app.jar"]

镜像大小:148MB 🎉

进化过程:

graph LR V1["第一版
471MB
🐘"] -->|"用 slim"| V2["第二版
291MB
🐕"] V2 -->|"多阶段构建
+ Alpine
+ JRE only"| V3["第三版
148MB
🐁"] V3 -->|"GraalVM
Native Image"| V4["终极版
~50MB
🐜"] style V1 fill:#ffcdd2 style V2 fill:#fff9c4 style V3 fill:#c8e6c9 style V4 fill:#b3e5fc

4.3 Dockerfile 最佳实践(血泪总结)

# ❌ 错误示范:每个命令一个 RUN(层数爆炸)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y wget
RUN apt-get install -y vim
RUN rm -rf /var/lib/apt/lists/*

# ✅ 正确姿势:合并 RUN,减少镜像层数
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      curl \
      wget \
    && rm -rf /var/lib/apt/lists/*
# 注意:vim 不要装!容器里 debug 用 docker exec!

# ❌ 错误示范:COPY 放在依赖安装前面(缓存全废)
COPY . /app
RUN npm install

# ✅ 正确姿势:先 COPY 依赖文件,再 COPY 源码
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
🧠 缓存原理:Dockerfile 每一条指令都是一层。如果某一层没变化,Docker 会用缓存。所以把不常变的放前面,常变的放后面

五、Docker Compose——真正的生产力工具

5.1 为什么需要 Docker Compose?

因为一个真实项目长这样:

graph TB USER["👤 用户"] --> NGINX["Nginx
反向代理"] NGINX --> APP1["App 实例 1
Spring Boot"] NGINX --> APP2["App 实例 2
Spring Boot"] APP1 --> MYSQL["MySQL 8.0
主数据库"] APP2 --> MYSQL APP1 --> REDIS["Redis 7
缓存"] APP2 --> REDIS APP1 --> ES["Elasticsearch
搜索引擎"] APP2 --> ES APP1 --> RABBIT["RabbitMQ
消息队列"] APP2 --> RABBIT style USER fill:#e1f5fe style NGINX fill:#fff9c4 style APP1 fill:#c8e6c9 style APP2 fill:#c8e6c9 style MYSQL fill:#ffcdd2 style REDIS fill:#ffcdd2 style ES fill:#ffcdd2 style RABBIT fill:#ffcdd2

你打算手动 docker run 7 个容器?还要配网络、卷、依赖顺序?

Docker Compose:一个 YAML 文件搞定一切。

5.2 完整的 docker-compose.yml 示例

version: '3.8'

services:
  # ============ Nginx 反向代理 ============
  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      app:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - frontend

  # ============ Spring Boot 应用 ============
  app:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - DB_HOST=mysql
      - DB_PORT=3306
      - REDIS_HOST=redis
      - TZ=Asia/Dubai
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    deploy:
      replicas: 2                # 跑两个实例
      resources:
        limits:
          memory: 512M
          cpus: '0.5'
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    restart: unless-stopped
    networks:
      - frontend
      - backend

  # ============ MySQL ============
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}   # 从 .env 文件读取
      MYSQL_DATABASE: payment_db
      MYSQL_USER: app_user
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
      - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    ports:
      - "3306:3306"              # 生产环境建议去掉,只让内部访问
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - backend

  # ============ Redis ============
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - backend

# ============ 持久化卷 ============
volumes:
  mysql_data:
    driver: local
  redis_data:
    driver: local

# ============ 网络 ============
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

5.3 Docker Compose 常用命令

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

# 查看服务状态
docker compose ps

# 查看日志
docker compose logs -f app

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

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

# 停止所有服务
docker compose down

# 停止并删除所有数据(危险!)
docker compose down -v    # -v 会删除 volumes!

六、Docker 网络——开始头疼了

6.1 Docker 网络模式

graph TB subgraph "Bridge 模式(默认)" BR["docker0 网桥
172.17.0.1"] C1_B["容器A
172.17.0.2"] C2_B["容器B
172.17.0.3"] BR --- C1_B BR --- C2_B end subgraph "Host 模式" HOST["宿主机网络
直接使用宿主机端口"] C1_H["容器
共享宿主机网络栈"] HOST --- C1_H end subgraph "None 模式" C1_N["容器
没有网络 🚫
与世隔绝"] end subgraph "自定义 Bridge" CBR["my-network
172.20.0.1"] C1_C["app
172.20.0.2"] C2_C["mysql
172.20.0.3"] CBR --- C1_C CBR --- C2_C C1_C -.->|"可通过容器名访问
mysql:3306 ✅"| C2_C end

🔑 关键知识点:默认 bridge 网络中,容器之间不能通过容器名互相访问。自定义网络中可以。这就是为什么 Docker Compose 默认会创建一个自定义网络。

这个坑大概会浪费你 2-4 小时。不客气。

6.2 常见网络问题决策树

graph TD START["容器网络出问题了!"] --> Q1{"容器能 ping 通
宿主机吗?"} Q1 -->|"不能"| A1["检查 Docker 网络模式
docker network inspect"] Q1 -->|"能"| Q2{"容器之间能
互相通信吗?"} Q2 -->|"不能"| Q3{"它们在同一个
网络中吗?"} Q3 -->|"不是"| A2["把它们加到同一个网络
docker network connect"] Q3 -->|"是"| A3["检查防火墙规则
iptables -L"] Q2 -->|"能"| Q4{"外部能访问
容器服务吗?"} Q4 -->|"不能"| Q5{"端口映射
配了吗?"} Q5 -->|"没有"| A4["加上 -p 参数"] Q5 -->|"配了"| A5["检查宿主机防火墙
和云服务器安全组 ☁️"] Q4 -->|"能"| A6["那你来找我干啥?🤷"]

七、Docker 数据管理——丢数据的噩梦

7.1 三种数据持久化方式

graph TB subgraph "方式一:Volumes(推荐 ✅)" V_ENGINE["Docker 管理"] V_DIR["/var/lib/docker/volumes/xxx/_data"] V_CT["容器内: /data"] V_ENGINE --> V_DIR V_DIR -.-> V_CT end subgraph "方式二:Bind Mounts(开发用 🔧)" B_HOST["宿主机目录
/home/joey/data"] B_CT["容器内: /data"] B_HOST -.-> B_CT end subgraph "方式三:tmpfs(临时数据 ⚡)" T_MEM["宿主机内存"] T_CT["容器内: /tmp"] T_MEM -.-> T_CT end
# Volume(Docker 管理,最推荐)
docker run -v mysql_data:/var/lib/mysql mysql:8.0

# Bind Mount(宿主机路径直接挂载)
docker run -v /home/joey/config:/app/config:ro nginx

# tmpfs(存内存里,重启就没了)
docker run --tmpfs /tmp:rw,noexec,nosuid,size=100m app

⚠️ 血泪教训:如果你没有配置持久化,容器一删,数据全没。MySQL 的数据、Redis 的持久化、上传的文件——统统灰飞烟灭。

这不是 bug,这是 feature。Docker 的哲学是:容器是牲畜(Cattle),不是宠物(Pet)。 随时可以杀掉重建。但你的数据不是。


八、Docker 镜像优化——从 1GB 到 50MB 的艺术

8.1 镜像大小对比

graph LR subgraph "基础镜像大小对比" U["ubuntu:22.04
77MB"] D["debian:12-slim
74MB"] A["alpine:3.19
7MB"] DL["distroless
~2MB"] S["scratch
0MB"] end U -.->|"换成 Debian"| D D -.->|"换成 Alpine"| A A -.->|"换成 Distroless"| DL DL -.->|"终极:从零开始"| S style U fill:#ffcdd2 style D fill:#fff9c4 style A fill:#c8e6c9 style DL fill:#b3e5fc style S fill:#e1bee7

8.2 镜像优化 Checklist

graph TD START["开始优化"] --> S1["✅ 1. 选择小基础镜像
alpine / distroless / slim"] S1 --> S2["✅ 2. 多阶段构建
build 和 runtime 分离"] S2 --> S3["✅ 3. 合并 RUN 指令
减少镜像层数"] S3 --> S4["✅ 4. 清理缓存和临时文件
apt clean / rm -rf /tmp/*"] S4 --> S5["✅ 5. 使用 .dockerignore
排除 .git, node_modules 等"] S5 --> S6["✅ 6. 固定版本号
别用 latest 标签"] S6 --> S7["✅ 7. 非 root 用户运行
USER appuser"] S7 --> DONE["🎉 镜像小且安全"]

.dockerignore 示例:

.git
.gitignore
node_modules
npm-debug.log
Dockerfile
docker-compose*.yml
.env
*.md
.idea
.vscode
target/

九、Docker 在生产中的坑——开始想放弃了

9.1 日志管理

# 你以为你的磁盘满了是因为数据多?
# 不,是因为 Docker 日志。

# 查看容器日志文件大小
du -sh /var/lib/docker/containers/*/*-json.log

# 全局配置日志大小限制(/etc/docker/daemon.json)
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

# 单个容器配置
docker run --log-opt max-size=10m --log-opt max-file=3 nginx
😱 真实案例:某服务器磁盘 100% 了,查了半天发现是一个容器的日志文件吃掉了 47GB。里面全是 NullPointerException 的堆栈信息,每秒打印 200 条。

9.2 容器健康检查

sequenceDiagram participant D as Docker Engine participant C as Container participant H as Health Check D->>C: 启动容器 Note over C: start_period: 30s
等待应用启动 loop 每隔 interval (10s) D->>H: 执行健康检查命令 H->>C: curl http://localhost:8080/health alt 响应 200 C-->>H: ✅ Healthy H-->>D: 状态: healthy else 超时或非200 C-->>H: ❌ Failed H-->>D: 失败计数 +1 Note over D: retries 次失败后
标记为 unhealthy end end

9.3 生产环境安全清单

graph TD SEC["🔒 Docker 安全加固"] --> S1["不用 root 运行容器
USER nonroot"] SEC --> S2["只读文件系统
--read-only"] SEC --> S3["限制资源
--memory --cpus"] SEC --> S4["不用 --privileged
除非你知道在干什么"] SEC --> S5["定期更新基础镜像
修复安全漏洞"] SEC --> S6["扫描镜像漏洞
docker scout / trivy"] SEC --> S7["使用 secrets 管理敏感信息
别把密码写在镜像里!"] style SEC fill:#ffcdd2

十、Docker 常见问题排查——放弃前的最后挣扎

10.1 遇到问题的标准流程

graph TD PROBLEM["💥 出问题了!"] --> STEP1["1. docker ps -a
容器还活着吗?"] STEP1 --> STEP2["2. docker logs 容器名
看看日志说了啥"] STEP2 --> STEP3["3. docker inspect 容器名
配置对吗?"] STEP3 --> STEP4["4. docker exec -it 容器名 sh
进去看看"] STEP4 --> STEP5["5. docker stats
资源够吗?"] STEP5 --> STEP6{"解决了吗?"} STEP6 -->|"是"| HAPPY["🎉"] STEP6 -->|"否"| STEP7["6. docker rm -f && docker run
删了重来"] STEP7 --> STEP8{"解决了吗?"} STEP8 -->|"是"| HAPPY STEP8 -->|"否"| STEP9["7. docker system prune -a
全部清理重来"] STEP9 --> STEP10{"解决了吗?"} STEP10 -->|"是"| HAPPY STEP10 -->|"否"| GIVEUP["8. 重装系统 💀
(从入门到放弃)"]

10.2 经典错误合集

错误信息原因解决方案
port is already allocated端口被占用lsof -i :端口号 找到并 kill
no space left on device磁盘满了docker system prune -a
OCI runtime error容器启动失败检查 CMD/ENTRYPOINT 命令
network not found网络不存在docker network create xxx
permission denied权限问题检查文件权限 / 用户 / SELinux
exec format error架构不匹配 (amd64/arm64)--platform 指定 或 buildx
name already in use容器名冲突docker rm 旧容器

十一、高级话题——如果你还没放弃的话

11.1 Docker 底层原理(简化版)

graph TB subgraph "Docker 的三大核心技术" subgraph "Namespace(隔离)" NS1["PID Namespace
进程隔离"] NS2["Network Namespace
网络隔离"] NS3["Mount Namespace
文件系统隔离"] NS4["UTS Namespace
主机名隔离"] NS5["IPC Namespace
进程间通信隔离"] NS6["User Namespace
用户隔离"] end subgraph "Cgroups(限制)" CG1["CPU 限制"] CG2["内存限制"] CG3["IO 限制"] CG4["网络带宽限制"] end subgraph "UnionFS(分层)" L1["Layer 1: Base Image
(alpine)"] L2["Layer 2: apt install
(只读)"] L3["Layer 3: COPY code
(只读)"] L4["Layer 4: 容器层
(可写)"] L1 --> L2 --> L3 --> L4 end end

用人话翻译:

  • Namespace:让每个容器觉得自己是独生子,拥有独立的世界
  • Cgroups:给每个容器分配零花钱(资源),不许超支
  • UnionFS:千层饼式的文件系统,一层叠一层,最上面那层才能写

11.2 Docker vs Podman vs Containerd

graph TB subgraph "Docker 架构" DC["Docker CLI"] --> DD["Docker Daemon
(dockerd)
⚠️ 需要 root"] DD --> CTRD1["containerd"] CTRD1 --> RUNC1["runc"] RUNC1 --> C1["容器"] end subgraph "Podman 架构" PC["Podman CLI"] --> CTRD2["conmon"] CTRD2 --> RUNC2["runc"] RUNC2 --> C2["容器"] Note1["✅ 无守护进程
✅ 无需 root
✅ 兼容 Docker CLI"] end subgraph "Containerd(K8s 默认)" KC["crictl / nerdctl"] --> CTRD3["containerd"] CTRD3 --> RUNC3["runc"] RUNC3 --> C3["容器"] Note2["✅ K8s 原生支持
✅ 更轻量"] end

🤔 那该用哪个?

  • 个人开发/学习 → Docker(生态最完善)
  • 安全敏感场景 → Podman(rootless)
  • K8s 集群 → Containerd(K8s 默认运行时)
  • 选择困难症 → Docker(别想了,先用起来)

十二、Docker 知识体系总览

mindmap root((Docker)) 基础 镜像 Image 容器 Container 仓库 Registry Dockerfile 进阶 Docker Compose 网络 Network 存储 Volume 多阶段构建 运维 日志管理 监控告警 安全加固 镜像优化 生态 Docker Hub Harbor 私有仓库 Docker Swarm Kubernetes 底层 Namespace Cgroups UnionFS OCI 标准

结语:从入门到放弃,然后再爬起来

Docker 的学习曲线长这样:

graph LR A["🤩 Docker 真好用!
一行命令启动 MySQL"] --> B["😊 Dockerfile 也不难嘛"] B --> C["🤔 Docker Compose 有点东西"] C --> D["😰 网络怎么不通?"] D --> E["😱 数据丢了!"] E --> F["💀 磁盘满了,日志 47GB"] F --> G["🪦 想放弃了..."] G --> H["😤 不行,我要搞懂它"] H --> I["🧠 原来如此!"] I --> J["🏆 真正掌握 Docker"] J --> K["😭 老板说要学 K8s 了..."]

放弃不是终点,K8s 才是。


📝 关于本文

这是「从入门到放弃」系列的第 N 篇。如果你觉得有用,欢迎访问 underestimated.cn 查看更多技术分享。

下一篇预告:《Kubernetes 从入门到放弃》——如果说 Docker 是一个容器,那 K8s 就是管理一群容器的噩梦...呃,我是说编排系统。


最后的最后,送你一条保命命令:

# 当一切都无法挽回的时候
alias yolo='docker system prune -a --volumes -f && echo "🔥 Everything is gone. Start fresh."'
评论区
暂无评论
avatar