第二章

镜像与容器:图纸和实物

第一层 · 镜像/容器/仓库 第一次跑起 hello world 容器

一、安装 Docker:三分钟上桌

小陈在服务器上装 Docker(以 CentOS 为例):

# 安装 Docker(CentOS 7+)
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io

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

# 验证
docker --version        # Docker version 24.0.x
docker run hello-world # 跑起第一个容器!

小陈看到屏幕上出现 "Hello from Docker!",兴奋地喊:"师傅!跑起来了!"

"别急,先搞懂你刚才干了什么。刚才那条 docker run hello-world 背后发生了四件事:"

docker run hello-world 发生了什么:
  ① Docker 发现本地没有 hello-world 镜像
  ② 去 Docker Hub(官方镜像仓库)下载 hello-world 镜像
  ③ 用镜像创建一个容器并运行
  ④ 容器打印 Hello,执行完毕,退出

恭喜:你已经完成了"拉镜像 + 跑容器 + 容器退出"全流程!

二、镜像(Image):只读的"图纸"

"先讲镜像(Image)。"老周说:

镜像(Image):
  定义:一个只读的、不可变的"模板"——包含应用和它需要的所有东西
        (代码、运行时、依赖、配置、环境变量……)

  特点:
    ① 只读(不可变):镜像一旦构建,内容不能改
    ② 分层:由一层层"只读层"堆叠而成(第三章细讲)
    ③ 可复用:同一个镜像能创建无数个容器
    ④ 可共享:能上传/下载(镜像仓库,第八章)

  类比:
    镜像 = 做蛋糕的"模具"(固定的、不变的)
    容器 = 用模具做出来的"蛋糕"(可以有很多个)
    镜像 = 图纸,容器 = 按图纸盖的房子
# 镜像相关命令
docker images                    # 查看本地有哪些镜像
docker pull nginx:1.25           # 拉取镜像(从仓库)
docker rmi nginx:1.25            # 删除镜像
docker tag nginx:1.25 yunjian/nginx:v1  # 给镜像打标签

"记住:镜像是不变的图纸,你永远不会'修改'镜像,只会'基于它'创建新镜像。"


三、容器(Container):可变的"实物"

"容器(Container)是镜像运行起来之后的实例——就是那个'按图纸盖出来的房子'。"老周说:

容器(Container):
  定义:镜像的"运行实例"——一个隔离的、可运行的进程环境

  特点:
    ① 可写:容器内可以改文件(但改的是容器自己的可写层)
    ② 隔离:有自己的文件系统、进程、网络(namespace 隔离)
    ③ 有生命周期:创建 → 运行 → 停止 → 删除
    ④ 可移植:同一镜像在任何机器上跑出的容器行为一致

  镜像 vs 容器:
    镜像 = 类(Class)      容器 = 对象(实例)
    镜像 = 图纸              容器 = 房子
    一个镜像 → 可以创建 N 个容器
# 容器相关命令
docker run -d --name web01 nginx   # 后台运行一个 nginx 容器
docker ps                          # 查看运行中的容器
docker ps -a                       # 查看所有容器(含已停止)
docker stop web01                  # 停止容器
docker start web01                 # 启动容器
docker rm web01                    # 删除容器

"镜像负责'不变'(标准),容器负责'多变'(运行)。 这就是容器世界的第一个哲学:标准化的图纸 + 可复制的实例。"


四、容器是怎么"隔离"出来的:Namespace 与 Cgroup

"你可能好奇:容器没有自己的内核,凭什么能'互不干扰'?"老周说,"靠两个 Linux 内核机制:"

① Namespace(命名空间):"看不见别人"
   把系统资源"分区"——每个容器只能看到自己的那部分:
   - PID namespace:容器只能看到自己的进程(看不到别的容器进程)
   - Network namespace:容器有自己的网络栈(自己的 IP、端口)
   - Mount namespace:容器有自己的文件系统视图
   - User namespace:容器里的 root 不是真 root(权限隔离)
   效果:容器 A 和容器 B 互相"看不见"对方

② Cgroup(控制组):"抢不过别人"
   限制每个容器能用的资源:
   - CPU 上限:这个容器最多用 2 核
   - 内存上限:最多 512MB(超了?杀进程!)
   - IO 带宽、网络带宽限制
   效果:容器 A 吃爆 CPU,也抢不走容器 B 的份额
# 给容器设置资源限制(Cgroup 的用法)
docker run -d --name web01 \
  --cpus=1.5 \        # 最多用 1.5 核
  --memory=512m \     # 最多 512MB 内存
  nginx

# 进入容器看 namespace 隔离
docker exec -it web01 bash   # 进入容器
ps aux                       # 只能看到容器自己的进程!

"Namespace 负责'看不见'(隔离视角),Cgroup 负责'抢不过'(限制资源)。 这两个内核机制,就是容器'轻量隔离'的底层秘密——不需要完整操作系统,靠内核自带的功能就够了。"


五、镜像/容器/仓库:Docker 世界的"三件套"

"Docker 最核心的三个概念,你现在都见过了:镜像(Image)、容器(Container)、仓库(Registry)。"老周画:

┌─────────┐    pull(下载)    ┌──────────┐
│ 仓库      │ ◀──────────────── │  镜像     │
│ Registry │                   │  Image    │
│ (Docker  │    push(上传)    │  (图纸)    │
│  Hub/私服)│ ─────────────────▶ │           │
└─────────┘                    └────┬─────┘
                                   │ run(创建)
                                   ▼
                              ┌──────────┐
                              │  容器     │
                              │ Container │
                              │  (实例)   │
                              └──────────┘

工作流:
  写 Dockerfile → 构建镜像(build)→ 推送到仓库(push)
  → 别的机器拉镜像(pull)→ 跑容器(run)

  Docker Hub:官方的公共镜像仓库(就像"应用商店")
  私有仓库:企业内部自建(Harbor 等,第八章讲)

"记一个动作链:build → push → pull → run。 这就是容器应用从开发到上线的完整路径。你以后看任何容器相关的工作流,都是这四步的变形。"


六、章末:老周的第一层总结

第一层:容器基础
├── 镜像(Image):只读图纸,不变、分层、可复用
├── 容器(Container):镜像的运行实例,可写、隔离、有生命周期
├── 仓库(Registry):镜像的"应用商店"(Docker Hub/私有仓库)
├── Namespace:隔离"看不见"(进程/网络/文件系统/用户)
├── Cgroup:限制"抢不过"(CPU/内存/IO 上限)
└── 三件套工作流:build → push → pull → run

"小陈,你现在能跑起别人的镜像了(nginx、hello-world)。但那些都是现成的——你还没做过自己的'图纸'。 下一章,我们写你的第一个 Dockerfile,把云间书店的 AI 荐书服务'装进集装箱'。记住那句话:容器不是用来跑 hello world 的,是用来打包你的业务的。"

✌ 语言