dockerfile学习
参考资料
思考
在Dockerfile里面RUN命令,那么复杂,为什么很多人都没有将其专门放到一个build.sh脚本中,然后执行呢?而都是用 \ &&来续行?原因是什么?访问不到环境变量?构建参数?
Demo
springcloud
FROM 10.131.9.12:5000/base/jdk8-redis:1.0.0
RUN echo "Asia/Shanghai" > /etc/timezone && rm -rf /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY pro.jar /work/pro.jar
ADD run.sh /
RUN chmod +x /run.sh
CMD ["/run.sh"]
FROM 指定基础镜像,必须是第一条指令,10.131.9.12:5000/base/jdk8-redis 表示具体的位置,1.0.0即tag标签。一般指定公有的源,上面是私有源的例子。
RUN 运行命令。 利用斜杆、&& 拼接多条指令。
1, shell 格式: RUN <命令>,就像直接在命令行中输入的命令一样
2, exec 格式:RUN [“可执行文件”,“参数1”,“参数2”],更像是函数调用中的格式
每一个RUN命令都会在 docker镜像中新建一层,所以应该尽量少用 RUN 命令,而且要在RUN 的最后要做必要的清除工作
COPY 复制文件。当前位置的文件,复制到镜像中的位置。
COPY 会将原文件的各种数据都保留,比如 读、写、执行权限,可以通过 –chown=
: 选项来改变文件的所属用户及所属组。
思考:/work/pro.jar,如果/work目录不存在,是否会报错?如果拷贝的文件不存在,是否会报错?如果镜像中,已经有该文件,是强制替换还是提醒、报错?
答疑:1、对于基础镜像,如果没有该目录,COPY的时候,镜像会自动增加该目录。(已测,但权限可能保留原有的,最好增加修改权限的指令)。2、同名文件,会覆盖,不提示。但是为了确保安全,最好先删除。3、复制的源文件,如果不存在,则会报错。
ADD (复制?拷贝?)
高级版的COPY,会下载、会解压(tar.gz tar.xz等格式)。
和 COPY 指令的功能,性质基本一致 。但是针对url、tar包,会自动处理。如: 原路径为 tar 压缩包,如果压缩文件格式为 gzip , bzip2 以及 xz 的情况下,ADD 指令将自动解压这个压缩文件到 <目标路径> 去,只有此种情况适合使用 ADD 指令。
CMD 指定运行的命令。格式同RUN。数组,一个数组即代表一条完整的指令。[“命令”,”参数1”,”参数2”,”参数3”]。将所有打包时需要运行的命令组合,放到run.sh文件中,+x使该文件具有可执行权限,然后运行。CMD与RUN区别: RUN命令在 image 文件的构建阶段执行,执行结果都会打包进入 image 文件;CMD命令则是在容器启动后执行。另外,一个 Dockerfile 可以包含多个RUN命令,但是只能有一个CMD命令。
命令格式也有两种,同RUN。 CMD 指令用于指定默认的容器主进程的启动命令。CMD可以启动多个进程,但是必须要有一个在前台。
思考:CMD命令如何含义?是指每次镜像启动时,都要运行的指令吗?另外,CMD命令,能否有多个?
run.sh文件如下:
#!/bin/sh
cd /work
if [ "${SW_AGENT_NAME}" != "" ] ; then
java -javaagent:/work/agent/skywalking-agent.jar -jar $JAVA_OPT pro.jar
else
java -jar $JAVA_OPT pro.jar
fi
weblogic-nginx
Dockerfile如下:
FROM 10.131.9.12:5000/base/weblogic-redis-nginx-autodeploy-apm:2.0.0
COPY nginx.conf /etc/nginx
ENV LANG zh_CN.utf8
WORKDIR /u01/oracle/autodeploy
ADD 项目目录 ./项目目录
ADD ./resouceDefine.py .
CMD nginx && ./startup.sh
ENV指定环境变量。
如:ENV JENKINS_HOME /opt/jenkins/data
RUN mkdir -p $JENKINS_HOME/plugins
WORKDIR指定工作目录。此设置会影响到?复制文件?运行文件?
CMD启动nginx,并运行startup.sh脚本(可能在镜像中已经存在),
思考:也可以不用数组?两种形式实际有如何区别?
php73
Dockerfile如下:
FROM 10.131.9.12:5000/base/rhel_php:php73-base
RUN rm -rf /yd/* && yum install -y yd-php73-apm-4.1.7 php73-soap QConf php73-qconf nscd
COPY nginx.conf /usr/local/nginx/conf/nginx.conf
COPY php.ini /usr/local/php/etc/php.ini
COPY httpd.conf /etc/httpd24/httpd.conf
ADD yd /yd
RUN chown -R www:www /yd/*
EXPOSE 80
ADD run.sh /
CMD ["/run.sh"]
FORM 公司内部,基础镜像。版本:Red Hat Enterprise Linux Server release 6.7. (cat /etc/redhat-release 而ubuntu cat /etc/os-release)
RUN 2 先删除原有yd下的文件 ,删除成功后,才会安装相应的php配置。
思考:yum源内部的?
ADD 增加yd文件夹内容
RUN 7 递归更改新增加的内容所有者的权限。
EXPOSE 对外暴露80端口 【实际上基础镜像已经暴露过此端口】
最后两条ADD CMD,增加run.sh文件,但是未为该文件增加可执行权限?
思考:会报错吗? CMD命令会继承或覆盖吗?
其他:
php-apm插件
run.sh文件如下:
#!/bin/sh
echo "Asia/Shanghai" > /etc/timezone
rm -rf /etc/localtime
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
if [ $APP_PORT ];then
sed -i "s/APP_PORT/$APP_PORT/g" /usr/local/nginx/conf/nginx.conf
else
sed -i "s/APP_PORT/80/g" /usr/local/nginx/conf/nginx.conf
fi
if [ $YD_SESSION_REDIS_PORT ];then
sed -i "1416c session.save_handler = redis" /usr/local/php/etc/php.ini
sed -i "1417a session.save_path = \"$YD_SESSION_REDIS_PORT\"" /usr/local/php/etc/php.ini
else
echo "Session save files"
fi
if [ $YD_PHP_APM ];then
echo 'php_admin_value auto_prepend_file "/etc/apm.php"' >> /etc/httpd24/httpd.conf
echo "extension=tideways.so" >> /usr/local/php/etc/php.ini
echo "tideways.sample_rate=100" >> /usr/local/php/etc/php.ini
echo "tideways.auto_prepend_library=0" >> /usr/local/php/etc/php.ini
else
echo "NO PHP APM";
fi
touch /var/log/app.log
chown -R www:www /var/log/app.log
echo success > /var/log/app.log
#find /yd -name ".*" | xargs rm -rf
/usr/local/nginx/sbin/nginx
/usr/local/apache/bin/httpd -DFOREGROUND
注意:run.sh文件最后两行,启动了nginx跟httpd服务。CMD启动的进程,可以启动多个进程,但是必须要有一个进程在前台。最起码Pod是这样要求,否则会陷入无限的重启中。
作用:设置时区。根据环境变量$APP_PORT来设置nginx端口,默认80端口。 设置php中session保存位置 $YD_SESSION_REDIS_PORT。$YD_PHP_APM变量? 创建app.log日志文件,写入成功命令。开启nginx http服务。
备注:sed操作
# 全局替换,将a替换为b。g表示全局替换 -i 表示保存文件
sed -i "s/a/b/g" test.txt
# 指定行数修改,空格分隔(或多个)
sed -i "5c helloword" a.txt
# 增加新行,即在第5行后增加。
sed -i "5a zhangsan" a.txt
# 根据文件夹的后缀来替换pro.jar
sed -i "s/pro.jar/`ls |grep.sh$`/g" run.sh
# 打印5到10行的内容 -n参数 表示仅显示处理后的结果。
sed -n "5,10p" a.txt
# 查找指定内容,进行替换 \n插入新行。
# 如果一行匹配到两处,也仍然只插入一次。 /se/d 匹配到则删除。
sed -i '/se/a oneoneone\ntwotwotwo' test.txt
nginx配置如下:
user www;
worker_processes auto;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 10240;
}
http {
include mime.types;
default_type application/octet-stream;
access_log off;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
gzip on;
gzip_min_length 4096;
gzip_buffers 4 8k;
gzip_types text/* text/css application/javascript application/x-javascript;
gzip_comp_level 1;
gzip_vary on;
gzip_http_version 1.1;
upstream php {
server 127.0.0.1:81;
}
server {
listen 80;
server_name localhost;
access_log /dev/null;
error_log /dev/null;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root /yd;
index index.php index.html index.htm;
if (!-e $request_filename) {
proxy_pass http://php;
}
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ \.php$ {
proxy_set_header Host $host:APP_PORT;
#proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:81;
}
}
}
nginx实际上,代理80端口,静态文件直接处理,php请求通过负载均衡交由apache处理(81端口),或者检测到.php扩展名,通过代理proxy_pass传递到81端口。在docker镜像中,整体只对外暴露80端口。
注意:APP_PORT在启动的时候,会被run.sh替换成具体的端口。
打包
有了Dockerfile之后,便可以打包。手动打包命令,
- 方式一:
docker build -t runoob/ubuntu:v1 .
-t 指定标签,标签内容runoob/ubuntu:v1
. 指定当前打包的目录。
- 方式二:
docker build -f /path/to/a/Dockerfile .
-f 指定Dockerfile所在目录
- 方式三:
docker build github.com/creack/docker-firefox
直接从git仓库打包。实际上该仓库,主要包含一个Dockerfile文件。
Dockerfile指令
如上文例子,打包指令大、小写不分区。默认全部大写,以示区分。可以使用#号进行注释。#注释,必须顶行写,否则会认为命令内容。
FROM
指定基础源。
VOLUME
指定卷标。参见 https://www.jianshu.com/p/ef0f24fd0674
在Dockerfile中,我们也可以使用VOLUME指令来申明contaienr中的某个目录需要映射到某个volume:
VOLUME /foo
这表示,在docker运行时,docker会创建一个匿名的volume,并将此volume绑定到container的/foo目录中,如果container的/foo目录下已经有内容,则会将内容拷贝的volume中。也即,Dockerfile中的VOLUME /foo与docker run -v /foo alpine的效果一样。
Dockerfile中的VOLUME使每次运行一个新的container时,都会为其自动创建一个匿名的volume,如果需要在不同container之间共享数据,那么我们依然需要通过docker run -it -v my-volume:/foo的方式将/foo中数据存放于指定的my-volume中。
ADD
如下,一个报错提醒。
When using ADD with more than one source file, the destination must be a directory and end with a /
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
增加chown参数,避免再执行下面的命令。
RUN chown -R www-data:www-data /var/www/html/
ADD的理解,
ADD --chown=www-data:www-data phpmyadmin/ /var/www/html/
phpmyadmin是本地目录, /var/www/html/是目标目录,可以不存在,会自动创建。上面的命令含义,会将phpmyadmin文件夹下的所有目录直接拷贝到/var/www/html/目录下。
如果是tar文件,则会自动解压,tar文件下的所有内容,直接拷贝到/var/www/html/。相当于,在后者的目录中,直接运行tar解压而已。
如果是普通的文件,则相当于,直接拷贝到后者。
终上,文件夹内,看到什么,就是什么。
COPY
报错如下:
Step 4/8 : COPY target/* .
When using COPY with more than one source file, the destination must be a directory and end with a /
更正为 COPY target/ .
CMD
跟ENTERPOINT命令相似,放在文件的最后一行(多个CMD只有最后一个生效),主要用来指定,容器启动后执行的命令。此命令,相当于docker run -d image:tag [命令] 后的命令。
ONBUILD
为镜像添加触发器。触发器可以是任何指令。如父镜像中定义了ONBUILD触发器,在子镜像的构建中会执行,但是在孙子镜像中不会执行。
ONBUILD ADD . /var/www/
可以使用docker inspect 命令来查看详情。
ARGS
Dockerfile中的ARG指令用以定义构建时需要的参数,使用格式如下:
ARG set_va
ARG a_nother_name=a_default_value
ARG指令定义的参数,在docker build命令中以–build-arg a_name=a_value形式赋值。如果docker build命令传递的参数,在Dockerfile中没有对应的参数,将抛出警告。 如果在Dockerfile中,ARG指令定义参数之前,就有其他指令引用了参数,则参数值为空字符串。
Docker自带的如下ARG参数,可以在其他指令中直接引用:
HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_proxy
例子:
下面是自己编写的nginx/lua安装的教程,注意下面的定义形式,空格、引号等。
FROM 10.131.9.12:5000/base/nginx:1.16
ARG NGINX_VERSION="1.10.3"
ARG LuaJIT_VERSION="2.0.4"
ARG KIT_VERSION="0.3.0"
ARG Lua_Mod_VERSION="0.10.8"
ARG LuaJIT_PATH="/usr/local/luajit"
ADD nginx-${NGINX_VERSION}.tar.gz /tmp
ADD LuaJIT-${LuaJIT_VERSION}.tar.gz /tmp
ADD ngx_devel_kit-${KIT_VERSION}.tar.gz /opt
ADD lua-nginx-module-${Lua_Mod_VERSION}.tar.gz /opt
RUN cd /tmp/LuaJIT-${LuaJIT_VERSION} \
&& make PREFIX=${LuaJIT_PATH} \
&& make install PREFIX=${LuaJIT_PATH} \
&& export LUAJIT_LIB=${LuaJIT_PATH}/lib \
&& export LUAJIT_INC=${LuaJIT_PATH}/include/luajit-2.0 \
&& cd /tmp/nginx-${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-stream_ssl_module --with-http_realip_module --add-module=/opt/ngx_devel_kit-${KIT_VERSION} --add-module=/opt/lua-nginx-module-${Lua_Mod_VERSION} \
&& make \
&& make install \
&& rm -rf /tmp/nginx-${NGINX_VERSION} \
&& rm -rf /tmp/LuaJIT-${LuaJIT_VERSION} \
&& rm -rf /opt/ngx_devel_kit-${KIT_VERSION} \
&& rm -rf /opt/lua-nginx-module-${Lua_Mod_VERSION} \
&& echo "${LuaJIT_PATH}/lib" >> /etc/ld.so.conf.d/libc.conf \
&& echo "${LuaJIT_PATH}/include/luajit-2.0" >> /etc/ld.so.conf.d/libc.conf \
&& ldconfig
COPY nginx.conf /usr/local/nginx/conf/nginx.conf
EXPOSE 80
ADD lua /yd/lua
ADD demo /yd/demo
ADD run.sh /
RUN chmod u+x /run.sh
CMD ["/run.sh"]
选择性构建:
#一个参数
docker build -t nginx_plugin:2.3 --build-arg LuaJIT_VERSION=2.0.5 .
#多个参数
docker build -t nginx_plugin:2.3 --build-arg LuaJIT_VERSION=2.0.5 --build-arg KIT_VERSION=0.3.1 .
# 禁用缓存 --no-cache
docker build -t nginx_plugin:2.3 --build-arg LuaJIT_VERSION=2.0.5 --build-arg KIT_VERSION=0.3.1 --no-cache .
注意,构建时,自定义的参数好像显示不出来,依然使用的是${变量命}的形式。但是,构建过程中,可以看到新建的目录等,确实有变化。重要:容易被缓存。
STOPSIGNAL
docker在1.9之后的dockerfile中新增关键字STOPSIGNAL。在github,官网,nginx的Dockerfile中见到的。
默认的stop-signal是SIGTERM,在docker stop的时候会给容器内PID为1的进程发送这个signal,通过–stop-signal可以设置自己需要的signal,主要的目的是为了让容器内的应用程序在接收到signal之后可以先做一些事情,实现容器的平滑退出,如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,这个时间默认是10s 。
具体参见:地址
HEALTHCHECK
以下示例包含一个健康检查。另外,还有多阶段构建。
(https://github.com/statping/statping/blob/dev/Dockerfile )其为健康检查的一个工具。state ping的缩写。
FROM statping/statping:base AS base
ARG BUILDPLATFORM
# Statping main Docker image that contains all required libraries
FROM alpine:latest
RUN apk --no-cache add libgcc libstdc++ ca-certificates curl jq && update-ca-certificates
COPY --from=base /go/bin/statping /usr/local/bin/
COPY --from=base /root/sassc/bin/sassc /usr/local/bin/
COPY --from=base /usr/local/share/ca-certificates /usr/local/share/
WORKDIR /app
VOLUME /app
ENV IS_DOCKER=true
ENV SASS=/usr/local/bin/sassc
ENV STATPING_DIR=/app
ENV PORT=8080
EXPOSE $PORT
HEALTHCHECK --interval=60s --timeout=10s --retries=3 CMD curl -s "http://localhost:$PORT/health" | jq -r -e ".online==true"
CMD statping --port $PORT
USER
USER mynewuser
WORKDIR
WORKDIR /home/mynewuser
跟docker run 中 -w的参数作用相同。
多阶段构建
参见HEALTHCHECK中的示例。
总结
构建镜像有两种方式。1、进入容器,更改容器后,commit容器的变化。2、跟据Dockerfile来进行打包。
Dockerfile方式构建,基于DSL(领域特定语言)的描述打包。构建过程,实际上是运行镜像,有部分指令是在构建过程中的容器中执行,如RUN ,有些可能是在容器启动有执行,如CMD、ENTERPOINT等。而ADD、COPY能将容器外部的文件添加到容器内部。
构建镜像过程中,要区分真机环境跟镜像运行环境。如RUN 后的指令,实际上操作的都是容器内的文件、环境。
参考文档
文件目录为Dockerfile所在目录