虽然 Dockerfile 简化了镜像构建的过程,而且把这个过程能够进行版本控制,可是不正当的 Dockerfile 使用也会致使不少问题:java
容器模型是进程而不是机器,不须要开机初始化。在须要时运行,不须要时中止,可以删除后重建,而且配置和启动的最小化。node
在 docker build 的时候,忽略部分无用的文件和目录能够提升构建的速度。好比.git目录。dockerignore的定义相似gitignore。具体使用方式能够参考连接。python
为了减小镜像的复杂性、镜像大小和构建时间,应该避免安装无用的包。mysql
一个容器只运行一个进程。容器起到了隔离应用隔离数据的做用,不一样的应用运行在不一样的容器让集群的纵向扩展以及容器的复用都变的更加简单。须要多个应用交互时请使用 link 命令进行组合或者使用docker-compose。nginx
须要掌握好Dockerfile的可读性和镜像层数之间的平衡。不推荐使用过多的镜像层。git
命令行按字母顺序排序有助于避免重复执行和提升Dockerfile可读性。apt-get update 应与 apt-get install 组合,换行使用反斜杠(\)。例如:github
RUN apt-get update && apt-get install -y \ bzr \ cvs \ git \ mercurial \ subversion
Dockerfile的每条指令都会将结果提交为新的镜像。下一条指令基于上一条指令的镜像进行构建。若是一个镜像拥有相同的父镜像和指令(除了 ADD ),Docker将会使用镜像而不是执行该指令,即缓存。golang
所以,为了有效的利用缓存,尽可能保持Dockerfile一致,而且将不变的放在前面而常常改变放在末尾。web
如不但愿使用缓存,在执行 docker build 的时候加上参数 --no-cache=true 。sql
Docker匹配镜像决定是否使用缓存的规则以下:
官方Image 使用的时区基本上都是标准的 UTC 时间,若是容器想使用中国标准时间,基于Debian的系统在Dockerfile中加入
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo "Asia/Shanghai" >> /etc/timezone
基于Centos的系统在Dockerfile中加入
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
有时候你可能感受官方的源更新或者安装软件比较慢,能够在Dockerfile修改官方默认源,例如alpine想使用阿里的源能够在Dockerfile中加入:
RUN echo -e "http://mirrors.aliyun.com/alpine/v3.5/main\nhttp://mirrors.aliyun.com/alpine/v3.5/community" > /etc/apk/repositories
推荐使用官方仓库中的镜像做为基础镜像。
把复杂的或过长的 RUN 语句写成以 \ 结尾的多行的形式,以提升可读性和可维护性。
apt-get update 和 apt-get install 一块儿执行,不然 apt-get install 会出现异常。
避免运行 apt-get upgrade 或 dist-upgrade ,在无特权的容器中,不少 必要 的包不能正常升级。若是基础镜像过期了,应当联系维护者。 推荐 apt-get update && apt-get install -y package-a package-b 这种方式,先更新,以后安装最新的软件包。
RUN apt-get update && apt-get install -y \ aufs-tools \ automake \ build-essential \ curl \ dpkg-sig \ libcap-dev \ libsqlite3-dev \ mercurial \ reprepro \ ruby1.9.1 \ ruby1.9.1-dev \ s3cmd=1.1.* \ && rm -rf /var/lib/apt/lists/*
此外,你能够经过移除/var/lib/apt/lists减小镜像大小。
注意:官方的Ubuntu和Debian会自动运行apt-get clean,因此不须要显式的调用
推荐使用 CMD ["executable","param1","param2"] 这样的格式。若是镜像是用来运行服务,须要使用 CMD["apache2","-DFOREGROUND"] ,这种格式的指令适用于任何服务性质的镜像。
ENTRYPOINT 应该用于 镜像的主命令,并使用 CMD 做为默认设置,以 s3cmd 为例:
ENTRYPOINT ["s3cmd"] CMD ["--help"]
获取帮助:
docker run s3cmd
或者执行命令:
docker run s3cmd ls s3://mybucket
这在镜像名与程序重名时很是有用。
ENTRYPOINT 也能够启动自定义脚本: 定义脚本:
#!/bin/bash set -e if [ "$1" = 'postgres' ]; then chown -R postgres "$PGDATA" if [ -z "$(ls -A "$PGDATA")" ]; then gosu postgres initdb fi exec gosu postgres "$@" fi exec "$@"
注意:这段脚本使用了exec命令以确保最终应用程序在容器内启动的PID为1。这段脚本容许容器接收Unix signals。
COPY ./docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh"]
这段脚本为用户提供了多种和 Postgres 交互的途径:
你能够简单地启动 Postgres:
docker run postgres。
或者运行 postgres 并传入参数:
docker run postgres postgres --help。
你甚至能够从镜像中启动一个彻底不一样的程序,好比 Bash:
docker run --rm -it postgres bash
应该尽量地使用默认端口。例如Apache web服务使用EXPOSE 80,MongoDB使用EXPOSE 27017。
ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && … ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
虽然 ADD 与 COPY 功能相似,但推荐使用 COPY 。 COPY 只支持基本的文件拷贝功能,更加的可控。而 ADD 具备更多特定,好比tar文件自动提取,支持URL。 一般须要提取tarball中的文件到容器的时候才会用到 ADD 。
若是在Dockerfile中使用多个文件,每一个文件应使用单独的 COPY 指令。这样,只有出现文件变化的指令才会不使用缓存。
为了控制镜像的大小,不建议使用 ADD 指令获取URL文件。正确的作法是在 RUN 指令中使用 wget 或 curl 来获取文件,而且在文件不须要的时候删除文件。
RUN mkdir -p /usr/src/things \ && curl -SL http://example.com/big.tar.gz \ | tar -xJC /usr/src/things \ && make -C /usr/src/things all
VOLUME 一般用做数据卷,对于任何可变的文件,包括数据库文件、代码库、或者容器所建立的文件/目录等都应该使用 VOLUME 挂载。
若是服务不须要特权来运行,使用 USER 指令切换到非root用户。使用 **RUN groupadd -r mysql && useradd -r -g mysql mysql **以后用 USER mysql 切换用户
要避免使用 sudo 来提高权限,由于它带来的问题远比它能解决的问题要多。若是你确实须要这样的特性,那么能够选择使用 gosu 。
最后,不要反复的切换用户。减小没必要要的layers。
为了清晰和可维护性,应该使用WORKDIR来定义工做路径。推荐使用WORKDIR来代替RUN cd … && do-something 这样的指令。
Dockerfile 中的其它指令都是为了定制当前镜像而准备的,惟有 ONBUILD 是为了帮助别人定制本身而准备的。
ONBUILD指令用来设置一些触发的指令,用于在当该镜像被做为基础镜像来建立其余镜像时(也就是Dockerfile中的FROM为当前镜像时)执行一些操做,ONBUILD中定义的指令会在用于生成其余镜像的Dockerfile文件的FROM指令以后被执行,上述介绍的任何一个指令均可以用于ONBUILD指令,能够用来执行一些由于环境而变化的操做,使镜像更加通用。
注意:
例如,Dockerfile使用以下的内容建立了镜像 image-A :
[...] ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src [...]
若是基于 image-A 建立新的镜像时,新的Dockerfile中使用FROM image-A指定基础镜像时,会自动执行ONBUILD指令内容,等价于在后面添加了两条指令。
FROM image-A #Automatically run the following ADD . /app/src RUN /usr/local/bin/python-build --dir /app/src
假设咱们要制做 Node.js 所写的应用的镜像。咱们都知道 Node.js 使用 npm 进行包管理,全部依赖、配置、启动信息等会放到 package.json 文件里。在拿到程序代码后,须要先进行 npm install 才能够得到全部须要的依赖。而后就能够经过 npm start 来启动应用。所以,通常来讲会这样写 Dockerfile:
FROM node:slim RUN mkdir /app WORKDIR /app COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/ CMD [ "npm", "start" ]
把这个 Dockerfile 放到 Node.js 项目的根目录,构建好镜像后,就能够直接拿来启动容器运行。可是若是咱们还有第二个 Node.js 项目也差很少呢?好吧,那就再把这个 Dockerfile 复制到第二个项目里。那若是有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让咱们继续看这样的场景维护的问题。
若是第一个 Node.js 项目在开发过程当中,发现这个 Dockerfile 里存在问题,好比敲错字了、或者须要安装额外的包,而后开发人员修复了这个 Dockerfile,再次构建,问题解决。第一个项目没问题了,可是第二个项目呢?虽然最初 Dockerfile 是复制、粘贴自第一个项目的,可是并不会由于第一个项目修复了他们的 Dockerfile,而第二个项目的 Dockerfile 就会被自动修复。
那么咱们可不能够作一个基础镜像,而后各个项目使用这个基础镜像呢?这样基础镜像更新,各个项目不用同步 Dockerfile 的变化,从新构建后就继承了基础镜像的更新?好吧,能够,让咱们看看这样的结果。那么上面的这个 Dockerfile 就会变为:
FROM node:slim RUN mkdir /app WORKDIR /app CMD [ "npm", "start" ]
这里咱们把项目相关的构建指令拿出来,放到子项目里去。假设这个基础镜像的名字为 my-node 的话,各个项目内的本身的 Dockerfile 就变为:
FROM my-node COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/
基础镜像变化后,各个项目都用这个 Dockerfile 从新构建镜像,会继承基础镜像的更新。
那么,问题解决了么?没有。准确说,只解决了一半。若是这个 Dockerfile 里面有些东西须要调整呢?好比 npm install 须要统一加一些参数,那怎么办?这一行 RUN 是不可能放入基础镜像的,由于涉及到了当前项目的 ./package.json,难道又要一个个修改么?因此说,这样制做基础镜像,只解决了原来的 Dockerfile 的前4条指令的变化问题,然后面三条指令的变化则彻底没办法处理。
ONBUILD 能够解决这个问题。让咱们用 ONBUILD 从新写一下基础镜像的 Dockerfile:
FROM node:slim RUN mkdir /app WORKDIR /app ONBUILD COPY ./package.json /app ONBUILD RUN [ "npm", "install" ] ONBUILD COPY . /app/ CMD [ "npm", "start" ]
此次咱们回到原始的 Dockerfile,可是此次将项目相关的指令加上 ONBUILD,这样在构建基础镜像的时候,这三行并不会被执行。而后各个项目的 Dockerfile 就变成了简单地:
FROM my-node
是的,只有这么一行。当在各个项目目录中,用这个只有一行的 Dockerfile 构建镜像时,以前基础镜像的那三行 ONBUILD 就会开始执行,成功的将当前项目的代码复制进镜像、而且针对本项目执行 npm install,生成应用镜像。
相似Java,Go等编译型项目,可使用ONBUILD指令进行优化Dockerfile。
编写onbuild Dockerfile以下:
FROM maven:3-jdk-8 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app ONBUILD ADD . /usr/src/app ONBUILD RUN mvn install
而后全部依赖maven编译的项目Dockerfile能够简化成以下形式:
FROM maven:3.3-jdk-8-onbuild CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]