Docker:教你如何把服务器装进口袋里,让工资涨得明明白白(三)

2,593 阅读9分钟

本文正在参加「金石计划」

四、docker镜像

4.1、什么是镜像

镜像是一种轻量级的,可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时所需的库、环境变量和配置文件。

4.2、镜像为什么这么大

我们看看镜像到底占用了多大的内存。

image-20230321143408602

好家伙,一个tomcat就600MB。要知道,一个单纯地tomcat服务器的内存才10几M,怎么到了docker这里就这么大了?原来是因为这个不仅仅把包含了tomcat,还包含了运行linux最基础的环境,相当于一个linux的精简指令集。

镜像其实有点像我们经常吃的花卷。

image-20200404142950068

docker底层维护使用的是方式是UnionFS(联合文件系统),tomcat8有十几二十个版本,我们都知道tomcat是依赖于jdk的,且linux核心库都是共通的,如果每一个镜像里面都包含这些基础的环境显然会使得镜像很臃肿。

所以docker就把一些公共的基础环境抽离了出来作为一个基础镜像,这个镜像在最底层,我们看不见。

4.3、UnionFS联合文件系统

Union文件系统是一种分层,轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。Union文件系统是Docker镜像的基础。

这种文件系统特性:就是一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录 。

4.4、docker镜像原理

docker的镜像实际是由一层一层的文件系统组成。linux内核由这几部分构成:

  1. bootfs(boot file system):主要包含bootloader和kernel,bootloader主要是引导加载kernel,Linux刚启动时会加载bootfs文件系统。在docker镜像的最底层就是bootfs。这一层与Linux/Unix 系统是一样的,包含boot加载器(bootloader)和内核(kernel)。当boot加载完,后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时会卸载bootfs。

  2. rootfs(root file system):在bootfs之上,包含的就是典型的linux系统中的/dev,/proc,/bin,/etc等标准的目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu/CentOS等等。

最直接的证明就是我们在拉取镜像的时候,他是一层一层的给我们下载,而不是一整个全部下载下来。

image-20230321160834322

4.2、docker分层的原因

为什么docker镜像要采用这种分层结构呢?最大的好处是如果使用分层的话可以实现资源共享。

比如:有多个镜像都是从相同的base镜像构建而来的,那么宿主机只需在磁盘中保存一份base镜像。同时内存中也只需要加载一份base镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。Docker镜像都是只读的。当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称为容器层,容器层之下都叫镜像层。

五、Dockerfile

5.1、Dockerfile

Dockerfile 用于构建 Docker 镜像,Dockerfile 文件是由一行行命令语句组成,基于这些命令即可以构建一个镜像。

image-20200404111908085

通过这张图我们可以发现,通过Dockerfile可以直接构建镜像,通过Dockerfile文件我们可以构建一个属于自己的镜像。

之前我们是不是已经学过了commit来构建一个镜像,那么为什么我们还需要Dockerfile来构建镜像呢?官方的镜像已经足够好了,且适合大多数的应用程序,但是如果我们想捣鼓一个属于自己业务的镜像,足够时候就必须使用Dockerfile来打成自定义镜像。

那么我们如何通过Dockerfile来构建属于我们自己的镜像呢?

  1. 在指定的位置创建一个Dockerfile文件,编写Dockerfile相关语法。
  2. 编写完了后,我们就可以通过Dockerfile来构建镜像。docker build -t镜像名:版本号 Dockerfile文件所在的目录

5.2、Dockerfile语法

Dockerfile有一个很重要的语法规则,那就是一行只能存在一条完整的命令,意味着我们不可以把多条命令写在一行。

读第一行的时候,会去基于这一行去构建一个临时镜像,紧接着会在这个临时镜像的基础上去执行第二条指令,再去构建一个临时镜像,以此执行,直到读到结束的指令的时候才会去构建一个真正的镜像。

Dockerfile这样做的原因是可以加快你构建镜像的速度,Dockerfile每次构建完一个临时镜像的时候都会将这个临时镜像放进docker cache(docker缓存区),当你想对你的Dockerfile修修改改的时候,比如说我临时加了1行,这个时候就不需要从0开始从头构建,Dockerfile会去比对一下,如果发现前面几行的代码都没有变化,那docker就不会再去构建前三行的临时镜像。

如果你不想用这种缓存机制的话,我们可以在build后面加上--no-cache。即build --no-cache把缓存给关掉。我们要熟记的几个保留字大概只有这么多:

官方说明:docs.docker.com/engine/refe…

保留字作用
FROM当前镜像是基于哪个镜像的 第一个指令必须是FROM
MAINTAINER镜像维护者的姓名和邮箱地址
RUN构建镜像时需要运行的指令,可以一数组的方式写多条指令
EXPOSE当前容器对外暴露出的端口号
WORKDIR指定在创建容器后,终端默认登录进来的工作目录,一个落脚点
ENV用来在构建镜像过程中设置环境变量
ADD将宿主机目录下的文件拷贝进镜像且ADD命令会自动处理URL和解压tar包
COPY类似于ADD,拷贝文件和目录到镜像中
将从构建上下文目录中<原路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置
VOLUME容器数据卷,用于数据保存和持久化工作
CMD指定一个容器启动时要运行的命令
Dockerfile中可以有多个CMD指令,但只有最后一个生效,CMD会被docker run之后的参数替换
ENTRYPOINT指定一个容器启动时要运行的命令
ENTRYPOINT的目的和CMD一样,都是在指定容器启动程序及其参数

5.2.1、FROM

Dockerfile 文件的第一条指令必须为 FROM 指令。并且,如果在同一 个 Dockerfile 中创建多个镜像时,可以使用多个 FROM 指令(每个镜 像一次),他所指定的镜像比如是已经存在的镜像,我们称为基础镜像,语法格式:

FROM 镜像名称
# 或者是
FROM 镜像名称:版本号

5.2.2、MAINTAINER

用于指定作者信息,语法格式为:

MAINTAINER 作者名称

5.2.3、RUN

RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像, 当命令较长时可以使用 \ 来换行。每个RUN命令都会在当前镜像的上层创建一个新的镜像来运行指令,语法格式为:

RUN 需要执行的命令
# 示范
RUN echo xiaolin

5.2.4、EXPOSE

指定运行该镜像的容器使用的端口,虽然我们在构建镜像的时候暴露了端口号,但是我们在运行容器的时候依然需要指定端口的映射。 我们使用EXPOSE只是告诉Docker运行该镜像的容器会使用80端口,出于安全的考虑,Docker并不会打开该端口,所以我们需要在使用该镜像运行容器的时候指定端口的映射。语法格式为:

EXPOSE 端口号

5.2.5、CMD

CMD指令提供容器默认运行的命令,RUN指令类似.都是执行一个命令。但是RUN命令指定的命令是在镜像构建的过程运行的,CMD的命令是在容器运行的时候运行的,DockerFile中可以有多个CMD指令,但是只有最后一个会生效。 如果我们在docker run命令中指定运行的命令的时候,CMD的指令会被覆盖,默认命令就不会执行。

5.2.6、ENTRYPOINT

这个命令和CMD指令十分相似,他和RUN指令不同的是,他不会被docker run的启动命令给覆盖。

5.2.7、ADD

将文件和目录复制到使用dockerfile构建的镜像中,目标的来源可以本地的地址也可以是远程地址.。

如果是本地地址,本地地址必须是构建目录中的相对地址。 对于远程URL,docker并不推荐使用,更建议使用的是curl或者wget的命令来获取,目标路径需要指定镜像中的绝对路径。

5.2.8、VOLUME

用于基于镜像创建的容器添加卷,一个卷可以存在一个或者多个容器的特定目录。这个目录可以绕过联合文件系统.提供共享数据和持久化数据的功能。

5.2.9、WORKDIR

这个指令是从指令创建一个容器,在容器内部设置工作目录。ENTRYPOINT和CMD的命令都会在这个目录下执行,我们也可以使用这个命令给后续的构建中指定工作目录。通常会使用绝对路径,如果使用了相对路径,那这个路径会一致传递下去。

5.2.10、USER

指定镜像会以什么样的用户去运行,如果没有指定USER,容器会使用root用户来运行。语法格式:

USER xiaolin

5.2.11、ONBUILD

镜像触发器,当一个镜像被其他镜像作为基础镜像时执行,会在构建过程中插入指令。

5.2.12、ENV

这个指令主要是来设置环境变量,这个环境变量在构建过程中和运行过程中都有效。比如配置JDK环境:

ENV JAVA_HOME /usr/local/jdk1.8.0_121
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV PATH $PATH:$JAVA_HOME/bin