最简单的dockerfile使用教程 – 创建一个支持SSL的Nginx镜像

什么是dockerfile?简单的说就是一个文本格式的脚本文件,其内包含了一条条的指令(Instruction),每一条指令负责描述镜像的当前层(Layer)如何构建。

下面通过一个具体的例子来学习dockerfile的写法。

新建一个dbuild文件夹,创建一个自定义的Nginx首页,逻辑很简单,显示一个自定义的图片文件train.jpg.

未分类

我想基于标准的Nginx镜像做一些修改,让Nginx支持SSL。SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。

为此我首先需要创建一个针对SSL的配置文件。

未分类

cat << '__EOF' > ssl.conf
server {
listen       443 ssl;
server_name  localhost;

ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;

location / {
root   /usr/share/nginx/html;
index  index.html index.htm;
}
}
__EOF

使用如下命令创建nginx.key和nginx.crt文件:

openssl req -x509 -nodes -newkey rsa:4096 -keyout nginx.key -out nginx.crt -days 365 -subj "/CN=$(hostname)"

未分类

一切就绪之后,下面就应该创建dockerfile了:

未分类

FROM nginx:stable

# copy the custom website into the image
COPY train.jpg /usr/share/nginx/html/
COPY index.html /usr/share/nginx/html/

# copy the SSL configuration file into the image
COPY ssl.conf /etc/nginx/conf.d/ssl.conf

# download the SSL key and certificate into the image
COPY nginx.key /etc/nginx/ssl/nginx.key
COPY nginx.crt /etc/nginx/ssl/nginx.crt

# expose the https port
EXPOSE 443

所有dockerfile第一行指令必定是FROM XXXX。

FROM的作用是指定基准镜像。该dockerfile以FROM后面指定的镜像为基础,在其上进行定制。

在 Docker Store 上有很多高质量的官方镜像,主要分为以下三大类:

  1. 开箱即用的服务类的镜像,比如网络服务器nginx ,也有数据库服务器诸如redis 、 mongo 、mysql 等;
  2. 方便开发、构建、运行各种语言应用的镜像,如 node 、 openjdk 、 python 等。
  3. 相对前两大类更为基础的操作系统镜像,如ubuntu 、 debian 、 centos 等

当然您如果不愿意基于这些官方已有镜像开始镜像构建,而是想从头开始,这也是可以的。Docker存在一个特殊的镜像,名为 scratch 。它是一个虚拟的概念,

表示一个空白的镜像。

直接使用FROM scratch 会让镜像体积更加小巧。

接下来的一系列copy指令都很好理解。

dockerfile开发完毕之后,执行命令:

docker build -t jerry-nginx:1.0 .

意思是基于当前目录开始构建镜像,注意末尾的.必不可少,代表“当前目录”。

通过docker build执行输出的日志可以观察到里面每一行的指令被逐行执行:

未分类

最后一行日志提示标签为jerry-nginx:1.0的景象被成功构建。

用下面的命令基于刚刚制作好的镜像运行一个容器:

docker run -d -p 443:443 -p 1082:80 jerry-nginx:1.0

基于http协议访问没有问题:

http://localhost:1082

未分类

基于https访问也能正常工作:

https://localhost:443

未分类

CICD之logstash服务的Dockerfile使用Gitlab Runner打docker包

gitlab提交代码后,经gitlab Runner打docker包,推送到docker仓库,然后kubernetes选择版本更新

Dockerfile

FROM openjdk:8-jre-alpine

# ensure logstash user exists
RUN addgroup -S logstash && adduser -S -G logstash logstash

# install plugin dependencies
RUN apk add --no-cache 
# env: can't execute 'bash': No such file or directory
        bash 
        libc6-compat 
        libzmq

# grab su-exec for easy step-down from root
RUN apk add --no-cache 'su-exec>=0.2'

# https://www.elastic.co/guide/en/logstash/5.0/installing-logstash.html#_apt
# https://artifacts.elastic.co/GPG-KEY-elasticsearch
ENV LOGSTASH_PATH /usr/share/logstash/bin
ENV PATH $LOGSTASH_PATH:$PATH

# LOGSTASH_TARBALL="https://artifacts.elastic.co/downloads/logstash/logstash-5.5.0.tar.gz"

COPY logstash-5.5.0.tar.gz /logstash.tar.gz
RUN set -ex; 
    apk add --no-cache --virtual .fetch-deps 
        ca-certificates 
        gnupg 
        openssl 
        tar ; 
    dir="$(dirname "$LOGSTASH_PATH")"; 
    mkdir -p "$dir"; 
    tar -xf /logstash.tar.gz --strip-components=1 -C "$dir"; 
    rm logstash.tar.gz; 
    apk del .fetch-deps; 
    export LS_SETTINGS_DIR="$dir/config"; 
# if the "log4j2.properties" file exists (logstash 5.x), let's empty it out so we get the default: "logging only errors to the console"
    if [ -f "$LS_SETTINGS_DIR/log4j2.properties" ]; then 
        cp "$LS_SETTINGS_DIR/log4j2.properties" "$LS_SETTINGS_DIR/log4j2.properties.dist"; 
        truncate -s 0 "$LS_SETTINGS_DIR/log4j2.properties"; 
    fi; 
# set up some file permissions
    for userDir in 
        "$dir/config" 
        "$dir/data" 
    ; do 
        if [ -d "$userDir" ]; then 
            chown -R logstash:logstash "$userDir"; 
        fi; 
    done; 
    logstash --version

COPY docker-entrypoint.sh /
RUN chmod +x /docker-entrypoint.sh
COPY logstash-shipper.conf /
RUN mkdir -p /data/logs/sincedb
RUN chown logstash.logstash -R /data/logs/sincedb
WORKDIR /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["-f", "/logstash-shipper.conf"]

docker-entrypoint.sh

#!/bin/bash
set -e
mkdir -p /data/logs/sincedb
chown logstash.logstash -R /data/logs/sincedb

# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then
    set -- logstash "$@"
fi

# Run as user "logstash" if the command is "logstash"
# allow the container to be started with `--user`
if [ "$1" = 'logstash' -a "$(id -u)" = '0' ]; then
    set -- su-exec logstash "$@"
fi

exec "$@"

logstash-5.5.0.tar.gz 从官方下载 https://www.elastic.co/cn/downloads/logstash

logstash-shipper.conf样例

input {
    file {
        path => [ "/data/logs/service/*/*.log"]
        type => "service"
        sincedb_path => "/data/logs/sincedb/service"
        codec => multiline {
            pattern => "^dddd-dd-dd dd:dd:dd.ddd .+"
            negate => true
            what => "previous"
            max_lines => 30
        }       
    }
    file {
        path => [ "/data/logs/web/*/access_log*.log"]
            codec => plain { format => "%{message}" }
        type => "web"
        sincedb_path => "/data/logs/sincedb/web"
    }
}
output {
    if [type] == 'service' {
        kafka {
            codec => plain { format => "%{message}" }
            bootstrap_servers => "139.219.*.*:9092"
        topic_id => "service"
        }
    }
    if [type] == 'web' {
        kafka {
                codec => plain { format => "%{message}" }
            bootstrap_servers => "139.219.*.*:9092"
        topic_id => "web"
        }
    }
}

service的日志开头是2017-12-01 12:01:01,所以pattern匹配时间,根据时间判断日志的起始点;web日志原封不动传过去,output到kafka集群,logstash-indexer从kafka获取日志后归入elasticsearch

logstash-indexer.conf示例

input {
        kafka {
                bootstrap_servers => "139.219.*.*:9092"
                topics => "service"
                type => "service"
        }
        kafka {
                bootstrap_servers =>"139.219.*.*:9092"
                topics => "web"
                type => "web"
        }
}
filter {
    if [type] != ['web'] {
        if "_grokparsefailure" in [tags] {
              drop { }
          }
        grok {
            match => {
                "message" => "%{TIMESTAMP_ISO8601:timestamp} %{GREEDYDATA}"
            }
        }
        date {
            match => ["timestamp","yyyy-MM-dd HH:mm:ss.SSS"]
            locale => "cn"
        }
    }
    if [type] == 'web' {
        if "_grokparsefailure" in [tags] {
              drop { }
          }
        grok {
                match => {
                    "message" => '%{IP} - - [%{HTTPDATE:time}] "%{WORD:methord} %{URIPATHPARAM:request} HTTP/%
{NUMBER:httpversion}" %{NUMBER:response} %{GREEDYDATA}'
                    }
            }
        date {
            match => ["time","dd/MMM/yyyy:HH:mm:ss +d+"]
            locale => "cn"
        }
    }
}
output {
        if [type] == 'service' {
                elasticsearch {
                        hosts => "172.16.1.1:9200"
                        index => "bbotte-service-%{+YYYY.MM.dd}"
                }
        }
        if [type] == 'web' {
                elasticsearch {
                        hosts => "172.16.1.1:9200"
                        index => "bbotte-web-%{+YYYY.MM.dd}"
                }
        }
}

最后就是gitlabci配置示例

# cat .gitlab-ci.yml
image: docker:latest

stages:
  - LogstashPubTest
  - LogstashPubProd

image-build-test:
  stage: LogstashPubTest
  script:
    - "current_date=`TZ='UTC-8' date +'%m%d%H%M'`"
    - "commit_sha=$CI_COMMIT_SHA"
    - "docker build -t bbotte.com:5000/logstash:$CI_COMMIT_REF_NAME-$current_date-${commit_sha:0:8} ."
    - "docker login -u admin -p 123456 bbotte.com:5000"
    - "docker push bbotte.com:5000/logstash:$CI_COMMIT_REF_NAME-$current_date-${commit_sha:0:8}"
  only:
    - test
image-build-master:
  stage: LogstashPubProd
  script:
    - "current_date=`TZ='UTC-8' date +'%m%d%H%M'`"
    - "commit_sha=$CI_COMMIT_SHA"
    - "docker build -t bbotte.com:5000/logstash:$CI_COMMIT_REF_NAME-$current_date-${commit_sha:0:8} ."
    - "docker login -u admin -p 123456 bbotte.com:5000"
    - "docker push bbotte.com:5000/logstash:$CI_COMMIT_REF_NAME-$current_date-${commit_sha:0:8}"
  only:
    - master

目录结构如下:

logstash$ ls -a
.   docker-entrypoint.sh  .git            logstash-5.5.0.tar.gz 
..  Dockerfile            .gitlab-ci.yml  logstash-shipper.conf

如何编写 Dockerfile 文件创建 Docker 镜像

一、前言

承接上篇文章 docker 镜像与容器,本篇来讲讲如何创建 Dockerfile 来构建一个镜像。上篇文章有讲到构建一个自定义镜像是手动去构建的,虽然步骤清晰,但是操作比较繁琐,镜像分发起来也不是很方便,所以有必要用一种更好的办法去替换这种模式去创建自定义镜像,于是 Dockerfile 就是最优替代方案。废话少说,现在就来看看如何编写一个 Dockerfile 文件并创建容器镜像的,先说明一个本篇文章的运行环境吧,有看过上篇文章的朋友应该知道,我用的 docker 的镜像加速地址是阿里云的,我觉得这是我用 docker 最无痛的环境了。

二、Dockerfile 示例

# Base images 基础镜像
FROM centos

#MAINTAINER 维护者信息
MAINTAINER lorenwe 

#ENV 设置环境变量
ENV PATH /usr/local/nginx/sbin:$PATH

#ADD  文件放在当前目录下,拷过去会自动解压
ADD nginx-1.13.7.tar.gz /tmp/

#RUN 执行以下命令
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 
    && yum update -y 
    && yum install -y vim less wget curl gcc automake autoconf libtool make gcc-c++ zlib zlib-devel openssl openssl-devel perl perl-devel pcre pcre-devel libxslt libxslt-devel 
    && yum clean all 
    && rm -rf /usr/local/src/*
RUN useradd -s /sbin/nologin -M www

#WORKDIR 相当于cd
WORKDIR /tmp/nginx-1.13.7

RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-pcre && make && make install

RUN cd / && rm -rf /tmp/

COPY nginx.conf /usr/local/nginx/conf/

#EXPOSE 映射端口
EXPOSE 80 443

#ENTRYPOINT 运行以下命令
ENTRYPOINT ["nginx"]

#CMD 运行以下命令
CMD ["-h"]

以上代码示例是我编写的一个认为很有代表性的 dockerfile 文件,涉及到的内容不多,但基本上把所有 dockerfile 指令都用上了,也包含一些细节方面的东西,为了达到示例的效果所以并不是最简洁的 dockerfile,建立一个文件夹将以上 dockerfile 放在该文件内,再去 nginx 官网把 nginx 源码包下来放到该文件夹内,之后再在该文件夹内打开命令行窗口,最好是以管理员权限打开命令行窗口,以免出现一些权限问题的错误,此时的目录结构应该是以下样子的

未分类

三、指令分析

FROM 表示的是这个 dockerfile 构建镜像的基础镜像是什么,有点像代码里面类的继承那样的关系,基础镜像所拥有的功能在新构建出来的镜像中也是存在的,一般用作于基础镜像都是最干净的没有经过任何三方修改过的,比如我用的就是最基础的 centos,这里有必要说明一下,因为我用的镜像加速源是阿里云的,所以我 pull 下来的 centos 是自带阿里云的 yum 源的镜像,如果你用的不是阿里云的镜像加速源,pull 下来的镜像 yum 源也不一样,到时 yum 安装软件的时候可能会遇到很多问题(你懂得)。

MAINTAINER 就是维护者信息了,填自己名字就可了,不用说什么了

ENV 设置环境变量,简单点说就是设置这个能够帮助系统找到所需要运行的软件,比如我上面写的是 “ENV PATH /usr/local/nginx/sbin:$PATH”,这句话的意思就是告诉系统如果运行一个没有指定路径的程序时可以从 /usr/local/nginx/sbin 这个路径里面找,只有设置了这个,后面才可以直接使用 ngixn 命令来启动 nginx,不然的话系统会提示找不到应用。

ADD 顾名思义,就是添加文件的功能了,但是他比普通的添加做的事情多一点,源文件可以是一个文件,或者是一个 URL 都行,如果源文件是一个压缩包,在构建镜像的时候会自动的把压缩包解压开来,示例我写的是 ‘ADD nginx-1.13.7.tar.gz /tmp/’ 其中 nginx-1.13.7.tar.gz 这个压缩包是必须要在 dockefile 文件目录内的,不在 dockerfile 文件目录内的 比如你写完整路径 D:test/nginx-1.13.7.tar.gz 都是会提示找不到文件的。

RUN 就是执行命令的意思了,RUN 可以执行多条命令, 用 && 隔开就行,如果命令太长要换行的话在末尾加上 ‘’ 就可以换行命令,RUN 的含义非常简单,就是执行命令,但其中有一些细节还是需要注意的,现在就通过上面的示例来看看需要注意的地方有哪些吧。其中 RUN rpm –import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 的作用就是导入软件包签名来验证软件包是否被修改过了,为做到安全除了系统要官方的之外软件也要保证是可信的。yum update -y 升级所有包,改变软件设置和系统设置,系统版本内核都升级,我们知道 linux 的软件存在依赖关系,有时我们安装新的软件他所依赖的工具软件也需要是最新的,如果没有用这个命令去更新原来的软件包,就很容易造成我们新安装上的软件出问题,报错提示不明显的情况下我们更是难找到问题了,为避免此类情况发生我们还是先更新一下软件包和系统,虽然这会使 docker 构建镜像时变慢但也是值得的,至于后面的命令自然是安装各种工具库了,接着来看这句 yum clean all ,把所有的 yum 缓存清掉,这可以减少构建出来的镜像大小,rm -rf /usr/local/src/ 清除用户源码文件,都是起到减少构建镜像大小的作用。RUN 指令是可以分步写的,比如上面的 RUN 可以拆成以下这样:

# 不推荐
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 
RUN yum update -y 
RUN yum install -y vim less wget curl gcc automake autoconf libtool make gcc-c++ zlib zlib-devel openssl openssl-devel perl perl-devel pcre pcre-devel libxslt libxslt-devel 
RUN yum clean all 
RUN rm -rf /usr/local/src/*

这样也是可以的,但是最好不要这样,因为 dockerfile 构建镜像时每执行一个关键指令都会去创建一个镜像版本,这有点像 git 的版本管理,比如执行完第一个 RUN 命令后在执行第二个 RUN 命令时是会在一个新的镜像版本中执行,这会导致 yum clean all 这个命令失效,没有起到精简镜像的作用,虽然不推荐多写几个 RUN,但也不是说把所有的操作都放在一个 RUN 里面,这里有个原则就是把所有相关的操作都放在同一个 RUN 里面,就比如我把 yum 更新,安装工具库,清除缓存放在一个 RUN 里面,后面的编译安装 nginx 放在另外一个 RUN 里面。

WORKDIR 表示镜像活动目录变换到指定目录,就相当于 linux 里面 cd 到指定目录一样,其实完全没有必要使用这个指令的,在需要时可以直接使用 cd 命令就行,因为这里使用了 WORKDIR,所以后面的 RUN 编译安装 nginx 不用切换目录,讲到这里又想起了另外一个问题,如下:

RUN cd /tmp/nginx-1.13.7

RUN ./configure

RUN ./configure这样可不可以呢,我想前面看懂的朋友应该知道答案了吧,这里还是再啰嗦一下,这样是会报找不到 configure 文件错误的,原因很简单,因为这个两个命令都不是在同一个镜像中执行的,第一个镜像 cd 进入的目录并不代表后面的镜像也进入了。
COPY 这个指令很简单,就是把文件拷贝到镜像中的某个目录,注意源文件也是需要在 dockerfile 所在目录的,示例的意思是拷贝一份 nginx 配置文件,现在就在 dockerfile 所在目录创建这个文件

user  www;
worker_processes  2;
daemon off;

pid        logs/nginx.pid;

events {    
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

配置很简单,就是对官方的配置文件把注释去掉了,注意里面的 daemon off; 配置,意思是关闭 nginx 后台运行,原因在上一篇文章中讲过,这里再来絮叨一下,容器默认会把容器内部第一个进程是否活动作为docker容器是否正在运行的依据,如果 docker 容器运行完就退出了,那么docker容器便会直接退出,docker run 的时候把 command 作为容器内部命令,如果使用 nginx,那么 nginx 程序将后台运行,这个时候 nginx 并不是第一个执行的程序,而是执行的 bash,这个 bash 执行了 nginx 指令后就挂了,所以容器也就退出了,如果我们设置了 daemon off 后启动 nginx 那么 nginx 就会一直占用命令窗口,自然 bash 没法退出了所以容器一直保持活动状态。

EXPOSE 示例注释写的是映射端口,但我觉得用暴露端口来形容更合适,因为在使用 dockerfile 创建容器的时候不会映射任何端口,映射端口是在用 docker run 的时候来指定映射的端口,比如我把容器的 80 端口映射到本机的 8080 端口,要映射成功就要先把端口暴露出来,有点类似于防火墙的功能,把部分端口打开。

ENTRYPOINT 和 CMD 要放在一起来说,这两者的功能都类似,但又有相对独特的地方,他们的作用都是让镜像在创建容器时运行里面的命令。当然前提是这个镜像是使用这个 dockerfile 构建的,也就是说在执行 docker run 时 ENTRYPOINT 和 CMD 里面的命令是会执行的,两者是可以单独使用,并不一定要同时存在,当然这两者还是有区别的。

先从 CMD 说吧,CMD 的一个特点就是可被覆盖,比如把之前的 dockerfile 的 ENTRYPOINT 这一行删除,留下 CMD 填写[“nginx”],构建好镜像后直接使用 docker run lorenwe/centos_nginx 命令执行的话通过 docker ps 可以看到容器正常运行了,启动命令也是 “ngixn”,但是我们使用 docker run lorenwe/centos_nginx bin/bash 来启动的话通过 docker ps 查看到启动命令变成了 bin/bash,这就说明了 dockerfile 的 CMD 指令是可被覆盖的,也可以把他看做是容器启动的一个默认命令,可以手动修改的。

而 ENTRYPOINT 恰恰相反,他是不能被覆盖,也就是说指定了值后再启动容器时不管你后面写的什么 ENTRYPOINT 里面的命令一定会执行,通常 ENTRYPOINT 用法是为某个镜像指定必须运行的应用,例如我这里构建的是一个 centos_nginx 镜像,也就是说这个镜像只运行 ngixn,那么我就可以在 ENTRYPOINT 写上[“nginx”],有些人在构建自己的基础镜像时(基础镜像只安装了一些必要的库)就只有 CMD 并写上 [‘bin/bash’],当 ENTRYPOINT 和 CMD 都存在时 CMD 中的命令会以 ENTRYPOINT 中命令的参数形式来启动容器,例如上面的示例 dockerfile,在启动容器时会以命令为 nginx -h 来启动容器,遗憾的是这样不能保持容器运行,所以可以这样启动 docker run -it lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf,那么容器启动时运行的命令就是 nginx -c /usr/local/nginx/conf/nginx.conf,是不是很有意思,可以自定义启动参数了。

当然还有一些没有用到的指令:

ARG,ARG指令用以定义构建时需要的参数,比如可以在 dockerfile中写上这句 ARG a_nother_name=a_default_value,ARG指令定义的参数,在docker build命令中以 –build -arg a_name=a_value 形式赋值,这个用的一般比较少。

VOLUME,VOLUME指令创建一个可以从本地主机或其他容器挂载的挂载点,用法是比较多的,都知道 docker 做应用容器比较方便,其实 docker 也可做数据容器,创建数据容器镜像的 dockerfile 就主要是用 VOLUME 指令,要讲明 VOLUME 用法有必要在开一篇文章,再此就不做介绍了,
USER,USER用来切换运行属主身份的。docker 默认是使用 root 用户,但若不需要,建议切换使用者身分,毕竟 root 权限太大了,使用上有安全的风险。LABEL,定义一个 image 标签。

四、构建演示

dockerfile 构建镜像的命令很简单,在我的示例中我的命令是 “docker build -t lorenwe/centos_nginx . “,注意后面的点不能省略,表示的从当前目录中寻找 dockerfile 来构建镜像

D:dockerlorenwe>docker build -t lorenwe/centos_nginx .
Sending build context to Docker daemon  995.8kB
Step 1/13 : FROM centos
 ---> d123f4e55e12
Step 2/13 : MAINTAINER lorenwe
 ---> Running in e5c7274f50e8
 ---> 606f7222e69a
Removing intermediate container e5c7274f50e8
Step 3/13 : ENV PATH /usr/local/nginx/sbin:$PATH
 ---> Running in 23716b428809
 ---> 5d8ee1b5a899
         ....
Successfully built eaee6b40b151
Successfully tagged lorenwe/centos_nginx:latest

看到以上内容就说明成功,构建过程可能需要一点点时间,毕竟要安装一些软件,如果你跟我一样是配置的阿里云的容器源构建时应该不会出现什么问题,因为我之前是有拉取过 centos ,所以在 build 时直接使用本地的 centos,如果你没有拉取过 centos,那么在 build 时还会把 centos 拉取下来

D:dockerlorenwe>docker images
REPOSITORY               TAG     IMAGE ID       CREATED          SIZE
lorenwe/centos_nginx     latest  eaee6b40b151   7 minutes ago    427MB
lorenwe/centos_net_tools latest  35f8073cede1   6 days ago       277MB
centos                   latest  d123f4e55e12   3 weeks ago      197MB
d4w/nsenter              latest  9e4f13a0901e   14 months ago    83.8kB

D:dockerlorenwe>docker run -itd --name nginx1 lorenwe/centos_nginx
15d4f108dab7c2f276209ebeb501cac0d3be828e1e81bae22d3fd97c617439eb

D:dockerlorenwe>docker ps
CONTAINER ID    IMAGE    COMMAND     CREATED    STATUS     PORTS     NAMES

D:dockerlorenwe>docker ps -a
CONTAINER ID   IMAGE                 COMMAND    CREATED   STATUS   PORTS   NAMES
15d4f108dab7   lorenwe/centos_nginx  "nginx -h"                            nginx1

D:dockerlorenwe>docker run -itd --name nginx2 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
b6b0e962ca3056d67c24145b08975ffddb9cc050fce5f09f65310fb323ffc1c3

D:dockerlorenwe>docker ps
CONTAINER ID   IMAGE                 COMMAND        CREATED    STATUS    PORTS     NAMES
b6b0e962ca30   lorenwe/centos_nginx  "nginx -c /usr/loc..."              80/tcp    nginx2

D:dockerlorenwe>docker run -itd -p 8080:80 --name nginx3 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
2f6997745641e3e3edbbfe5213e6235cab3b5a929f116a2c132df504156090c6

D:dockerlorenwe>docker ps
CONTAINER ID   IMAGE                 COMMAND    CREATED   STATUS     PORTS                  NAMES
2f6997745641   lorenwe/centos_nginx  "nginx -c /usr/loc..."          0.0.0.0:8080->80/tcp   nginx3
b6b0e962ca30   lorenwe/centos_nginx  "nginx -c /usr/loc..."          80/tcp                 nginx2

D:dockerlorenwe>docker stop nginx2
nginx2

其中 “docker run -itd -p 8080:80 –name nginx3 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf” 中的 -p 8080:80 表示把主机的 8080 端口映射到容器的 80 端口,因为之前我们在 dockerfile 中把 80 端口暴露出来了,做好端口映射后现在就可以在主机中打开浏览器访问 127.0.0.1:8080 就能看到 nginx 的欢迎页面了 (^v^).

D:dockerlorenwe>docker run -itd -v D:/docker/lorenwe/html:/usr/local/nginx/html  -p 8081:80 --name nginx4 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
cd2d4eb70a39057aed3bfcb64e1f03433e2054d7ff5d50098f49d2e6f2d9e02e

我再在原来的参数中加入了 -v 参数,其作用就是把一个本地主机的目录挂载到容器内部,这个目录是一个共享的状态,两边都可以进行修改,这就是容器的共享卷,其作用就不言而喻了,现在我们在 D:dockerlorenwe 的目录下新建一个叫 html 的文件夹,再在 html 文件夹内新建一个 index.html 随便写上一点内容后再去主机浏览器上访问一下 127.0.0.1:8081 看看是不是你想要看到内容。虽然通过 -v 参数可以满足大部分应用场景,但是 docker 的 VOLUME 还有其他更好用法,欲知后事如何,请看下回分解!

使用Dockerfile创建带Apache服务的Centos Docker镜像

在宿主机上准备的文件清单:

Dockerfile
#启动ssh和apache服务的角本
run.sh

以上文件都放到/root/apache_centos目录下

mkdir -p /root/apache_centos
cd /root/apache_centos

基础镜像:以镜像centos为基础的开放SSH服务的镜像

[root@localhost apache_centos]# docker images
REPOSITORY      TAG          IMAGE ID         CREATED        VIRTUAL SIZE
sshd         dockerfile    411d5fb16366        23 hours ago        278 MB
centos            latest      0f73ae75014f        5 weeks ago         172.3 MB

一、准备run.sh文件

在/root/apache_centos目录新建run.sh

vim run.sh
#!/bin/bash
/usr/sbin/sshd &
/usr/local/apache2/bin/httpd -D FOREGROUND

二、准备Dockerfile

在/root/apache_centos目录新建Dockerfile

vim Dockerfile

文件内容如下:

#新生成的镜像是基于sshd:dockerfile镜像
FROM sshd:dockerfile
MAINTAINER by Steven
#安装wget
RUN yum install -y wget
WORKDIR /usr/local/src
#下载并解压源码包
RUN wget http://apache.fayea.com/httpd/httpd-2.4.17.tar.gz
RUN tar -zxvf httpd-2.4.17.tar.gz
WORKDIR httpd-2.4.17
#编译安装apache
RUN yum install -y gcc make apr-devel apr apr-util apr-util-devel pcre-devel
RUN ./configure --prefix=/usr/local/apache2  --enable-mods-shared=most  --enable-so
RUN make
RUN make install
#修改apache配置文件
RUN sed -i 's/#ServerName www.example.com:80/ServerName localhost:80/g' /usr/local/apache2/conf/httpd.conf
#启动apache服务
RUN /usr/local/apache2/bin/httpd
#复制服务启动脚本并设置权限
ADD run.sh /usr/local/sbin/run.sh
RUN chmod 755 /usr/local/sbin/run.sh
#开放80端口
EXPOSE 80
CMD ["/usr/local/sbin/run.sh"]

需要注意的是:在Dockerfile文件中更换当前目录不可以用“cd”命令,而要改用“WORKDIR”.

三、生成镜像

docker build -t apache_dockerfile:centos .

查看生成的镜像:

[root@localhost apache_centos]# docker images
REPOSITORY      TAG            IMAGE ID        CREATED          VIRTUAL SIZE
apache_dockerfile centos        f8f30b4a0ee8        24 minutes ago     440 MB
apache         centos        f92c55dddd07        17 hours ago       423.9 MB
sshd          dockerfile     411d5fb16366        23 hours ago       278 MB
centos         latest        0f73ae75014f        5 weeks ago       172.3 MB

四、根据镜像生成的容器并进行测试

1、生成新的容器

docker run -d -p 2222:22 -p 8000:80 apache_dockerfile:centos /usr/local/sbin/run.sh

将容器的22端口和80端口分别映射到到宿主机上的2222端口和8000端口,并运行服务脚本。

2、查看新生成的容器

[root@localhost apache_centos]# docker ps -a
CONTAINER ID IMAGE              COMMAND           CREATED      STATUS       PORTS                                        NAMES
ae560e497f39 apache_dockerfile:centos "/usr/local/sbin/run  45 seconds ago  Up 44 seconds   0.0.0.0:2222->22/tcp, 0.0.0.0:8000->80/tcp   condescending_bardeen
6490cd244c10 apache:centos        "/usr/local/apache2/  17 hours ago   Exited (0) 17 hours ago                            loving_wright
673e946b57e4 sshd:dockerfile      "/usr/local/sbin/run   18 hours ago   Exited (137)                                                    reverent_bell

3、测试

测试apache

[root@localhost apache_centos]# curl localhost:8000
<html><body><h1>It works!</h1></body></html>

成功!

测试ssh

[root@localhost apache_centos]# ssh localhost -p 2222
root@localhost's password:

成功!

从零开始使用 Docker 打包 Django 开发环境 (2) Dockerfile

1. 基本概念

Dockerfile 是一些列构建 Docker 镜像的指令集合。Docker 通过读取 Dockerfile 指令自动构建镜像。Dockerfile 类似于 Makefile,都是一种文本文件,按照构建镜像顺序组织所有的指令。

Docker 镜像的构建命令:

$ docker build .

这条命令中,Docker CLI 的处理流程如下:

  • 把当前目录及子目录当做上下文传递给 Docker Daemon
  • 从当前目录(不包括子目录)中找到 Dockerfile 文件
  • 检查 Dockerfile 的语法
  • 依次执行 Dockerfile 中的指令,根据指令生成中间过渡镜像(存储在本地,为之后的指令或构建作缓存)

2. Docker 文件组成

Dockerfile 一般包含下面几个部分:

  • 基础镜像,以哪个镜像作为基础进行制作,用法是 FROM 基础镜像名称
  • 维护者信息,需要写下该 Dockerfile 编写人的姓名或邮箱,用法是MANITAINER 名字/邮箱
  • 镜像操作命令,对基础镜像要进行的改造命令,比如安装新的软件,进行哪些特殊配置等,常见的是 RUN 命令
  • 容器启动命令,当基于该镜像的容器启动时需要执行哪些命令,常见的是 CMD 命令或 ENTRYPOINT 命令

3. Dockerfile 命令

3.1 FROM

语法:FROM image[:tag]

解释:设置要制作的镜像基于哪个镜像,FROM 指令必须是整个 Dockerfile 的第一个指令,如果指定的镜像不存在默认会自动从 Docker Hub 上下载。如果不指定 tag,默认是 latast。

3.2 MAINTAINER

语法:MAINTAINER name

解释:MAINTAINER 指令允许你给将要制作的镜像设置作者信息

3.3 RUN

语法:

  • RUN command #将会调用/bin/sh -c command
  • RUN [“executable”, “param1”, “param2”] #将会调用exec执行,以避免有些时候shell方式执行时的传递参数问题,而且有些基础镜像可能不包含/bin/sh

解释:RUN指令会在一个新的容器中执行任何命令,然后把执行后的改变提交到当前镜像,提交后的镜像会被用于Dockerfile中定义的下一步操作,RUN中定义的命令会按顺序执行并提交,这正是Docker廉价的提交和可以基于镜像的任何一个历史点创建容器的好处,就像版本控制工具一样。

3.4 CMD

语法:

  • CMD [“executable”, “param1”, “param2”] #将会调用exec执行,首选方式
  • CMD [“param1”, “param2”] #当使用ENTRYPOINT指令时,为该指令传递默认参数
  • CMD command [ param1|param2 ] #将会调用/bin/sh -c执行

解释:CMD 指令中指定的命令会在镜像运行时执行,在 Dockerfile 中只能存在一个,如果使用了多个 CMD指令,则只有最后一个 CMD 指令有效。当出现 ENTRYPOINT 指令时,CMD 中定义的内容会作为 ENTRYPOINT 指令的默认参数,也就是说可以使用 CMD 指令给 ENTRYPOINT 传递参数。

注意:RUN 和 CMD 都是执行命令,他们的差异在于 RUN 中定义的命令会在执行 docker build 命令创建镜像时执行,而 CMD 中定义的命令会在执行docker run命令运行镜像时执行,另外使用第一种语法也就是调用 exec 执行时,命令必须为绝对路径。

3.5 EXPOSE

语法:EXPOSE port [ …]

解释:EXPOSE 指令用来告诉 Docker 这个容器在运行时会监听哪些端口,Docker 在连接不同的容器(使用–link参数)时使用这些信息。

3.6 ENV

语法:ENV key value

解释:ENV 指令用于设置环境变量,在 Dockerfile 中这些设置的环境变量也会影响到 RUN 指令,当运行生成的镜像时这些环境变量依然有效,如果需要在运行时更改这些环境变量可以在运行 docker run 时添加–env key=value参数来修改。

注意:最好不要定义那些可能和系统预定义的环境变量冲突的名字,否则可能会产生意想不到的结果。

3.7 ADD

语法:ADD src dest

解释:ADD 指令用于从指定路径拷贝一个文件或目录到容器的指定路径中,是一个文件或目录的路径,也可以是一个 url,路径是相对于该 Dockerfile 文件所在位置的相对路径, 是目标容器的一个绝对路径,例如 /home/yooke/Docker/Dockerfile 这个文件中定义的,那么 ADD /data.txt /db/指令将会尝试拷贝文件从 /home/yooke/Docker/data.txt 到将要生成的容器的 /db/data.txt,且文件或目录的属组和属主分别为 uid 和 gid 为0的用户和组,如果是通过 url 方式获取的文件,则权限是600。

注意:

  • 如果执行 docker build somefile 即通过标准输入来创建时,ADD 指令只支持 url 方式,另外如果 url 需要认证,则可以通过 RUN wget … 或 RUN curl … 来完成,ADD 指令不支持认证。
  • src 路径必须与 Dockerfile 在同级目录或子目录中,例如不能使用ADD ../somepath,因为在执行docker build时首先做的就是把 Dockerfile 所在目录包含子目录发送给 docker 的守护进程。
  • 如果 src 是一个 url 且 dest 不是以 ‘/’ 结尾,则会下载文件并重命名为 dest 。
  • 如果 src 是一个 url 且 dest 以 ‘/’ 结尾,则会下载文件到 dest filename,url 必须是一个正常的路径形式,’http://example.com’ 像这样的 url 是不能正常工作的。
  • 如果 src 是一个本地的压缩包且 dest 是以 ‘/’ 结尾的目录,则会调用 ‘tar -x’ 命令解压缩,如果 dest 有同名文件则覆盖,但 src 是一个 url 时不会执行解压缩。

3.8 COPY

语法:COPY src dest

解释:用法与 ADD 相同,不过 src 不支持使用url,所以在使用 docker build somefile 时该指令不能使用。

3.9 ENTRYPOINT

语法:

  • ENTRYPOINT [‘executable’, ‘param1’, ‘param2’] #将会调用exec执行,首选方式
  • ENTRYPOINT command param1 param2 #将会调用/bin/sh -c执行

解释:ENTRYPOINT 指令中指定的命令会在镜像运行时执行,在 Dockerfile 中只能存在一个,如果使用了多个 ENTRYPOINT 指令,则只有最后一个指令有效。ENTRYPOINT 指令中指定的命令(exec执行的方式)可以通过 docker run 来传递参数,例如 docker run images -l 启动的容器将会把 -l 参数传递给 ENTRYPOINT 指令定义的命令并会覆盖 CMD 指令中定义的默认参数(如果有的话),但不会覆盖该指令定义的参数,例如 ENTRYPOINT [‘ls’,’-a’],CMD [‘/etc’],当通过 docker run image 启动容器时该容器会运行 ls -a /etc 命令,当使用 docker run image -l 启动时该容器会运行 ls -a -l 命令,-l 参数会覆盖 CMD 指令中定义的/etc参数。

注意:

  • 当使用 ENTRYPOINT 指令时生成的镜像运行时只会执行该指令指定的命令。
  • 当出现 ENTRYPOINT 指令时 CMD 指令只可能(当 ENTRYPOINT 指令使用 exec 方式执行时)被当做 ENTRYPOINT 指令的参数使用,其他情况则会被忽略。

3.10 VOLUME

语法:VOLUME [‘samepath’]

解释:VOLUME 指令用来设置一个挂载点,可以用来让其他容器挂载以实现数据共享或对容器数据的备份、恢复或迁移。

3.11 USER

语法:USER [username|uid]

解释:USER指令用于设置用户或uid来运行生成的镜像和执行 RUN 指令。

3.12 WORKDIR

语法:WORKDIR /path/to/workdir

解释:WORKDIR 指令用于设置 Dockerfile 中的 RUN、CMD 和 ENTRYPOINT 指令执行命令的工作目录(默认为/目录),该指令在 Dockerfile 文件中可以出现多次,如果使用相对路径则为相对于 WORKDIR 上一次的值,例如 WORKDIR /data,WORKDIR logs,RUN pwd 最终输出的当前目录是 /data/logs。

4. 最佳实践

4.1 使用 .dockerignore 文件

在 docker 构建镜像的第一步,docker CLI 会先在上下文目录中寻找 .dockerignore 文件,根据 .dockerignore 文件排除上下文目录中的部分文件和目录,然后把剩下的文件和目录传递给 docker 服务。.dockerignore 语法同 .gitignore。

4.2 避免安装不必要的包

为了减小复杂度、依赖、文件大小和创建的时间,应该避免安装额外或者不必要的包。例如,我们不必在一个数据库镜像中包含一个文本编辑器。

4.3 对于多行参数要做字典序排序

只要有可能,通过对多行参数进行字母数字排序来缓解后续变更。这将帮助你避免重复的包并且更容易更新。在反斜线( )前添加一个空格是个好习惯。

RUN apt-get update && apt-get install -y   
  bzr 
  cvs 

4.4 尽量利用 build 镜像的缓存

未分类

在创建镜像过程中,Docker 将按照 Dockerfile 指定步骤执行每个指令。 一般情况下,对于每条命令,docker 都会生成一层镜像。如果在构建某个镜像层的时候,发现这个镜像层已经存在了,就直接使用,而不是重新构建。

大部分指令是通过与缓存进行对比该指令、执行指令的基础镜像,判断是否使用缓存。除了 ADD 和 COPY,这两个指令会复制文件内容到镜像内,docker 还会检查每个文件内容校验和(不包括最后修改时间和最后访问时间),如果校验和不一致,则不会使用缓存。

4.5 每个镜像只有一个功能

不要在容器里运行多个不同功能的进程,每个镜像中只安装一个应用的软件包和文件,需要交互的程序通过 pod(kubernetes 提供的特性) 或者容器之间的网络进行通信。这样可以保证模块化,不同的应用可以分开维护和升级,也能减小单个镜像的大小。

4.6 不要在构建中升级版本

更新将发生在基础镜像里,不要在你的容器内来apt-get upgrade更新。因为在隔离情况下,如果更新时试图修改 init 或改变容器内的设备,更新可能会经常失败。它还可能会产生不一致的镜像,因为你不再有你的应用程序该如何运行以及包含在镜像中依赖的哪种版本的正确源文件。

Dockerfile参考(18) – SHELL设置执行命令的shell

格式:

  1. SHELL ["executable", "parameters"]

SHELL指令可以覆盖命令的shell模式所使用的默认shell。Linux的默认shell是[“/bin/sh”, “-c”],Windows的是[“cmd”, “/S”, “/C”]。SHELL指令必须以JSON格式编写。
SHELL指令在有两个常用的且不太相同的本地shell:cmd和powershell,以及可选的sh的windows上特别有用。
SHELL指令可以出现多次。每个SHELL指令覆盖之前的SHELL指令设置的shell,并影响随便的指令。例如:

  1. FROM windowsservercore
  2.  
  3. # Executed as cmd /S /C echo default
  4. RUN echo default
  5.  
  6. # Executed as cmd /S /C powershell -command Write-Host default
  7. RUN powershell -command Write-Host default
  8.  
  9. # Executed as powershell -command Write-Host hello
  10. SHELL ["powershell", "-command"]
  11. RUN Write-Host hello
  12.  
  13. # Executed as cmd /S /C echo hello
  14. SHELL ["cmd", "/S"", "/C"]
  15. RUN echo hello

当RUN, CMD和ENTRYPOINT使用shell形式时,将使用SHELL指令设置的shell执行。
以下的示例是windows常见的模式,可以使用SHELL指令精简:

  1. RUN powershell -command Execute-MyCmdlet -param1 "c:foo.txt"

由docker解析会是:

  1. cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:foo.txt"

这个命令之所以低效有两个原因。首先,没有必须调用cmd.exe。第二,每个使用shell模式的RUN指令需要一个额外的powershell。
为了优化这个命令更有效率,有两个方法,其中之一使用RUN指令的JSON形式,如:

  1. RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 "c:\foo.txt""]

虽然没有调用了cmd.exe,不过需要对双引号进行转义,看起来比较冗长。另一种机制是使用SHELL指令和shell形式,使得windows用户可以使用更自然的语法,特别是与escape指令一起用时:

  1. # escape=`
  2.  
  3. FROM windowsservercore
  4. SHELL ["powershell","-command"]
  5. RUN New-Item -ItemType Directory C:Example
  6. ADD Execute-MyCmdlet.ps1 c:example
  7. RUN c:exampleExecute-MyCmdlet -sample ‘hello world’

构建结果为:

  1. PS E:dockerbuildshell> docker build -t shell .
  2. Sending build context to Docker daemon 3.584 kB
  3. Step 1 : FROM windowsservercore
  4.  —> 5bc36a335344
  5. Step 2 : SHELL powershell -command
  6.  —> Running in 87d7a64c9751
  7.  —> 4327358436c1
  8. Removing intermediate container 87d7a64c9751
  9. Step 3 : RUN New-Item -ItemType Directory C:Example
  10.  —> Running in 3e6ba16b8df9
  11.  
  12.  
  13.     Directory: C:
  14.  
  15.  
  16. Mode                LastWriteTime         Length Name
  17. —-                ————-         —— —-
  18. d—–         6/2/2016   2:59 PM                Example
  19.  
  20.  
  21.  —> 1f1dfdcec085
  22. Removing intermediate container 3e6ba16b8df9
  23. Step 4 : ADD Execute-MyCmdlet.ps1 c:example
  24.  —> 6770b4c17f29
  25. Removing intermediate container b139e34291dc
  26. Step 5 : RUN c:exampleExecute-MyCmdlet -sample ‘hello world’
  27.  —> Running in abdcf50dfd1f
  28. Hello from Execute-MyCmdlet.ps1 – passed hello world
  29.  —> ba0e25255fda
  30. Removing intermediate container abdcf50dfd1f
  31. Successfully built ba0e25255fda
  32. PS E:dockerbuildshell>

Docker 1.12开始添加了此SHELL功能。

Dockerfile参考(17) – HEALTHCHECK检查容器是否正常工作

HEALTHCHECK指令有两种形式:

  • HEALTHCHECK [OPTIONS] CMD command [通过在容器内运行一个命令来检查容器健康情况]
  • HEALTHCHECK NONE [禁用从base镜像继承的任何healthcheck]
  • HEALTHCHECK指令告诉Docker如何测试一个容器来检查它是否工作正常。这个可以用来检测如web server陷入了死循环且已经无法处理新的连接了,即使server进程仍然在运行。
    当一个容器设置了healthcheck之后,除了正常的状态,它多了一个health状态。这个状态初始为starting。当健康检查通过后,它变成了healthy(不管之前是什么状态)。当连续出现几次失败后,就变成unhealthy。
    在CMD之前的选项有:

  • –interval=DURATION [默认30s]
  • –timeout=DURATION [默认30s]
  • –retries=N [默认3]
  • 当容器启动之后,首先等待interval秒然后进行健康检查,然后等这次检查完成后再等interval秒之后继续再次检查,如此循环。
    如果有一个检查所花的时间超过了timeout秒,那么就认为这次检查失败了。
    如果连续retries次失败,就认为此容器状态为unhealthy。
    Dockerfile中只能使用一个HEALTHCHECK指令,如果设置了多次,就取最后一个。
    这个命令的CMD之后的命令可以是一个shell命令[如HEALTHCHECK CMD /bin/check-running]或者exec数组[像Dockerfile命令ENTRYPOINT使用的数组]。
    命令退出状态表示容器的健康状态。可能的值为:

  • 0: success – 容器是健康的。
  • 1: unhealthy – 容器工作不正常。
  • 2: reserved – 不要使用这个退出代码
  • 例如,隔5分钟检查一次容器确保web server能够在3秒内正常输出主页:

    1. HEALTHCHECK –interval=5m –timeout=3s
    2.   CMD curl -f http://localhost/ || exit 1

    为了方便调查失败原因,检查命令的输出(UTF-8编码)会写到health状态中,并且可以通过docker inspect查看。输出应该尽量短,不大于4096字节。
    HEALTHCHECK功能是在Docker 1.12添加的。

    Dockerfile参考(16) – ONBUILD向镜像添加触发指令

    格式:

    1. ONBUILD [INSTRUCTION]

    ONBUILD指令向镜像添加稍后要执行的触发指令,该触发指令在该镜像作为另一个镜像构建的base镜像时执行。触发指令在另一个镜像构建的Dockerfile的FROM指令后马上执行,就像FROM指令后插入触发指令一样。
    任何的构建指令都可以注册为触发指令。
    如果你正在构建的镜像会作为构建其它镜像的base镜像时,ONBUILD会有用,例如一个应用程序的构建环境或者可能需要用户自定义配置的daemon。
    例如,如果你的镜像是一个可重复使用的Python应用程序镜像,你将需要把应用程序源码添加到指定的目录,和有可能需要在添加代码之后执行脚本启动应用程序。你不能仅仅的在base镜像中调用ADD和RUN,因为这时还没有应用程序源代码,并且每个应用程序配置都可能不一样。你可以简单地为应用程序开发者提供一个样本Dockerfile来复制粘贴到它们的应用程序,不过这个比较低效,容易出错和难以更新,因为它与应用程序特定代码混合了。
    解决方案是使用ONBUIL来注册一个在下一个镜像构建时执行的高级指令。
    下面是它的工作原理:
    1.当镜像构建程序遇到ONBUILD指令时,构建程序把这个触发器添加到正在构建的镜像元数据中。这个ONBUILD指令不会影响到当前镜像的构建过程。
    2.在完成构建镜像后,在关键词OnBuild下,所有的触发器明显地存储到了镜像。之后也可以使用docker inspect来查看它们。
    3.之后其它镜像构建可以会用到上面的镜像作为base镜像,这个可以直接使用FROM引入base镜像。在构建程序执行Dockerfile中的FROM指令时,其中部分处理过程查找ONBUILD触发器,并按原来注册的顺序来执行触发指令。如果任何的一个触发器失败了,FROM指令将中断,反过来导致构建失败。如果所有的触发器指令执行成功,FROM指令就执行完成了,构建继教执行FROM后的指令。
    4.构建新镜像完成时会清除触发器,也就是使用带触发器镜像作为base镜像构建新镜像不会继承它的触发器。
    例如base镜像的Dockerfile如下,镜像名称为baseimg:

    1. […]
    2. ONBUILD ADD . /app/src
    3. ONBUILD RUN /usr/local/bin/python-build –dir /app/src
    4. […]

    然后下面是构建新镜像的Dockerfile,镜像名称newimg:

    1. FROM baseimg
    2. […]

    当构建newimg镜像时,在构建程序执行FROM baseimg指令时,会依次执行baseimg注册的触发指令ADD . /app/src和RUN /usr/local/bin/python-build –dir /app/src,相当于在FROM baseimg指令后面自动添加了这两个指令。

    Dockerfile参考(15) – ARG指令定义由用户在命令行赋值的变量

    格式:

    1. ARG <name>[=<default value>]

    ARG指令定义了一个变量,能让用户可以在构建期间使用docker build命令和其参数–build-arg =对这个变量赋值。如果用户指定了一个构建参数没有定义在Dockerfile的话,将输出错误。

    1. One or more build-args were not consumed, failing build.

    Dockerfile作者可以指定ARG一次定义一个变量,或者指定ARG多次定义多个变量。例如:

    1. FROM busybox
    2. ARG user1
    3. ARG buildno

    Dockerfile作者也可以为这个变量指定一个默认值:

    1. FROM busybox
    2. ARG user1=someuser
    3. ARG buildno=1

    如果ARG指定了一个默认值并且在构建期间没有传递值过去,那么就使用默认值。
    ARG变量定义从在Dockerfile定义的行生效,而不是从在命令行参数的使用或其它地方。
    例如下面的Dockerfile:

    1. 1 FROM busybox
    2. 2 USER ${user:-some_user}
    3. 3 ARG user
    4. 4 USER $user

    使用如下命令构建:

    1. $ docker build –build-arg user=what_user Dockerfile

    第2行的USER的值为some_user,因为user变量在第3行才定义。第4行的USER值为what_user,因为user变量在它之前定义了并且在命令行给user变量赋值为what_user。在ARG指令定义变量之前引用这个变量的得,都会得到空值。

    警告:不推荐在构建期间的命令行传递密码如github密钥,用户凭证等数据。构建期间设置的变量可以通过docker history命令来查看。

    可以使用ARG或ENV指令指定可用于RUN指令的变量。使用ENV定义的环境变量始终会覆盖同一名称的ARG指令定义的变量。例如:

    1. 1 FROM ubuntu
    2. 2 ARG CONT_IMG_VER
    3. 3 ENV CONT_IMG_VER v1.0.0
    4. 4 RUN echo $CONT_IMG_VER

    然后使用如下命令构建镜像:

    1. $ docker build –build-arg CONT_IMG_VER=v2.0.1 Dockerfile

    在这种情况中,RUN指令解析CONT_IMG_VER变量的值为v1.0.0而不是ARG设置并由用户传递过来的v2.0.1。
    使用上面的示例,但不一样的ENV定义可以在ARG和ENV指令之前创建更有用的交互:

    1. 1 FROM ubuntu
    2. 2 ARG CONT_IMG_VER
    3. 3 ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
    4. 4 RUN echo $CONT_IMG_VER

    不像ARG指令,ENV的值始终会存在于镜像中。使用如下不带–build-arg构建镜像:

    1. $ docker build Dockerfile

    使用这个Dockerfile示例,CONT_IMG_VER仍然会存在于镜像中,不过它的值会是默认的v1.0.0,当然你也可以在命令行中更改它。
    Docker有一组预设置的ARG变量,你不需要在Dockerfile中定义就能够使用它。

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy
  • 要设置这些变量,可以在命令行赋值

    1. –build-arg <varname>=<value>

    ARG对构建缓存的影响

    ARG变量不像ENV变量始终存在于镜像中。不过ARG变量会以类似的方式对构建缓存产生影响。如果Dockerfile中定义的ARG变量的值与之前定义的变量值不一样,那么就有可能产生“cache miss”。比如RUN指令使用ARG定义的变量时,ARG变量的值变了之后,就会导致缓存失效。

    Dockerfile参考(14) – WORKDIR设置RUN CMD ENTRYPOINT等指令的工作目录

    格式:

    1. WORKDIR /path/to/workdir

    WORKDIR指令设置Dockerfile中的任何RUN, CMD, ENTRYPOINT, COPY和ADD指令的工作目录。如果WORKDIR指定的目录不存在,即使随后的指令没有用到这个目录,都会创建。
    单个Dockerfile可以使用多次WORKDIR。如果提供一个相对路径,当前的工作目录将与上个WORKDIR指令相关,如:

    1. WORKDIR /a
    2. WORKDIR b
    3. WORKDIR c
    4. RUN pwd

    pwd命令的输出为/a/b/c。
    WORKDIR可以解析之前由ENV设置的环境变量。如:

    1. ENV DIRPATH /path
    2. WORKDIR $DIRPATH/$DIRNAME
    3. RUN pwd

    pwd命令输出为/path/$DIRNAME。