环境:centos7.9 docker version 20.10.14
如何构建一个镜像?我们知道,构建镜像一般有两种方法:
1、手动修改容器内容,比如安装一个工具等等,然后使用docker commit 容器id 镜像:tag
来创建新的镜像
2、通过在Dockerfile
中定义一系列的指令和参数来构建镜像,dockerfile
是一个包含用于组合镜像的命令的文本文档,可以使用在命令行中调用任何命令, 然后docker通过读取Dockerfile中的指令自动生成镜像,docker build
命令用于从Dockerfile
构建镜像。
Dockerfile 一般分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。
注意:Dockerfile文件必须以Dockerfile为文件名字,这命名是有特殊含义的,后面会讲到。
Dockerfile的指令与两种书写形式,一种是shell格式,另一种是exec格式。
FROM mysql:5.6 #FROM指令定义基础镜像,FROM指令必须是第一个指令
MAINTAINER Benjamin <qq.com> #MAINTAINER指令指定维护者信息,此指令为可选
ENV JAVA_HOMR="/usr/local/java" #ENV定义环境变量,后续指令中可以通过$JAVA_HOME来引用环境变量,ENV定义的变量可以在容器中使用
ARG name="xiaolming" #ARG也是定义环境变量,与ENV不同的是,ARG定义的环境变量只能在Dockerfile中使用,在容器中不存在
RUN yum install -y httpd #RUN指令用于运行一个命令,如使用yum安装httpd服务
COPY /file*.txt /file/ #COPY用于复制宿主机文件到容器指定目录,COPY支持通配符
ADD hello-world /app/ #ADD也是用于复制宿主机文件到容器指定目录,ADD也支持通配符,与COPY不同的是,ADD会自动解压文件
WORKDIR /app/ #WORKDIR指令用于用于设置当前工作目录
USER root #USER指令用于切换用户
VOLUME /data #VOLUME指令用于定义匿名卷,表示将容器内的/data目录挂载为匿名卷到宿主机上
EXPOSE 80 8089/udp #EXPOSE用于声明容器端口,如果未指定协议,则默认为TCP,仅仅是声明,并没有绑定宿主机端口映射
CMD ["nginx", "-g", "daemon off;"] #CMD指令的作用是指定容器主进程的默认启动指令
ENTRYPOINT ["/usr/sbin/nginx","-g","daemon off;"] #ENTRYPOINT指令也是指定容器主进程的默认启动指令,但与CMD有区别。
各个指令详细介绍如下:
FROM mysql:5.6
说明:FROM指令用来指定基础镜像信息,FROM必须为第一个指令,tag是可选的,如果不写tag,会使用latest版本的基础镜像,alpine、busybox镜像
一般可以作为基础镜像。
MAINTAINER Benjamin Yang <[email protected]>
说明:MAINTAINER 指令用来指定维护者信息,告诉别人谁负责维护它,当然你可以不指定维护者信息。
ENV <key> <value>
ENV <key>=<value>...
ENV sex "man"
ENV name="xiaolming"
说明:ENV指令用于定义环境变量,定义好后的变量,在后续的指令中就可以直接使用 $name的形式引用环境变量,同时,ENV定义的变量即能在Dockerfile中使用,还能在容器中使用。
ARG sex "man"
ARG name="xiaolming"
说明:ARG指令也是用于定义环境变量,与ENV定义环境变量相比,ARG定义的环境变量只能在Dockerfile中使用,换句话说,ARG所设置的构建环境的环境变量在将来容器运行时是不会存在的。
RUN <command> (shell格式,该指令在shell中运行,默认情况下/bin/sh -c在Linux运行)
RUN ["executable", "param1", "param2"](exec格式)
RUN ["yum", "install", "httpd"]
RUN yum install -y httpd && yum -y install wget
说明:RUN指令用来指定操作指令,比如yun安装一个依赖、软件等等,RUN指令有两种方式:一种是直接shell执行,另外一种是exce
注意,每执行一个RUN指令都会生成一层镜像,RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指
定--no-cache参数。
我们为了减少RUN生成的镜像层,可以使用&&符号来将多个RUN指令合起来。
COPY <src>... <dest>
COPY ["" , ... "" ]
COPY hello-world /app/ #注意:目标路径/app/一定要加斜杠,不然就表示重命名hello-world文件根app文件了,/app/不存在则自动创建目录
COPY /file*.txt /file/ #COPY支持通配符匹配文件
说明:COPY指令用于复制宿主机文件到容器内指定路径,COPY指令复制tar,tar.gz等格式的文件时,并不会自动解压tar、tar.gz等压缩包,仅复制文
件而已。COPY复制文件时会保留文件的元属性,如文件属组,创建时间,读、写、执行权限等元数据。
ADD <src>... <dest>
ADD hello-world /app/
ADD https://cdn.mysql.com/archives/mysql-8.0/mysql-8.0.25-linux-glibc2.12-i686.tar.xz /home/mysql/
说明:ADD指令也是复制宿主机文件到容器内指定路径,但ADD比COPY更高级,ADD指令复制tar,tar.gz,gzip,bzip2以及xz的情况下等格式的文件时,
会自动解压压缩包文件,同时ADD指令的<源路径>可以是一个 URL,Docker引擎会试图去下载,然后放置到 <目标路径>。下载后的文件权限自动设置为600
,如果需要更改权限,需要再增加一层RUN进行权限修改。如果下载的是一个压缩包,一般情况是 RUN 指令,然后使用wget或curl去下载,然后更改权
限、解压、清理下载的源文件。
WORKDIR <dir>
WORKDIR /app/
说明:WORKDIR目录用于设置当前工作目录,ORKDIR /app/就表示进入/app/目录,如果目录不存在则自动创建目录
USER root
说明:USER指令用于切换用户执行后续操作。
VOLUME /data
说明:VOLUME指令用于定义匿名卷,表示将容器内的/data目录挂载为一个匿名卷到宿主机上,这个匿名卷卷名由docker自动生成;
ps: 一般不会在 Dockerfile 中用到VOLUME指令,我们更常见的还是在 docker run 的时候指定 -v 参数来指定挂载的数据卷。
EXPOSE <port> [<port>/<protocol>...]
EXPOSE 80
EXPOSE 80/tcp 8089/udp
说明:EXPOSE指令告诉Docker容器在运行时监听指定的网络端口。如果未指定协议,则默认为TCP。
EXPOSE指令仅仅是声明运行时容器打算使用什么端口,并不会自动在宿主进行端口映射。
CMD指令有三种格式:
CMD ["executable","param1","param2"] (exec格式,一般推荐使用的格式)
CMD ["param1","param2"] (参数列表格式)
CMD command param1 param2 (shell格式)
CMD nginx -g "daemon off;"
CMD ["nginx", "-g", "daemon off;"]
说明:CMD指令的作用是指定容器主进程的默认启动指令。同时,如果在docker run运行时指定了新的指令行参数则会覆盖这个CMD默认指令。
ENTERYPOINT指令格式:
ENTERYPOINT command (shell模式)
ENTERYPOINT ["executable","param1","param2"] (exec模式)
ENTRYPOINT ["/usr/sbin/nginx","-g","daemon off;"]
ENTERYPOINT不会被docker run 的指令行参数指定的指令所覆盖,而且这些指令行参数会被当作参数送给 ENTERYPOINT 指令指定的程序,
但是,如果运行 docker run 时使用了--entrypoint 选项,将覆盖 entrypoint 指令指定的程序,
使用ENTERYPOINT的优点是:在执行 docker run 的时候可以指定ENTRYPOINT 运行所需的参数。
注意:如果Dockerfile中存在多个ENTRYPOMIT指令,仅最后一个生效。
下面通过编写一个简单的Python网站程序,再编写Dockerfile文件来构建镜像,运行镜像,如下:
注意:Dockerfile文件必须以Dockerfile为文件名字。
[root@node2 ~]# mdkir dockerfile #先创建一个目录
[root@node2 ~]# cd dockerfile #进入目录
[root@node2 dockerfile]# docker pull centos:7.8.2003 #下载一个centos7版本的基础镜像
[root@node2 dockerfile]# vim my_website.py #编写我们的网站python程序
#coding:utf8
from flask import Flask
app=Flask(__name__)
@app.route('/')
def hello():
return "Being single is better than being in an unfaithful relationship."
if __name__=="__main__":
app.run(host='0.0.0.0',port=8080) #网站对外暴露的是8080端口
[root@node2 dockerfile]# vim Dockerfile #编写dockerfile文件,文件名是固定,必须是Dockerfile
FROM centos:7.8.2003 #FROM指定基础镜像为centos:7.8.2003
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo #下载yum源
RUn curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo #下载epel源
RUN yum makecache #建立缓存
RUN yum -y install python3-devel python3-pip #安装python环境
RUN pip3 install flask #安装flask
COPY my_website.py /opt/ #把我们的python程序复制到容器的/opt目录
WORKDIR /opt/ #指定当前工作目录是/opt
EXPOSE 8080 #对外暴露端口为8080,因为我们python程序就是8080端口
CMD ["python3","my_website.py"] #容器启动时运行python程序
[root@node2 dockerfile]#
#构建镜像,--no-cache表示不缓存,镜像是分层的,如果已经构建过了则docker会使用缓存,这里使用--no-cache表示不使用缓存
# -t 指定镜像名称和版本,如果不指定-t ,则构建出来的镜像没有名称和版本号
# 最后一个点.表示当前目录,docker build指令会默认寻找当前目录下名叫Dockerfile的文件进行构建镜像
[root@node2 dockerfile]# docker build --no-cache -t my_website:1.1.1 .
#看到下面的Successfully 表示镜像构建成功
Successfully built e72b9af57bca
Successfully tagged my_website:1.1.1
[root@node2 dockerfile]# docker images my_website #查看镜像
REPOSITORY TAG IMAGE ID CREATED SIZE
my_website 1.1.1 e72b9af57bca 6 seconds ago 1.05GB
[root@node2 dockerfile]# docker run -d -it --name my_website -p 80:8080 my_website:1.1.1 #启动并运行my_website:1.1.1镜像
#查看my_website容器状态,已经是启动了,对外暴露80端口,容器内部端口是8080
[root@node2 dockerfile]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1a0f7c714c4b my_website:1.1.1 "python3 my_website.…" 18 minutes ago Up 18 minutes 0.0.0.0:80->8080/tcp, :::80->8080/tcp my_website
验证访问:
[root@node2 ~]# curl http://192.168.44.135/ #访问网站,已经能正确访问了,谷歌浏览器访问也正常
Being single is better than being in an unfaithful relationship.
[root@node2 ~]#