快速掌握dockerfile

本文引用至: dockerfilehtml

docker 之因此这么牛逼, 一是在于他强大的生态环境, 以及,他container和writable layer 的新颖的概念.node

docker镜像的简单剖析

docker的images,咱们能够理解为积木, 一层一层往上搭, 最后完成一个工程化的大项目.
在最初,docker实际上,只有一个静态的image(Ps: read-only). 至关于只能读, 因此, 你全部的改动并不会影响到原来的image上, 只会一层一层的叠加, 好比, 你在Ubuntu的image上面, 再接一层nodeJS的image. 实际上的结果是, 两个image叠加起来.
这里放一张 the Docker book的说明图:nginx

docker image

(Ps: 算啦,估计你们也没听懂,仍是在下面根据实例,来进行细致的区分吧)web

docker 在下载image的时候,会在/var/lib/docker目录下建立相关的image 目录. 而运行的container则会放在/var/lib/docker/containers中.docker

另外,docker中的image,是存储在docker仓库. 如今,咱们经过快速建立自已的仓库来仔细了解一下docker是怎样拥有这样一个完善的生态的.shell

docker 仓库

首先, 要想拥有本身的docker 仓库, 你得有一个本身的docker帐号.so, 那就想apply 一个呗. 在docker hub上面注册一下本身的帐号就行.apache

登陆指令

在docker中,不只支持web查看docker中的内容, 并且还支持使用命令行登陆.npm

// 登陆到docker
docker login // 而后输入帐户密码就ok了
// 使用完毕,想要登出
docker logout

实际上,docker会将你的认证信息存放在. ~/.docker/config.json当中。json

images 经常使用命令

若是浏览了上面的docker仓库, 会发如今一个repository里面会存在不少images, 好比ubuntu的repository.不一样的images发布,表明的都是特定的版本系统. 因此,在拉取的时候,须要额外注意一下你须要的指定docker images.ubuntu

images的拉取

在container中,咱们讲过,使用docker run的时候, 你会发现若是你的images里面没有存在指定的image时, docker会主动去docker hub里面找,而后下载,而且自动运行.

// 运行最新版的ubuntu image
docker run -t -i ubuntu:latest

若是,你想本身手动下载images的话,能够直接pull

// 手动拉取images
docker pull ubuntu:latest
// 拉取12.04版本的ubuntu images
docker pull ubuntu:12.04

若是在拉取的时候,想知道这个image是不是真正存在的话,就可使用.docker 提供的搜索指令.

搜索指定docker

在docker中,可使用自带的search命令,搜索全部含有指定term的image. 至关于js中的search 方法.

// 搜索name中含有demo的image
docker search demo
// 结果为: 名字. 一般为: author/image_name . 一般搜索的就是这个
// 描述: 就是一段文字描述
NAME  DESCRIPTION  STARS OFFICIAL   AUTOMATED

查到以后,咱们就可使用pull将指定的库,拉下来了.

建立本身的image

上面说过, contianer是copy image运行的进程容器,image是不会变的read-only 块. 可是,若是咱们在container里面, 改动了一些设置,好比,下载了node, 并且,我想要保存我此次改动, 以致于,下次我想从新,启动该image时, 他已经具有了node.

// 如今我再ubuntu:latest上面安装了node
// 伪代码
npm install node -g

docker提供了一个很是快捷的方式就是建立本身的docker image. 使用docker commit.

// 查看刚才改动的container ID
docker ps -a -q -l
// 获得 docker_id, 提交到本身的库中
docker commit docker_id villainHR/node
// 以后会返回新的image id

须要注意,docker commit提交的并非一个总体的100+MB的ubuntu+node. 他只会将两个仓库的差别提交,好比原来image和新的image比起来,就是多了一个npm install node -g命令.

使用Dockerfile

Dockerfile是为了迅速的构建image而出现的. 他与docker commit 的区别在于. 可以迅速的更替历史image 命令. 好比,咱们之前下载的npm是version 2, 如今想要更换为npm@3的话,则难度就不是通常的了. 可是,若是咱们可以像写命令同样将下载的配置命令下载Dockerfile里面, 那么之后咱们想要更换版本,就是很方便的啦.
ok, 如今咱们来了解一下Dockerfile是怎样的运行的.

dockerfile demo讲解

这里,咱们利用dockerfile 来搭建一个简单的webServer. 首先建立一个你本身的dockerfile目录

mkdir first_docker
cd first_docker
touch Dockerfile

而后, 确保你有ubuntu:latest image.由于, 接下来咱们就是基于它,来搭建咱们的server.

# first dockerfile demo
FROM ubuntu:latest
# 设置该dockerfile的做者和联系邮箱
MAINTAINER Jimmy "villainhr@gmail.com"
# 开始配置环境, 下载apt-get,生成index.html的文件
RUN apt-get update && apt-get install -y nginx
RUN echo 'first demo' > /usr/share/nginx/html/index.html
# 暴露server的port
EXPOSE 80

说一下上面的命令内涵.

  • FROM: 用来指定第一层image, 这是必须有的. 而且指定的image是存在在你的computer中. 至关因而 docker run.

  • RUN: 这是用来在container中,作出相应的修改. 至关于 修改+docker commit xxx. 给原来的image加上一层layer. 而后, docker会在你commit新一层以后,从新docker run你最新改动过的image

  • MAINTAINER: 设置做者和联系邮箱.其实就是docker commit 后面的Name参数. 并且加上了联系邮箱. 这是在dockerfile 运行完后,会自动添加到image上的.

  • EXPOSE: 用来给最新的container 设置与外部交流的port

上面简单的介绍了基本的dockerfile的命令. 不过, 这尼玛太简单了,不符合咱们一向追求到底的风格.
这里, 咱们在来细说一下RUN这个命令. 实际上, 这应该是dockerfile的关键. RUN的原理很简单, 就是commit + run. 先建立一个新的image 而后 在这个基础上将原有的container替换为新的,若是某一步的RUN发生错误,则container会停在那个阶段, 这样,你能够直接进入该container去查看,你那一步的RUN发生了什么BUG。 另外, 使用RUN的时候, 须要注意, 因为,dockerfile是由上到下解析的, 好比你一开始FROM ubuntu的image, 那么此时的环境是停留在ubuntu的shell中的.
好比:

RUN touch demo.js
// 等同于
/bin/sh -c touch demo.js

因此, 若是你调用的image 并无shell的话, 那么久须要使用exec调用系统shell 来执行命令.

// 调用系统的shell来运行, 实际上就是 exec xxx xxx xxx.
RUN ["npm","install","node"]

运行dockerfile

上面的dockerfile文件配置好了以后,就轮到咱们运行dockerfile.直接运行docker build便可.

// 注意后面的".", 用来指定搜索dockerfile文件的路径. 
docker build -t="jimmy/first_dockerfile" .

说一下docker build的指令吧.

// 基本格式为:
docker build -t="repository/name:tag"  directory
// -t用来指定生成的image的name,好比仓库,image的名字以及他的tag,若是你不指定tag, 那么docker会自动添加latest代替。
// directory 用来相对于当前运行build的目录, 搜索指定的dockerfile.固然,你也可使用绝对路径了

顺利的话,应该就会有, 下列的信息出来.

Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:latest
 ---> c5f1cf30c96b
Step 2 : MAINTAINER jimmy "villainhr@gmai.com"
 ---> Running in 078148a5086a
 ---> 11b061f665d1
Removing intermediate container 078148a5086a
Step 3 : RUN cd /var
 ---> Running in ffd3141e64c8
 ---> a4d7c5303b60
Removing intermediate container ffd3141e64c8
Step 4 : RUN touch demo.js
 ---> Running in c8393a6fcc98
 ---> 109b402b9adc
Removing intermediate container c8393a6fcc98
Step 5 : EXPOSE 80
 ---> Running in 2c064f4bac57
 ---> ff7ad58a5d8a
Removing intermediate container 2c064f4bac57
Successfully built ff7ad58a5d8a

而后, 你可使用docker images查看.就会发现多出来一个image.

dockerfile cache

上面已经提到过,使用docker build的时候,若是你的dockerfile中的某一步出现问题的话,你生成的image会停留在那一步.当你fix errors时, 从新运行docker build, 此时,docker是不会真的重头来建一遍的,他会使用你改动line的前一个image,而后以此为基点继续向下构建.
不过,若是你使用缓存的话,他前面的版本id是不会发生改变的.若是你想完整的获得一个新的ID的话,就能够在build的时候,禁用掉cache.

docker build --no-cache -t="jimmy/first_dockerfile" .

不过,该方法是不推荐的. 由于一个很是棒的cache机制,就被你硬生生的cancel. 并且,这也极力不推荐使用该方法进行cache的取消.觉得,有些地方,咱们彻底能够利用cache来加快速度.这就须要使用到ENV关键字.来帮助咱们,另外利用cache.
在讲解ENV以前,先给你们讲解一下docker cache的运行机理.
(是否是感受很激动~)
实际上,机理就一句话:ID命中. 由于docker在你每次运行一行命令的时候,会自动生成一个id值.

Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:latest
 ---> c5f1cf30c96b  // 这就是ID值

docker借由这个ID值,来判断是否有cache镜像.因此,这里就须要借一下ENV这个比较费的指令,来灵活的帮助咱们使用cache.

配置化cache

ENV的就是给docker来设置变量的. 基本格式为:

# 一个一个的赋值
ENV key value
// demo:
ENV name jimmy
ENV age 18
# 另外,还能够一块儿赋值
ENV key=value[...]
// demo:
ENV name=jimmy age=18

而经过ENV咱们就能够完美的告诉docker 从这里开始,你就不能使用cache,本身的从新来.(由于,每条指令都会生成layer而且有独立的id,一旦你更改的ENV,那么从该指令开始id都会发生改变,也就匹配不到缓存了)

看个demo:

# 第一个dockerfile
FROM ubuntu:latest
MAINTAINER jimmy "villainhr@gmai.com"
ENV REFRESH first # 这里设置的是refresh=first
RUN cd /var
RUN touch demo.js
EXPOSE 80

// 使用docker build ... 后面就会生成一系列新的id和images
// 如今修改dockerfile
# 第二个dockerfile
FROM ubuntu:latest
MAINTAINER jimmy "villainhr@gmai.com"
ENV REFRESH second # 这里设置的是refresh=second
RUN cd /var
RUN touch demo.js
EXPOSE 80

// 开始运行docker build... 你会发现,从下面语句开始.
ENV REFRESH second
// 其docker id就已经发生了改变,而且docker 没有了use cache的提示.说明,下面就没有命中缓存了. 因此,若是你想在某一段不使用缓存,只须要将ENV后面的value改变便可.

建立完后, 咱们可使用docker history,查看一下刚才建立的image的整个流程.

// 查看image建立的过程
docker history jimmy/first_dockerfile 
// 输出的结果为:
2322ddc85cc3        10 hours ago        /bin/sh -c #(nop) EXPOSE 80/tcp                 0 B                 
b39397abc7aa        10 hours ago        /bin/sh -c touch demo.js                        0 B                 
3c9a4daf4c42        10 hours ago        /bin/sh -c cd /var                              0 B                 
b1c2f890a262        10 hours ago        /bin/sh -c #(nop) ENV REFRESH=second            0 B                 
2cf0ee3c373c        10 hours ago        /bin/sh -c #(nop) MAINTAINER jimmy "villainhr   0 B

俺的目的,实际上是想让大家看看,docker在每一层是怎么执行的--/bin/sh. 了解了以后,咱们就继续了.

docker container的接口暴露

上面经过dockerfile 已经暴露了一个80接口,用来和外部通讯。 不过,若是咱们没有使用EXPOSE暴露接口的话, 那应该怎么作呢?
咱们能够直接在外部运行docker image, 手动指定暴露的端口.

# 一样,暴露80端口给外部交互
docker run -d -p 80 --name demo jimmy/node \
node -jimmy app.js

# -d是daemon的意思
# -p 80 表示暴露80的port给外部
# node -jimmy app.js 表示在jimmy/node image里面运行的指令

这里, 咱们须要额外了解一下80端口的开启. docker 实际上是在底层上面,虚拟化了存储. 而且,docker在运行的时候,会自动向主机要一个ip(假的), 至关于,有了本身的host. (这不就一个主机吗?)
这里咱们开启的80端口,是docker在内部虚拟开启的, 他会从32768 到 61000端口之间,随机抽一个映射到docker开启的80端口上, 依此来和外部进行真正的交互(膜拜///).

# 使用docker ps -l 来查看开启状况
docker ps -l
# 获得: 只截取了一部分.
0.0.0.0:49154->80 tcp
# 或者指定查看docker端口开启状况
docker port c96f2c18bb64 80 // ID也可使用name代替
# 返回:
 0.0.0.0:49154

手动指定端口

若是你不想让docker决定的绑定的接口是哪个,ok, 你能够本身指定.

# 手动指定端口
# 指定docker的8080链接到container暴露的80端口
docker run -d -p 8080:80 --name demo jimmy/node \
node -jimmy app.js
# 甚至你也能够指定ip+port
# 指定docker的127.0.0.1:8080链接container的80
docker run -d -p 127.0.0.1:8080:80 --name demo jimmy/node \
node -jimmy app.js

利用EXPOSE

在写dockerfile的时候,咱们已经了解了,使用EXPOSE能够完美的实现端口的暴露. 但若是,咱们在dockerfile里面暴露多个port的话,那么-p的参数,感受有点鸡肋啊喂~
不过,如今咱们可使用-P(注意是大写). 来手动开启全部在dockerfile中,经过EXPOSE暴露的端口.

docker run -d -P --name demo jimmy/node \
node -jimmy app.js

外部访问

经过端口开启以后,咱们就能够间接的访问docker的路由, 来访问在docker里面开启的端口了.

# 假如上面咱们经过dockre暴露的端口是34251的话,就能够在docker环境外访问了.
ping localhost:34251

dockerfile经常使用指令

自动化运行CMD

你是否是已经厌烦了使用docker run 来运行命令了呢? 你是否是已经讨厌重复的copy命令运行了呢?
那么请使用CMD吧.
CMD的做用是,用来指定当你调其对应的container时, 运行的命令.
好比在dockerfile中,指定/bin/bash.

# 当调起container时,运行/bin/bash
docker run  -t -i jimmy/ubuntu:latest /bin/bash
# 等同于在dockerfile中指定CMD
CMD ["/bin/bash"]
// 运行docker run
docker run -t -i jimmy/ubuntu:latest

不过,若是你在run后面手动指定指令运行的话,会默认覆盖掉CMD提供的命令.
熟悉了CMD,感受有种RUN的感受. 但,这二者的区别仍是很大的

  • RUN: 通常是用来给image增长layer来完善image, 他一旦执行完,就和后面的运行没有关系了

  • CMD: 这个在docker build过程当中,是没有半毛钱关系的. 他只和在调用image时,关系比较大

强制运行ENTRYPOINT

这里的ENTRYPOINTCMD很类似. 能够说,在必定程度上二者能够互相替代,但,二者的实际意义相差仍是挺大的.
ENTRYPOINT的主要功能是强制执行的环境.

# 指定ENTRYPOINT为/bin/sh
ENTRYPOINT ["/bin/sh"]
// 而后在build以后,调起container
# 咱们尝试在run后面加上参数:
docker run -t -i jimmy/demo /bin/bash/
// 不出意外的话,会获得一个bug提示:
>> /bin/sh: 0: Can't open /bin/bash/

因此, ENTRYPOINT的主要功能其实是,指定了内部运行命令的解析器. 而使用docker run添加的命令,会被当作参数添加给ENTRYPOINT.

# 已经指定了ENTRYPOINT ["/bin/sh"]
# 运行docker run
docker run -t -i jimmy/demo /bin/bash/
# 实际上至关于(不出错才怪嘞...)
/bin/sh /bin/bash/

另外,咱们还可使用CMD配合ENTRYPOINT写成默认参数的效果.

# 默认执行 /bin/bash default.sh
ENTRYPOINT ["/bin/bash"]
CMD ["default.sh"]
# 若是你在docker run中指定了参数的话,则CMD会默认被代替 
docker run jimmy/demo sam.sh

不过,CMD和ENTRYPOINT都只能在dockerfile里面出现一次.

指定运行目录WORKDIR

既然,咱们可以在dockerfile里面运行指定的命令。 但,有时,咱们仅仅是想在不一样的目录中执行不一样的命令. 那,在dockerfile中,如何作到灵活的目录切换呢?
那就得使用docker提供的WORKDIR命令了.

# 在/var/data里面建立data.js
WORKDIR /var/data
RUN touch data.js
# 而后在/etc 下建立data.conf文件
WORKDIR /etc
RUN touch data.conf

而且当你在使用docker run时,他也会停留在使用WORKDIR指定的目录中.

环境变量的设置ENV

ENV在dockerfile里面的用处,应该算是灰常大的. 什么灵活更新,什么变量设置,什么更改全局变量等. 都是so easy.
ENV究竟是用来干吗的?
答: 就是用来设置变量的啊喂. 只是他是设置全局变量.
好比像PATH神马的之类的.

# 设置一个DATA的全局变量.
ENV DATA=jimmy

ENV最独特之处在于,他所设置的变量,会在你运行的时候生效.即,若是你修改了PATH,他也会在container中当即生效.

# 修改环境变量
ENV PATH=$PATH:/user/bin
// 如今进入到运行的container中
echo $PATH
>> /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/data

挂载通用盘VOLUME

在说里面正式的内容以前,咱们先来讲一下,什么叫作VOLUME. 说人话吧,VOLUME叫作数据卷, 至关于通用盘同样的东西. 他其实也是一个存储装置,咱们就把他叫作硬盘吧. 这个硬盘不普通,有>1的外接口.(说人话) 每个外接口,均可以接入到一个操做系统里面. 即,实现了多个系统的数据共享.
一句话:

VOLUME就是一个数据共享盘

而,docker秉承着,虚拟储存idea, 想下面idea践行到底.

wirte once, run anywhere

(感受,在哪见过)
因此, dockerfile提供了一个VOLUME的指令,可以让咱们指定数据卷的位置.

# 指定/opt/data为数据卷
VOLUME ["/opt/data"]
# 指定多个目录为数据卷/opt/data, /opt/project
VOLUME ["/opt/data","/opt/project"]

固然,关于数据卷的操做,确定不止挂载这一点,还有迁移,备份等等,相关操做. 具体,能够参考: Docker VOLUME

添加外部文件ADD

有时,咱们仅仅是想将外部文件copy到container中,docker有办法吗?
nonsense
docker 提供了ADD命令,来帮助咱们完成文件的添加. 不过,这里ADD有点限制, 即, 你添加的文件或者目录,只能在docker build运行的目录下, 由于,这是docker在调起container的时候,只将该目录放进了daemon(尴尬~)

# 现假设,docker build运行的目录为: /data
// 只能添加指定目录下
// 将/data/sam.js 添加到image中的/opt/node/sam.js
// 若是存在该文件,则不会被覆盖
ADD sam.js /opt/node/
# 添加文件,还可使用通配符
// 将全部的js文件,添加到node目录下
ADD *.js /opt/node/
# 若是destination不是绝对路径,则相对于最近的WORKDIR
// 若是最近的WORKDIR为/var
// 则下列添加的路径为/var/opt/node
ADD *.js opt/node/

当文件被添加到指定目录中时,该文件的权限是755,而且UID和GID都是0.
ADD 还支持url添加,以及文件自动解压.

# 使用url添加
// 将指定路由的文件放到根目录当中
ADD http://example.com/foobar /
# 自动解压tar.gz文件
// 将文件解压事后放在指定目录中
ADD latest.tar.gz /var/www/wordpress/

纯粹的COPY

COPY和ADD很是相似. 咱们能够作个类比:

ADD 包含 COPY

COPY作的事情比不上ADD, 他比ADD少了解压缩和URL下载的功能. 不过,他耗费的性能比较少,他只作纯粹的添加和下载.他的结构和ADD一毛同样. 不过, 有一点,COPY的时候,若是遇到目录不存在的状况下,COPY会自动建立

COPY file.js /opt/data/

添加我的信息LABEL

顾名思义,使用LABEL就是给你的image打上独一无二的标签.让别人可以了解,这个Image是属于你的. 又或是,用来提醒你本身,这个image如今处于哪个版本状态.

# 设置本身的label
LABEL owner="jimmy" version="0.0.1"

在建立完image以后, 咱们可使用docker inspect来查看咱们已经打的LABEL

docker inspect jimmy/node
...
labels:{
    owner:"jimmy",
    version:"0.0.1"
}
...

本人以为, 这个指令其实真的,有时, 母鸡用到什么地方去...
而且,书写的时候,最好多个连着写,由于这样只会增长一层image.(image的层数是有限制的)

参数形式ARG

这是docker提供的另一个,让我有点懵逼的命令. 他的实际效果和ENV的区别能够趋近于无。

# 使用ARG定义变量
ARG buildno
# 设置默认值
ARG user1=someuser

固然,咱们能够在命令中,手动指定替换.

# 在dockerfile定义了默认变量
ARG user=jimy
# 在运行时,进行手动替换
docker build --build-arg user=sam -t jimmy/demo .

上面说了ARG和ENV比较相似,不过,里面的区别仍是有的. 即, ARG只能用在docker build的阶段, 而且不会被保存在image中,这就是和ENV的区别.

模板image之ONBUILD

由于dockerfile的构建的层数有限制,因此,这也带给了咱们一些麻烦, 若是搭建的环境过多,则会形成写到一半,发现dockerfile已经full. 这时候, 就轮到ONBUILD出场了. ONBUILD的做用在于,他能够完美的实现模板image的搭建.
ONBUILD的主要做用在于,他定义的命令,能够在子dockerfile中使用.(md... 好绕口)

# 使用ONBUILD 默认下载Apache
ONBUILD RUN apt-get update && apt-get install -y apache2

// 而后运行docker file 会获得下列结果
 Step 3 : ONBUILD RUN apt-get update && apt-get install -y apache2
   ---> Running in 0e117f6ea4ba
   ---> a79983575b8

//而后生成一个新的image,咱们这里暂且叫他jimmy/demo

接下来,咱们再来写一个dockerfile

# 这里继承上面的jimmy/demo
FROM jimmy/demo:latest
ENV SEX=001

// 运行上面的dockerfile,获得:
Step 0 : FROM jimmy/demo
# Executing 1 build triggers
   Step onbuild-0 : ADD . /var/www/
   ---> 1a018213a59d
   ---> 1a018213a59d
 Step 1: ENV SEX=001
 ...

细心的童鞋能够发现这一条命令:

Step onbuild-0 : RUN apt-get update && apt-get install -y apache2
   ---> 1a018213a59d
   ---> 1a018213a59d

他竟然在这里自动运行了. 因此,咱们能够将ONBUILD命令理解为模板命令. 即,子dockerfile里面运行时一样生效(这里,我没有说grandchildren的事).
ONBUILD只能往下延伸一级. 至关于你用ONBUILD定义的命令,有两次有效次数,一次在build原来Image时,已经用掉了. 因此, 另一次(在子dockerfile中使用)用掉了以后就无效了. grandchildren dockerfile就无法使用了.

相关文章
相关标签/搜索