Docker 安装
-
Mac: https://docs.docker.com/docker-for-mac/install/ -
Ubuntu: https://docs.docker.com/engine/install/ubuntu/ -
Windows: https://docs.docker.com/desktop/install/windows-install/
其他 OS 的安装在 Docker 官网都能找到,就不占篇幅了,这个自行处理,如果有问题可以留言讨论。这边特别提醒一下,本文的示例都是在 Ubuntu 上完成的。
安装完成之后,如果想要让非 root user 也能用 docker 命令,也就是不用在每次执行 docker 指令前都加 sudo 的话,记得执行下列指令,可以将你目前的使用者加入 docker 群组中,已获得执行的权限:
# your-user 记得换成自己的使用者名称
# 执行完成后,退出该使用者,再重新登录一次让命令生效
$ sudo usermod -aG docker your-user
安装完成后,如果要检查安装有没有成功,可以用以下这个指令检查看看版本:
$ docker --version
Docker version 24.0.5, build ced0996
或是执行以下两个命令来测试:
# Show the Docker version information
$ docker version
# Display system-wide information
$ docker info
Docker 基本组成
上面这张图算是概括了 Docker 的重要组成与指令了,我们先来看看 Docker 中重要的三个最基本的东西:
-
image: 这是一个只读 的模板,可用来创建 container,如果有学过面向对象语言,可以想成 image 就是 class、container 是 object 或是 instance。 -
container: 这就是我们的主角,是一个独立、隔离的空间,里面包括了我们运行一个应用程序所需要的组件。 -
Docker registry: 这其实就很像 github,里面存放了很多 images,公开的像是 Docker Hub ,你也可以创建自己私人的 registry。
我相信这样说完之后,没概念的还是没概念,就让我们直接启动一个容器来看看吧!
Docker 指令: 启动容器
在正确安装 docker 后,执行此指令:
$ docker container run -it node:20 /bin/bash
应该会看到以下的画面:
-
docker container run
:启动一个新的 container。 -
-i
:--interactive
启动交互模式,保持标准输入打开,通过终端窗口与容器内部的进程进行实时交互操作。 -
-t
:--tty
让 Docker 分配一个虚拟终端(pseudo-TTY),并且绑定到容器的标准输出上。 -
node:20
: 启动这个 container 所使用的 image。 -
/bin/bash
:容器启动后要执行的命令。
简单地说,以上指令就是我们「从 image node:20 启动了一个 container,并且开启了它的输入/输出,然后让它执行 /bin/bash
这个命令」。
在执行完这行指令后,你可以看到下图所示的画面,其中 4c9bee8fef4b
为容器 ID 用于区分不同的容器实例,每次启动新的容器都会不同,要注意的是,这时候我们已经不在我们原本的环境中了,而是「进入到」了 container 中,可以在这里执行 node -v
指令,会发现这个环境中已经安装了 node,且版本是 20.5.1。
这时候我们在 container 中执行 ps aux
会看到它 PID 为 1 的 process 就是我们刚刚指定的 /bin/bash
,这个非常的重要,但我们要放到后面再讨论了。
这里让我们先做个小测试:
-
在刚刚创建的容器中,创建一个文件,例如 touch AAA.txt
,建议完成后用ls
确认一下。 -
开启另外一个命令窗口,同样再执行一次 docker container run -it node:20 /bin/bash
,然后一样进入了一个容器,执行ls
看看,应该会看不到AAA.txt
这个文件。
这里很重要的就是我们提到的 container 是一个独立的、隔离的环境,当你启动了两个容器时,即便是从同一个 image 启动起来,Docker 也是会帮你创建出两个不同的容器的:
这时候再开启一个新的命令窗口,然后执行以下指令:
$ docker container ls
这个指令是用来列出目前有哪些 container 在运行中,执行后应该可以看到:
这里看到目前在环境中已经启动了两个容器,而你的 CONTAINER ID 与 NAMES 会跟上面图片的不同。
Docker Image
在更近一步讨论容器的其他操作之前,我们先来想一下, node:20 这个 image 是哪里来得呢?
不知道你有没有注意到,我们刚刚启动了两个容器的执行过程其实不太一样:
启动第一个容器的画面:
启动第二个容器的画面:
第一个容器启动时会多了很多东西,第一行是 Unable to find image 'node:20' locally
,这句话的意思是在「本地找不到这个 image」,所以接下来就会开始 pull from library/node
,也就是去 registry 拉取我们所指定的 image,没有指定的话,通常就是去 Docker Hub 拉,例如 https://hub.docker.com/node)
,从这个页面我们可以发现 Docker Hub 提供了不同版本的 node 环境,而我们刚刚用的是 20 这个版本。
而当我们启动第二个容器的时候,因为本地已经有了 node:20 这个 image,所以就可以直接启动,而不用再去拉取 image 了。
Docker Hub 上有各式各用的 image,非常地好用,例如你想要启动一个 MySQL,但不想装在自己的电脑上,这时候你就可以从 Docker Hub 上拉取一个 MySQL,还可以随时换不同的版本。不过这边建议尽量用官方提供的 image 比较好,毕竟你不知道别人提供的 image 里放了什么东西…
在启动容器之前,也可以先用指令把 image 拉取好,例如 $ docker image pull node:18
,这就是要从 Docker Hub 上拉下一个 tag 为 18 的 node image 到本机来。
这边补充一下 tag,docker image 的名称中 :
前面的是名称,后面的是 tag( image_name:tag_name
),tag 通常用来表现一些特殊的信息,例如版本,像 node:20
,就是版本为 20 的 node image。当然,既然叫 tag,不是叫 version 什么的,就表示它不一定是要用来标注版本,基本上就是留一个栏位让你来标注一些信息,而且这是非必要的、可以不用放,例如 docker image pull node
,当没有放 tag 时,它会自动帮你拉 node:latest
这个 image,也就是说, latest
是默认的 tag。
如果想要看看自己的环境中目前有哪些 image,可以用以下指令:
$ docker image ls
这边可以看到,两个 image 的大小分别都有 1 G 多,虽然这不是这两个 images 真正占用的硬盘空间,但如果你使用的 images 都比较没有关联,那日积月累下来,还是很占硬盘空间的。所以没事不要乱拉,或是要定期清理,才不会占用太多硬盘空间喔,之后再来分享怎么清理。(虽然我们后面会讨论到 image layer 共用的部分,但积少成多,在主机中放了一堆 images,硬盘还是会被撑爆的…)
Image Layer
刚刚在说到 image 时,我们说 image 是一个「 只读」 的模板,这是什么意思呢?还记得我们刚刚做的测试吗?在第一个 container 中新增了一个 AAA.txt,但后来启动的第二个 container 中并没有看到这个文件。
这是因为当我们从 image 启动一个 container 时候,Docker 会载入这个 image 作为只读层,并且在上面加上一个可写层,而我们在这个 container 中的操作实际上就是发生在这个可写层中:
不管你在这个可写层中做了多少事情,例如在这个 container 中加入一个新的文件,这个文件是存在这个独立的可写层中的,当你下次又从 node:20 这个 image 来创建另外一个新的 container 时,仍会用原本蓝色这个区块(原本的 node:20 )来建构这个 container,并且在上面加上另外一个可写层,创建出另外一个 container。
那总不能每次都是一个新的环境,而无法保留我们之前做过的事对吧,因此 Docker 提供了一个指令,让我们可以把我们的容器打包成一个新的 image: docker container commit CONTAINER_ID [Repository:[Tag]]
# 在本文的示例中,执行指令如下,记得把 container id 换成你自己的:
$ docker container commit 4c9bee8fef4b node:20-updated
当执行完这个指令后,来查看一下目前本机中有的 images:
多了一个 node:20-updated
,用这个 image 来启动一个 container 看看:
用这个新的 image 建构出来的 container 会有 AAA.txt
这个文件,这是因为我们用 node:20-updated
这个我们 commit 出来的新 image,会在原有的 node:20
上面再加上一层(layer),并且把我们刚刚对那个 container 做的操作给封装起来,变成一个新的 image,当我们用这个新的 image 来建构新的 container 时,就会在其上再加上一层可写层来让我们操作:
Docker 很聪明的是,虽然我们在电脑中目前有 node:20 与 node:20-updated 两个不同的 image,不过 Docker 并不会重复存两个 node:20 ,也就是重复的部分 Docker 只会存一个,这样就大大的节省了空间,此外也可以理解为什么要做成只读了,只读能做到共用而不互相影响。(关于这部分,在后面更进阶的讨论中,再来聊实际上的操作,这边就先这样简单地认识一下。)
当然,原本我们从 Docker Hub 上拉下来的 node:20 也不会只有单独一层,所以当我们在 pull 这个 image 时,会看到的画面是像下面这样:
按照这个过程, node:20 至少是由 8 个 layer 构成的。还记得我们 pull node:18
的过程吗:
这里 de4cac68b616
显示的是 Already exists
,这是因为我们在拉 node:20 时,已经拉过了,因此在拉 node:18 时,已经存在的就不用再重拉一次,这也再次验证了在 Docker 中,layer 是共用的、不会多占空间的。
这边有个不太常用的指令,但在熟悉 Docker 时可以玩玩看:
$ docker image history node:20
我们也来查看一下 node:20-updated 这个 image 并且比较看看:
这边可以看到 node:20-updated 比起 node:20 会多了一个 78c69cf95de8 ,这个就是我们刚刚启动第一个 container 操作后创建出来的一层,而这之下,就跟 node:20 一模一样了。
最后,这个 commit 出来的新 image,你也可以把它推送(push)到 Docker Hub 或是其他的 image repository 上去分享给别人。不过,在实际工作中,我们很少直接用 commit
这个指令,通常都会通过 Dockerfile
来做新的 image,这个就留到下次讨论了。
其他 Docker 生命周期指令
延续刚刚的测试,我们在 node:20-updated
创建出来的 container 中执行 exit
退出这个 container,这时候如果执行 docker container ls
会发现,只剩下两个 container,而刚刚退出的那个 container 已经看不到了:
但如果加上 -a
,就可以查看所有的 container:
这是因为我们刚刚在启动 container 时,要它执行的命令是 /bin/bash
(且 PID 为 1),而当我们下了 exit
指令时,是在退出这个 bash,也就是退出了 PID 为 1 的这个 process,既然主要的 process 已经停止执行了,这个 container 自然也就关闭了,我们之后再来讨论怎么让 container 持续执行。从这里也可以从 Status 这个栏位看到 container 已经退出多久。
这时候如果想要回到这个 container 里,可以通过 docker container start CONTAINER_ID
来回到这个 container 里:
docker container start
只是重新启动这个 container,执行后我们还是在本机中,如果要进入 container 中,可以通过 exec
来进入 container 中,基本上这个指令常用的参数,例如 -it
,跟 docker container run
的时候差不多,我们就先不讨论了。
docker container exec -it CONTAINER_ID /bin/bash
这时候我们一样再执行 exit,然后再下 docker container ls
来检查,却会发现这个 container 还是活着的、跟刚刚不一样,我们通过 docker container exec
再进去一次,这次在里面执行一下 ps aux
:
这时候我们可以看到有两个 /bin/bash
的 process 在运行,我们通过 docker container exec
所执行的 /bin/bash
其实是 PID 为 26 的这一个,所以当我们执行 exit
时,退出的是这一个 bash,而 PID 为 1 的这个 /bin/bash
仍在执行中,所以这个 container 会持续存活。
跟 docker container exec
有一个用起来很像的指令 docker container attach CONTINER_ID
:
attach
这个指令一样会进入 container 中,用起来跟刚刚的 docker container exec -it CONTAINER_ID
效果很像,但如果进去后执行 ps aux
会发现只有一个 /bin/bash
process。你应该可以猜到,如果这时候执行了 exit
,会退出的是 PID 为 1 的 /bin/bash
process,进而退出这个 container。此时再通过 docker container ls
或是 docker container ls -a
来确认,会发现这个 container 已经关闭了。所以,虽然 attach 也能进入这个 container 中,但我自己很少用,以免一不小心把 container 给关掉…
移除 image 跟 container
前面有提到,如果电脑中存有太多的 image,会占硬盘空间,因此建议定期清除用不到的 image,那我们来试试看清除刚刚拉下来的那个 node:18 ,其指令为 docker image rm IMAGE_ID
:
这边可以看到,移除的时候也是逐层移除,不过,我们刚刚 pull
node:18 时,明明有 8 层,这边却只移除了 4 层,这样真的有移干净吗?
但通过 docker image ls
来查看时,的确又已经看不到 node:18 了:
还记得前面有提到 layer 是共用的吗?也有提到 node:20 与 node:18 有共用一些 layer,而因为共用的那些 layer 在 node:20 还需要用到,所以在移除 node:18 时,当然就不会移掉。
我们来试着移除刚刚我们创建出来的那个 node:20-updated 看看:
移除失败,根据错误信息可以知道是因为这个 image 正在被 container 422f119c50f6 给使用着,通过 docker container ls -a
检查看看:
果然,有一个 id 为 422f119c50f6 的 container 是通过这个 image 来建构的,如果这个 container 还在启动状态,那就需要先通过 docker container stop CONTAINER_ID
关闭这个 container,如果已经是关闭状态,那就可以用 docker container rm CONTAINER_ID
来移除这个 container:
这时候就可以用 docker image rm IMAGE_ID
来移除 image:
结语
这次纪录了 docker 的基本组件与操作,主要讨论的是 docker container 的执行与一些基本的操作。也许在做过这些练习与测试之后,你心里会有一大堆疑问,有的话是很棒的事情,但如一开始所说的,我想先讲一些基本的操作,先会基本的运用,有点感觉后,然后再慢慢深入讨论。
下次仍旧会是基础篇,让我们来讨论怎么让 container 间可以彼此沟通,然后再来讨论我最喜欢的 Dockerfile
,这可是我个人认为 docker 可以如此成功的重要因素之一。
指令整理
这边把本文讨论过的指令都整理起来,方便大家复习与查找,另外也会列出旧版的指令对照,推荐使用新版的指令,虽然较长,但具有一致的结构,非常好学!
# 查看 docker 版本
$ docker --version
$ docker version
# 查看 docker 系统信息
$ docker info
# 从 node:20 image 启动一个 docker container 并开启输出入
$ docker container run -it node:20 /bin/bash
# 旧版指令
# docker run -it node:20 /bin/bash
# 查看目前正在运行中的 container
$ docker container ls
# 旧版指令
# docker ps
# 查看全部的 container,包括已经停止的。 (a -> all)
$ docker container ls -a
# 旧版指令
# docker ps -a
# 从 DockerHub 上拉下版本为 18 的 node image
$ docker image pull node:18
# 旧版指令
# docker pull node:18
# 查看目前环境中的 docker images
$ docker image ls
# 旧版指令
# docker images
# 用 container id 为 4c9bee8fef4b 的 container
# 创建一个叫做 node:20-updated 的 image
$ docker container commit 4c9bee8fef4b node:20-updated
# 旧版指令
# docker commit 4c9bee8fef4b node:20-updated
# 查看 node:20 这个 image 的历史
$ docker image history node:20
# 旧版指令
# docker history node:20
# 停止 4c9bee8fef4b 这个 contaienr
$ docker container stop 4c9bee8fef4b
# 旧版指令
# docker stop 4c9bee8fef4b
# 启动 4c9bee8fef4b 这个 container
$ docker container start 4c9bee8fef4b
# 旧版指令
# docker start 4c9bee8fef4b
# 在 4c9bee8fef4b 这个 container 中执行 /bin/bash 这个命令,并且开启输出入
# 因为是执行 /bin/bash 又开启了输出入,所以就像是「进入」了这个 container 中
$ docker container exec -it 4c9bee8fef4b /bin/bash
# 旧版指令
# docker exec -it 4c9bee8fef4b /bin/bash
# 移除 4c9bee8fef4b 这个 container
$ docker container rm 4c9bee8fef4b
# docker rm 4c9bee8fef4b
# 移除所有停止的 containers
$ docker container prune -f
# 移除 node:18 这个 image
# 移除 image 前要先移除用这个 image 启动的 containers
$ docker image rm node:18
# 旧版指令
# docker rmi node:18
原文始发于微信公众号(程序猿技术充电站):Docker 终极入门教程-上篇
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/224760.html