第8章 Dockerfile自动构建
1.Dockerfile介绍
通过前面的练习我们已经掌握了手动制作镜像的方法,但是这种方法命令繁多,而且不能自动化,操作起来比较复杂。
实际上在企业中我们会使用Dockerfile来自动化构建镜像。
Dockerfile是一种可以被docker解释并执行的脚本,拥有自己固定的指令。
有了这个利器之后我们运维就可以解放双手了,只要编写好脚本就可以按照我们期望的结果构建相应的镜像。
如果需要更新,只需要修改Dockerfile里很少的代码就可以重复的构建。
2.Dockerfile操作命令说明
Docker通过对于在Dockerfile中的一系列指令的顺序解析实现自动的image的构建
通过使用build命令,根据Dockerfiel的描述来构建镜像
通过源代码路径的方式
通过标准输入流的方式
Dockerfile指令:
只支持Docker自己定义的一套指令,不支持自定义
大小写不敏感,但是建议全部使用大写
根据Dockerfile的内容顺序执行
FROM:
FROM {base镜像}
必须放在DOckerfile的第一行,表示从哪个baseimage开始构建
MAINTAINER:
可选的,用来标识image作者的地方
RUN:
每一个RUN指令都会是在一个新的container里面运行,并提交为一个image作为下一个RUN的base
一个Dockerfile中可以包含多个RUN,按定义顺序执行
RUN支持两种运行方式:
RUN <cmd> 这个会当作/bin/sh -c “cmd” 运行
RUN [“executable”,“arg1”,。。],Docker把他当作json的顺序来解析,因此必须使用双引号,而且executable需要是完整路径
RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 RUN command1 的执行仅仅是当前进程,一个内存上的变化而已,其结果不会造成任何文件。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。而如果需要将两条命令或者多条命令联合起来执行需要加上&&。如:cd /usr/local/src && wget xxxxxxx
CMD:
CMD的作用是作为执行container时候的默认行为(容器默认的启动命令)
当运行container的时候声明了command,则不再用image中的CMD默认所定义的命令
一个Dockerfile中只能有一个有效的CMD,当定义多个CMD的时候,只有最后一个才会起作用
CMD定义的三种方式:
CMD <cmd> 这个会当作/bin/sh -c "cmd"来执行
CMD ["executable","arg1",....]
CMD ["arg1","arg2"],这个时候CMD作为ENTRYPOINT的参数
EXPOSE 声明端口
格式为 EXPOSE <端口1> [<端口2>...]。
EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。
ENTRYPOINT:
entrypoint的作用是,把整个container变成了一个可执行的文件,这样不能够通过替换CMD的方法来改变创建container的方式。但是可以通过参数传递的方法影响到container内部
每个Dockerfile只能够包含一个entrypoint,多个entrypoint只有最后一个有效
当定义了entrypoint以后,CMD只能够作为参数进行传递
entrypoint定义方式:
entrypoint ["executable","arg1","arg2"],这种定义方式下,CMD可以通过json的方式来定义entrypoint的参数,可以通过在运行container的时候通过指定command的方式传递参数
entrypoint <cmd>,当作/bin/bash -c "cmd"运行命令
ADD & COPY:
当在源代码构建的方式下,可以通过ADD和COPY的方式,把host上的文件或者目录复制到image中
ADD和COPY的源必须在context路径下
当src为网络URL的情况下,ADD指令可以把它下载到dest的指定位置,这个在任何build的方式下都可以work
ADD相对COPY还有一个多的功能,能够进行自动解压压缩包
ENV:
ENV key value
用来设置环境变量,后续的RUN可以使用它所创建的环境变量
当创建基于该镜像的container的时候,会自动拥有设置的环境变量
WORKDIR:
用来指定当前工作目录(或者称为当前目录)
当使用相对目录的情况下,采用上一个WORKDIR指定的目录作为基准
USER:
指定UID或者username,来决定运行RUN指令的用户
ONBUILD:
ONBUILD作为一个trigger的标记,可以用来trigger任何Dockerfile中的指令
可以定义多个ONBUILD指令
当下一个镜像B使用镜像A作为base的时候,在FROM A指令前,会先按照顺序执行在构建A时候定义的ONBUILD指令
ONBUILD <DOCKERFILE 指令> <content>
VOLUME:
用来创建一个在image之外的mount point,用来在多个container之间实现数据共享
运行使用json array的方式定义多个volume
VOLUME ["/var/data1","/var/data2"]
或者plain text的情况下定义多个VOLUME指令
4.Dockerfile小试身手
构建思路:
1.先不要着急写Dockerfile,首先手动进入容器,然后正常执行安装步骤。并确保运行正常
2.收集好安装步骤的命令以及正确的配置文件
3.将收集的配置文件按照一定规范保存在相应的目录下
4.根据收集到的安装步骤编写Dockerfile
5.构建镜像并启动测试
小项目:使用Dockerfile构建Centos7+Nginx镜像
#1.创建目录
[root@docker-11 ~]# mkdir dockerfile
[root@docker-11 ~]# cd dockerfile/
[root@docker-11 ~/dockerfile]# mkdir nginx_base
[root@docker-11 ~/dockerfile]# cd nginx_base/
#2.准备文件
[root@docker-11 ~/dockerfile/nginx_base]# cat > local.repo << 'EOF'
[local]
name=local
enable=1
gpgcheck=0
baseurl=http://10.0.0.100
EOF
[root@docker-11 ~/dockerfile/nginx_base]# ll
total 8
-rw-r--r-- 1 root root 292 Jul 22 15:01 Dockerfile
-rw-r--r-- 1 root root 65 Jul 22 15:47 local.repo
#3.编写Dockerfile
[root@docker-11 ~/dockerfile/nginx_base]# cat > Dockerfile << 'EOF'
FROM centos:7
RUN rm -rf /etc/yum.repos.d/*
ADD local.repo /etc/yum.repos.d/local.repo
RUN yum makecache fast \
yum install nginx -y \
yum clean all
EXPOSE 80
CMD ["nginx","-g","daemon off;"]
EOF
#4.构建镜像
[root@docker-11 ~/dockerfile/nginx_base]# cat build.sh
#!/bin/bash
docker build -t centos7_nginx:1.20 .
[root@docker-11 ~/dockerfile/nginx_base]# bash build.sh
#5.启动测试
[root@docker-11 ~/dockerfile/nginx_base]# docker run -d -p 10.0.0.11:80:80 centos7_nginx:1.20
[root@docker-11 ~/dockerfile/nginx_base]# curl -I 10.0.0.11
5.Dockerfile分阶段构建
Dockerfile 的分阶段构建(Multi-stage builds)是一种优化 Docker 构建过程的方法,特别适用于减小最终镜像的大小、简化构建过程和减少安全隐患。这种方法允许您在一个 Dockerfile 中使用多个 FROM 指令来创建多个构建阶段。每个阶段都可以使用不同的基础镜像,并且您可以从一个阶段选择性地复制文件到另一个阶段,这意味着您可以在一个阶段编译和构建应用程序,然后在另一个轻量级的阶段中运行它,从而丢弃构建阶段中不需要的所有额外文件和依赖项。
优点
- 减小镜像大小:由于最终镜像不包含用于构建应用程序的临时文件和工具,因此镜像大小会大大减小。
- 安全性增强:减少了最终镜像中不必要的软件和依赖项,降低了潜在的安全风险。
- 构建速度提升:在构建过程中可以利用缓存,如果初步阶段没有变化,它们可以被重用,加快构建速度。
- 简化构建逻辑:可以在单个 Dockerfile 中管理整个应用的构建和部署过程,避免了多个 Dockerfile 或脚本的复杂性。
dockerfile:
FROM maven:3-openjdk-8
COPY settings.xml /usr/share/maven/conf/settings.xml
ADD hello-world-war.tar.gz /opt/
WORKDIR /opt/hello-world-war
RUN mvn clean package
FROM tomcat_jdk_221:8.5.50
COPY --from=0 /opt/hello-world-war/target/hello-world-war-1.0.0.war /opt/tomcat/webapps/hello-world.war
EXPOSE 8080
CMD ["/opt/tomcat/bin/catalina.sh","run"]
清理分阶段构建产生的中间镜像:
docker image prune -a
6.Dockerfile构建最佳实践
避免安装不必要的软件包
只安装容器必须的软件,不是必须要的不要安装,比如一些工具类的命令,wget net-tools等
安装完成后清除缓存
安装完成后我们可以把不用的压缩包以及软件缓存等文件删除掉,以减少镜像的体积
一个容器内不要跑太多的应用
Docker倡导一个容器应该只关注一件事,应该保证一个容器只有一个进程。
但是这也不是硬性要求,比如nginx+php+mysql的应用,那么我们也可以将nginx+php放在一个容器里,mysql单独放一个容器。
但是我们还是希望一个容器专注干一件事,尽量保持干净和模块化。
最小化镜像层数
Dockerfile里每条RUN,COPY,ADD指令都会创建指令层,所以我们可以将多条RUN命令尽可能的合并成一个RUN指令,这样就减少了构建镜像的层数。
增加可读性
我们刚才说了可以讲多个RUN指令合并成一个RUN指令,但是这样做可能会导致RUN指令很长很长,那么我们可以像shell命令那样使用\来换行,增加可读性,例如:
RUN yum install git \
wget \
net-tools \
tree
使用supervisor控制多个进程
supervisor介绍:
supervisor是一款可以控制/监控多个进程运行的软件
当supervisor监控的服务进程意外退出时,supervisor可以自动重启服务进程
但是有个前提,由supervisor控制的进程必须是前台启动的,否则会启动失败
在容器中使用时supervisor自己本身可以修改配置文件设置为前台启动
supervisor配置解释:
[program:服务名称]
command=nginx -g 'daemon off;' #服务前台运行命令
autostart=true #启动supervisor立刻启动服务进程
autorestart=true #如果服务进程意外退出,由supervisor重新拉起
startsecs = 5 #启动5秒没退出,就认为程序启动正常,默认1秒
startretries=3 #启动失败自动重试次数,默认是3
user=nginx #启动进程的用户,默认root
redirect_stderr = true #记录错误日志
stdout_logfile_maxbytes = 20MB #日志最大大小,默认50M
stdout_logfile_backups = 20 #日志文件备份数量,默认10个
stdout_logfile = /var/log/supervisor/nginx.log #日志路径
举例:
#1.安装软件
yum -y install supervisor
#2.编写进程配置文件
[root@b7757c17bf36 ~]# cat /etc/supervisord.d/nginx_php.ini
[program:nginx]
command=nginx -g 'daemon off;'
autostart=true
autorestart=true
startsecs = 5
redirect_stderr = true
stdout_logfile_maxbytes = 20MB
stdout_logfile_backups = 20
stdout_logfile = /var/log/supervisor/nginx.log
[program:php-fpm]
command=php-fpm -c /etc/php.ini -y /etc/php-fpm.conf
autostart=true
autorestart=true
startsecs = 5
redirect_stderr = true
stdout_logfile_maxbytes = 20MB
stdout_logfile_backups = 20
stdout_logfile = /var/log/supervisor/php-fpm.log
#3.修改supervisord配置文件放在前台启动
sed -i "s#nodaemon=false#nodaemon=true#g" /etc/supervisord.conf
#4.启动supervisord程序
supervisord -c /etc/supervisord.conf
#5.使用命令
supervisorctl update
supervisorctl status
supervisorctl start nginx
supervisorctl restart nginx
supervisorctl stop nginx
6.使用Dockerfile创建云盘镜像
基于Dockerfile构建云盘镜像
1.先创建目录
[root@docker-11 ~]# cd dockerfile/
[root@docker-11 ~/dockerfile]# ls
nginx_base
[root@docker-11 ~/dockerfile]# mkdir kod
[root@docker-11 ~/dockerfile]# cd kod/
[root@docker-11 ~/dockerfile/kod]#
2.收集配置文件
mkdir conf
cd conf
cat > local.repo << EOF
[local]
name=local
enable=1
gpgcheck=0
baseurl=http://10.0.0.100
EOF
docker cp b7757c17bf36:/etc/php-fpm.d/www.conf .
docker cp b7757c17bf36:/etc/nginx/conf.d/cloud.conf .
docker cp b7757c17bf36:/etc/supervisord.conf .
docker cp b7757c17bf36:/etc/supervisord.d/nginx_php.ini .
3.准备代码目录
mkdir code/
cd code
docker cp b7757c17bf36:/code/ .
tar zcvf code.tar.gz code
4.编写Dockerfile
FROM centos:7
RUN rm -rf /etc/yum.repos.d/*
ADD conf/local.repo /etc/yum.repos.d/local.repo
RUN yum install nginx php-fpm php-mbstring php-gd supervisor -y
ADD conf/www.conf /etc/php-fpm.d/www.conf
ADD conf/cloud.conf /etc/nginx/conf.d/cloud.conf
ADD conf/supervisord.conf /etc/supervisord.conf
ADD conf/nginx_php.ini /etc/supervisord.d/nginx_php.ini
ADD code/code.tar.gz /
RUN chown -R nginx:nginx /code/
EXPOSE 80
CMD ["supervisord","-c","/etc/supervisord.conf"]
5.构建镜像
docker build -t kod:v2 .
6.启动容器测试
docker run -d --name kod_v1 -p 8080:80 kod:v1
8.构建Tomcat镜像
8.1 构建基础CentOS7镜像
#1.基础镜像需要的操作
安装网络工具包
配置yum源
更改时区
#2.创建目录
mkdir centos7
cd centos7/
#3.准备配置文件
[root@docker-11 centos7]# ll
总用量 16
-rw-r--r-- 1 root root 1759 7月 22 21:02 CentOS-Base.repo
-rw-r--r-- 1 root root 664 7月 22 21:02 epel.repo
#4.编写dockerfile
FROM centos:7
RUN rm -f /etc/yum.repos.d/*
ADD CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo
ADD epel.repo /etc/yum.repos.d/epel.repo
ADD supervisord.conf /etc/supervisord.conf
RUN yum install net-tools bash-completion supervisor -y \
&& yum clean all \
&& rm -f /etc/localtime \
&& ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& groupadd -g 1000 www \
&& useradd -u 1000 -g 1000 www -M -s /sbin/nologin
#5.构建命令
docker build -t centos7_base:v1 .
8.2 构建基础JDK镜像
#1.创建目录
mkdir dockerfile/jdk/
#2.准备配置文件
[root@docker-11 ~]# cd dockerfile/jdk/
[root@docker-11 jdk]# docker cp 28e7ff621f8c:/etc/profile .
[root@docker-11 jdk]# vim profile
[root@docker-11 jdk]# tail -4 dockerfile/jdk/profile
export JAVA_HOME=/opt/jdk
export TOMCAT_HOME=/opt/tomcat
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$TOMCAT_HOME/bin:$PATH
export CLASSPATH=.$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar
#3.将jdk上传到指定目录
[root@docker-11 ~]# tree dockerfile/
dockerfile/
└── jdk
├── jdk-8u60-linux-x64.tar.gz
└── profile
#4.编写Dockerfile
[root@docker-11 jdk]# cat Dockerfile
FROM centos7_base:v1
ADD jdk-8u60-linux-x64.tar.gz /opt/
ADD profile /etc/profile
RUN ln -s /opt/jdk1.8.0_60 /opt/jdk && \
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'Asia/Shanghai' > /etc/localtime
ENV JAVA_HOME /opt/jdk
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/:$JRE_HOME/lib/
ENV PATH $PATH:$JAVA_HOME/bin
#5.编写构建命令脚本
[root@docker-11 jdk]# cat > /root/dockerfile/jdk/build.sh << 'EOF'
#!/bin/bash
docker build -t centos7_jdk:8u60 .
EOF
[root@docker-11 jdk]# bash build.sh
#6.查看构建后的镜像
[root@docker-11 jdk]# docker images|grep jdk
centos7_jdk 8u60 58a880ff9253 19 seconds ago 569MB
#7.使用jdk镜像启动容器测试
[root@docker-11 jdk]# docker run -it --rm centos7_jdk:8u60 java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
8.3 构建Tomcat镜像
#1.创建目录
[root@docker-11 ~]# mkdir dockerfile/tomcat
#2.上传压缩包
[root@docker-11 ~]# cd dockerfile/tomcat
[root@docker-11 tomcat]# wget https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.5.69/bin/apache-tomcat-8.5.69.tar.gz
#3.编写Dockerfile
[root@docker-11 tomcat]# cat > Dockerfile << 'EOF'
FROM centos7_jdk:8u60
ADD apache-tomcat-8.5.69.tar.gz /opt/
RUN ln -s /opt/apache-tomcat-8.5.69 /opt/tomcat
EOF
#4.编写构建命令脚本
[root@docker-11 jdk]# cat > /root/dockerfile/jdk/build.sh << 'EOF'
#!/bin/bash
docker build -t tomcat_base:8.5.69 .
EOF
[root@docker-11 tomcat]# tree
.
├── apache-tomcat-8.5.69.tar.gz
├── build.sh
└── Dockerfile
#5.构建镜像
[root@docker-11 tomcat]# bash build.sh
#6.测试访问
[root@docker-11 tomcat]# docker run -d -it tomcat_base:8.5.69 /bin/bash
[root@docker-11 tomcat]# docker exec -it b2011b0b3eb3 /bin/bash
[root@b2011b0b3eb3 /]# /opt/tomcat/bin/catalina.sh start
8.4 构建业务镜像
#1.创建目录
[root@docker-11 ~]# mkdir dockerfile/webapp1
#2.编写业务文件
[root@docker-11 ~]# cd dockerfile/webapp1
[root@docker-11 webapp1]# mkdir app
[root@docker-11 webapp1]# echo "V1" > app/index.jsp
[root@docker-11 webapp1]# tar zcf app.tar.gz app/
#3.修改tomcat配置文件
[root@docker-11 webapp1]# docker cp b2011b0b3eb3:/opt/tomcat/conf/server.xml .
[root@docker-11 webapp1]# vim server.xml
<Host name="localhost" appBase="/opt/tomcat/webapps"
#4.编写supervisor配置文件
[root@docker-11 webapp1]# cat tomcat.ini
[program:tomcat]
command=/opt/tomcat/bin/catalina.sh run
autostart=true
autorestart=true
startsecs = 5
redirect_stderr = true
stdout_logfile_maxbytes = 20MB
stdout_logfile_backups = 20
stdout_logfile = /var/log/supervisor/tomcat.log
#5.编写Dockerfile文件
[root@docker-11 webapp1]# cat Dockerfile
FROM tomcat_base:8.5.69
ADD tomcat.ini /etc/supervisord.d/tomcat.ini
ADD server.xml /opt/tomcat/conf/server.xml
ADD app.tar.gz /opt/tomcat/webapps/
EXPOSE 8080
CMD ["supervisord","-c","/etc/supervisord.conf"]
#6.编写构建脚本
[root@docker-11 webapp1]# cat > build.sh << 'EOF'
#!/bin/bash
docker build -t tomcat_app:v1 .
EOF
#7.运行容器测试
docker run -d -it -p 8080:8080 tomcat_app:v1