Docker 使用
这篇笔记从"容器"的历史讲起:chroot 是起点,LXC 是雏形,Docker 让它流行,Kubernetes 让它标准化,containerd 让它沉淀为云原生时代的基石。最后是容器启动顺序的实战记录和常用命令速查。
一、容器技术的历史(按时间顺序)
timeline
title 容器技术发展史
1979 : chroot 出现(文件系统隔离)
2000 : FreeBSD Jail 第一个容器雏形
2008 : LXC 发布
2013 : Docker 开源
2014 : Docker 1.0 / Kubernetes 公布
2015 : OCI 成立,容器标准化
2016 : Docker Swarm 内置编排
2017 : containerd 捐给 CNCF
2019 : containerd 毕业 / Docker 企业版出售
2020 : K8s 宣布弃用 dockershim
2022 : K8s 移除 dockershim
1. 萌芽:chroot 与隔离技术(1979~2008)
容器的核心思想是隔离——让一个进程以为自己独占了一台机器。这个思想最早可追溯到 1979 年 Unix V7 引入的 chroot:它能让进程的根目录被"换掉",进程只能看到指定目录里的文件,相当于最早的文件系统隔离。
之后的隔离技术按"隔离的彻底程度"逐步升级:
- 2000 年:FreeBSD 推出 Jail,第一次实现了文件系统、进程、网络等全方位隔离,被视为第一个真正意义上的"容器"技术
- 2006 年:Google 工程师提出 cgroups(控制组,最早叫 process containers),限制进程的 CPU、内存等资源用量,2008 年合入 Linux 内核 2.6.24
- 同期 Linux 内核还陆续合入了 namespaces(命名空间):让进程拥有独立的 PID、网络、挂载点等视图
cgroups(资源限制)+ namespaces(视图隔离),正是现代容器技术的两大支柱。
2. 雏形:LXC(2008)
2008 年,IBM 工程师 Daniel Lezcano 基于 cgroups + namespaces 开发了 LXC(LinuX Containers),让 Linux 上可以运行"接近完整操作系统"的隔离环境。LXC 是 Linux 社区的第一个容器方案,但使用门槛高、镜像分发困难,始终是"技术极客的工具"。
3. 诞生:Docker 开源(2013)
2013 年 3 月,一家叫 dotCloud 的 PaaS 公司(创始人 Solomon Hykes)把内部使用的容器管理工具开源,命名 Docker。它最初就是基于 LXC 的封装,但做了三件革命性的事:
- 镜像(Image):把"应用 + 运行环境"打包成一个可分发的只读镜像,配合 Dockerfile 可以自动构建
- 分层存储:镜像由一层层可复用的文件层组成,多个镜像共享底层,传输和存储都大幅节省
- 一条命令运行:
docker run让任何人都能一键启动一个应用环境,"Build, Ship and Run"(构建、运输、运行)成为它的口号
2013 年底,Docker 0.7 用自研的 libcontainer 取代了对 LXC 的依赖,从此不再受制于外部容器方案。Docker 一出生就踩中了"微服务兴起、环境一致性成为痛点"的时代节拍,迅速成为开发者工具界的新星。
4. 爆发与标准化:Docker 1.0、Kubernetes、OCI(2014~2015)
- 2014 年 6 月:DockerCon 大会发布 Docker 1.0,dotCloud 正式更名 Docker Inc.,标志着 Docker 进入商业化阶段
- 2014 年 6 月:Google 公布 Kubernetes(k8s),2015 年 7 月发布 1.0——容器编排战争从此开打
- 2014 年:Docker 收购 fig 并改名 docker-compose,多容器编排的本地化体验大大简化
- 2015 年 6 月:Docker、Google、CoreOS、Red Hat 等公司共同成立 OCI(Open Container Initiative,开放容器倡议),Docker 捐出 libcontainer 演化的 runc 作为参考实现。容器格式从此有了公开标准,不再是任何一家公司的私有技术
5. 编排大战:Swarm vs Kubernetes(2016~2017)
容器解决了"怎么跑",还缺"怎么管"——大量容器的调度、扩缩容、负载均衡、故障恢复,需要一个编排系统。2016~2017 年爆发了三方混战:
- Docker Swarm:Docker 1.12(2016 年)内置 Swarm mode,与 Docker 深度集成,上手最简单
- Kubernetes:Google 出品,理念先进、生态繁荣,得到了微软、AWS、Red Hat 等巨头支持
- Mesos + Marathon:老牌资源管理器,擅长大数据场景
最终 Kubernetes 胜出,成为容器编排的事实标准。2017 年 Docker 也宣布企业版支持 Kubernetes——"编排之战"落幕。
6. 拆分与沉淀:containerd、dockershim 谢幕(2017~2022)
容器生态后来发生了两件影响深远的事:
- 2017 年:Docker 将容器运行时 containerd(负责镜像管理、容器生命周期)捐给 CNCF(云原生计算基金会),2019 年 2 月成为 CNCF 首批毕业项目之一。containerd 从"Docker 内部组件"变成了"行业标准运行时"
- 2019 年 11 月:Docker 把企业版业务出售给 Mirantis,聚焦开发者工具(Docker Desktop)
- 2020~2022 年:Kubernetes 宣布弃用 dockershim(k8s 调用 Docker 的中间层),2022 年 5 月 K8s 1.24 正式移除。K8s 改用 containerd 直接管理容器——Docker 作为"容器运行时"的时代结束,但 Docker 镜像是标准,Docker Desktop 仍是开发者的最爱
今天:云原生时代
- 事实标准变为:OCI 标准 + containerd/runc,所有容器工具都兼容 Docker 镜像
- 出现了 podman、nerdctl 等无守护进程的替代品,以及 buildkit 等新一代构建工具
- 容器 + Kubernetes 构成了云原生(Cloud Native)世界的底座
二、Docker 的核心概念
镜像(Image)
镜像是只读的模板,包含一个应用运行所需的完整环境:代码、依赖、配置、基础系统文件。镜像是分层的——由一层层只读文件层(layer)堆叠而成,每一层都有哈希值标识:
- 两个镜像共享相同的基础层时,只存储一份,节省空间
- 构建时只有变化的层会被重建,未变化的层直接复用缓存,构建飞快
- Dockerfile 中的每一条指令(
RUN、COPY等)通常产生一个层
容器(Container)
容器是镜像的运行实例。镜像本身不能运行,docker run 会在镜像之上加一个可写的容器层,启动其中的进程。容器可以被启动、停止、删除,容器内产生的数据写在该可写层中——容器一删除,可写层的数据也随之消失(要持久化请用数据卷)。
仓库(Registry)
仓库是存放镜像的地方,最著名的是 Docker Hub(Docker 官方仓库)。镜像的全名形如 仓库地址/命名空间/镜像名:标签,如 mysql:8.0。docker pull 从仓库拉取镜像,docker push 上传镜像。企业内网通常搭建私有仓库(registry / Harbor)。
数据卷(Volume)
容器删除后数据就没了,因此需要数据卷把数据放到宿主机上持久化。两种方式:
- 命名卷(named volume):由 Docker 管理,
docker run -v mydata:/var/lib/mysql,备份迁移方便 - 绑定挂载(bind mount):直接映射宿主机目录,
docker run -v /home/data:/data,修改宿主机文件即时生效,方便调试
网络(Network)
Docker 默认提供三种网络模式:
| 模式 | 说明 |
|---|---|
| bridge(默认) | 容器之间通过虚拟网桥互通,配合 -p 8080:80 把容器端口映射到宿主机 |
| host | 容器直接使用宿主机网络,无端口映射,性能最好 |
| none | 容器没有网络,适合离线任务 |
多个容器要互相访问时,把它们加入同一个自定义网络,直接用容器名作为主机名访问,比记忆 IP 方便得多(这是 docker-compose 的默认行为)。
Dockerfile
Dockerfile 是构建镜像的脚本,常用指令:
| 指令 | 作用 |
|---|---|
FROM |
指定基础镜像,必须是第一条指令 |
RUN |
执行构建时的命令(安装依赖等) |
COPY / ADD |
把文件复制进镜像 |
WORKDIR |
设置工作目录 |
ENV / ARG |
设置环境变量 / 构建参数 |
EXPOSE |
声明容器监听的端口 |
CMD / ENTRYPOINT |
指定容器启动时执行的命令 |
HEALTHCHECK |
定义健康检查 |
Docker 的整体架构
docker client ──> docker daemon (dockerd) ──> containerd ──> runc ──> 容器进程
│
└── 镜像存储(分层文件)
- 客户端:
docker命令行,把用户指令发给守护进程 - dockerd:Docker 守护进程,负责镜像管理、网络、卷等,是用户接触最多的部分
- containerd:更底层的容器运行时,负责镜像解包、容器生命周期,可被 K8s 等直接调用
- runc:OCI 标准的参考实现,真正调用内核能力创建容器进程
容器 vs 虚拟机
| 对比项 | 容器 | 虚拟机 |
|---|---|---|
| 隔离级别 | 进程级(共享宿主机内核) | 硬件级(独立内核,虚拟 CPU/内存) |
| 启动速度 | 秒级 | 分钟级 |
| 资源占用 | 小(只打包应用层) | 大(每个都要完整系统) |
| 镜像大小 | MB 级 | GB 级 |
| 隔离强度 | 较弱(共享内核) | 强(完全隔离) |
容器快、轻,但隔离不如虚拟机彻底,两者常混合使用。
三、容器启动顺序(实战)
这里使用 tomcat9和mysql8 作为演示,其中 mysql8的启动,参考 教程/MySql8 下的启动过程
tomcat9依赖mysql8,因此,我们将tomcat9的容器 放在 mysql8容器启动之后。
mysql8容器没有要依赖的容器,设置成随docker启动自启就行,因此,只设置 tomcat9的顺序
整个过程中,关键的地方是 延迟容器启动时间
首先拉取 tomcat9的 docker镜像
docker pull tomcat:9-jdk8-corretto
创建一个存放应用文件的目录,同时将tomcat的logs和webapp目录映射到宿主机上
# 创建应用文件目录
mkdir -p /home/tomcat9/app_files
# 创建tomcat的映射目录
mkdir -p /home/tomcat9/logs
mkdir -p /home/tomcat9/webapp
启动容器
docker run -d --name tomcat9 -p 8080:8080 -v /home/tomcat9/logs:/usr/local/tomcat/logs -v /home/tomcat9/webapp:/usr/local/tomcat/webapps -v /home/tomcat9/app_files:/data/files tomcat:9-jdk8-corretto
配置容器启动顺序
将docker设置为开机自启
systemctl enable docker.service
编辑rc-local.service 文件
vim /lib/systemd/system/rc-local.service
末尾添加以下内容
[Install]
WantedBy=multi-user.target
Alias=rc-local.service
编辑 rc.local 文件
先查看一下tomcat9容器的id
docker ps
我这里,tomcat9的容器id是 f887619c2c11
编辑 rc.local文件
vim /etc/rc.local
文件末尾添加以下内容
sleep 10s;docker start f887619c2c11
延迟10s启动tomcat9容器
ok,重启服务器,试一下效果。
四、Docker 常用命令
下面按用途分组整理常用命令。命令中的
<>表示替换成实际值,如<镜像名>填mysql:8.0。
镜像相关
# 拉取镜像
docker pull <镜像名>
# 列出本地镜像
docker images
docker image ls
# 删除镜像(先删依赖的容器)
docker rmi <镜像名或ID>
# 给镜像打标签
docker tag <镜像名> <新名称:新标签>
# 推送镜像到仓库
docker push <镜像名>
# 根据 Dockerfile 构建镜像
docker build -t <镜像名> .
# 镜像导出/导入(离线迁移)
docker save -o myimage.tar <镜像名>
docker load -i myimage.tar
# 查看镜像分层和构建历史
docker history <镜像名>
# 查看镜像详细信息
docker inspect <镜像名>
容器相关
# 运行容器:-d 后台 -p 端口映射 --name 命名 -v 数据卷
docker run -d -p 8080:8080 --name myapp -v /home/data:/data <镜像名>
# 列出容器(-a 包含已停止的)
docker ps
docker ps -a
# 启停/重启容器
docker start <容器名或ID>
docker stop <容器名或ID>
docker restart <容器名或ID>
# 删除容器(-f 强制删除运行中的)
docker rm <容器名或ID>
docker rm -f <容器名或ID>
# 查看日志(-f 持续输出 --tail 只看末尾 N 行)
docker logs -f --tail 100 <容器名或ID>
# 进入容器执行命令
docker exec -it <容器名或ID> bash
# 在容器和宿主机之间复制文件
docker cp 宿主机文件 <容器名或ID>:/容器路径
docker cp <容器名或ID>:/容器路径 宿主机路径
# 查看容器资源占用
docker stats
docker top <容器名或ID>
# 暂停/恢复容器
docker pause <容器名或ID>
docker unpause <容器名或ID>
# 把容器打包成新镜像(不推荐,应使用 Dockerfile)
docker commit <容器名或ID> <新镜像名>
数据卷
# 创建/查看/删除命名卷
docker volume create mydata
docker volume ls
docker volume rm mydata
# 清理未使用的卷
docker volume prune
# 查看卷挂载详情
docker inspect <容器名或ID>
网络
# 创建自定义网络(容器之间可用容器名互相访问)
docker network create mynet
# 查看网络列表/详情
docker network ls
docker network inspect mynet
# 把容器接入/断开网络
docker network connect mynet <容器名或ID>
docker network disconnect mynet <容器名或ID>
# 删除网络
docker network rm mynet
docker compose(多容器编排)
# 启动服务(-d 后台)
docker compose up -d
# 查看服务状态
docker compose ps
# 查看日志
docker compose logs -f
# 停止并删除服务
docker compose down
# 重新构建镜像后启动
docker compose up -d --build
# 在某个服务容器内执行命令
docker compose exec <服务名> bash
老版本使用
docker-compose(带横杠),新版本使用docker compose(空格)。
清理与系统
# 查看磁盘占用
docker system df
# 一键清理:停止的容器、未使用的镜像和网络
docker system prune -a
# 只清理某类资源
docker container prune # 停止的容器
docker image prune -a # 未使用的镜像
docker volume prune # 未使用的卷
# 查看 Docker 版本与系统信息
docker version
docker info
其他
# 登录/退出镜像仓库(默认 Docker Hub)
docker login
docker logout
# 镜像仓库搜索
docker search <关键词>