Dokcerfile
使用 Dockerfile 定制镜像:
镜像的定制实际上就是定制每一层所添加的配置、文件。
如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜
像,
那么无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就Dockerfile。
1、为什么要使用Dockerfile
问题:在dockerhub中官方提供很多镜像已经能满足我们的所有服务了,为什么还需要自定义镜像
核心作用:日后用户可以将自己应用打包成镜像,这样就可以让我们应用进行容器运行.还可以对官方镜像做扩展,以打包成我们生产应用的镜像。
Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,
因此每一条指令的内容,就是描述该层应当如何构建。
此处以定制 nginx 镜像为例,使用 Dockerfile 来定制。
2、dockerfile构建过程:
从基础镜像运行一个容器
执行一条指令,对容器做出修改
执行类似docker commit 的操作,提交一个新的镜像层
再基于刚提交的镜像运行一个新的容器
执行dockerfile中的下一条指令,直至所有指令执行完毕
基于Dockerfile构建镜像可以使用docker build命令。docker build命令中使用-f可以指定具体的dockerfile文件
如果不指定Dockerfile文件,Dockerfile的命名必须为Dockerfile。大小写不能变。
若不是这个名字,需要 -f 指定文件路径
[root@master01 kubernetes ]#docker build --help
-f, --file string Name of the Dockerfile (Default is ‘PATH/Dockerfile’)
典型用法
docker build -t ImageName:TagName dir
1
选项
-t 给镜像加一个Tag
ImageName 给镜像起的名称
TagName 给镜像的Tag名
Dir Dockerfile所在目录
根据目录下的 Dockerfile 文件构建镜像
例子
docker build -t chinaskill-redis:v1.1 .
1
chinaskill-redis 是镜像名
v1.1 是 tag 标签
. 表示当前目录,即Dockerfile所在目录
docker build -f dockerfiles/Dockerfile.debug -t myapp_debug .
在一个空白目录中,建立一个文本文件,并命名为 Dockerfile :
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile
其内容为:
FROM nginx RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
这个 Dockerfile 很简单,一共就两行。涉及到了两条指令, FROM 和 RUN 。
FROM 指定基础镜像
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。而 FROM 就是指定基础镜像,
因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。
在 Docker Store 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,
如nginx 、 redis 、 mongo 、mysql 等;也有一些方便开发、构建、运行各种语言应用的镜像,
如 node 、 openjdk 、 python 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。
如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,
如ubuntu 、 debian 、 centos 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。
除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch 。
这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
FROM scratch
如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。
不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,
比如 swarm 、 coreos/etcd 。对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,
所需的一切库都已经在可执行文件里了,因此直接 FROM scratch 会让镜像体积更加小巧。
使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,
这也是为什么有人认为 Go是特别适合容器微服务架构的语言的原因之一。
RUN 执行命令
RUN 指令是用来执行命令行命令的。由于命令行的强大能力, RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:
shell 格式: RUN <命令> ,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
3、dockerfile的规则
格式:
#为注释
指令(大写) 内容(小写)
尽管指令大小写不敏感,但是我们强烈建议指令用大写,内容用小写表示
docker是按顺序执行dockerfile里的指令集的(从上到下一次执行)
每一个dockerfile的第一个非注释行指令,必须是"FROM" 指令,用于为镜像文件构建过程中,指定基准镜像。后续的指令运行
于此基准镜像提供的运行环境中
实践中,基准镜像可以是任何可用镜像文件,
默认情况下docker build会在docker主机上(本地)查找指定的镜像文件,当其不存在时,则会从远端(Docker registry)上拉取所需镜像文件
MAINTAINER: 维护者信息
格式:
MAINTAINER
示例:
MAINTAINER Jasper Xu
MAINTAINER sorex@163.com
MAINTAINER Jasper Xu sorex@163.com
4、核心的dockerfile指令:
USER/WORKDIR指令
ADD/EXPOSE指令
RUN/ENV指令
CMD/ENTRYPOINT指令
1、USER/WORKDIR指令
USER指定进程使用哪个用户运行,WORKDIR指定进程的工作目录,进去docker时所在目录
[root@223 ~ ]# cat Dockerfile
FROM docker.io/littefun91/nginx_with_ailiyunyuan:v1.0
USER nginx
WORKDIR /usr/share/nginx/html
[root@223 ~ ]# docker run -it littlefun91/nginx_with_aliyunyuan:v1.0_with_user_workdir /bin/bash
nginx@9328a34b97cf:/usr/share/nginx/html$
nginx@9328a34b97cf:/usr/share/nginx/html$
nginx@9328a34b97cf:/usr/share/nginx/html$ whoami
nginx
nginx@9328a34b97cf:/usr/share/nginx/html$
nginx@9328a34b97cf:/usr/share/nginx/html$
nginx@9328a34b97cf:/usr/share/nginx/html$ pwd
/usr/share/nginx/html
切换到镜像中的指定路径,设置工作目录
在 WORKDIR 中需要使用绝对路径,如果镜像中对应的路径不存在,会自动创建此目录
一般用 WORKDIR 来替代 RUN cd && 切换目录进行操作的指令
WORKDIR 指令为 Dockerfile 中跟随它的任何 RUN、CMD、ENTRYPOINT、COPY、ADD 指令设置工作目录
如果 WORKDIR 不存在,即使它没有在任何后续 Dockerfile 指令中使用,它也会被创建
2、ADD/EXPOSE指令
ADD指令的功能是将主机构建环境(上下文)目录中的文件和目录、以及一个URL标记的文件 拷贝到镜像中。
其格式是: ADD 源路径 目标路径
把当前config目录下所有文件拷贝到/config/目录下
ADD config/ /config/
EXPOSE指令:指的是让容器哪个端口暴露出来
有如下注意事项:
1、如果源路径是个文件,且目标路径是以 / 结尾, 则docker会把目标路径当作一个目录,会把源文件拷贝到该目录下。
如果目标路径不存在,则会自动创建目标路径。
2、如果源路径是个文件,且目标路径是不是以 / 结尾,则docker会把目标路径当作一个文件。
如果目标路径不存在,会以目标路径为名创建一个文件,内容同源文件;
如果目标文件是个存在的文件,会用源文件覆盖它,当然只是内容覆盖,文件名还是目标文件名。
如果目标文件实际是个存在的目录,则会源文件拷贝到该目录下。 注意,这种情况下,最好显示的以 / 结尾,以避免混淆。
3、如果源路径是个目录,且目标路径不存在,则docker会自动以目标路径创建一个目录,把源路径目录下的文件拷贝进来。
如果目标路径是个已经存在的目录,则docker会把源路径目录下的文件拷贝到该目录下。
4、如果源文件是个归档文件(压缩文件),则docker会自动帮解压。
ADD指令可以让你使用URL作为参数。当遇到URL时候,可以通过URL下载文件并且复制到。
ADD http://foo.com/bar.go /tmp/main.go
COPY和ADD相似,但是功能少一些。
在Docker 1.0发布时候,包括了新指令COPY。不像是ADD,COPY 更加直接了当,只复制文件或者目录到容器里。
COPY不支持URL,也不会特别对待压缩文件。如果build 上下文件中没有指定解压的话,那么就不会自动解压,只会复制压缩文件到容器中。
COPY是ADD的一种简化版本,目的在于满足大多数人“复制文件到容器”的需求。
Docker 团队的建议是在大多数情况下使用COPY。拷贝文件的原则:使用COPY(除非你明确你需要ADD)
如果单纯复制文件,dockerfile推荐使用COPY
[root@220 ~ ]# ll
-rw-r–r-- 1 root root 59 Jun 9 10:46 Dockerfile
-rw-r–r-- 1 root root 2380 Jun 9 10:46 index.html
COPY…
COPY[“”…“”]
复制指令,从上下文目录中复制文件或者目录到容器里指定路径。
格式:
COPY [–chown=:] <源路径1>… <目标路径>
COPY [–chown=:] [“<源路径1>”,… “<目标路径>”]
[–chown=:]:可选参数,用户改变复制到容器内文件的拥有者和属组。
<源路径>:源文件或者源目录,这里可以是通配符表达式,其通配符规则要满足 Go 的 filepath.Match 规则。例如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。
例;替换/usr/share/nginx下的index.html
cd /root/dockerfile/test1
cat dockerfile
FROM centos
MAINTAINER xianchao
RUN yum install wget -y
RUN yum install nginx -y
COPY index.html /usr/share/nginx/html/
EXPOSE 80
ENTRYPOINT [“/usr/sbin/nginx”,“-g”,“daemon off;”]
[root@220 ~ ]# cat Dockerfile
FROM nginx
ADD index.html /usr/share/nginx/html/
EXPOSE 80
[root@220 ~ ]# docker build . -t nginx_with_expose:v2.0
Sending build context to Docker daemon 468.8MB
Step 1/3 : FROM nginx
—> 0e901e68141f
Step 2/3 : ADD index.html /usr/share/nginx/html/
—> ef87faa6f741
Step 3/3 : EXPOSE 80
—> Running in 617141822bba
Removing intermediate container 617141822bba
—> fd6a2aeaf727
Successfully built fd6a2aeaf727
Successfully tagged nginx_with_expose:v2.0
[root@220 ~ ]# docker run -itd -p 8865:80 nginx_with_expose:v2.0
[root@220 ~ ]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9a988daab135 nginx_with_expose:v2.0 “/docker-entrypoint.…” 3 seconds ago Up 2 seconds 0.0.0.0:8865->80/tcp, :::8865->80/tcp youthful_chandrasekhar
若是通过centos镜像,然后通过dockerfile安装nginx,默认nginx是没起来的
docker run通过/bin/bash 进入容器后。nginx进程是没起来的
需要进去容器,或者docker run时 使用nginx -g “daemon off” 启动nginx
3、RUN/ENV指令:
RUN是构建镜像时,执行的操作
ENV是将环境变量固化到镜像中
RUN:指定在当前镜像构建过程中要运行的命令
包含两种模式
1、Shell
RUN (shell模式,这个是最常用的,需要记住)
RUN echo hello
2、exec模式
RUN “executable”,“param1”,“param2”
RUN [“/bin/bash”,”-c”,”echo hello”]
等价于/bin/bash -c echo hello
[root@220 ~ ]# cat Dockerfile
FROM nginx
ENV jinghao=“jing_hao”
RUN touch $jinghao.sh
[root@220 ~ ]# docker build -t nginx_with_jinghao .
[root@220 ~ ]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx_with_jinghao latest 30a140de4242 4 seconds ago 142MB
[root@220 ~ ]# docker run -it nginx_with_jinghao:latest /bin/bash
root@6220e1fc4b7e:/# ls -l
-rw-r–r-- 1 root root 0 Jun 9 06:04 jing_hao.sh 这个是根据环境变量 run指令 创建的
root@6220e1fc4b7e:/# env
HOSTNAME=6220e1fc4b7e
PWD=/
PKG_RELEASE=1~bullseye
HOME=/root
NJS_VERSION=0.7.3
TERM=xterm
SHLVL=1
jinghao=jing_hao 这个就是固化进来的
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NGINX_VERSION=1.21.6
_=/usr/bin/env
[root@220 ~ ]# cat Dockerfile
FROM centos:7
ENV VER=“9.11.4”
RUN yum install -y bind-$VER
4、CMD/ENTRYPOINT指令:
使用CMD构建的镜像,当容器启动时就执行 CMD配置的命令。CMD是启动容器时执行的操作
cmd命令的三种格式
CMD [“executable”,“param1”,“param2”] (exec form, this is the preferred form)
CMD [“param1”,“param2”] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)
注意:如果在dockerfile里出现多个CMD,只有最后一个CMD会生效
CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。
[root@220 ~ ]# cat Dockerfile
FROM centos:7
RUN yum install -y httpd
CMD [“httpd”,“-D”,“FOREGROUND”] 容器启动时执行 httpd -D FOREGROUND 从而让httpd进程启动起来
[root@220 ~ ]# docker build . -t centos_with_httpd:v1.3
[root@220 ~ ]# docker run -itd -p 2256:80 centos_with_httpd:v1.3
[root@220 ~ ]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2848add746ea centos_with_httpd:v1.3 “httpd -D FOREGROUND” 4 seconds ago Up 3 seconds 0.0.0.0:2256->80/tcp, :::2256->80/tcp elastic_fermi
5、ENTRYPOINT指令:
如果容器启动时没带 CMD指令,默认会执行/entrypoint.sh
当然也可以手动编辑entrypoint.sh然后add到镜像中
/entrypoint.sh 必须带执行权限
[root@220 ~ ]# cat entrypoint.sh
#!/bin/bash
/sbin/nginx -g “daemon off;”
[root@220 ~ ]# chmod +x entrypoint.sh
[root@220 ~ ]# cat Dockerfile
FROM centos:7
ADD entrypoint.sh /
RUN yum install -y epel-release -q && yum install -y nginx
ENTRYPOINT /entrypoint.sh
[root@220 ~ ]# docker build . -t centos_with_nginx:v3
[root@220 ~ ]# docker run --rm -p 8898:80 centos_with_nginx:v3
进入容器查看:
[root@220 ~ ]# docker exec -it intelligent_benz /bin/bash
[root@eeb9d46f54f0 /]# ls -l
total 16
-rw-r–r-- 1 root root 12114 Nov 13 2020 anaconda-post.log
lrwxrwxrwx 1 root root 7 Nov 13 2020 bin -> usr/bin
drwxr-xr-x 3 root root 18 Jun 15 08:53 boot
drwxr-xr-x 5 root root 340 Jun 15 08:54 dev
-rwxr-xr-x 1 root root 41 Jun 15 08:52 entrypoint.sh 这个就是add进来的文件
当不指定CMD的时候,默认执行entrypoint.sh
CMD指令会被(run)运行时执行的命令替换掉,ENTRYPOINT 指令不会被替换掉
也可以直接跟指定的命令:
例;替换/usr/share/nginx下的index.html
cd /root/dockerfile/test1
cat dockerfile
FROM centos
MAINTAINER xianchao
RUN yum install wget -y
RUN yum install nginx -y
COPY index.html /usr/share/nginx/html/
EXPOSE 80
ENTRYPOINT [“/usr/sbin/nginx”,“-g”,“daemon off;”]
ENTRYPOINT
两种写法
#exec 格式
ENTRYPOINT [“executable”, “param1”, “param2”]
#shell 格式
ENTRYPOINT command param1 param2
重点
ENTRYPOINT 指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有其他传入值作为该命令的参数
ENTRYPOINT 的值可以通过 docker run --entrypoint 来覆盖掉
只有 Dockerfile 中的最后一条 ENTRYPOINT 指令会起作用
ENTRYPOINT 和 CMD 联合使用
当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令
换句话说实际执行时,会变成
“”
灵魂拷问
那么有了 CMD 后,为什么还要有 ENTRYPOINT 呢?这种 “” 有什么好处么?
CMD 和 ENTRYPOINT 区别
CMD # 指定这个容器启动的时候要运行的命令,不可以追加命令
ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令
啥意思?这其实也是 ENTRYPOINT 的应用场景之一,下面来看
测试 CMD
编写 dockerfile 文件
FROM centos
CMD [“ls”,“-a”]
构建镜像
docker build -f CMD.dockerfile -t test .
运行容器
docker run test
.
…
.dockerenv
bin
dev
etc
home
lib
lib64
lost+found
media
运行容器并追加命令
docker run test -l
docker: Error response from daemon: OCI runtime create failed: container_linux.go:380:
starting container process caused: exec: “-l”: executable file not found in $PATH: unknown.
看到可执行文件找不到的报错,executable file not found
跟在镜像名后面的是 command,运行时会替换 CMD 的默认值,因此这里的 -l 替换了原来的 CMD,而不是追加在原来的 ls -a 后面
而 -l 根本不是命令,所以自然找不到
如果想加入 -i 参数,必须重写 ls 命令
docker run test ls -a -l
total 56
drwxr-xr-x 1 root root 4096 Oct 28 09:36 .
drwxr-xr-x 1 root root 4096 Oct 28 09:36 …
-rwxr-xr-x 1 root root 0 Oct 28 09:36 .dockerenv
lrwxrwxrwx 1 root root 7 Nov 3 2020 bin -> usr/bin
drwxr-xr-x 5 root root 340 Oct 28 09:36 dev
drwxr-xr-x 1 root root 4096 Oct 28 09:36 etc
可以了,但这明显不是最优选择,ENTRYPOINT 就可以解决这个问题
测试 ENTRYPOINT
编写 dockerfile 文件
FROM centos
ENTRYPOINT [“ls”,“-a”]
构建镜像
docker build -f ENTRYPOINT.dockerfile -t test .
运行容器并追加命令
docker run test -l
total 56
drwxr-xr-x 1 root root 4096 Oct 28 09:38 .
drwxr-xr-x 1 root root 4096 Oct 28 09:38 …
-rwxr-xr-x 1 root root 0 Oct 28 09:38 .dockerenv
lrwxrwxrwx 1 root root 7 Nov 3 2020 bin -> usr/bin
drwxr-xr-x 5 root root 340 Oct 28 09:38 dev
drwxr-xr-x 1 root root 4096 Oct 28 09:38 etc
drwxr-xr-x 2 root root 4096 Nov 3 2020 home