docker简单使用(三)

docker简单使用(三)

Dockerfile简介二

简单介绍Dockerfile的一些指令

COPY

COPY,复制文件指令,格式:

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

另一种格式类似函数调用:COPY ["<源路径>",... "<目标路径>"]

COPY指令作用是从构建上下文目录中<源路径>(相对于上下文的路径)下的文件复制到镜像中的目标路径

示例:

1
COPY test1.txt /usr/local/test/

将当前上下文目录下的test1.txt复制到新镜像的/usr/local/test/文件夹下
<目标路径>可以是容器内的绝对路径,也可以是相对于WOKDIR的相对路径

ADD

不实用的命令,有拷贝的功能,同时,可以拷贝url,但是使用ADD url时,docker会先将url的文件下载下来,如果是个压缩包,还需要自己添加一层RUN进行解压,剔除不需要的文件,再复制;所以不如直接RUN wget,然后解压缩,剔除文件复制

ADD命令在有一种情况下很有用,源路径是一个gzip,bzip2以及xz的压缩包,ADD会自动解压压缩包到目标路径。

在《docker practice》中指出,在COPY和ADD指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用COPY指令,仅在需要自动解压缩场合使用ADD

CMD

首先要理解,容器是进程,不是虚拟机,在正常linux中,运行进程一般都伴随有启动参数,类似的,容器也有,CMD就是用于指定默认的容器主进程的启动命令的

格式:
shell格式:CMD <命令>
exec格式:CMD ["可执行文件", "参数1", "参数2"...]

在运行时,可以使用CMD来指定新的命令来替代镜像中设置的默认命令,例如,ubuntu镜像的默认CMD是/bin/bash,如果我们直接docker run -it ubuntu:16.04会直接进入bash;也可以指定别的命令,例如输出系统版本:docker run -it ubuntu:16.04 cat /etc/os-rlease

在指令格式上,推荐使用CMD ["可执行文件", "参数1", "参数2"...],这类格式会被解析成JSON,因此一定要是用双引号"
如果使用shell格式,例如CMD echo $JAVA_HOME在实际执行中会变更为CMD ["sh", "-c", "echo $JAVA_HOME"],实际会被包装成sh -c的参数形式。(这也是shell中可以直接使用环境变量的原因)

前面说过,容器其实就是进程,本身就是进程,所以就不存在什么进程里面的程序有后台执行的说法了,容器中的应用,都是在前台执行的,不存在systemctl这样的操作,不会像虚拟机中有systemctl start mysqld这样的操作,如果使用CMD写成这样的:CMD systemctl start nginx,会发现容器执行后立刻退出。

这是因为,docker容器,默认会把容器内部第一个进程,也就是pid=1的程序作为docker容器正在运行的依据,如果容器中pid=1的程序挂了,那docker容器就会直接退出;在执行CMD systemctl start nginx时候,实际执行的是CMD ["sh", "-c", "systemctl start nginx"],起初pid=1的程序是bash,但是后面接上了systemctl start nginx(后台守护模式daemon启动nginx),使得systemctl start nginx进程启动后,sh也结束了(CMD会更改默认命令),当pid=1的程序结束,容器就退出了。所以只要运行程序时候,在非守护模式下,容器就不会退出,因此,容器内启动nginx可以:CMD ["nginx", "-g", "daemon off"]

ENTRYPOINT

和CMD类似,不同的是,CMD会被docker run覆盖,而ENTERPOINT不会,例如以下一个最简单的镜像:

1
2
FROM ubuntu:16.04
CMD ["/bin/echo", "test"]

构建镜像:docker build -t echotest .
运行容器:

1
2
[root@localhost myip]# docker run echotest 
test

但是既然容器是进程,那如果像其他进程一样,加参数,效果如何:

1
2
3
[root@localhost myip]# docker run echotest -i
docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "exec: \"-i\": executable file not found in $PATH": unknown.
ERRO[0001] error waiting for container: context canceled

直接就报错了。。这是因为跟在镜像名之后的执行,会替换调CMD的默认值,但是-i又不是个指令,所以就报错了。

ENTRYPOINT在这点上就可以做到带参数,例如:

1
2
FROM ubuntu:16.04
ENTRYPOINT ["/bin/echo", "test"]

构建:docker build -t echotest2 .,运行容器:

1
2
[root@localhost echotest]# docker run echotest2 -i
test -i

可以看到,-i可以带进去。

ENV

顾名思义,设置环境变量ENV JAVA_VERSION 1.8.0_191

ARG

和ENV类似,都是设置环境变量。区别在于:

1
2
3
The ARG instruction defines a variable that users can pass at build-time to the builder with the docker build command using the --build-arg <varname>=<value> flag.ARG指令定义了用户可以在编译时或者运行时传递的变量,如使用如下命令:--build-arg <varname>=<value>

The ENV instruction sets the environment variable <key> to the value <value>. The environment variables set using ENV will persist when a container is run from the resulting image.ENV指令是在dockerfile里面设置环境变量,不能在编译时或运行时传递。

例如,在Dockerfile中定义:

1
2
ARG a_key1
ARG a_key2 = a_value2

ARG指令定义的参数,在docker build命令中可以通过–build-arg a_key1=avalue1来覆盖

VOLUME

创建一个可以从本地主机或其他容器挂载的挂在点,格式:VOLUME ["/data"]
对于数据库类需要保存动态数据的应用,数据库文件应该保存在卷(volume)。为了防止运行时用户忘记将动态文件所保存的目录挂在为卷,在写Dockerfile时,就可以事先指定某些目录挂载为匿名卷,这样运行时如果用户不挂载,应用也可以正常运行,不会向容器存储层写大量数据

VOLUME ["/data"],这里的/data目录就会在运行时自动挂载为匿名卷,任何向/data中写入的信息都不会记录进容器存储层。当然,docker run时候也可以覆盖这个设置:

1
docker run -d -v mydata:/data/tmp mysql

EXPOSE

声明端口,格式:EXPOSE <port1> <port2>
用来指定要映射出去的端口,例如,容器内部开启了nginx,就需要将80(或者指定的端口)暴露出去:EXPOSE 80。这个需要-P(大写)配合,启动容器是,加上-P,让它自动分配。如果想指定具体端口,使用-p(小写)

WORKDIR

指定工作目录,格式:WORKDIR <工作目录路径>,作用就是为后续的RUNCOPY等指定工作目录
在Dockerfile中,每个RUN,都会启一个容器,都是一个启动容器执行命令提交存储层文件变更的操作。
示例,如果Dockerfile这么写:

1
2
3
4
5
FROM ubuntu:16.04
RUN cd /home \
&& mkdir myapp \
&& cd myapp
RUN echo "docker practice" > test1.txt

然后构建镜像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost myworkfile]# docker build -t workfile1 .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu:16.04
---> 2a697363a870
Step 2/3 : RUN cd /home && mkdir myapp && cd myapp
---> Running in a33b48c39597
Removing intermediate container a33b48c39597
---> daebc80cbf9d
Step 3/3 : RUN echo "docker practice" > test1.txt
---> Running in 500473ff3363
Removing intermediate container 500473ff3363
---> 4a1668a9a53c
Successfully built 4a1668a9a53c
Successfully tagged workfile1:latest

可以看到,构建过程中,出现了两个中间容器a33b48c39597500473ff3363,所以这两个RUN,其实运行的是不一样的容器,最终结果,启动容器后在/home/myapp下可能就找不到test1.txt,验证:

1
2
3
4
5
6
7
[root@localhost myworkfile]# docker run -it workfile1 bash
root@3a7bbb6da9a2:/# cd /home/
root@3a7bbb6da9a2:/home# ls
myapp
root@3a7bbb6da9a2:/home# cd myapp/
root@3a7bbb6da9a2:/home/myapp# ls
root@3a7bbb6da9a2:/home/myapp#

没有找到test1.txt

使用WORKDIR:

1
2
3
FROM ubuntu:16.04
WORKDIR /home/myapp
RUN echo "docker practice" > test2.txt

构建镜像,运行容器,查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost myworkfile2]# docker build -t workfile2 .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu:16.04
---> 2a697363a870
Step 2/3 : WORKDIR /home/myapp
---> Running in a64c73b81fcb
Removing intermediate container a64c73b81fcb
---> c7868d92cec6
Step 3/3 : RUN echo "docker practice" > test2.txt
---> Running in f18afb589fee
Removing intermediate container f18afb589fee
---> 8733481c9491
Successfully built 8733481c9491
Successfully tagged workfile2:latest
1
2
3
4
5
[root@localhost myworkfile2]# docker run -it workfile2 bash
root@8bc345cf4fe7:/home/myapp# ls
test2.txt
root@8bc345cf4fe7:/home/myapp# cat test2.txt
docker practic

看到在/home/myapp下有test2.txt,并且内容是我们指定的。

Ps 在Dockerfile中,这边只使用WORKDIR /home/myapp并没有创建该目录,但是,后面执行就是在该目录下,这是因为WORKDIR会帮你建立目录

USER

WORKDIR类似,指定之后执行RUNCOPY等命令的用户;

示例一:

1
2
3
FROM ubuntu:16.04
USER kyle
RUN echo "Hello world"

构建镜像:

1
2
3
4
5
6
7
8
9
10
11
[root@localhost myuser2]# docker build -t myuser2 .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu:16.04
---> 2a697363a870
Step 2/3 : USER kyle
---> Running in b9af960d2702
Removing intermediate container b9af960d2702
---> ba5592dfdba1
Step 3/3 : RUN echo "Hello world"
---> Running in d93f1b59934c
unable to find user kyle: no matching entries in passwd file

发现报错,kyle该用户不存在,镜像myuser2构建不成功

示例二:

1
2
3
4
5
[root@localhost myuser1]# cat Dockerfile 
FROM ubuntu:16.04
RUN groupadd -r tests && useradd -r -g tests kyle
USER kyle
RUN echo "Hello World"

构建镜像,运行容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost myuser1]# docker build -t myuser1 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM ubuntu:16.04
---> 2a697363a870
Step 2/4 : RUN groupadd -r tests && useradd -r -g tests kyle
---> Running in 95030508e783
Removing intermediate container 95030508e783
---> 8910cd5c0ee0
Step 3/4 : USER kyle
---> Running in 5ca75c068a8b
Removing intermediate container 5ca75c068a8b
---> 17e120181a8a
Step 4/4 : RUN echo "Hello World"
---> Running in 022b81376556
Hello World
Removing intermediate container 022b81376556
---> 73527faf6379
Successfully built 73527faf6379
Successfully tagged myuser1:latest
1
2
3
[root@localhost myuser1]# docker run -it myuser1 bash
kyle@55b05690325a:/$ whoami
kyle

成功构建镜像,切换用户。

这两个示例可以看出,使用USER之前,必须创建好用户,也就是**USER不会帮你建用户,只是切换用户**

MAINTAINER

指定制作作者信息,格式:MAINTAINER <name>,例如MAINTAINER kyle kyle@xxx.com

以上,就是Dockerfile一些常用的指令,基本是借鉴的《docker practice》

文章目录
  1. COPY
  2. ADD
  3. CMD
  4. ENTRYPOINT
  5. ENV
  6. ARG
  7. VOLUME
  8. EXPOSE
  9. WORKDIR
  10. USER
  11. MAINTAINER
|