dockerfile学习

参考资料

官网关于docker的资料

思考

在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脚本(可能在镜像中已经存在),

思考:也可以不用数组?两种形式实际有如何区别?

具体参见weblogic-nginx.md

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命令会继承或覆盖吗?

其他:

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 /foodocker 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指令详解

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所在目录