这篇笔记分成四个部分:
1. Docker 容器是什么
2. Docker 容器与虚拟机技术的对比
3. Docker 容器的核心概念
4. Docker 容器的安装
5. Docker 容器的基本操作
一、Docker 容器是什么?
大部分普通的软件工程师对“容器”的认知,确实源于 Docker。但 Docker 其实是源于 LXC(Linux Container)容器技术。LXC 并不是新技术,是一种操作系统级的虚拟化技术。这种技术能够将单个操作系统管理的资源划分到独立的分组中,以便能更好的在孤立的组之间平衡资源的使用。同时,由于是基于内核级别的运行,因此并不需要模拟特殊的、复杂的硬件指令,这令容器技术更轻量,更简单。LXC 已经被集成了主流的 Linux 内核中,已经成为 Linux 系统容器技术事实上的标准。
Docker 源于LXC,而进一步优化了 LXC 的使用体验。通过提供丰富的容器管理工具(如打包、分发、版本管理、移植等),避免用户对底层进行操作,降低了 LXC 的使用门槛,最终推动了容器技术被广泛应用。
二、Docker 容器与虚拟机技术的对比
提到容器,就避不开和传统虚拟机技术进行对比,而且容器本身也是一种虚拟化技术。因此有必要先对虚拟化技术进行一定的了解。
虚拟化(Virtualization)是一种资源管理技术,是将计算机的各种实体资源,如处理器、内存、存储、网络等,予以抽象和转换后呈现出来,打破实体间不可切割的障碍,使用户可以用到比原本的组态更好的方式来应用这些资源。
虚拟化的核心是对资源进行抽象,目标往往是为了在同一个主机上运行多个系统或应用,从而提高资源的利用率,降低成本,方便管理。
主流的虚拟化技术可以分为如下几个类别:
- 全虚拟化:虚拟机将模拟出完整的底层硬件环境,包括特权指令的执行过程,虚拟化平台的技术难度非常高,但可以做到客户操作系统完全不感知和免修改。典型的技术实现有 VMWare、VirtualBox、QEMU等。
- 硬件辅助虚拟化:虚拟机将利用硬件辅助支持(主要是CPU,主流技术是 Intel-VT 和 AMD-V)特权指令的执行过程,来实现完全的虚拟化,也可以做到客户操作系统完全不感知和免修改。典型的技术实现有 VMWare、Xen、KVM等。
- 半虚拟化:虚拟机只模拟出部分硬件环境,其他则需要操作系统进行修改适配。
- 操作系统级虚拟化:操作系统内核通过创建多个系统实例(内核+库)来隔离不同的应用,容器相关技术就属于这个范畴。
那么,容器和传统虚拟机技术的差异是什么呢?
传统虚拟机技术如 VMware ,VisualBox 需要模拟出完整的服务器环境,每台虚拟机都需要拥有自己的操作系统,即 GuestOS,同时还包括必要的库文件和应用程序。而虚拟机一旦启动,预分配给它的资源将全部被占用。
而容器技术则和宿主机共享硬件资源及操作系统。每个容器不需要拥有自己的操作系统,只需要包含应用和依赖包,与其他容器共享内核,但在彼此独立的进程空间中运行。
下面这两幅图直观的反映出两者的区别。
第一幅图:容器引擎部署在裸金属服务器上
第二幅图:容器引擎部署在虚拟机上
目前,这两种部署方式在都是广泛存在的。
通过使用容器,可以更轻松打包应用程序的代码、配置和依赖关系,将其变成容易使用的构建块,从而实现环境一致性、运营效率、开发人员生产力和版本控制等诸多目标。容器能帮助应用程序快速、可靠、一致地部署,不受部署环境的影响,迁移更简单。同时,容器还赋予对资源更多的精细化控制能力,让基础设施效率更高。
比如,基于常见的 LAMP(Linux+Apach+MySQL+PHP)组合运维一个网站,传统的做法是在虚拟机或物理机的操作系统上,单独安装 Apache、MySQL、PHP 以及它们的依赖环境,并进行复杂的配置和测试。这个过程可以参见我之前写过的一篇文章(LAMP开发环境搭建日志)。经历过,才知道坑有多深(当然,在传统方式下,通过XAMPP也是另一种简化的部署方案)。但通过容器打包 LAMP 的方案则聪明、轻量的多。后面也会写一篇实战的笔记,更直观的体验两种方式的差异。但不管怎样,自己都不再会再把时间花费在独自安装各个组件的事情上了。
所以,容器技术受到了广大开发和运维人员的喜爱,能够给大家带来很多实际的好处:
- 更快速的交付和部署。显而易见,容器更容易构建一套标准统一的环境,研发和运维人员可以使用同相同的环境来部署应用。所以在 DevOps 领域,容器是明星技术。
- 更高效的资源利用。容器不需要 GuestOS,这使得资源占用就具备很大优势了。而且由于容器共享宿主机(HostOS)的内核空间,理论上能够获得更高的执行效率。
- 更轻松的迁移和扩展。Docker 容器几乎可以在各种基础设施平台上运行,只需要该平台上部署了 Docker Engine。比如物理机、虚拟机、公有云、私有云、混合云等等。
- 更简单的更新管理。这主要是指基于 Dockerfile 的自动化部署与软件管理方式。
Docker 容器作为一种轻量级的虚拟化技术,和传统虚拟机技术的特性差异如下表所示:
特性 | 容器 | 虚拟机 |
---|---|---|
启动速度 | 秒级 | 分钟级 |
存储占用 | MB级 | GB级 |
性能对比 | 接近原生级别 | 理论达不到原生级别 |
系统量级 | 单机可支持上千个 | 单机支持几十个 |
安全能力 | 依赖宿主系统安全能力 | 完全隔离 |
单独提一下容器被广泛诟病的安全问题。传统虚拟机技术利用完全模拟或 Intel 的 VT-d 和 VT-x 的 ring-1 硬件隔离技术将物理资源完全进行隔离,不同的虚拟机即使底层运行在同一台物理主机上,彼此之间是完全独立的,在技术上能够实现虚拟机突破和彼此交互的防护。而 Docker 利用的只是 Linux 系统自身的进程空间隔离机制实现容器间的隔离,Docker 租户的 root 和宿主机的 HostOS 的 root 是等同的,也就是说,Docker 的特权租户可以直接对宿主机进行无限制操作,这相对虚拟机机制确实存在较大的安全隐患。目前,Docker 也引入了相应的安全选项和镜像签名机制,较大的改善了容器的安全能力。
三、Docker 容器的核心概念
Docker 有三个核心概念:
- 镜像(Image)
- 容器(Container)
- 仓库(Repository)
理解了这三个核心概念,对于理解 Docker 整个生命周期会很有帮助。下面是一张被多次引用的图,清晰的描述了 Docker 的生命周期。
核心概念1:镜像
Docker 镜像(Image)和虚拟机镜像的概念是一样的,可以将其理解为一个面向 Docker 引擎的只读模板,可用于批量创造可运行的容器实例。Docker 镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。镜像是创建 Docker 容器的基础,而 Docker 体系能够提供一套简单的机制创建和更新已有的镜像,也可以从网上下载一个制作好的现成的镜像。
核心概念2:容器
Docker 容器(Container)是一个运行时的概念,可以理解为一个简易的 Linux 系统环境(包括 root 用户权限、进程空间、网络空间等概念)以及运行在其中的应用程序。每个 Docker 容器本质上就是一个 Linux 进程,拥有自己的上下文环境,彼此隔离。Docker 引擎能够对一个容器进行启动、停止、删除等操作。
核心概念3:仓库
Docker 仓库(Reopsitory)类似代码仓库,是 Docker 体系集中存放镜像文件的场所。一般情况下,一个仓库会包含同一个软件不同版本的镜像,用“标签”的概念来标记版本 。可以通过<仓库名>:<标签>的格式来精确定位具体采用哪个镜像。如果不指定标签,则采用 latest 作为默认标签。
根据存储的镜像公开与否,可以分为公开仓库(Public)和私有仓库(Private),目前最大的公开仓库是 Docker Hub,用户可以在自己的本地网络中创建私有仓库。
Docker 利用仓库管理镜像的理念类似于 Git,默认情况下 Docker 会在中央仓库(Docker Hub 和 Docker Cloud)寻找镜像文件。
Docker 基本架构
Docker 采用 C/S 架构。 Docker Server 负责构建、运行和分发 Docker 镜像。 Docker Client 和 Docker Server 可以运行在同一台机器上,也可以通过 RESTful 或其他网络接口进行远程通信,如下图所示:
从上图能够看到,Docker 体系的核心组件包括如下几部分:
- Docker Client
- Docker Daemon
- Docker Container
- Docker Image
- Docker Registry
1) Docker Client
Docker Client 提供了命令行界面 (CLI) ,是用户与 Docker 进行交互的主要窗口。通过 Client 可以构建、运行和停止容器,还可以与远程的 Docker Server 进行交互。
andy-zhang@localhost:~$ sudo docker
[sudo] password for andy-zhang:
Usage: docker [OPTIONS] COMMAND
A self-sufficient runtime for containers
Options:
--config string Location of client config files (default "/home/andy-zhang/.docker")
-c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default
context set with "docker context use")
-D, --debug Enable debug mode
-H, --host list Daemon socket(s) to connect to
-l, --log-level string Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
--tls Use TLS; implied by --tlsverify
--tlscacert string Trust certs signed only by this CA (default "/home/andy-zhang/.docker/ca.pem")
--tlscert string Path to TLS certificate file (default "/home/andy-zhang/.docker/cert.pem")
--tlskey string Path to TLS key file (default "/home/andy-zhang/.docker/key.pem")
--tlsverify Use TLS and verify the remote
-v, --version Print version information and quit
Management Commands:
builder Manage builds
config Manage Docker configs
container Manage containers
context Manage contexts
engine Manage the docker engine
image Manage images
network Manage networks
node Manage Swarm nodes
plugin Manage plugins
secret Manage Docker secrets
service Manage services
stack Manage Docker stacks
swarm Manage Swarm
system Manage Docker
trust Manage trust on Docker images
volume Manage volumes
Commands:
attach Attach local standard input, output, and error streams to a running container
build Build an image from a Dockerfile
commit Create a new image from a container's changes
cp Copy files/folders between a container and the local filesystem
create Create a new container
diff Inspect changes to files or directories on a container's filesystem
events Get real time events from the server
exec Run a command in a running container
export Export a container's filesystem as a tar archive
history Show the history of an image
images List images
import Import the contents from a tarball to create a filesystem image
info Display system-wide information
inspect Return low-level information on Docker objects
kill Kill one or more running containers
load Load an image from a tar archive or STDIN
login Log in to a Docker registry
logout Log out from a Docker registry
logs Fetch the logs of a container
pause Pause all processes within one or more containers
port List port mappings or a specific mapping for the container
ps List containers
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
rename Rename a container
restart Restart one or more containers
rm Remove one or more containers
rmi Remove one or more images
run Run a command in a new container
save Save one or more images to a tar archive (streamed to STDOUT by default)
search Search the Docker Hub for images
start Start one or more stopped containers
stats Display a live stream of container(s) resource usage statistics
stop Stop one or more running containers
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
top Display the running processes of a container
unpause Unpause all processes within one or more containers
update Update configuration of one or more containers
version Show the Docker version information
wait Block until one or more containers stop, then print their exit codes
2) Docker Daemon
Docker Daemon 是最核心的后台服务进程,以 Linux 后台服务的方式运行,也称为守护进程。它负责响应来自 Docker Client 的请求,内部对请求进行路由分发,交给具体的管理模块进行处理。
默认情况下,Docker Daemon 只能接收本地 Docker Client 的请求。如果要处理远程 Client 的请求,需要在配置文件中打开 TCP 监听
# 编辑 Docker 配置文件
andy-zhang@localhost:~$ vim.tiny /etc/systemd/system/multi-user.target.wants/docker.service
[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
# 在 ExecStart后面添加 “-H tcp://0.0.0.0”
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
然后重启 Docker Daemon 服务
systemctl daemon-reload
systemctl restart docker.service
通过以下命令访问远程 Docker Server
docker -H 服务器IP地址 info
3) Docker Container
上文已经对此进行了描述,不再赘述:
Docker 容器(Container)是一个运行时的概念,可以理解为一个简易的 Linux 系统环境(包括 root 用户权限、进程空间、网络空间等概念)以及运行在其中的应用程序。每个 Docker 容器本质上就是一个 Linux 进程,拥有自己的上下文环境,彼此隔离。Docker 引擎能够对一个容器进行启动、停止、删除等操作。
4) Docker Image
上文已经对此进行了描述,不再赘述:
Docker 镜像(Image)和虚拟机镜像的概念是一样的,可以将其理解为一个面向 Docker 引擎的只读模板,可用于批量创造可运行的容器实例。Docker 镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。镜像是创建 Docker 容器的基础,而 Docker 体系能够提供一套简单的机制创建和更新已有的镜像,也可以从网上下载一个制作好的现成的镜像。
5) Docker Registry
上文已经对此进行了描述,不再赘述:
Docker 仓库(Reopsitory)类似代码仓库,是 Docker 体系集中存放镜像文件的场所。一般情况下,一个仓库会包含同一个软件不同版本的镜像,用“标签”的概念来标记版本 。可以通过<仓库名>:<标签>的格式来精确定位具体采用哪个镜像。如果不指定标签,则采用 latest 作为默认标签。
根据存储的镜像公开与否,可以分为公开仓库(Public)和私有仓库(Private),目前最大的公开仓库是 Docker Hub,用户可以在自己的本地网络中创建私有仓库。
Docker 利用仓库管理镜像的理念类似于 Git,默认情况下 Docker 会在中央仓库(Docker Hub 和 Docker Cloud)寻找镜像文件。
运行 docker push、docker pull、docker search 命令时,实际上是 Docker Client 通过 Docker Daemon 与 Docker Registry 进行通信。
关于 Docker 的架构详解,有必要会单独写一篇学习笔记,本篇笔记不再展开了。
四、Docker 容器的安装
Docker 官方文档是最好的安装指南:Install Docker on Ubuntu
以 Ubuntu 为例,可以采用两种方式进行安装:
- Docker Repository(推荐方式)
- Docker Debian Package
方式一:Docker Repository
- 更新
apt
列表
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
- 允许 apt 能够通过 HTTPs 方式使用仓库
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
- 添加 Dokcer 官方 GPG Key
sudo apt-key fingerprint 0EBFCD88
- 设置 Docker-CE 稳定版的仓库
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
- 安装最新版本的 Docker
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
如果想安装指定版本的 Docker 怎么办?
# 获取 Docker 版本列表
apt-cache madison docker-ce
# 安装指定版本,修改 "<>" 中的版本号
sudo apt-get install docker-ce=<18.03.1~ce-0~ubuntu>
- 检查 Docker 版本
sudo docker version
Client: Docker Engine - Community
Version: 19.03.5
API version: 1.40
Go version: go1.12.12
Git commit: 633a0ea838
Built: Wed Nov 13 07:29:52 2019
OS/Arch: linux/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 19.03.5
API version: 1.40 (minimum version 1.12)
Go version: go1.12.12
Git commit: 633a0ea838
Built: Wed Nov 13 07:28:22 2019
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.2.10
GitCommit: b34a5c8af56e510852c35414db4c1f4fa6172339
runc:
Version: 1.0.0-rc8+dev
GitCommit: 3e425f80a8c931f88e6d94a8c831b9d5aa481657
docker-init:
Version: 0.18.0
GitCommit: fec3683
方式二:Docker Debian Package
下载 Docker 压缩包
下载地址:Ubuntu Docerk 压缩包地址安装 Docker
sudo dpkg -i /home/andy/docker/package.deb
五、Docker 容器的基本操作
首先,通过一个 HelloWord 的例子,对 Docker 体系各组件的协作流程有个整体的理解,这对进一步掌握和理解 Docker 的操作方法会有很好的帮助:
从 hello-world 打印中,官方还给出了如何运行一个 Ubuntu 容器的例子,可以认为在这个容器中运行的是一个极简的 Ubuntu Linux 系统,一个极为轻量的 GuestOS。
Docker 的操作命令也是非常多的,执行 docker --help
能够看到所有命令接口。这篇笔记只记录一些关于镜像、容器、仓库的基本操作方法。
andy-zhang@localhost:~$ sudo docker --help
[sudo] password for andy-zhang:
Usage: docker [OPTIONS] COMMAND
A self-sufficient runtime for containers
Options:
--config string Location of client config files (default "/home/andy-zhang/.docker")
-c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default
context set with "docker context use")
-D, --debug Enable debug mode
-H, --host list Daemon socket(s) to connect to
-l, --log-level string Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
--tls Use TLS; implied by --tlsverify
--tlscacert string Trust certs signed only by this CA (default "/home/andy-zhang/.docker/ca.pem")
--tlscert string Path to TLS certificate file (default "/home/andy-zhang/.docker/cert.pem")
--tlskey string Path to TLS key file (default "/home/andy-zhang/.docker/key.pem")
--tlsverify Use TLS and verify the remote
-v, --version Print version information and quit
Management Commands:
builder Manage builds
config Manage Docker configs
container Manage containers
context Manage contexts
engine Manage the docker engine
image Manage images
network Manage networks
node Manage Swarm nodes
plugin Manage plugins
secret Manage Docker secrets
service Manage services
stack Manage Docker stacks
swarm Manage Swarm
system Manage Docker
trust Manage trust on Docker images
volume Manage volumes
Commands:
attach Attach local standard input, output, and error streams to a running container
build Build an image from a Dockerfile
commit Create a new image from a container's changes
cp Copy files/folders between a container and the local filesystem
create Create a new container
diff Inspect changes to files or directories on a container's filesystem
events Get real time events from the server
exec Run a command in a running container
export Export a container's filesystem as a tar archive
history Show the history of an image
images List images
import Import the contents from a tarball to create a filesystem image
info Display system-wide information
inspect Return low-level information on Docker objects
kill Kill one or more running containers
load Load an image from a tar archive or STDIN
login Log in to a Docker registry
logout Log out from a Docker registry
logs Fetch the logs of a container
pause Pause all processes within one or more containers
port List port mappings or a specific mapping for the container
ps List containers
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
rename Rename a container
restart Restart one or more containers
rm Remove one or more containers
rmi Remove one or more images
run Run a command in a new container
save Save one or more images to a tar archive (streamed to STDOUT by default)
search Search the Docker Hub for images
start Start one or more stopped containers
stats Display a live stream of container(s) resource usage statistics
stop Stop one or more running containers
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
top Display the running processes of a container
unpause Unpause all processes within one or more containers
update Update configuration of one or more containers
version Show the Docker version information
wait Block until one or more containers stop, then print their exit codes
Run 'docker COMMAND --help' for more information on a command.
镜像的基本操作:
1)获取镜像
【命令】:docker pull NAME[:TAG]
【说明】:使用该指令从网络上下载指定的镜像文件。如果不显示的指定 “TAG”,默认选择 “latest” 标签,即对应仓库中最新版本的镜像文件。
以获取 Ubuntu 镜像为例:
andy-zhang@localhost:~$ sudo docker pull ubuntu:latest
[sudo] password for andy-zhang:
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
5c939e3a4d10: Pull complete
c63719cdbe7a: Pull complete
19a861ea6baf: Pull complete
651c9d2d6c4f: Pull complete
Digest: sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110
Status: Downloaded newer image for ubuntu:latest
Docker 仓库中最新的 Ubuntu 版本为 18.04.3。从下载过程中能看到一个小细节,镜像是分层的,“5c939e3a4d10” 这样的数字代表每一层的ID。分层其实是 AUFS(Advanced Union File System,联合文件系统) 的重要概念,可以实现 Docker 镜像的增量保存与更新。AUFS 是比较重要的机制,但这里不再展开了。
docker pull 指令默认从 Docker 官方的注册服务器(也可以称作镜像源)中进行下载,"docker pull ubuntu:latest" 这条指令等价于 “docker pull registry.hub.docker.com/ubuntu:latest”,也就是从默认的注册服务器“registry.hub.docker.com”中的 “ubuntu” 仓库中下载标签为 “latest” 的镜像文件。镜像源可以改变,比如从 DockerPool 社区的镜像源 “dl.dockerpol.com” 下载,但需要在命令中进行显示指定。
镜像下载到本地后,就可以进行各种操作了,比如利用该镜像创建一个 Ubuntu 容器,并在其中运行bash应用:
andy-zhang@localhost:~$ sudo docker run -i -t ubuntu bash
root@f90e0ca4b855:/# pwd
/
root@f90e0ca4b855:/# whoami
root
root@f90e0ca4b855:/# uname -a
Linux f90e0ca4b855 4.4.0-62-generic #83-Ubuntu SMP Wed Jan 18 14:10:15 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
2)查看镜像
【命令】:docker images
【说明】:使用该指令查询本地已经拥有的镜像。
andy-zhang@localhost:~$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest ccc6e87d482b 2 weeks ago 64.2MB
hello-world latest fce289e99eb9 13 months ago 1.84kB
【命令】:docker inspect NAME[:TAG]
【说明】:获取镜像的详细信息,返回 JSON 格式的消息。使用者一般情况下应该不太需要查询如此详细的信息。
andy-zhang@localhost:~$ sudo docker inspect hello-world:latest
[
{
"Id": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e",
"RepoTags": [
"hello-world:latest"
],
"RepoDigests": [
"hello-world@sha256:9572f7cdcee8591948c2963463447a53466950b3fc15a247fcad1917ca215a2f"
],
"Parent": "",
"Comment": "",
"Created": "2019-01-01T01:29:27.650294696Z",
"Container": "8e2caa5a514bb6d8b4f2a2553e9067498d261a0fd83a96aeaaf303943dff6ff9",
"ContainerConfig": {
"Hostname": "8e2caa5a514b",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/hello\"]"
],
"ArgsEscaped": true,
"Image": "sha256:a6d1aaad8ca65655449a26146699fe9d61240071f6992975be7e720f1cd42440",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {}
},
"DockerVersion": "18.06.1-ce",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/hello"
],
"ArgsEscaped": true,
"Image": "sha256:a6d1aaad8ca65655449a26146699fe9d61240071f6992975be7e720f1cd42440",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"Architecture": "amd64",
"Os": "linux",
"Size": 1840,
"VirtualSize": 1840,
"GraphDriver": {
"Data": {
"MergedDir": "/var/lib/docker/overlay2/8fcf5abbb31dbe70e7fe9e6cc3d4515b9dfa243adee48efff812bcca9551d488/merged",
"UpperDir": "/var/lib/docker/overlay2/8fcf5abbb31dbe70e7fe9e6cc3d4515b9dfa243adee48efff812bcca9551d488/diff",
"WorkDir": "/var/lib/docker/overlay2/8fcf5abbb31dbe70e7fe9e6cc3d4515b9dfa243adee48efff812bcca9551d488/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:af0b15c8625bb1938f1d7b17081031f649fd14e6b233688eea3c5483994a66a3"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
3)查找镜像
【命令】:docker search NAME
【说明】:搜索远端仓库中的镜像,默认为 Docker Hub 官方仓库。而且结果会按照星级进行自动排序。也可以直接访问 Docker Hub 的网址(Docker Hub),更直观一些。
andy-zhang@localhost:~$ sudo docker search php
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
php While designed for web development, the PHP … 5025 [OK]
phpmyadmin/phpmyadmin A web interface for MySQL and MariaDB. 937 [OK]
adminer Database management in a single PHP file. 339 [OK]
php-zendserver Zend Server - the integrated PHP application… 178 [OK]
webdevops/php-nginx Nginx with PHP-FPM 149 [OK]
webdevops/php-apache-dev PHP with Apache for Development (eg. with xd… 115 [OK]
webdevops/php-apache Apache with PHP-FPM (based on webdevops/php) 96 [OK]
bitnami/php-fpm Bitnami PHP-FPM Docker Image 84 [OK]
phpunit/phpunit PHPUnit is a programmer-oriented testing fra… 74 [OK]
nazarpc/phpmyadmin phpMyAdmin as Docker container, based on off… 60 [OK]
circleci/php CircleCI images for PHP 27
thecodingmachine/php General-purpose ultra-configurable PHP images 26 [OK]
adrianharabula/php7-with-oci8 Latest PHP 7.1 with apache and Oracle oci8 19 [OK]
phpdockerio/php72-fpm PHP 7.2 FPM base container for PHPDocker.io. 19 [OK]
bitnami/phpmyadmin Bitnami Docker Image for phpMyAdmin 16 [OK]
phpdockerio/php7-fpm PHP 7 FPM base container for PHPDocker.io. 14 [OK]
graze/php-alpine Smallish php7 alpine image with some common … 13 [OK]
phpdockerio/php56-fpm PHP 5.6 FPM base container for PHPDocker.io 11 [OK]
appsvc/php Azure App Service php dockerfiles 10 [OK]
phpdockerio/php71-fpm PHP 7.1 FPM base container for PHPDocker.io. 7 [OK]
phpdockerio/php72-cli PHP 7.2 CLI base container for PHPDocker.io. 4 [OK]
phpdockerio/php56-cli PHP 5.6 CLI base container for PHPDocker.io … 1 [OK]
phpdockerio/php71-cli PHP 7.1 CLI base container for PHPDocker.io. 1 [OK]
phpdockerio/php7-cli PHP 7 CLI base container image for PHPDocker… 1 [OK]
isotopab/php Docker PHP 0 [OK]
4)删除镜像
【命令】:docker rmi NAME[:TAG]
【说明】:删除指定的镜像文件,该镜像所有的 AUFS 层都会被删除掉。需要注意的是,如果存在用该镜像创建的容器时,镜像文件是无法被删除的,虽然可以通过添加 “-f” 参数强制删除,但不推荐这样干,可能会因为依赖关系导致出现一些莫名其妙的问题。详情参见如下示例中的注释。
#查询本地的镜像
andy-zhang@localhost:~$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest ccc6e87d482b 2 weeks ago 64.2MB
hello-world latest fce289e99eb9 13 months ago 1.84kB
#删除hello-world镜像,但提示冲突,因为存在用该镜像创建的容器,删除失败
andy-zhang@localhost:~$ sudo docker rmi hello-world
Error response from daemon: conflict: unable to remove repository reference "hello-world" (must force) -
container 3dfa00a4e8b7 is using its referenced image fce289e99eb9
#查询系统中存在的容器,果然发现了用hello-world镜像创建的容器
andy-zhang@localhost:~$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3dfa00a4e8b7 hello-world "/hello" 49 minutes ago Exited (0) 49 minutes ago boring_brattain
#删除该容器
andy-zhang@localhost:~$ sudo docker rm 3df
3df
#再次查询,hello-world容器已经不存在了
andy-zhang@localhost:~$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
#再次删除,删除成功,所有AUFS层都被删除掉
andy-zhang@localhost:~$ sudo docker rmi hello-world:latest
Untagged: hello-world:latest
Untagged: hello-world@sha256:9572f7cdcee8591948c2963463447a53466950b3fc15a247fcad1917ca215a2f
Deleted: sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e
Deleted: sha256:af0b15c8625bb1938f1d7b17081031f649fd14e6b233688eea3c5483994a66a3
#再次查询本地的镜像,已经没有hello-world
andy-zhang@localhost:~$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest ccc6e87d482b 2 weeks ago 64.2MB
5)创建镜像
创建镜像有三种方式:
- 基于容器创建
- 基于本地模板创建
- 基于 Dockerfile 创建
基于 Dockerfile 的方式非常重要,后面单独写篇笔记,这里主要写一些第一种方式,基于容器创建。
【命令】:docker commit BASED_CONTAINER NAME[:TAG]
【说明】:基于已有的容器创建一个新的镜像。
下面的例子表示基于正在运行的一个容器,对其进行修改后生成一个新的镜像。
#查看本地存在的镜像,只有一个ubuntu
andy-zhang@localhost:~$ sudo docker images
[sudo] password for andy-zhang:
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest ccc6e87d482b 2 weeks ago 64.2MB
#基于ubuntu镜像启动一个容器,ed6ad16ae768就是容器ID
andy-zhang@localhost:~$ sudo docker run -it ubuntu:latest bash
root@ed6ad16ae768:/#
#创建了一个文件,这时候该相对原ubuntu镜像已经发生了改变
root@ed6ad16ae768:/# cd /home/
root@ed6ad16ae768:/home# touch hello-andy
root@ed6ad16ae768:/home# ll
total 8
drwxr-xr-x 1 root root 4096 Feb 3 07:36 ./
drwxr-xr-x 1 root root 4096 Feb 3 07:36 ../
-rw-r--r-- 1 root root 0 Feb 3 07:36 hello-andy
#退出系统,也就是退出容器
root@ed6ad16ae768:/home# exit
exit
#基于ed6ad16ae768容器创建,名字为hello-andy
andy-zhang@localhost:~$ sudo docker commit ed6ad16ae768 hello-andy
sha256:fb6f9a2b58f7b232c24cbdf4746dcfbcf93ed3b0991d81e53f6d6054311af493
#再次查看本地存在的镜像,多了hello-andy这个镜像,该镜像其实就是在ubuntu系统中多创建了一个文件
andy-zhang@localhost:~$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-andy latest fb6f9a2b58f7 12 seconds ago 64.2MB
ubuntu latest ccc6e87d482b 2 weeks ago 64.2MB
#基于hello-andy镜像启动一个容器,1f235281f026是该容器ID
andy-zhang@localhost:~$ sudo docker run -it hello-andy:latest bash
root@1f235281f026:/# cd /home/
#能够看到,该容器是已经包含刚刚创建的hello-andy文件
root@1f235281f026:/home# ll
total 8
drwxr-xr-x 1 root root 4096 Feb 3 07:36 ./
drwxr-xr-x 1 root root 4096 Feb 3 07:39 ../
-rw-r--r-- 1 root root 0 Feb 3 07:36 hello-andy
使用这种方法,可以逐步构筑一个可复用的新镜像,比如一个 LAMP(Linux+Apache+MySQL+PHP)。
6)导出镜像
【命令】:docker save -o FILE
【说明】:将本地的镜像导出到本地文件系统中,这样就可以向传送普通文件一样传送 Docker 镜像文件,而不必通过 “pull” 指令从镜像仓库中获取。
andy-zhang@localhost:~$ sudo docker save -o hello-world-andy.tar hello-world:latest
andy-zhang@localhost:~$ ll
total 65104
drwxr-xr-x 4 andy-zhang andy-zhang 4096 Feb 3 00:13 ./
drwxr-xr-x 3 root root 4096 Jan 31 07:43 ../
-rw------- 1 andy-zhang andy-zhang 1887 Feb 2 02:19 .bash_history
-rw-r--r-- 1 andy-zhang andy-zhang 220 Jan 31 07:43 .bash_logout
-rw-r--r-- 1 andy-zhang andy-zhang 3771 Jan 31 07:43 .bashrc
drwx------ 2 andy-zhang andy-zhang 4096 Jan 31 15:19 .cache/
-rw------- 1 root root 12800 Feb 3 00:13 hello-world-andy.tar
-rw-r--r-- 1 andy-zhang andy-zhang 655 Jan 31 07:43 .profile
drwxrwxr-x 2 andy-zhang andy-zhang 4096 Jan 31 19:46 software/
7)导入镜像
【命令】:docker load < FILE
【说明】:从本地文件系统导入 Docker 镜像文件到本地仓库,是导出的逆操作。
#基于存在的ubuntu容器创建一个新的镜像ubuntu-hello-andy,前文已经描述,不再赘述
andy-zhang@localhost:~$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest ccc6e87d482b 2 weeks ago 64.2MB
hello-world latest fce289e99eb9 13 months ago 1.84kB
andy-zhang@localhost:~$ sudo docker run -it ubuntu:latest bash
root@3e96a231dff7:/# touch /home/hello-andy
root@3e96a231dff7:/# ls /home
hello-andy
root@3e96a231dff7:/# exit
exit
andy-zhang@localhost:~$ sudo docker commit 3e96a231dff7 ubuntu-hello-andy:latest
sha256:0cf13cb05975c7b83b56c4e52ba697f7007ba7d3751b43787bae74d8476d7efc
andy-zhang@localhost:~$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-hello-andy latest 0cf13cb05975 10 seconds ago 64.2MB
ubuntu latest ccc6e87d482b 2 weeks ago 64.2MB
hello-world latest fce289e99eb9 13 months ago 1.84kB
#将新的ubuntu-hello-world镜像导出到本地文件系统
andy-zhang@localhost:~$ sudo docker save -o ubuntu-hello-andy.tar ubuntu-hello-andy:latest
andy-zhang@localhost:~$ ll
total 65104
drwxr-xr-x 4 andy-zhang andy-zhang 4096 Feb 3 00:35 ./
drwxr-xr-x 3 root root 4096 Jan 31 07:43 ../
-rw------- 1 root root 66609152 Feb 3 00:35 ubuntu-hello-andy.tar
#为了查看导入效果,先删除本地镜像库中的ubuntu-hello-world镜像删除
andy-zhang@localhost:~$
andy-zhang@localhost:~$ sudo docker rmi ubuntu-hello-andy
Untagged: ubuntu-hello-andy:latest
Deleted: sha256:0cf13cb05975c7b83b56c4e52ba697f7007ba7d3751b43787bae74d8476d7efc
Deleted: sha256:9f9d42f5978c3879d67fb975eb028bfc23e1f880de02ab99929cdb742a400e2c
andy-zhang@localhost:~$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest ccc6e87d482b 2 weeks ago 64.2MB
hello-world latest fce289e99eb9 13 months ago 1.84kB
#从本地文件系统的ubuntu-hello-andy.tar导入到本地镜像库
andy-zhang@localhost:~$ sudo docker load < ubuntu-hello-andy.tar
44e4e13a1f5b: Loading layer [==================================================>] 3.584kB/3.584kB
Loaded image: ubuntu-hello-andy:latest
#导入成功,运行成功
andy-zhang@localhost:~$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-hello-andy latest 0cf13cb05975 2 minutes ago 64.2MB
ubuntu latest ccc6e87d482b 2 weeks ago 64.2MB
hello-world latest fce289e99eb9 13 months ago 1.84kB
andy-zhang@localhost:~$ sudo docker run -it ubuntu-hello-andy bash
root@651596d03b09:/# ls /home/
hello-andy
8)上传镜像
【命令】:docker push NAME[:TAG]
【说明】:将本地镜像上传到 Docker Hub,前提是在 Docker Hub 上注册了用户。由于注册申请还没有通过 Docker Hub 的审批,这里就不举例了。
容器的基本操作:
1)创建&启动容器
Docker 容器非常的轻量,可以随时创建或删除。有两种方法新建一个容器d
方法一:
【命令】:docker create NAME[:TAG] + docker start
【说明】:执行 create 指令创建的容器不会自动运行,处于停止状态,需要执行 start 指令将其启动起来。
#查询系统中存在哪些容器
andy-zhang@localhost:~$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3e96a231dff7 ubuntu:latest "bash" 3 hours ago Exited (127) 3 hours ago angry_wu
81418356128d hello-world:latest "/hello" 3 hours ago Exited (0) 2 minutes ago stoic_merkle
#利用hello-world镜像创建一个新的容器
andy-zhang@localhost:~$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest ccc6e87d482b 2 weeks ago 64.2MB
hello-world latest fce289e99eb9 13 months ago 1.84kB
andy-zhang@localhost:~$ sudo docker create hello-world
a98bb9e79779affb3cb57df091d3a58ef5d9a0924562e8148e7d08fa3af6e639
#再次查询系统中存在哪些容器,系统中已经存在两个基于hello-world镜像创建的容器
andy-zhang@localhost:~$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a98bb9e79779 hello-world "/hello" 8 seconds ago Created bold_mendel
3e96a231dff7 ubuntu:latest "bash" 3 hours ago Exited (127) 3 hours ago angry_wu
81418356128d hello-world:latest "/hello" 3 hours ago Exited (0) 3 minutes ago stoic_merkle
#用start指令启动新创建的容器
andy-zhang@localhost:~$ sudo docker start -i a98bb9e79779
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
方法二:
【命令】:docker run NAME[:TAG]
【说明】:执行 run 指令等价于 start+start,是更常用的一种方式。执行 run 指令后,Docker 后台引擎的动作是:
- 检查本地是否存在指定的镜像,如果没有就从公有仓库中下载
- 利用镜像创建容器
- 基于 AUFS 在只读的镜像外层挂在一层读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中
- 从地址池中分配一个 IP 地址给容器
- 执行用户指定的应用程序
- 执行完毕后自动终止容器运行
这里解释一下上面运行 Ubuntu 容器的时候,为何会跟着 “-i” 和 “-t” 两个参数。
#run指令不携带任何参数
andy-zhang@localhost:~$ sudo docker run ubuntu:latest
[sudo] password for andy-zhang:
#run指令携带-i和-t参数
andy-zhang@localhost:~$ sudo docker run -i -t ubuntu:latest
root@3dd7d01955af:/# ps
PID TTY TIME CMD
1 pts/0 00:00:00 bash
10 pts/0 00:00:00 ps
-t 选项让 Docker 分配一个伪终端(pseudo-tty),并绑定到容器的标准输入上;-i 选项则让容器的标准输入保持打开状态。对于 Ubuntu 应用,默认会执行 bash。那么,如果希望能够进行持续性的交互,这两个参数就是必要的了。
另外,很多时候,需要让 Docker 容器以守护形式(Daemonized)运行(Web服务器是典型的守护形式程序),可以增加 -d 选项来实现。下面这个例子,以守护形式启动一个 Ubuntu 的容器,执行 shell 程序,通过 docker logs CONTAINER
指令查看容器后台输出信息。
andy-zhang@localhost:~$ sudo docker run -d ubuntu:latest /bin/sh -c "while true; do echo hello andy; sleep 1; done"
andy-zhang@localhost:~$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f00b81c23380 ubuntu:latest "/bin/sh -c 'while t…" 47 seconds ago Up 45 seconds angry_mclaren
3dd7d01955af ubuntu:latest "/bin/bash" 39 minutes ago Exited (0) 4 minutes ago elastic_brattain
andy-zhang@localhost:~$ sudo docker logs f00b81c23380
hello andy
hello andy
hello andy
hello andy
hello andy
hello andy
2)终止容器
【命令】:docker stop
【说明】:终止容器的运行有两种方式,一种方式是执行 “stop” 指令方式,首先向容器发送一个 SIGTERM 信号,等待一段时间后(默认10秒)再向容器发送一个 SIGKILL 信号彻底终止容器;另一种方式是自然终止,也就是容器中运行的程序执行完毕后自行退出,容器会紧跟着中止运行。
下面的示例是终止上面创建的 while 死循环的容器:
#容器正在运行
andy-zhang@localhost:~$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f00b81c23380 ubuntu:latest "/bin/sh -c 'while t…" 10 minutes ago Up 10 minutes angry_mclaren
#执行终止指令
andy-zhang@localhost:~$ sudo docker stop f00b81c23380
f00b81c23380
#容器终止,能够明显感受到SIGTERM和SIGKILL信号10s间隔
andy-zhang@localhost:~$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f00b81c23380 ubuntu:latest "/bin/sh -c 'while t…" 11 minutes ago Exited (137) 4 seconds ago angry_mclaren
3)进入容器
【命令】:docker exec CONTAINER COMMAND
【说明】:采用守护形式启动容器后,容器进入后台运行,用户无法看到容器中发生了什么。有的时候,有必要进入容器进行一些操作,这时候可以利用 “exec” 指令,如下示例:
#以守护形式启动一个容器
andy-zhang@localhost:~$ sudo docker run -d ubuntu:latest /bin/sh -c "while true; do echo hello andy; sleep 1; done"
andy-zhang@localhost:~$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5390d39f4ee0 ubuntu:latest "/bin/sh -c 'while t…" 53 seconds ago Up 53 seconds nostalgic_babbage
#使用exec指令进入正在运行的容器中,执行各种操作
andy-zhang@localhost:~$ sudo docker exec -i -t 5390d39f4ee0 bash
root@5390d39f4ee0:/# ps -a
PID TTY TIME CMD
146 pts/0 00:00:00 ps
root@5390d39f4ee0:/# ps -ax
PID TTY STAT TIME COMMAND
1 ? Ss 0:00 /bin/sh -c while true; do echo hello andy; sleep 1; done
131 pts/0 Ss 0:00 bash
152 ? S 0:00 sleep 1
153 pts/0 R+ 0:00 ps -ax
4)删除容器
【命令】:docker rm CONTAINER
【说明】:删除处于终止状态的容器,如果容器运行中,需要先停止,否则会提示失败,可以使用 “-f” 选项强制进行删除。
#删除正在运行的容器,提示失败
andy-zhang@localhost:~$ sudo docker rm 5390d39f4ee0
Error response from daemon: You cannot remove a running container
5390d39f4ee0c41731ec784bb3ed9c41b62b04ac6cfd391e4690eb07e574d4e5. Stop the container before attempting removal or force remove
#强制删除
andy-zhang@localhost:~$ sudo docker rm -f 5390d39f4ee0
5390d39f4ee0
#删除成功
andy-zhang@localhost:~$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
别混淆:删除容器是 “rm” 指令,删除镜像是 “rmi” 指令。
仓库的基本操作:
仓库(Repository)是集中存放镜像的地方。但仓库容易和注册服务器(Registry)相混淆。准确的说,注册服务器是存放仓库的具体服务器,可以存储多个仓库;而每个仓库下面可以存放多个镜像,用标签(TAG)进行区分。通常情况下,一个仓库中存放的都是同一类型的镜像。
也就是说,精确定位具体的镜像文件的 “URL” 是:Image = Registry + Repository + Tag。
这三者的关系可以用下面的图来表示:
镜像资源一般可以分成两类,一类是官方提供的,由 Docker 公司创建、验证、发布;另一类是第三方组织/个人提供的。在前面镜像基本操作的笔记中,提到了 “search” 指令。从该指令的查询结果中能够看出来,由官方发布的镜像,往往用单个单词作为名字;而第三方组织或个人提供的,往往都带有用户名前缀。
如下面的示例所示,官方发布的镜像有 “php”、“adminer”、“php-zendserver”,第三方组织的则有 “phpmyadmin/phpmyadmin”、“webdevops/php-ngnix”等。
andy-zhang@localhost:~$ sudo docker search php
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
php While designed for web development, the PHP … 5025 [OK]
phpmyadmin/phpmyadmin A web interface for MySQL and MariaDB. 937 [OK]
adminer Database management in a single PHP file. 339 [OK]
php-zendserver Zend Server - the integrated PHP application… 178 [OK]
webdevops/php-nginx Nginx with PHP-FPM 149 [OK]
webdevops/php-apache-dev PHP with Apache for Development (eg. with xd… 115 [OK]
webdevops/php-apache Apache with PHP-FPM (based on webdevops/php) 96 [OK]
bitnami/php-fpm Bitnami PHP-FPM Docker Image
在当前的工作中,主要适用官方提供的仓库就可以满足要求,对于如何创建和适用私有仓库,在这篇笔记中不再涉及,如果工作中有需要时,再进行研究了。
以上,就是 Docker 基本的概念与基本的操作方法。敲了很多字,但心情很爽。后面还会继续补充几篇笔记,涉及 Docker 的数据管理、基础网络配置、Dockerfile、经典应用、Docker 安全等内容。
永远学习。
也向一线抗战的医护人员致敬。