编写Dockerfile的最佳实践

Docker可以通过Dockerfile自动构建镜像,在Dockerfile文件中包含构建镜像的全部指令,在这篇文章中将会Docker社区中编写Dockerfile构建镜像的最佳的实践

构建镜像一般准则和建议

  • 使用.dockerignore文件

在很多案例中,最好的方式把Dockerfile放在一个空的目录的中,然后仅仅添加Dockerfile需要构建的文件,可以提高构建速度,也可以通过.dockerignore排除一些不需要的文件与目录,这个文件支持模式匹配,与Git的.gitignore文件类似

  • 避免不必要的安装包

为了减少复杂性,依赖,文件大小以及构建时间,必须要避免一些不必要的安装包,比如你没有必要在数据库的镜像中安装一个文本编辑器

  • 一个容器一个进程

几乎在所有情况下,你只需要在一个容器中运行一个进程,解偶应用程序到多个容器中,这样可以很容易实现水平扩展和容器的重用.如果某个服务依赖其它的服务,可以使用container linking

  • 最小化layers的数量

你需要考虑Dockerfile可读性和长期维护性找到平衡点并尽可能减少layers的使用

  • 排序多行参数
RUN apt-get update && apt-get install -y \  
  bzr \
  cvs \
  git \
  mercurial \
  subversion
  • 构建缓存

在构建镜像期间将会按顺序执行Dockerfile中的每一个指令,并检查是否已存在需要用到的镜像,而不是创建一个新的重复镜像,如果你不想使用缓存,可以在docker build命令后面指定--no-cache=true选项

介绍Dockerfile

  • FROM

建议基于官方的镜像来构建新的镜像

-RUN

一如既往,为了让你的Dockerfile有更好的可读性,可理解性以及可维护性,需要在多行中切割复杂的RUN声明

  • apt-get

在Dockerfile的RUN指令中最常见的命令,因为它可以安装一个包,但你需要注意以下这些问题

1 避免RUN apt-get upgrade 或者 dist-upgrade,因为它升级该发行版,即14.04 > 16.04

2 避免在单独的一行中RUN apt-get update可能会因为缓存而造成包安装不成功的问题,比如

   FROM ubuntu:14.04
    RUN apt-get update
    RUN apt-get install -y curl

在镜像image构建之后,layers存在于Dcoker的缓存中,假设你在最后的修改中添加一个软件包

   FROM ubuntu:14.04
    RUN apt-get update
    RUN apt-get install -y curl nginx

Docker检查初始化和修改指令并重用上一步的缓存,apt-get update不会再被执行,因为已经存在一个缓存版本,nginx和curl安装到的有可能会是过时的版本
3 最佳的编写方式

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/*
  • CMD

CMD指令将会在容器中被使用,一般按照这个的格式编写CMD [“executable”, “param1”, “param2”…],在大多数情况下,应给予CMD指令一个可交互的shell (bash, python, perl,node, etc),比如CMD ["perl", "-de0"], CMD ["python"], or CMD [“php”, “-a”],使用这个意味着你可以这样创建容器

$ docker run -it python
  • EXPOSE

EXPOSE指令声明container容器将会监听来自那个端口的连接,即容器公开那个端口对外服务,在传统的应用中,比如Apache web服务器的镜像中EXPOSE 80,MongoDB镜像EXPOSE 27017

对于外部的访问,可以在docker run 创建容器时使用-p选项指定端口,对于container linking,docker提供环境变量,直接在容器中使用(比如, MYSQLPORT3306_TCP)

  • ENV

为了让软件更好的运行,可以使用ENV指令更新PATH环境变量,比如:ENV PATH /usr/local/nginx/bin:$PATH,确保CMD [“nginx”] 很好的工作,也可以使用ENV指令自定义环境变量

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

虽然ADD与COPY功能类似,一般来说,复制是首选,这是因为COPYADD更加透明,COPY仅支持本地文件复制到容器,ADD可以请求远程文件,把打包文件提取到容器

ADD rootfs.tar.xz /  

如果你有多个Dockerfile,都有相同的步骤,要分别COPY它们,而不是一次COPY所有,docker会每一步添加构建缓存,确保每个复制的文件都是一致的,ie:

COPY requirements.txt /tmp/  
RUN pip install --requirement /tmp/requirements.txt  
COPY . /tmp/  
  • ENTRYPOINT

为image设置主要命令,这样就可以用很简单命令的创建并运行容器

ENTRYPOINT ["s3cmd"]  
CMD ["--help"]  

运行一个容器

$ docker run s3cmd

ENTRYPOINT指令也可以组合shell脚本使用,比如:Postgres的官方镜像

#!/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执行bash命令,也就是说最终运行的应用将会成为容器的pid,这将允许应用接收Unix信号发送容器中

将执行脚本复制到容器中,这样就可以很简单的启动容器

COPY ./docker-entrypoint.sh /  
ENTRYPOINT ["/docker-entrypoint.sh"]  
$ docker run postgres

也可以传递参数到容器中

$ docker run postgres postgres --help

也可以使用bash与容器进行交互

$ docker run --rm -it postgres bash
  • VOLUME

VOLUME指令用于公开docker容器创建的数据库存储区域,配置,文件或者文件夹,建议使用VOLUME来维护用户服务可变的部分,即把可变的数据公开的外部存储

  • USER

如果一个服务不需要任何权限即可运行,可以使用USER指令改变为非Root用户,只需在Dockerfile中添加RUN指令

RUN groupadd -r postgres && useradd -r -g postgres postgres  

在Dockerfile使用gosu切换用户

  • WORKDIR

为了保证Dockerfile的简洁性和可靠性,你必须总是使用绝对路径作为WORKDIR,此外应该使用WORKDIR来替换相对路径的切换RUN cd … && do-something

thank Docker

你的欣赏是我最大的动力

Yanxiong Huang

My name is Yanxiong Huang. graduated from Nanyang middle school.Love Linux,familiar with Node.js,Docker,Serverless... and more Web technology.Contact Me:QQ 31356617;Email:huangaynxiong2013@gmail.com

guangzhou,china http://www.myfreax.com

乐在分享