Docker 学习微语
前言
什么是 Docker
Docker 是一个用于构建、运行和传送应用程序的平台,就像它的 logo 所表达的一样可以将我们的应用程序打包成一个个的集装箱,然后这个小鲸鱼就会帮我们把它运送到任何需要的地方。有了 Docker,我们就可以将应用程序和它运行时所需要的各种依赖包、第三方软件库、配置文件等打包在一起,以便在任何环境中都可以正确地运行。
Docker 和虚拟机又有什么区别
虚拟机
虚拟机是完整的操作系统,和实际的 Windows 和 Linux 系统一样可以在这个操作系统中运行应用程序,这是通过虚拟化技术来实现的。虚拟化技术是一种将物理资源虚拟为多个逻辑资源的技术,它可以将一台物理服务器虚拟成多个逻辑服务器,每个逻辑服务器都有自己的操作系统、CPU、内存、硬盘和网络接口等等,它们之间是完全隔离的可以独立运行。
虚拟机在一定程度上实现了资源的整合,可以将一台服务器的计算能力、存储能力、网络资源分配给多个逻辑服务器,实现多台服务器的功能。但是它的缺点也非常明显,每台虚拟机都需要占用大量的资源比如 CPU、内存、硬盘、网络等等,而且启动速度非常慢,通常需要几分钟甚至几十分钟,而大部分的情况下,其实我们的一台服务器上只需要运行一个主要对外提供服务的应用程序就可以了,并不需要一个完整的操作系统所提供的所有功能,但是虚拟机却需要启动一个完整的操作系统,包括操作系统的内核、各种系统服务、各种工具、甚至图形界面等等,这些我们并不需要的服务占用了大量的资源,导致了资源的浪费和启动速度慢的问题。
容器
了解了虚拟机之后,我们再来看一下容器。Docker 和容器是两个不同的概念,Docker 非常的流行,以至于很多人把 Docker 和容器混为一坛,其实 Docker 只是容器的一种实现,是一个容器化的解决方案和平台;而容器是一种虚拟化技术和虚拟机类似,也是一个独立的环境,可以在这个环境中运行应用程序。和虚拟机不同的是,容器并不需要在容器中运行一个完整的操作系统,而是使用宿主机的操作系统,所以启动速度非常快,通常只需要几秒钟,同时因为需要的资源更少,所以可以在一台物理服务器上运行更多的容器,这样就可以更加充分的利用服务器的资源,减少资源的闲置和浪费。
虚拟机与容器对比总结
特性 | 容器 | 虚拟机 |
---|---|---|
启动 | 秒级 | 分钟级 |
硬盘使用 | 一般为 MB | 一般为 GB |
性能 | 接近原生 | 弱于 |
系统支持量 | 单机支持上千个容器 | 一般几十个 |
安装 Docker
由于本文档的重点并不是安装 Docker,故安装部分仅作简单阐述,若在安装过程中遇到任何问题,请根据报错信息自行使用搜索引擎查找相关解决方案,或使用本站联系方式联系我(不保证可用性)。
Windows
手动下载安装
点击以下 链接 下载 Docker Desktop for Windows。
下载好之后双击 Docker Desktop Installer.exe
开始安装。
使用 winget 安装
1 |
|
使用 WSL2 运行 Docker
若你的 Windows 版本为 Windows 10 专业版或家庭版 v1903 及以上版本可以使用 WSL2 运行 Docker,具体请查看 Docker Desktop WSL 2 backend。
macOS
使用 Homebrew 安装
Homebrew 是一个流行的包管理器,最初为 macOS 设计,后来也扩展到了 Linux 系统。它的主要目的是简化软件安装过程,推荐 macOS 用户安装。若没有 Homebrew 可以参考这篇教程,
1 |
|
手动下载安装包安装
对于 Intel 芯片的 Mac 使用:https://desktop.docker.com/mac/main/amd64/Docker.dmg
对于 Apple 芯片的 Mac 使用:https://desktop.docker.com/mac/main/arm64/Docker.dmg
OrbStack
此外,也可以使用:OrbStack
Linux
1 |
|
默认情况下,docker
命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root
用户和 docker
组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root
用户。因此,更好地做法是将需要使用 docker
的用户加入 docker
用户组。
1 |
|
小试牛刀
安装完成后我们就可以使用 Docker 的各种命令了,输入docker version
命令能看到 Docker 的版本信息就说明 Docker 已经安装成功了:
如果像上图一样就返回了 Client 的信息,则说明 Server 没有启动,这里补充一下:
Docker 是使用 Client-Server 架构模式,DockerClient 和 DockerDemon 之间通过 Socket 或者 Restful API 进行通信,DockerDemon 就是服务端的守护进程,它负责管理 Docker 的各种资源,DockerClient 负责向 DockerDemon 发送请求,DockerDemon 接收到请求之后进行处理,然后将结果返回给 DockerClient,这里的 DockerDemon 是一个后台进程,用来接收并处理来自 Docker 客户端的请求,然后将结果返回给客户端,所以我们在终端中输入的各种 Docker 命令实际上都是通过 Docker 客户端发送给 DockerDemon 然后 DockerDemon 再进行处理,最后再将结果返回给客户端,然后就可以在终端中看到执行结果了。
启动 Server 后执行的结果是类似这样的:
其他常用 Docker 命令有:
1 |
|
配置国内镜像源
因为网络问题国内从 Docker Hub 以及 ghcr.io 等站点拉取镜像有时会遇到困难(超时、无响应、速度慢等),此时可以配置镜像加速器,但原本的国内众多容器镜像服务商都因为各种原因停止了服务,当前可用的镜像服务可尝试如下提供的部分,无法保障一直可用。截止 2024-11-18,当前容器镜像统计:
镜像加速器 | 镜像加速器地址 | 专属加速器? | 其它加速? |
---|---|---|---|
~~https://registry.docker-cn.com~~ |
|||
DaoCloud 镜像站 | https://docker.m.daocloud.io |
白名单模式 | |
Azure 中国镜像 | https://dockerhub.azk8s.cn |
仅供内部访问 | Docker Hub、GCR、Quay |
科大镜像站 | https://docker.mirrors.ustc.edu.cn |
仅供内部访问 | |
阿里云 | https://<your_code>.mirror.aliyuncs.com |
需登录,系统分配 | |
~~https://reg-mirror.qiniu.com~~ |
|||
~~https://hub-mirror.c.163.com~~ |
|||
腾讯云 | https://mirror.ccs.tencentyun.com |
仅供内部访问 | |
Docker 镜像代理 | https://dockerproxy.com |
Docker Hub、GCR、K8S、GHCR | |
百度云 | https://mirror.baidubce.com |
||
南京大学镜像站 | https://docker.nju.edu.cn |
||
~~https://docker.mirrors.sjtug.sjtu.edu.cn~~ |
|||
中科院软件所镜像站 | https://mirror.iscas.ac.cn |
Linux
编辑文件:/etc/docker/daemon.json
,写入如下内容(如果文件不存在请新建该文件)
1 |
|
请确保该文件符合 json 规范,否则 Docker 将不能启动。
Windows & macOS
在 Docker Desktop 主窗口中找到 Settings
,打开配置窗口后在左侧导航菜单选择 Docker Engine
,在右侧像如下所示编辑 json 文件,之后点击 Apply & Restart
保存后 Docker 就会重启并应用配置的镜像地址了。
1 |
|
检查镜像配置是否生效
执行 docker info
,如果从结果中看到了如下内容,说明配置成功。
1 |
|
Docker 相关概念
Docker 中有非常重要的三个基本概念:镜像(Image)、容器(Container)和仓库(Repository)。
理解了这三个概念,就理解了 Docker 的整个生命周期。
镜像(Image)
镜像是一个只读的模板,它可以用来创建容器,容器是 Docker 的运行实例,它提供了一个独立的可移植的环境,可以在这个环境中运行应用程序。
Docker 镜像 是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像 不包含 任何动态数据,其内容在构建之后也不会被改变。
容器(Container)
镜像和容器的关系就像面向对象中的类和实例的关系一样,我们可以定义一个类中有哪些属性和方法,用一好的类就是一个模板,然后我们可以根据这个模板来创建多个实例,这些实例就是这个类的对象,对应到 Docker 中,镜像就是一个模板,容器就是这个模板的一个实例,可以有一个也可以有多个。容器是镜像运行时的实体,容器可以被创建、启动、停止、删除、暂停等。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root
文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会混淆容器和虚拟机。
前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为 容器存储层。
容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。
按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者 绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。
数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。
仓库(Repository)
镜像如何分享给别人呢,这就涉及到我们刚刚提到的第三个概念:Docker 仓库。
Docker 仓库是用来存储 Docker 镜像的地方,最流行和最常用的仓库就是 Docker Hub,它是一个公共的Docker 仓库,用来集中存储和管理 Docker 镜像,我们可以在这里下载各种镜像,也可以将自己的镜像上传到这里,这样就可以实现镜像的共享和复用,这也是 Docker 非常流行的一个重要原因。
一个 Docker Registry 中可以包含多个 仓库(Repository
);每个仓库可以包含多个 标签(Tag
);每个标签对应一个镜像。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签>
的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest
作为默认标签。
以 Ubuntu 镜像 为例,ubuntu
是仓库的名字,其内包含有不同的版本标签,如,16.04
, 18.04
。我们可以通过 ubuntu:16.04
,或者 ubuntu:18.04
来具体指定所需哪个版本的镜像。如果忽略了标签,比如 ubuntu
,那将视为 ubuntu:latest
。
仓库名经常以 两段式路径 形式出现,比如 jwilder/nginx-proxy
,前者往往意味着 Docker Registry 多用户环境下的用户名,后者则往往是对应的软件名。但这并非绝对,取决于所使用的具体 Docker Registry 的软件或服务。
Image、Container 和 Repository 的关系
下面这一张图很形象地展示了 Image、Container、Repository 和 Registry/Hub 这四者的关系:
- Dockerfile 是一个文本文件,包含了一系列的指令和参数,用于定义如何构建一个 Docker 镜像。运行
docker build
命令并指定一个 Dockerfile 时,Docker 会读取 Dockerfile 中的指令,逐步构建一个新的镜像,并将其保存在本地。 docker pull
命令可以从指定的 Registry/Hub 下载一个镜像到本地,默认使用 Docker Hub。docker run
命令可以从本地镜像创建一个新的容器并启动它。如果本地没有镜像,Docker 会先尝试从 Registry/Hub 拉取镜像。docker push
命令可以将本地的 Docker 镜像上传到指定的 Registry/Hub。
使用镜像
拉取镜像(docker pull)
拉取镜像的命令格式为 docker pull 仓库名/镜像名[:标签]
这里解释一下,默认是从 DockerHub 这个官方仓库拉取镜像,设置了国内镜像源的情况下则是从镜像源仓库拉取镜像,一般来说国内镜像源的内容与官方仓库是一只的备份。这里说的仓库是镜像源这个大仓库,而上面命令中提到的仓库名/镜像名[:标签]
中的“仓库名”可以理解为每个用户自己创建的小仓库,也可以将这个小仓库理解为“用户名”:用户名/镜像名[:标签]
。
完整的拉取地址格式应该为:[Docker Registry 地址[:端口号]/仓库(用户)名/镜像(软件)名[:标签]
一般来说默认情况下使用 DockerHub 或自己配置的镜像仓库拉取镜像,此时[Docker Registry 地址[:端口号]
这一部分就可以省略不写;
如果仓库(用户)名
也不写的话就默认为library
,使用的就是官方仓库,ubuntu、mysql 等镜像都可以通过这种方式下载;
如果最后的[:标签]
也省略的话就默认使用:latest
标签,即默认拉取最新版本的镜像,下面给出一个从 DockerHub 中使用官方仓库拉取 ubuntu 18.04 版本镜像的示例:
1 |
|
查看镜像(docker images)
要想查看已经下载下来的镜像,可以使用 docker image ls
或 docker images
列表包含了 仓库名
、标签
、镜像 ID
、创建时间
以及 所占用的空间
,这里着重讲一下镜像 ID,镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个 标签。因此从上面的例子中,我们可以看到 mirror-ghcr:latest
和 mirror-ghcr:v1.11.17
拥有相同的 ID,因为它们对应的是同一个镜像。
另外,由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保存一份即可,比如刚刚我们提到的 mirror-ghcr
这两个镜像使用只占用一份存储空间,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。
删除镜像(docker rmi)
删除本地镜像的命令格式为:docker image rm [选项] <镜像1> [<镜像2> ...]
其中,<镜像> 可以是 镜像短 ID、镜像长 ID、镜像名 或 镜像摘要,例如:
1 |
|
从上面这几个命令的运行输出信息可以看到删除信息分为两类:Untagged 和 Deleted。
我们使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged
的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete
行为就不会发生。所以并非所有的 docker image rm
都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。
状态 | Untagged | Deleted |
---|---|---|
影响对象 | 仅删除镜像的标签,镜像数据仍然存在 | 镜像的实际数据被完全移除 |
何时出现 | 镜像标签被移除,但镜像仍有其他引用 | 镜像没有标签引用,且未被容器使用 |
命令结果 | 不会减少本地存储占用 | 减少本地存储占用 |
除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。容器是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的。因此该镜像如果被这个容器所依赖的,那么删除必然会导致故障。如果这些容器是不需要的,应该先将它们删除,然后再来删除镜像。如果直接删除一个被容器引用了的镜像会发生如下错误:
操作容器
启动容器
新建并启动
docker run -t -i ubuntu:18.04 /bin/bash
这个命令则启动一个 bash 终端,允许用户进行交互。其中,-t
选项让 Docker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i
则让容器的标准输入保持打开。在交互模式下,用户可以通过所创建的终端来输入命令。
当利用 docker run
来创建容器时,Docker 在后台运行的标准操作包括:
- 检查本地是否存在指定的镜像,不存在就从 registry 下载
- 利用镜像创建并启动一个容器
- 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
- 从地址池配置一个 IP 地址给容器
- 执行用户指定的应用程序
- 执行完毕后容器被终止
启动一个停止的容器
docker container start
或 docker start
命令可以将一个已经终止(exited
)的容器启动运行
后台运行
更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d
参数来实现。
不使用 -d
参数运行容器:docker run -it ghcr.io/librespeed/speedtest
容器会在前台运行,并实时输出结果:
使用 -d
参数运行容器:docker run -d ghcr.io/librespeed/speedtest
启动后会返回一个唯一的 id,此时容器会在后台运行并不会把输出的结果实时输出,要获取容器的输出信息,可以通过 docker logs
命令。
停止容器
可以使用 docker container stop
或 docker stop
来终止一个运行中的容器。
此外,当 Docker 容器中指定的应用终结时,容器也自动终止。例如只启动了一个终端的容器,用户通过 exit
命令或 Ctrl+d
来退出终端时,所创建的容器立刻终止。
终止状态的容器可以用 docker container ls -a
或 docker ps -a
命令看到。例如:
进入容器
某些时候需要进入正在后台运行的容器进行操作,可以使用 docker attach
或 docker exec
命令,推荐使用 docker exec
命令,原因会在下面说明。
attach
1 |
|
使用 attach 如果从中退出,则容器也会停止:
exec
docker exec
后边可以跟多个参数,这里主要说明 -i
-t
参数。只用 -i
参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux 命令提示符,但命令执行结果仍然可以返回:
当 -i
-t
参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符:
此外,使用 exec 时退出容器不会导致容器的停止,这就是推荐使用 docker exec
的原因。
删除容器
可以使用 docker container rm
或 docker rm
来删除一个处于终止状态的容器;
如果要删除一个运行中的容器,可以添加 -f
参数。Docker 会发送 SIGKILL
信号给容器:
使用命令 docker container prune
可以清理所有处于终止状态的容器。
更多……
Dockerfile、Docker Compose、等高级用法待更新。。。