使用Dockerfile定制镜像
镜像的定制实际上就是每一层所添加的配置文件。
Dockerfile是一个文本文件,其中包含多条指令。每条指令构建一层。
FROM:定制的镜像都是基于FROM的镜像。
其中如果以scratch为基础镜像,意味着不需要任何镜像基础。
RUN
此指令用来执行命令行命令。主要有两种格式:
shell格式
:RUN就像直接在命令行中输入的命令一样。 - 例如:FROM:定制的镜像都是基于FROM的镜像。
exec格式
:RUN["可执行文件","参数1","参数2"]更像是函数调用的格式- 例如:
RUN ["./test.php", "dev", "offline"] == RUN ./test.php dev offline
- 例如:
Dockerfile总每一个指令都会在docker上新建立一层。所以针对多条指令可以用&&
符号连接命令,这样执行后,只会创建1层镜像。
例如
FROM centos
RUN yum install wget
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
&& tar -xvf redis.tar.gz
同时每一层构建完成后,一定要清理掉无关文件。避免镜像臃肿
构建镜像
docker build -t nginx:v3 .
镜像构建上下文
在docker build
命令后有一个 .
。表示的是当前目录,实际上是表示的上下文路径
- docker build工作原理
- Docker在运行时分为Docker引擎和客户端工具,就是服务端守护进程和客户端工具
- 本机执行的docker功能都是使用远程调用形式在服务端(Docker引擎)完成
- 构建镜像时,用户需要指定构建镜像上下文的路径,docker build命令会将路径下的所有内容打包,然后上传给Docker引擎。
- 超出上下文路径的文件是无法
COPY
的
- 超出上下文路径的文件是无法
Dockerfile指令详解
COPY复制文件
-
格式:
COPY [ -- chown=<user>:<group>] <源路径> ... <目标路径>
COPY [--chown=:] ["<源路径1>",... "<目标路径>"]
-
<源路径>可以是通配符 例如 COPY hom* /mydir
-
<目标路径>可以是容器内部的绝对路径,也可以是相对于工作目录的相对路径
-
使用COPY,源文件的各种元数据都会保留。例如权限,变更时间
ADD更高级的复制文件
与COPY基本一致,在COPY的基础上添加了功能
实际上最适合的场景就是自动解压到目标目录
比如<源路径>可以是URL,这种情况下Docker引擎会试图下载这个链接的文件放到<目标路径>
如果权限信息不正确,需要用RUN再次修改权限,所以并不适用,不推荐
- 如果<源路径>为tar压缩文件,ADD只能会自动解压缩这个压缩文件到<目标路径>
CMD容器启动命令
CMD指令就是用于指定默认的容器主进程的启动命令
在运行时可以指定新的命令来替代镜像设置中的默认命令
与RUN指令运行时间点区别:
- CMD 在docker run时运行
- RUN 是在 docker build
运行格式:
- shell格式:
CMD <命令>
- exec 格式:
CMD["可执行文件","参数1","参数2"]
采取 shell 格式,实际的命令会被包装成sh -c 的参数格式进行执行。比如:
CMD ["sh","-c","echo $HOME"]
这就是可以使用环境变量的原因,因为这些环境变量会被shell
进行解析处理。
因为docker不是虚拟机,所i容器中的应用都应该以前台执行,而不是像虚拟机,物理机中用system启动后台服务。
CMD所启动的命令实际都依靠于主进程sh
,当sh
作为主进程退出了,自然就会令容器退出。
正确的做法时直接执行相关的可执行文件,并且要求以前台形式运行。
CMD ["nginx","-g","deamon off"]
ENTRYPOINT入口点
如果Dockerfile中存在多个ENTRYPOINT指令,仅最后一个生效
ENTRYPOINT
的格式和RUN
指令格式一样,分为exec格式和shell格式
- 制定了ENTRYPOINT后,CMD的含义就发生了变化,不再是直接运行器命令,而是将CMD内容作为参数传为ENTRYPOINT
- 实际执行时将变成:
<ENTRYPOINT> "<CMD>"
- 实际执行时将变成:
场景一:让镜像变成像命令一样使用
FROM ubuntu:18.04
RUN apt-get update
&& apt-get install -y curl
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://myip.ipip.net" ]
如果此时用docker build -t myip
构建镜像,只需要执行docker run myip
但是当希望添加参数比如 docker build -t myip -i
会提示可执行文件找不到。
原因:
-
跟在镜像后的command运行时会替换CMD的默认值。这里的
-i
会替换原来的CMD,所以自然找不到 -
如果希望加入-i参数,必须重新完整输入命令
docker run myip curl -s http://myip.ipip.net -i
此时用ENTRYPOINT就可以解决这个问题
修改最后一句ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]
,此时可以使用 docker run myip -i
场景二:运行前的准备工作
启动容器就是启动主进程,但有时,启动主程序前需要进行准备工作
此时可以写一个脚本,然后放入ENTRYPOINT中去执行,而这个脚本将会接到的参数(也就是CMD)作为命令,放在脚本最后执行
eg : redis官方镜像
FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]
其中为了redis服务创建redis用户,并在最后指定entrypoint为docker-entrypoint.sh脚本
#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
find . ! -user redis -exec chown redis '{}' +
exec gosu redis "$0" "$@"
fi
exec "$@"
脚本的内容就是根据CDM内容判断,如卡换对应的身份
ENV设置环境变量
两种格式:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>
这个指令就是设置环境变量,无论是后面的其他指令RUN
,还是运行的应用,都可以直接使用这里定义的环境变量
ENV VERSION=1.0 DEBUG=ON
NAME="HAPPY TREE"
#后续的命令可以直接使用VERSION
RUN curl -SLO "https://ppp.ok/v$VERSION"
ARG构建参数
ARG <参数名>[=<默认值>]
构建参数和ENV的效果一样,都是设置环境变量。但是,ARG锁构建的环境变量,在将来容器运行时不存在。
构建命令中可以用--build-arg <参数名>=<值>
来覆盖。
- ARG指令有生效范围,如果在FROM指令之前指定,那么只能用于FROM指令中
VOLUME 定义匿名卷
定义匿名数据券,再启动容器时忘记挂载数据券,会自动挂载到匿名券。
VOLUME ["<路径1>","<路径2>"]
VOLUME "<路径1>"
作用:
- 避免重要的数据,因为容器启动而丢失
- 避免容器不断变大
为了防止用户忘记将动态文件所保存目录挂载为卷。在Dockerfile中,我们可以实现指定某些目录为匿名卷,这样运行时忘记挂载也可以正常运行
VLOUME /data
docker run -d -v mydata:/data xxxx
#可以通过-v 参数指修改挂载点
#在这行命名命名参数中,就是用了mydata这个命名权挂载到了/data
EXPOSE暴漏端口
格式为EXPOSE<端口1> [<端口2>]
仅仅是声明端口,并不会因为这个声明就开启这个端口的服务。
而-p
是映射宿主端口和容器端口,就是将容器对端口服务公开给外界访问
WORKDIR指定工作目录
格式为WORKDIR <工作目录路径>
- 使用WORKDIR指令可以指定工作目录,以后的各层目录都被改成指定目录,如果不存在WORKDIR会帮你创建目录
- WORKDIR存在相对路径
USER指定当前用户
格式:USER <用户名>[:<用户组>]
- USER则是改变之后层执行
RUN
,CMD
,ENTRYPOINT
这类命令身份 - USER切换的用户必须是事前建立好的否则无法切换
HEALTHCHECK 健康检查
ONBUILD
Dockerfile多阶段构建
多阶段构建
之前的做法
在Docker 17.05 版本之前,在构建Docker镜像时,通常会采取两种方式
1.全部放入一个Dockerfile
一种方式是将所有的构建过程编在一个Dockerfile中,包含项目机器依赖库的编译、测试、打包等流程,如此可能带来一些问题:
- 镜像构建层次多,镜像体积较大,部署时间变长
- 源代码存在泄露的风险
例如:
编写 app.go 文件 // 编写对应dockerfile (其中包含了编译测试打包) // 最后构建镜像
2.分散到多个Dockerfile
另一种方式是事先在一个Dockerfile将项目及其依赖库编译测试打包好后,再将其拷贝到运行环境中,这种方式需要编写两个Dockerfile和一些编译脚本才能将两个阶段整合
使用多阶段构建
为了解决以上问题,Dockerv17.05开始支持多阶段构建,使用多阶段构建可以解决前面的问题
FROM golang:1.9-alpine as builder
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld/
RUN go get -d -v github.com/go-sql-driver/mysql
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest as prod
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/go/helloworld/app .
CMD ["./app"]
其他制作镜像的方式
从rootfs压缩包导入
docker import [option] <file>|<URL> - [<depository><:TAG>]]
压缩包可以是本地文件、远程Web文件,甚至是标准输入中得到的。也所报将会在镜像/
目录展开,直接作为镜像第一层提交
$ docker import
http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz
openvz/ubuntu:16.04
此命令自动下载了tar.gz文件,并作为跟文件系统展开导入,保存为镜像openvz/ubuntu:16.04
导入成功后,可以在docker image ls
总看到导入的镜像
Docker镜像导入和导出 docker save和docker load
保存镜像
使用docker save命令可以将镜像保存为归档文件docker save alpine -o filename
若使用gzip压缩:
docker save alpine | gzip > alpine-latest.tar.gz
加载镜像
docker load -i alpine-latest.tar.gz