docker初学者(三)

编程

分层存储

docker是分层存储的,构建镜像的时候我们要保证每一层都只包含我们的应用需要的东⻄,不要包含不需要的文件,因为每一层在构建之后不再发生变化,所以即使你在之上的层删除了那些不需要的文件,这些文件也只是被标记为删除,实际上并没有真正删除;如果每一层都包含一些可有可无的文件,就会使得我们的镜像越来越臃肿。一个镜像实际上并不是一个文件,而是一组分层文件。分层存储还使得不同的镜像可以共享某些层,便于镜像的复用。

通俗理解下首先,我们有一张没有斑点的奶牛图片(相当于我们有一个基础镜像):

现在,我希望从这张图片得到一张有斑点的奶牛图像,我不是直接在这个原始图片画斑点,而是将斑点单独作为一层,把斑点层和原始图片叠加,就得到了斑点奶牛(相当于我们commit一个新镜像):

这样做有什么好处呢?设想一下,如果你直接把你想要的黑色斑点画到原图上,那么其他人想在原始的无斑点奶牛图片上做一些其它的创作就会很麻烦。但是,如果采取分层的方式,他们也只需要设计自己想要的斑点就可以了,从而原始的无斑点奶牛图片可以共享:

假设,你现在把黑色斑点奶牛作为基础图片,希望在黑色斑点奶牛的基础上设计新的斑点,那么你不能直接擦除黑色斑点奶牛的斑点,你只能在你的图层用白色把这些斑点覆盖,然后再画新的斑点。这就是上面所说的标记为删除,而不是真正删除的原因。

Dockerfile

Dockerfile 是一个文本文件,包含构建镜像的一条条指令,每一条指令构建一层,每一条指令描述当前层如何构建。

比如上一篇,我们创建了一个net:v1.0的镜像。

我们可以使用dockerfile来构建这个镜像。

FROM ubuntu:16.04

RUN apt-get update && apt-get install -y apt-utils

RUN apt-get install -y vim

RUN apt-get install -y net-tools

RUN apt-get install -y iputils-ping

RUN apt-get install -y apache2

RUN apt-get install -y apache2-utils

RUN apt-get install -y openssh-server

RUN apt-get install -y openssh-client

RUN mkdir /var/run/sshd

RUN echo "root:root" |chpasswd

RUN sed -ri "s/^PermitRootLogins+.*/PermitRootLogin yes/" /etc/ssh/sshd_config

RUN sed -ri "s/UsePAM yes/#UsePAM yes/g" /etc/ssh/sshd_config

EXPOSE 22

CMD ["/usr/sbin/sshd", "-D"]

FROM

FROM 指令指定基础镜像,我们定制的镜像是在基础镜像之上进行修改的。FROM 指令必须是 Dockerfile 文件的第一条指令。

RUN

RUN 是用来执行命令的,这条指令的格式有两种:

Shell 格式是“RUN <命令>”,我们的 Dockerfile 就是使用的 Shell 格式。

exec 格式:RUN [<"可执行文件">,<“参数1”>,<“参数2”>,... ],和函数调用的格式很相似。

EXPOSE

EXPOSE 用来暴露端口,格式为:EXPOSE <端口1> [<端口2>……]

值得注意的是,EXPOSE 只是声明运行容器时提供的服务端口,这仅仅是一个声明,在运行容器的时候并不会因为这个声明就会开启端口服务,你依旧需要使用 -P 或者 -p 参数映射端口。在 Dockerfile 中写这样的端口声明有助于使用者理解这个镜像开放哪些服务端口,以便配置映射。并且,可以在 docker run 命令执行的时候使用 -P 参数随机映射宿主主机端口到 EXPOSE 的容器端口。

ENV 设置环境变量

相当于 linux中的 export

比如 ENV JAVA_HOME=/etc/java

之后命令中就可以使用 ${JAVA_HOME} 来代替目录

ARG

也是定义参数,但是与ENV不同的是,容器运行时不会存在这些环境变量。

可以用 docker build --build-arg <参数名>=<值> 来覆盖。

COPY 复制文件

将本地文件添加到容器中

COPY <源路径>... <目标路径>

COPY ["<源路径1>",... "<目标路径>"]

<源路径> 可以是多个、以及使用通配符,通配符规则满足Go的filepath.Match 规则,如:COPY hom* /mydir/ COPY hom?.txt /mydir/

<目标路径>使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。

ADD 高级复制

类似于copy,但是复制tar文件能够自动解压,也能够下载url上的文件。

ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /

<源路径> 可以是一个 URL ,下载后的文件权限自动设置为 600 。

CMD

要理解CMD,首先需要知道,容器的本质是一个进程。既然是进场,那么总得做点什么对吧。

比如我们一开始使用docker run的命令就会在后面几个 bash,表示执行bash指令。

所以CMD 就是为容器主进程启动命令而存在的。比如,在我们的文件中,我们使用 CMD 开启了 ssh 进程。

使用 CMD 命令的时候,初学者容易混淆前台运行和后台运行。再强调一遍,Docker 不是虚拟机,容器是进程,所以容器中的应用都应该以前台模式运行,比如,如果你把我们的 Dockerfile 的最后一行写成“CMD service ssh start”,那么我们使用 docker run 从镜像运行容器后,容器马上就退出了。这是因为,容器就是为了主进程而存在的,一旦执行完我们的“service ssh start”,主进程就认为完成了任务,于是就退出了。所以,注意应该直接执行 sshd 可执行文件,并以前台模式执行:CMD ["/usr/sbin/sshd", "-D"]

一个dockerfile只有最后一个CMD命令会生效。

运行dockerfile

在dockerfile文件所在的目录下运行。(这个其实是上下文路径的意思,不是简单的当前目录,不过一般就是这么用就行)

sudo docker build -t net:v1.2 .

这样我们就创建了一个新的镜像 net:v1.2

可以使用 docker run来运行这个容器。

问题在于,但我们查看镜像的时候,发现这个镜像竟然比commit的 net:v1.1 还要庞大。这是因为对于dockerfile来说,每一个命令都是盖楼,相当于会追加一层(参考上面的分层概念)。

所以,当我们执行了非常多的RUN 命令之后,这个镜像已经被包裹了很多层。所以可以改造下

FROM ubuntu:16.04

RUN apt-get update && apt-get install -y apt-utils

&& apt-get install -y vim

&& apt-get install -y net-tools

&& apt-get install -y iputils-ping

&& apt-get install -y apache2

&& apt-get install -y apache2-utils

&& apt-get install -y openssh-server

&& apt-get install -y openssh-client

&& mkdir /var/run/sshd

&& echo "root:root" |chpasswd

&& sed -ri "s/^PermitRootLogins+.*/PermitRootLogin yes/" /etc/ssh/sshd_config

&& sed -ri "s/UsePAM yes/#UsePAM yes/g" /etc/ssh/sshd_config

EXPOSE 22

CMD ["/usr/sbin/sshd", "-D"]

通过减少&& 代替RUN 减少层级。

一些其他的命令

WORKDIR

使用 WORKDIR 指定工作目录,以后各层的指令就会在这个指定的目录下运行。在这里,再提一下分层存储的概念,比方说你过你在 Dockerfile 里面这样写(假设你的 test.txt 文件在 /mydir 目录下):

RUN cd /mydir

RUN echo "Hello world." > test.txt

在 docker build 的时候会报错,提示找不到 test.txt 文件,因为在第一层的 cd 切换目录并不会影响第二层。此时,你就应该使用 WORKDIR 命令。

举例:

WORKDIR /mydir

RUN echo "Hello world." > test.txt

USER

指定用户。这个命令会影响其后的命令的执行身份。当然,前提是你先创建用户。

RUN    groupadd    -r  zsc &&  useradd -r  -g  zsc zsc

USER zsc

ENTRYPOINT

ENTRYPOINT 命令和 CMD 一样有 shell 格式和 exec 格式,并且和 CMD 命令一样用来指定容器启动程序及参数。

这里要比较一下ENTRYPOINT和CMD的区别。

创建镜像cmd

FROM ubuntu:16.04

WORKDIR /

CMD ["ls"]

docker build -t cmd .

创建镜像 etp

FROM ubuntu:16.04

WORKDIR /

ENTRYPOINT ["ls"]

docker build -t etp .

然后运行两个容器

run 命令我们没有添加容器命令和参数,这个时候自动使用了 CMD 和 ENTRYPOINT 指定的命令 ls

再次运行容器

我们看到,CMD命令会被 run之后命令替代,这里替代成了 -l (它不是个命令,所以运行不起来)

对于 ENTRYPOINT 会将run之后的命令作为参数传入,这里就传入成了 ls -l

参考

大部分内容为 《Docker 极简入门指南》 摘录的学习笔记

以上是 docker初学者(三) 的全部内容, 来源链接: utcz.com/z/510490.html

回到顶部