docker出现的历史
环境配置是软件开发的一个麻烦事,环境配置包括操作系统和各种依赖项,开发人员经常会遇到本地环境运行正常,而生产环境却出问题的尴尬事。
虚拟机技术是一种带环境安装的解决方案,它可以模拟一个真实的操作系统,但这种方式存在资源占用多(操作系统)、启动慢的缺点。
Linux容器技术是另一种虚拟化技术,它是进程级别的虚拟化方案,Linux容器不是模拟一个完整的操作系统,而是对进程进行隔离。对于容器内的进程来说,它接触到的资源都是虚拟的,从而实现与底层系统的隔离。
💡**Docker技术属于Linux容器技术的一种封装,它是目前最流行的 Linux 容器解决方案。**Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。
docker技术的出现主要解决了应用部署时环境配置的问题,
安装
基础概念
docker的三大基础概念是:镜像、容器、仓库。
- 镜像(Image):
镜像是 Docker 的核心概念之一,它是一个轻量级、独立、可执行的软件包,包含运行应用程序所需的一切:代码、运行时、库、环境变量以及配置文件。Docker 镜像可以作为容器的模板,用于创建和运行容器实例。镜像是只读的,一旦创建就不会改变。
开发人员可以把镜像类比为面向对象开发语言中的类。
- 容器(Container):
容器是 Docker 运行时的实例,基于 Docker 镜像启动。它包含了应用程序及其所有依赖的环境,被隔离运行并且与宿主系统共享内核。容器可以被快速启动、停止、删除,具有轻量级、快速部署等特点。
开发人员可以把镜像类比为面向对象开发语言中的类的实例;
- 仓库(Repository):
仓库是用来存储 Docker 镜像的地方。它可以分为公共仓库(如 Docker Hub)和私有仓库。仓库允许用户上传、下载和共享镜像,可以方便地管理和分发镜像。
基础技术-联合文件系统
理解容器的基础技术,对于我们掌握和使用容器技术有很大的帮助。
联合文件系统对于 Docker 技术具有重要意义,它支持了 Docker 的分层镜像和高效的容器运行,使得 Docker 在构建、传输、存储和部署方面更加高效和轻量化。
联合文件系统(UnionFS)
是一种堆叠的文件系统机制,它可以将多个目录挂载到一个目录下。它的结构如下图所示:
- lowerdir 基础层,是原始文件所在的位置;
- upperdir,客户端所做的任何修改都将反映在“upperdir”层上;
- merged 所有层合并后的最终视图;
我们的基础层称为“lowerdir”即原始文件所在的位置。
客户端所做的任何修改都将反映在“upperdir”层上:
- 如果更改文件,新版本将写入其中(file1)。
- 如果删除文件,将在该层上创建一个删除标记(file2)。
- 创建一个新文件(file4)。
最后,“merged”是所有层合并后的最终视图。
镜像
我们都知道,操作系统分为 内核 和 用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:18.04 就包含了完整的一套 Ubuntu 18.04 最小系统的 root 文件系统。
Docker 镜像 是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像 不包含 任何动态数据,其内容在构建之后也不会被改变。
分层存储
Docker镜像由一些松耦合的只读镜像层组成。Docker负责堆叠这些镜像层,并且将它们表示为单个统一的对象。
有多种方式可以查看和检查构成某个镜像的分层,比如当我们执行 docker pull 命令时,每一个 pull complete 行都代表镜像的每一层。
$ docker image pull ubuntu:latest
latest: Pulling from library/ubuntu
952132ac251a: Pull complete
82659f8f1b76: Pull complete
c19118ca682d: Pull complete
8296858250fe: Pull complete
24e0251a0e2c: Pull complete
Digest: sha256:f4691c96e6bbaa99d...28ae95a60369c506dd6e6f6ab
Status: Downloaded newer image for ubuntu:latest
Docker通过存储引擎(linux 文件系统)的方式来实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统。所有镜像层堆叠并合并,对外提供统一的视图。
🤔为什么 linux 镜像采用分层存储?
Linux镜像系统采用分层设计的主要原因是为了提高镜像的复用性、可维护性和效率。以下是一些具体原因:
- 层与层之间的共享: 分层设计使得镜像可以由多个层组成,每个层只包含相应的变更或修改。如果多个镜像使用相同的基础层,这些层在不同镜像间可以实现共享,节省存储空间。
- 增量构建和更新: 当容器运行时,只需要加载和应用那些发生了变化的层,而不需要重新构建整个镜像。这样可以节省时间和资源,提高构建和更新的效率。
- 易于管理和维护: 分层设计使得镜像的管理和维护更加灵活和方便。可以分别管理不同的层,跟踪不同层的变化,方便更新和维护。
- 镜像重用: 分层设计使得可以更好地重用镜像的不同层。多个镜像可以共享相同的基础层,提高镜像的复用性。
- 增强安全性: 每个层都是只读的,不可更改。这样可以确保镜像层内容的安全性,避免因为意外或恶意修改而导致的问题。
docker 仓库
像 Maven 仓库一样,Docker 也存在 docker 仓库,Docker仓库是一个存储、分发和管理Docker镜像的中心化服务。它类似于一个云端的代码仓库,用于存储Docker镜像,并提供了访问、分享和管理这些镜像的功能。
Docker Hub是Docker官方的公共镜像仓库,也是 docker pull
命令默认拉取的仓库地址, 考虑到下载速度,实际我们也可以使用 阿里云加速器、DaoCloud 加速器 。
修改Docker的默认镜像仓库地址,可以按照以下步骤进行:
- 编辑Docker配置文件:
使用编辑器打开Docker配置文件。对于Linux系统,配置文件通常位于/etc/docker/daemon.json,在该配置文件中添加或修改registry-mirrors字段,指定新的镜像仓库地址,注意仓库地址是一个列表,Docker在拉取镜像时会依次尝试从这些镜像仓库中获取镜像:
{
"registry-mirrors": ["镜像仓库地址1","镜像仓库地址2"]
}
- 重启Docker服务:
sudo systemctl restart docker
拉取镜像
只需要给出镜像的名字和标签,就能在官方仓库中定位一个镜像(采用“:”分隔)。从官方仓库拉取镜像时,docker image pull命令的格式如下:
只需要给出镜像的名字和标签,就能在官方仓库中定位一个镜像(采用“:”分隔)。从官方仓库拉取镜像时,docker image pull命令的格式如下:
docker image pull <repository>:<tag>
比如:
$ docker image pull mongo:3.3.11
//该命令会从官方Mongo库拉取标签为3.3.11的镜像
$ docker image pull redis:latest
//该命令会从官方Redis库拉取标签为latest的镜像
$ docker image pull alpine
//没有声明标签时候,默认会拉取标签为latest的镜像
其他常用指令
- 列出镜像:
使用docker image ls
命令可以列出镜像
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
redis latest 5f515359c7f8 5 days ago 183 MB
nginx latest 05a60462f8ba 5 days ago 181 MB
mongo 3.2 fe9198c04d62 5 days ago 342 MB
<none> <none> 00285df0df87 5 days ago 342 MB
ubuntu 18.04 329ed837d508 3 days ago 63.3MB
ubuntu bionic 329ed837d508 3 days ago 63.3MB
- 删除镜像
如果要删除本地的镜像,可以使用docker image rm
命令,其格式为:
$ docker image rm [选项] <镜像1> [<镜像2> ...]
其中,<镜像>
可以是 镜像短 ID
、镜像长 ID
、镜像名
或者 镜像摘要
。
比如我们有这么一些镜像:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 0584b3d2cf6d 3 weeks ago 196.5 MB
redis alpine 501ad78535f0 3 weeks ago 21.03 MB
docker latest cf693ec9b5c7 3 weeks ago 105.1 MB
nginx latest e43d811ce2f4 5 weeks ago 181.5 MB
我们可以用镜像的完整 ID,也称为 长 ID,来删除镜像。使用脚本的时候可能会用长 ID,但是人工输入就太累了,所以更多的时候是用 短 ID 来删除镜像。docker image ls
默认列出的就已经是短 ID 了,一般取前3个字符以上,只要足够区分于别的镜像就可以了。
比如这里,如果我们要删除 redis:alpine 镜像,可以执行:
$ docker image rm 501
Untagged: redis:alpine
Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d
Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
Deleted: sha256:127227698ad74a5846ff5153475e03439d96d4b1c7f2a449c7a826ef74a2d2fa
Deleted: sha256:1333ecc582459bac54e1437335c0816bc17634e131ea0cc48daa27d32c75eab3
Deleted: sha256:4fc455b921edf9c4aea207c51ab39b10b06540c8b4825ba57b3feed1668fa7c7
我们也可以用镜像名,也就是 <仓库名>:<标签>
,来删除镜像。
$ docker image rm centos
Untagged: centos:latest
Untagged: centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c
Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a
Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38
当然,更精确的是使用 镜像摘要
删除镜像。
$ docker image ls --digests
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
node slim sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228 6e0c4c8e3913 3 weeks ago 214 MB
$ docker image rm node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
Untagged: node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
容器
容器是镜像的运行时实例。正如从虚拟机模板上启动VM一样,用户也同样可以从单个镜像上启动一个或多个容器。
启动一个新的容器
启动容器的一个简单的方式是通过docker container run
命令,命令的基础格式为docker container run <options> <image>:<tag> <arg...>
。
opition的常用参数:
-d:指定容器在后台(detached mode)运行。
-it:在交互模式下运行容器,并分配一个伪终端,通常与 -d 配合使用。
--name:为容器指定一个名称。
-p:映射主机与容器的端口。
-v:挂载主机目录到容器。
--network:指定容器连接的网络。
--rm:容器停止后自动删除。
--env:设置环境变量。
示例:
- 启动一个容器:注意此时会将执行的结果输出在当前宿主机上
$ docker run nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
// ......
2024/06/25 14:43:45 [notice] 1#1: using the "epoll" event method
2024/06/25 14:43:45 [notice] 1#1: nginx/1.25.4
2024/06/25 14:43:45 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14)
2024/06/25 14:43:45 [notice] 1#1: OS: Linux 5.15.0-91-generic
2024/06/25 14:43:45 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2024/06/25 14:43:45 [notice] 1#1: start worker processes
2024/06/25 14:43:45 [notice] 1#1: start worker process 28
2024/06/25 14:43:45 [notice] 1#1: start worker process 29
- 守护态运行容器:更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d 参数来实现。
$ docker run -d nginx
6ecf2e8416504cb0fc2a79aea20d231b61daac5f20babac276ac7fb483de5637
- 指定容器名称为my_container
$ docker run --name my_container nginx
- 指定容器端口映射:指定将主机的8080端口映射到容器的80端口
$ docker run -d -p 8080:80 nginx
设置环境变量并运行一个容器:
$ docker run -d --env ENV_VAR=VALUE nginx
终止容器
可以使用 docker container stop
来终止一个运行中的容器。
此外,当 Docker 容器中指定的应用终结时,容器也自动终止。
例如只启动了一个终端的容器,用户通过 exit
命令或 Ctrl+d
来退出终端时,所创建的容器立刻终止。
终止状态的容器可以用 docker container ls -a 命令
看到。例如
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ba267838cc1b ubuntu:18.04 "/bin/bash" 30 minutes ago Exited (0) About a minute ago trusting_newton
处于终止状态的容器,可以通过 docker container start
命令来重新启动。
此外,docker container restart
命令会将一个运行态的容器终止,然后再重新启动它。
进入容器
在使用 -d 参数时,容器启动后会进入后台。
某些时候需要进入容器进行操作,可以使用 docker exec
命令,基本语法 为docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
其中,CONTAINER
参数可以是容器的Container ID
或者Container Name
常用的参数通常会结合 -i、-t 参数使用,可以看到我们熟悉的 Linux 命令提示符。
$ docker exec -it postgresql bash
root@69d137adef7a:/#
导入和导出
导出容器
如果要导出本地某个容器,可以使用 docker export
命令。
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7691a814370e ubuntu:18.04 "/bin/bash" 36 hours ago Exited (0) 21 hours ago test
$ docker export 7691a814370e > ubuntu.tar
这样将导出容器快照到本地文件。
导入容器快照:
可以使用docker import
从容器快照文件中再导入为镜像,例如
复制
$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
test/ubuntu v1.0 9d37a6082e97 About a minute ago 171.3 MB
此外,也可以通过指定 URL 或者某个目录来导入,例如:
$ docker import http://example.com/exampleimage.tgz example/imagerepo
INFO
💡注:用户既可以使用 docker load
来导入镜像存储文件到本地镜像库,也可以使用 docker import
来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
其他命令
- **移除镜像:**docker rm 根据容器的名称或者 ID 来删除容器。
数据卷
数据卷
是 docker 提供的持久化数据技术。数据卷与容器是解耦的,从而可以独立地创建并管理卷,并且即便停止或删除容器,数据卷也不会被删除。
通常情况下,我们使用数据卷
的方式是将数据卷
绑定到宿主机本地目录下,但数据卷
并不仅是本地目录的映射,Docker引擎在背后做了一些额外的工作,包括数据权限、数据卷驱动等。
默认情况下,Docker创建新卷时采用内置的local驱动。使用-d参数可以指定不同的驱动。第三方驱动可以通过插件方式接入,这些插件(驱动)包括:
- 块存储:相对性能更高,适用于对小块数据的随机访问负载。目前支持Docker卷插件的块存储例子包括HPE 3PAR、Amazon EBS以及OpenStack块存储服务(Cinder)。
- 文件存储:包括NFS和SMB协议的系统,同样在高性能场景下表现优异。支持Docker卷插件的文件存储系统包括NetApp FAS、Azure文件存储以及Amazon EFS。
- 对象存储:适用于较大且长期存储的、很少变更的二进制数据存储。通常对象存储是根据内容寻址,并且性能较低。
创建和管理卷
使用如下命令创建数据卷
:
$ docker volume create my-vol
查看宿主机中所有的数据卷
$ docker volume ls
DRIVER VOLUME NAME
local my-vol
在主机里使用以下命令可以查看指定数据卷
的信息
$ docker volume inspect my-vol
[
{
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
挂载卷在容器上
在用 docker run
命令的时候,使用 --mount
或-v
标记来将数据卷
挂载到容器里。在一次 docker run 中可以挂载多个数据卷。
INFO
-v
是相对简单的挂载方法,而--mount
则提供更灵活、更高级的挂载选项。
-v
选项的基本格式为:
- -v /host/path:/container/path ,表示将主机上的/host/path目录挂载到容器内的/container/path目录。
- -v volume_name:/container/path,挂载数据卷到容器内的/container/path目录。
下面创建一个名为 web 的容器,并加载一个数据卷
到容器的 /usr/share/nginx/html 目录。
$ docker run -d -P \
--name web \
# -v my-vol:/usr/share/nginx/html \
--mount source=my-vol,target=/usr/share/nginx/html \
nginx:alpine
删除数据卷
数据卷
是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除数据卷
,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 数据卷。
如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v
这个命令。
$ docker volume rm my-vol
docker volume prune
会删除未被容器或者服务副本使用的全部卷,谨慎使用!
docker的一些用法的思考:
- docker最适合的场景可能是用项目的方式来部署,比如我一个项目需要mysql、nginx、minio等服务,我把这些都打包在一起进行部署;
- 如果知识用docker运行一些niginx、tomcat等等,还是把这些东西作为一个公共的服务来用的话,只是利用到了docker的安装便捷性,但可能也带来了其他的问题;