请您假定您在开发一个应用。您使用的是一台笔记本电脑,而且您的开发环境具有特定的配置。其他开发人员身处的环境配置可能稍有不同。您正在开发的应用依赖于您当前的配置,还要依赖于某些特定文件。与此同时,您的企业还拥有标准化的测试和生产环境,且具有自身的配置和一系列支持文件。您希望尽可能多在本地模拟这些环境,而不产生重新创建服务器环境的开销。
因此,您要如何确保应用能够在这些环境中运行和通过质量检测,并且在部署过程中不出现令人头疼的问题,也无需重新编写代码和进行故障修复?答案就是使用容器。容器可以确保您的应用拥有必需的配置和文件,使得这些应用能够在从开发到测试、再到生产的整个流程中顺利运行,而不出现任何不良问题。这样可以避免危机,做到皆大欢喜。
让多个操作系统在单个虚拟机监控程序上运行以实现虚拟化,并不能达成和使用容器同等的轻量级效果。事实上,在仅拥有容量有限的有限资源时,您需要能够可以进行密集部署的轻量级应用。Linux 容器可从单个操作系统运行,在所有容器中共享该操作系统,因此应用和服务能够保持轻量级,并行快速运行。
借助 Docker ,您可将容器当做重量轻、模块化的虚拟机使用。同时,您还将获得高度的灵活性,从而实现对容器的高效创建、部署及复制,并能将其从一个环境顺利迁移至另一个环境。
docker的主要目标是"Build,Ship and Run any App,Angwhere",构建,运输,处处运行
构建:做一个docker镜像
运输:docker pull
运行:启动一个容器
此处使用的环境为wsl2
由于 apt
源使用 HTTPS 以确保软件下载过程中不被篡改。因此,我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。
$ sudo apt-get update
$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
为了确认所下载软件包的合法性,需要添加软件源的 GPG
密钥。
$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 官方源
# $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
然后,我们需要向 sources.list
中添加 Docker 软件源
$ echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 官方源
# $ echo \
# "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
# $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
以上命令会添加稳定版本的 Docker APT 镜像源,如果需要测试版本的 Docker 请将 stable 改为 test。
更新 apt 软件包缓存,并安装 docker-ce
:
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io
$ sudo systemctl enable docker
$ sudo systemctl start docker
出现以上信息则安装成功。
默认情况下,docker
命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root
用户和 docker
组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root
用户。因此,更好地做法是将需要使用 docker
的用户加入 docker
用户组。
建立 docker
组:
$ sudo groupadd docker
将当前用户加入 docker
组:
$ sudo usermod -aG docker $USER
镜像是Docker的三大组件之一。
Docker运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker会从镜像仓库下载该镜像。
下面学习更多关于镜像的内容,包括:
从仓库获取镜像
管理本地主机上的镜像
介绍镜像实现的基本原理
之前提到过,Docker Hub 上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像。
从 Docker 镜像仓库获取镜像的命令是 docker pull
。其命令格式为:
$ docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
具体的选项可以通过 docker pull --help
命令看到,这里我们说一下镜像名称的格式。
Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号]
。默认地址是 Docker Hub(docker.io
)。
仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>
。对于 Docker Hub,如果不给出用户名,则默认为 library
,也就是官方镜像。
比如:
$ docker pull ubuntu:18.04
上面的命令中没有给出 Docker 镜像仓库地址,因此将会从 Docker Hub (docker.io
)获取镜像。而镜像名称是 ubuntu:18.04
,因此将会获取官方镜像 library/ubuntu
仓库中标签为 18.04
的镜像。docker pull
命令的输出结果最后一行给出了镜像的完整名称,即: docker.io/library/ubuntu:18.04
。
从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的 sha256
的摘要,以确保下载一致性。
在使用上面命令的时候,你可能会发现,你所看到的层 ID 以及 sha256
的摘要和这里的不一样。这是因为官方镜像是一直在维护的,有任何新的 bug,或者版本更新,都会进行修复再以原来的标签发布,这样可以确保任何使用这个标签的用户可以获得更安全、更稳定的镜像。
如果从 Docker Hub 下载镜像非常缓慢,可以参照 镜像加速器 一节配置加速器。
有了镜像后,我们就能够以这个镜像为基础启动并运行一个容器。以上面的 ubuntu:18.04
为例,如果我们打算启动里面的 bash
并且进行交互式操作的话,可以执行下面的命令。
$ docker run -it --rm ubuntu:18.04 bash
docker run
就是运行容器的命令,具体格式我们会在 容器 一节进行详细讲解,我们这里简要的说明一下上面用到的参数。
-it
:这是两个参数,一个是 -i
:交互式操作,一个是 -t
终端。我们这里打算进入 bash
执行一些命令并查看返回结果,因此我们需要交互式终端。
--rm
:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm
。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm
可以避免浪费空间。
ubuntu:18.04
:这是指用 ubuntu:18.04
镜像为基础来启动容器。
bash
:放在镜像名后的是 命令,这里我们希望有个交互式 Shell,因此用的是 bash
。
进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里,我们执行了 cat /etc/os-release
,这是 Linux 常用的查看当前系统版本的命令,从返回的结果可以看到容器内是 Ubuntu 18.04.1 LTS
系统。
最后我们通过 exit
退出了这个容器。
要想列出已经下载下来的镜像,可以使用 docker image ls
命令。
docker image ls
列表包含了 仓库名
、标签
、镜像 ID
、创建时间
以及 所占用的空间
。
镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个 标签。
docker image ls
列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。
可以通过 docker system df
命令来便捷的查看镜像、容器、数据卷所占用的空间。
镜像列表中,还可以看到一个特殊的镜像,这个镜像既没有仓库名,也没有标签,均为
。
由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为
的镜像。这类无标签镜像也被称为 虚悬镜像(dangling image)
可以用下面的命令专门显示这类镜像:
docker image ls -f dangling=true
一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的,可以用下面的命令删除。
docker image prune
为了加速镜像构建、重复利用资源,Docker 会利用 中间层镜像。所以在使用一段时间后,可能会看到一些依赖的中间层镜像。默认的 docker image ls
列表中只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加 -a
参数。
$ docker image ls -a
这样会看到很多无标签的镜像,与之前的虚悬镜像不同,这些无标签的镜像很多都是中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错。只要删除那些依赖它们的镜像后,这些依赖的中间层镜像也会被连带删除。
有时候我们只希望列出部分镜像。docker image ls
有好几个参数可以帮助做到这个事情。
根据仓库名列出镜像
$ docker image ls ubuntu
列出特定的某个镜像,也就是说指定仓库名和标签
$ docker image ls ubuntu:18.04
docker image ls
还支持强大的过滤器参数 --filter
,或者简写 -f
。
比如,我们希望看到在 ubuntu
之后建立的镜像,可以用下面的命令:
$ docker image ls -f since=ubuntu:18.04
想查看某个位置之前的镜像也可以,只需要把 since
换成 before
即可。
此外,如果镜像构建时,定义了 LABEL
,还可以通过 LABEL
来过滤。
利用 docker image ls
把所有的虚悬镜像的 ID 列出来,用到了 -q
参数。
docker image ls -q
--filter
配合 -q
产生出指定范围的 ID 列表,然后送给另一个 docker
命令作为参数,从而针对这组实体成批的进行某种操作的做法在 Docker 命令行使用过程中非常常见,不仅仅是镜像,将来我们会在各个命令中看到这类搭配以完成很强大的功能。
我们可能只是对表格的结构不满意,希望自己组织列;或者不希望有标题,这样方便其它程序解析结果等,这就用到了 Go 的模板语法。
下面的命令会直接列出镜像结果,并且只包含镜像ID和仓库名:
$ docker image ls --format "{{.ID}}: {{.Repository}}"
如果要删除本地的镜像,可以使用 docker image rm
命令,其格式为:
$ docker image rm [选项] <镜像1> [<镜像2> ...]
其中,<镜像>
可以是 镜像短 ID
、镜像长 ID
、镜像名
或者 镜像摘要
。
我们也可以用镜像名
,也就是 <仓库名>:<标签>
,来删除镜像。
当然,更精确的是使用 镜像摘要
删除镜像。
$ docker image ls --digests
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
node slim sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228 6e0c4c8e3913 3 weeks ago 214 MB
删除行为分为两类,一类是 Untagged
,另一类是 Deleted
。
当我们使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged
的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete
行为就不会发生。所以并非所有的 docker image rm
都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。
像其它可以承接多个实体的命令一样,可以使用 docker image ls -q
来配合使用 docker image rm
,这样可以成批的删除希望删除的镜像。我们在“镜像列表”章节介绍过很多过滤镜像列表的方式都可以拿过来使用。
比如,我们需要删除所有仓库名为 redis
的镜像:
$ docker image rm $(docker image ls -q redis)
或者删除所有在 mongo:3.2
之前的镜像:
$ docker image rm $(docker image ls -q -f before=mongo:3.2)