关于Docker的随手一记
Docker是一种运行与Linux和Windows上的软件,用于创建、管理和编排容器。
随着互联网的发展,往日的大型单体应用正被逐渐分解成小的、可以独立运行的组件,也就是我们一直在讲的微服务。微服务彼此之间解耦,可以独立的开发、部署、升级、伸缩。这使得我们可以依据业务需要对每一个模块的服务实现快速迭代。微服务在给我们带来很多的便捷,但在系统包含较多微服务组件的情况下,服务的部署和运维工作却显得复杂繁琐。
一个微服务架构中的组件,是具有独立性的,常常会有不同团队开发不同的组件,每个服务可能使用不同的开发语言、不同的运行环境以及使用不同的库并在需求升级的时候替换。例如下图体现了微服务下多个应用在同一个主机上运行可能会有依赖冲突。
当程序较少或者是大型单体应用时,可以利用虚拟机的隔离技术,通过虚拟机的虚拟化技术为每个应用程提供不同的运行环境。但是在微服务的模式下,如果为每个服务都分配一个虚拟机,会存在巨大的资源消耗。
那么一个来自灵魂啊拷问,什么是容器呢?
“一个容器仅仅是运行在宿主机上被隔离的单个进程”。容器类似虚拟机,容器允许你在同一台机器上运行多个服务,不仅提供不同环境给每个服务,而且将他们互相隔离。开销要比虚拟机小的很多。
首先来看看容器和虚拟机的对比,也许就能比较明朗的了解容器到底是什么。
如上图所示,当你在一台主机上运行三个虚拟机的时候,意味着你拥有三个完全分离的操作系统,它们运行并且共享一台裸机。在虚拟机之下是宿主机的操作系统与一个管理程序,它将物理硬件资源分成较小部分的虚拟硬件资源,从而被每个虚拟机里的操作系统使用。运行在虚拟机里的应用程序会执行虚拟机里的操作系统的系统调用,然后虚拟机内核会通过管理程序在宿主机上的物理CPU来执行x86指令。
多个容器的方式则会完全执行运行在宿主机上的同一个内核的系统调用,此内核是唯一一个在宿主机操作系统上执行x86指令的内核。CPU也不需要做任何对虚拟机能做那样的虚拟化。
总的来说:
1.容器比虚拟机更加轻量级。
2.每个虚拟机需要运行自己的一组系统进程,而容器不需要
3.虚拟机提供完全隔离的环境,每个虚拟机运行在它自己的Linux内核上
4.容器调用的是同一个内核(此内核是唯一一个在宿主机操作系统上执行x86指令的内核)
5.容器通过Linux命名空间来实现隔离机制,还有一种方式是Linux控制组(cgroups)
粗略的描述了容器的概念后,我们来看看什么是Docker,Docker的三个主要概念组,以及日常开发中一些常见的使用。
Docker是一个打包、分发和运行程序的平台
尽管容器技术已经出现了很久,却是随着Docker容器平台的出现而变得广为人知。Docker是第一个使容器能在不同机器之间移植的系统。Docker不仅简化了打包应用的流程,也简化了打包应用的库和依赖,甚至整个操作系统的文件也能被打包成一个简单的可移植的包,这个包可以被用来在任何其他运行Docker的机器上使用。
Docker有三个主要的概念:镜像、镜像仓库、容器
- 镜像: Docker镜像里包含了你打包的应用程序及其所有依赖的环境。它包含应用程序可用的文件系统和其他元数据,比如镜像运行时的可执行文件路径。(基于Docker容器的镜像和虚拟机镜像的一个很大的区别是容器镜像是由多层构成的,能在多个镜像之间共享和征用)
- 镜像仓库:Docker镜像仓库用于存放Docker镜像,以及促进不同人和不同电脑之间共享这些镜像。
- 容器:Docker容器通常是一个Linux容器,它基于Docker镜像被创建。如果将镜像类比为java中的class,那么容器相当于运行过程中尝试的类实例。一个运行中的容器是一个运行在Docker主机上的进程,但它和主机,以及所有运行在主机上的其他进程都是隔离的。
构建、分发和运行Docker镜像
1.开发人员首先构建一个镜像
2.然后把构建好的镜像推送到镜像仓库
3.然后将镜像从镜像仓库中拉取到任何运行着Docker的机器上
4.运行镜像(Docker会基于镜像创建一个独立的容器,并运行二进制可执行文件制定其作为镜像的一部分)
Docker镜像的整体认知
Docker镜像是由多层组成的,每层叠加之后,从外部来看如一个独立的对象。镜像的内部是一个精简的操作系统(OS),同时包含着应用运行所必须的文件和依赖包。
镜像和容器的关系,前面也有提到,镜像就像停止运行的容器(类),换句话来说,镜像可以理解为一种构建时结构,而容器可以理解为一种运行时结构,如下图所示:
一旦执行指令 docker container run 和 docker service create 从某个镜像启动一个或者多个容器后,镜像与容器二者之间就变成了互相依赖的关系。在镜像启动的容器停止之前,该镜像是无法被删除的。
镜像的拉取
只需要给出镜像的名字和标签,就能在官方仓库中定位一个镜像,通过如下命令向仓库拉去镜像。
::docker image pull <repository>:<tag>
::
如果没有在仓库名后面制定具体的镜像标签,则Docker会假设用户希望拉去标签为lastest的镜像。(标有lastest标签的镜像不保证这是仓库中最新的镜像)
查看本地镜像列表
::docker image ls
::
Docker提供 —filter 参数来过滤 docker image ls 命令返回的镜像列表内容。
但其实在日常工作中更习惯使用 docker imgaes 指令 结合 grep 来查找匹配的镜像列表。例如:下图为查找镜像名称为 project-manager的镜像列表
通过CLI方式搜索 Docker Hub
::docker search
::
Docker search 命令允许通过CLI的方式搜索Docker Hub 。 可以通过name字段的内容进行匹配,并且基于返回内容中任意列的值进行过滤。
例如:下面指令会查找所有“NAME”包含ubuntu的镜像
删除镜像
::docker image rm
::
删除镜像操作会在当前主机上删除该镜像以及相关的镜像层。如果某个镜像层被多个镜像共享,只有当全部依赖该镜像层的镜像都删除后,该镜像层才会被删除。
如果被删除的镜像上存在运行状态的容器,那么删除操作不会被运行,需要停止并删除该镜像相关的全部容器。
还需要深刻一下镜像分层的概念
如上图所示,Docker镜像由一些松耦合的只读镜像层组成。Docker负责堆叠这些镜像层,并将它们表示为单个统一的对象。
所有的Docker镜像都是起始于一个基础镜像层,当进行修改或者增加新的内容时,就会在当前镜像层之上创建新的镜像层。在添加额外的镜像层时,镜像始终保持是当前所有镜像层的组合。
多个镜像之间可以并且确实会共享镜像层,有效的节省空间,并且提升了性能。 拉取镜像时,时常会出现如下图所示的“Already exists ” ,这部分的镜像层就是共享复用了其他镜像的镜像层。
构建镜像的Dockerfile
在日常的开发中,基本上都是通过Dockerfile来创建一个镜像。Dockerfile是一个Docker镜像的描述文件,文件内部包含了一条条指令,每一条指令构建一个镜像层,因此每一条dockerfile的指令内容就是描述了该层应当如何构建。(注:有些Dockerfile的指令并不会创建新的镜像层,比如:EVN、EXPOSE、CMD以及ENTRYPOINT,不过这些命令会在镜像中添加元素数据)
以下为Dockerfile中常用指令的介绍:
- FROM 指定基础镜像(FROM 指令用于指定其后构建新镜像所使用的基础镜像。FROM 指令必是 Dockerfile 文件中的首条命令,启动构建流程后,Docker 将会基于该镜像构建新镜像,FROM 后的命令也会基于这个基础镜像。)
::FROM <image>
::
::FROM <image>:<tag>
::
::FROM <image>:<digest>
::
FROM 必须 是 Dockerfile 中第一条非注释命令
- RUN 执行命令 (在镜像的构建过程中执行特定的命令,并生成一个中间镜像。)
shell格式
::RUN <command>
::
exec格式
::RUN [”executable”, “param1”, “param2”]
::
RUN 命令将在当前 image 中执行任意合法命令并提交执行结果。命令执行提交后,就会自动执行 Dockerfile 中的下一个指令。
层级 RUN 指令和生成提交是符合 Docker 核心理念的做法。它允许像版本控制那样,在任意一个点,对 image 镜像进行定制化构建。
RUN 指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定 —no-cache 参数,如:docker build —no-cache。
- COPY 复制文件 (COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的<目标路径>位置)
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]
使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。
- ADD 更高级的复制文件(ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。比如<源路径>可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到<目标路径>去。)
ADD <源路径>... <目标路径>
ADD ["<源路径>",... "<目标路径>"]
- ENV 设置环境变量 (设置环境变量,无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。)
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
- EXPOSE (为构建的镜像设置监听端口,使容器在运行时监听。)
::EXPOSE <port> [<port>…]
::
EXPOSE 指令并不会让容器监听 host 的端口,如果需要,需要在 docker run 时使用 -p、-P 参数来发布容器端口到 host 的某个端口上。
- VOLUME 定义匿名卷 (向基于所构建镜像创始的容器添加卷,一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统。)
::VOLUME [”/data”]
::
~ 卷可以容器间共享和重用
~ 容器并不一定要和其它容器共享卷
~ 修改卷后会立即生效
~ 对卷的修改不会对镜像产生影响
~卷会一直存在,直到没有任何容器在使用它
- WORKDIR 指定工作目录
- USER 指定当前用户
- CMD (CMD用于指定在容器启动时所要执行的命令。)
CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2
注意:与 RUN 指令的区别:RUN 在构建的时候执行,并生成一个新的镜像,CMD 在容器运行的时候执行,在构建时不进行任何操作。
- ENTRYPOINT (用于给容器配置一个可执行程序)
- LABEL 用于为镜像添加元数据
- ARG 用于指定传递给构建运行时的变量
- ONBUILD 用于设置镜像触发器
- STOPSIGNAL用于设置停止容器所要发送的系统调用信号
Docker 容器的简单了解
容器是镜像运行的实例。一个镜像可以启动一个或者多个容器。如下图所示:
启动一个简单的容器
::docker container run
::
例如如下平时工作的启动命令:
查看容器列表
::docker container ls
::
列出所有运行状态的容器,如果使用-a标记,可以看见处于停止状态的容器
平时工作更喜欢使用 docker ps -a | grep 来查看容器列表。
容器进程 (其实我平时理解是:进入到运行的容器中去)
::docker container exec -it <container-name-or-ID> bash
::
停止容器
::docker container stop <container-name-or-ID>
::
重启处于停止状态的容器
::docker container start <container-name-or-ID>
::
删除容器
::docker container rm <container-name-or-ID>
::
显示容器的配置细节与运行时信息
::docker container inspect <container-name-or-ID>
::
参考说明:
1.本文结合了书集《深入浅出Docker》、《Kubernetes in Action》中关于容器与Docker的介绍
2.文章Dockerfile总结部分转载自文章《Dockerfile 指令详解》作者:纯洁的微笑
3.部分图片来源于网络,侵删。