内容目录
- docker 是什么
- docker 解决什么问题
- 解决虚拟机资源消耗问题。
- 快速部署。
- 提供一次性的环境。
- 提供弹性的云服务。
- 组建微服务架构。
- docker 安装部署与使用
- 安装 docker 引擎
- 使用 docker
- 理解 docker 的架构
- docker 命令
- 卷的概念
- 自制镜像并发布
- docker 网络
- docker pipework
- docker 网络端口映射
- 总结
docker 可能会改变软件行业
1. docker 是什么
大家应该都用过虚拟机,在 Windows 上装个 Linux 虚拟机是很多程序员的常用方案。公司的生产环境也大多采用虚拟机。虚拟机将物理硬件资源虚拟化,按需分配使用,用起来和真实操作系统一模一样。当废弃不用时,直接删除虚拟机文件即可回收资源,管理起来非常方便。
但虚拟机有个明显缺点:它非常“重”,对硬件资源的消耗也大。于是,Linux 发展出了另一种虚拟化技术——Linux 容器(Linux Containers,缩写为 LXC)。它并不模拟一个完整的操作系统,却能提供类似虚拟机的隔离效果。如果说虚拟机是操作系统级别的隔离,那么容器就是进程级别的隔离。想想看,这种更轻量的隔离级别,优势无疑是启动更快、更节省资源。
而 Docker,就是对 Linux 容器的一种封装,它提供了简单易用的用户接口,是目前最流行的 Linux 容器解决方案。
下面是百科的定义:
Docker 是基于 Go 语言的开源应用容器引擎,并遵从 Apache 2.0 协议。Docker 让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器完全使用沙箱机制,相互之间不会有任何接口。
2. docker 解决什么问题
1. 解决虚拟机资源消耗问题。
在传统架构下,服务器操作系统之上运行着虚拟机,虚拟机上又运行着客户操作系统,客户操作系统之上才是用户的应用程序。这样一来,一台服务器 80% 的资源开销可能都花在了硬件虚拟化和客户机操作系统本身上。

图1. 虚拟机架构与容器架构区别
如图 1 所示,如果采用 Docker 容器技术,情况就大不相同。容器上直接运行着应用,容器和宿主机服务器使用同一内核,容器的文件系统使用物理服务器的文件系统(但做了隔离),看上去每个容器都有自己独立的文件系统。同时,在物理服务器上建立虚拟网桥设备,每个容器通过它连接网络。容器直接使用物理服务器的 CPU、内存、硬盘,无需额外的硬件虚拟化层和完整的客户机操作系统,因此资源消耗极低,单个容器的性能更接近物理服务器的原生性能。
一台普通家用电脑运行一个 Linux 虚拟机可能已经非常卡顿,但却可以使用 Docker 轻松虚拟出几十甚至上百个容器。如果换成性能强劲的服务器,使用 Docker 搭建私有云服务就不再是梦想。
2. 快速部署。
软件开发中的一个经典难题就是环境配置。在自己电脑上运行得好好的软件,换一台机器可能就“趴窝”了,除非你能保证操作系统设置、各种组件和库的安装完全一致。比如部署一个 Java Web 系统,机器必须安装正确版本的 Java、设置好环境变量,可能还得装 Tomcat、Nginx。换个环境,所有步骤都得重来一遍。
使用 Docker,你可以将应用程序及其所有依赖打包成一个文件(Docker 镜像文件)。运行这个镜像,就会启动一个容器,并在容器内运行你的应用,就像在真实的物理机上运行一样。有了 Docker,就能实现“一次构建,处处运行”,这也为自动化发布铺平了道路。
3. 提供一次性的环境。
这对于测试场景非常有用。比如,你想在本地测试别人的软件;或者在持续集成(CI)流程中,需要为单元测试和构建提供干净、一致的环境。用 Docker 启动或关闭一个容器,就像启动或关闭一个进程一样简单快速,秒级完成。
4. 提供弹性的云服务。
正因为 Docker 容器可以做到随开随关,它非常适合需要动态扩容和缩容的场景,这也是现代云服务的核心特性之一。
5. 组建微服务架构。
通过运行多个容器,一台物理机就能轻松模拟出完整的微服务架构,甚至分布式架构。这为开发、测试和演示复杂的系统拓扑提供了极大的便利。
3. docker 安装部署与使用
本文以 Ubuntu 18.04 系统为例进行介绍。其他操作系统请参考 官方文档 。
1. 安装 docker 引擎
获取最新版本的 Docker 安装包:
aaron@ubuntu:~$ wget -qO- https://get.docker.com/ | sh
执行上述命令,输入当前用户密码,即可自动下载并安装最新版 Docker。
安装完成后会有一个提示:
If you would like to use Docker as a non-root user, you should now consider
adding your user to the "docker" group with something like:
sudo usermod -aG docker aaron
Remember that you will have to log out and back in for this to take effect!
WARNING: Adding a user to the "docker" group will grant the ability to run
containers which can be used to obtain root privileges on the
docker host.
Refer to https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface
for more information.
如果你希望以非 root 用户身份直接运行 docker,需要执行:
sudo usermod -aG docker aaron
将用户 aaron 添加到 docker 用户组中,然后重新登录系统,否则可能会遇到如下权限错误:
docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.38/containers/create: dial unix /var/run/docker.sock: connect: permission denied.
See 'docker run --help'.
执行下列命令启动 Docker 引擎:
aaron@ubuntu:~$ sudo service docker start
通常安装成功后已默认设置开机自启。如需手动设置,可执行:
sudo systemctl enable docker
sudo systemctl start docker
最后,运行一个测试容器,验证安装是否成功:
aaron@ubuntu:~$ sudo docker run hello-world
2. 使用 docker
1. 理解 docker 的架构
使用前,先来了解一下 Docker 的基本架构,这有助于你理解后续的命令和概念。

Docker 架构图
- Docker 镜像是存放在 Docker 仓库中的文件,它是用于创建 Docker 容器的模板。
- Docker 容器是独立运行的一个或一组应用,你可以将其理解为前文提到的轻量级虚拟服务器。
- Docker 主机是一个物理或虚拟的机器,用于执行 Docker 守护进程和容器。
- Docker 客户端(也就是我们用的命令行工具)通过 Docker API 与 Docker 守护进程进行通信。
2. docker 命令
查看帮助
docker --help #查看所有命令帮助
docker COMMAND --help #查看具体命令COMMAND的帮助
查看 Docker 系统信息
docker info
可以看到镜像存储池、已用数据大小、总数据大小、基本容器大小、当前运行容器数量等信息。
搜索镜像
你可以从 Docker Hub 等公共仓库搜索他人制作好的镜像。
docker search ubuntu
docker search centos

搜索 ubuntu 镜像结果
从搜索结果可以看到,有的镜像已经集成了 PHP、Java、Ansible 等应用。你也可以制作包含自己应用或服务的镜像,分享给他人。对方拿到镜像后,无需复杂的环境配置,直接使用 Docker 运行即可,是不是非常方便?
拉取镜像
从仓库下载镜像到本地。
docker pull centos
docker pull ubuntu
导入本地镜像文件
docker load < image_xxx.tar
查看本地镜像列表
docker images
docker images -a
查看镜像详细信息
docker inspect ubuntu
删除镜像
通过镜像名或 ID 来删除。
docker rmi ubuntu
删除全部镜像:
docker rmi $(docker images -q)
查看镜像构建历史
docker history ubuntu
运行容器
你可以把 Docker 容器理解为一个沙盒中运行的进程,这个沙盒包含了进程运行所需的文件系统、系统库、环境变量等资源。但沙盒本身不会自动运行程序,需要你指定一个启动命令。
运行一个 Ubuntu 容器并进入其交互式 Shell:
aaron@ubuntu:~$ docker run -i --name="ubuntu1" --hostname="ubuntu1" ubuntu /bin/sh
cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 ubuntu1
whoami
root
uname -a
Linux ubuntu1 4.15.0-34-generic #37-Ubuntu SMP Mon Aug 27 15:21:48 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
上述命令创建了一个名为 ubuntu1、主机名也为 ubuntu1 的容器,并进入了 /bin/sh。在容器内,我们查看了 hosts 文件、当前用户和内核版本(与宿主机一致)。就像在一个全新的操作系统中一样,可以使用各种 Linux 命令。
在另一个终端,用同样的方法创建一个 ubuntu2 容器,然后使用 docker ps 查看正在运行的容器。

查看正在运行的容器
输入 exit 即可退出当前容器的交互模式。
后台运行容器
docker run -d ubuntu
命令会返回一串长长的容器 ID。请注意,如果容器内没有持续运行的进程,容器会很快自动退出。
运行容器并指定 MAC 地址
docker run -d --name='centos3' --hostname='centos3' --mac-address="02:42:AC:11:00:24" docker-centos6.10-hadoop-spark
列出所有容器
docker ps -a
列出最近一次启动的容器
docker ps -l
检查容器详细信息
docker inspect centos1
获取容器的各种 ID 和网络信息
# 获取容器完整ID
docker inspect -f '{{.Id}}' centos1
# 获取容器在宿主机上的进程ID(PID)
docker inspect -f '{{.State.Pid}}' centos1
# 获取容器IP地址
docker inspect -f '{{.NetworkSettings.IPAddress}}' centos1
# 获取容器网关
docker inspect -f '{{.NetworkSettings.Gateway}}' centos1
# 获取容器MAC地址
docker inspect -f '{{.NetworkSettings.MacAddress}}' centos1
进入正在运行的容器
除了创建时通过 -i 参数进入,对于已运行的容器,可以使用 exec 命令进入:
docker exec -it centos /bin/sh
查看容器日志
docker logs centos1
查看容器内文件系统的变更
列表会显示 A(增加)、D(删除)、C(改变)三种事件。
docker diff centos1
查看容器内运行的进程
docker top centos1
从容器内复制文件到宿主机
docker cp centos1:/etc/passwd /tmp/
ls /tmp/passwd
通过网络(如SCP)也可以实现同样功能,有时更方便。
停止容器
docker stop centos1
停止所有容器:
docker kill $(docker ps -a -q)
启动已停止的容器
docker start centos1
删除容器
删除前需要先停止容器。
docker stop centos1
docker rm centos1
删除所有容器:
docker kill $(docker ps -a -q)
docker rm $(docker ps -a -q)
3. 卷的概念
为了能够持久化数据以及在容器间共享数据,Docker 引入了卷(Volume)的概念。卷是容器中的一个特殊目录,这个目录下的文件实际存储在宿主机上,而不是容器的可写层中。
数据卷提供了很多有用的特性:
- 可以在容器之间共享和重用。
- 对数据卷的修改会立即生效。
- 对数据卷的更新,不会影响镜像本身。
- 数据卷会一直存在,即使容器被删除。
注意:数据卷的使用类似于 Linux 下的 mount 挂载。容器中被指定为挂载点的目录,其原有内容会被隐藏,显示的是挂载的数据卷内容。
创建和使用数据卷
mkdir -p /root/volume1
mkdir -p /root/volume2
docker run -d -v /volume1 --name='centos5' docker-centos6.10-hadoop-spark
docker run -d -v /root/volume1:/volume1 --name='centos6' docker-centos6.10-hadoop-spark
docker run -d -v /root/volume1:/volume1 -v /root/volume2:/volume2 --name='centos7' docker-centos6.10-hadoop-spark
docker run -d -v /root/volume1:/volume1:ro --name='centos8' docker-centos6.10-hadoop-spark
使用 docker run 创建容器时,通过 -v 标记来创建并挂载数据卷。你可以挂载多个卷,可以设置只读属性(ro)。如果不指定宿主机目录(如第一个命令),Docker 会自动在宿主机上创建一个目录,可以通过 docker inspect 查看其具体路径。
可以分别进入这些容器,查看 /volume1、/volume2 目录。
数据卷共享
如果想让一个容器访问另一个容器的数据卷,可以使用 --volumes-from 参数。
数据卷容器
这是一种专门用于提供数据卷给其他容器挂载的容器,适合需要在多个容器间共享持续更新数据的场景。
-
创建一个名为 dbdata 的数据卷容器:
docker run -d -v /dbdata --name dbdata docker-centos6.10-hadoop-spark
-
在其他容器中使用 --volumes-from 来挂载 dbdata 容器中的数据卷:
docker run -d --volumes-from dbdata --name db1 docker-centos6.10-hadoop-spark
docker run -d --volumes-from dbdata --name db2 docker-centos6.10-hadoop-spark
这样,db1 和 db2 容器就能共享 dbdata 容器中 /dbdata 目录的数据了。
4. 自制镜像并发布
提交容器修改以创建新镜像
docker commit centos1 centos111
将现有的容器 centos1 的当前状态提交,形成一个新的镜像 centos111。使用 docker images 可以看到这个新镜像。这是快速创建自定义镜像的一种方法。
查看镜像:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos111 latest d691a75ee371 23 minutes ago 501.5 MB
使用新镜像创建容器:
docker run -d --name='centos111' centos111
导出和导入镜像
当需要将一台机器上的镜像迁移到另一台机器时,就需要用到导出和导入。
在机器A上导出镜像:
docker save docker-centos6.10-hadoop-spark > docker-centos6.10-hadoop-spark2.tar
或
docker save -o docker-centos6.10-hadoop-spark docker-centos6.10-hadoop-spark2.tar
使用 scp 或其他方式将 docker-centos6.10-hadoop-spark2.tar 文件拷贝到机器B。
在机器B上导入镜像:
docker load < docker-centos6.10-hadoop-spark2.tar
或
docker load -i docker-centos6.10-hadoop-spark2.tar
发布镜像到仓库
docker push centos6.8-lamp1
这将镜像推送到你配置的镜像仓库(默认为 Docker Hub)。
4. docker 网络
Docker 启动时会在宿主机上创建一个名为 docker0 的虚拟网桥。它会从 RFC 1918 定义的私有地址段中随机选择一个宿主机未使用的地址和子网,默认通常是 172.17.0.1/16。这个 16 位的子网掩码为容器提供了 65534 个可用的 IP 地址。
docker0 不是一个普通的网络接口,而是一个虚拟的以太网桥,可以自动在绑定到它上面的其他网卡间转发数据包,从而实现容器与宿主机、容器与容器之间的通信。
Docker 每创建一个容器,就会创建一对“Veth Pair”接口(可以想象成一根网线的两端)。其中一端作为 eth0 接口放在容器内,另一端以类似 vethAQI2QT 的名称连接到宿主机,并绑定到 docker0 网桥上。这样,所有容器和宿主机就处在同一个虚拟子网中。
Docker NAT 网络
这是 Docker 容器的默认网络模式。容器通过 NAT(网络地址转换)方式访问外网。在这种模式下,容器内可以访问宿主机以外的网络,但宿主机以外的机器无法直接访问容器内部。
Docker Bridge 网络
Bridge 模式也可以让容器访问外网。但与默认模式不同的是,通过额外的配置(如端口映射或自定义网桥),宿主机以外的机器也有可能访问到容器网络。
6. docker pipework
Docker 自带的网络功能相对基础,无法满足一些复杂的应用场景。因此社区涌现了许多增强 Docker 网络能力的开源工具,如 pipework、weave、flannel 等。
pipework 由 Docker 工程师 Jérôme Petazzoni 开发,是一个用 200 多行 Shell 脚本实现的 Docker 网络配置工具,简单易用。
安装 pipework
git clone https://github.com/jpetazzo/pipework
cp pipework/pipework /bin/
或
wget http://172.17.1.240/docker/software/pipework
chmod a+x pipework
cp pipework /bin/
使用 pipework 配置容器网络
-
运行一个不配置网络的容器:
docker run -d --net='none' --name='centos9' docker-centos6.10-hadoop-spark
-
使用 pipework 为容器配置网络,并将其连接到 docker0 网桥。网关地址在 IP 后通过 @ 指定:
pipework docker0 centos9 172.18.0.100/16@172.18.0.1
7. docker 网络端口映射
对于使用默认 docker0 网络的容器(NAT模式),外网无法直接访问。此时,可以通过端口映射的方式,将容器内的服务端口暴露给宿主机,从而让外部访问。
运行一个容器,并将其 22 端口映射到宿主机的 38022 端口:
docker run -d -p 38022:22 --name='centos10' docker-centos6.10-hadoop-spark
现在,可以通过访问宿主机的 38022 端口来连接容器内的 SSH 服务:
ssh localhost -p 38022
其他服务器也可以通过访问这台物理宿主机的 IP 地址和 38022 端口来访问该容器。
可以一次映射多个端口:
docker run -d -p 38022:22 -p 38080:80 --name='centos11' docker-centos6.10-hadoop-spark
其底层原理是通过宿主机上的 iptables 规则实现端口转发。当然,也可以通过配置 iptables 规则实现整个容器 IP 的转发。
4. 总结
容器作为进程级别的虚拟化技术,相比传统的虚拟机拥有显著优势。
(1)启动快
容器内的应用直接就是宿主机系统的一个进程,而不是虚拟机内部的操作系统进程。所以启动容器相当于启动本机的一个进程,而不是启动一整个操作系统,速度自然快得多。
(2)资源占用少
容器只占用它实际需要的资源,不会像完整的虚拟机操作系统那样占用所有预设资源。多个容器可以共享宿主机内核等资源,而虚拟机通常是资源独享。
(3)体积小
容器镜像只需要包含应用及其运行依赖的组件,而虚拟机镜像则是整个操作系统的打包。因此容器镜像文件通常比虚拟机镜像文件小很多。
总而言之,容器就像一种轻量级的虚拟机,能够提供必要的环境隔离和虚拟化能力,但开销和复杂度却低得多,是现代 DevOps 和 云原生 应用架构中的重要基石。如果你想深入探讨容器技术的更多细节或实践案例,欢迎访问云栈社区与广大开发者交流。