3 使用 Dockerfile 实现 自动化构建(创建) 镜像
2016-12-03 12:35
1171 查看
正常来说,如果下载的一个镜像不尽如人意,想要做一些修改,然后保存为自己的镜像,怎么做呢?首先启动容器,然后逐条执行自己想要做的修改,最后执行 docker commit 命令 保存镜像。但是这样是不是太麻烦了,docker 给我提供了简单方法:我们可以把这些命令保存到一个文件里DockerFile,然后让 docker 加载这个文件,生成新的镜像文件,并且启动容器。Docker 提供的 Dockerfile 是一个类似
Makefile的工具,主要用来自动化构建镜像。既然能自动化创建镜像,那么我们何必去手动创建镜像呢。本文用来讲解Dockerfile 的用法、语法,并且提供一个实例用以更深入地了解 Dockerfile。贴一个 Dockerfile 的实例然后开始正文:
# Memcached # # VERSION 2.2 # use the ubuntu base image provided by dotCloud FROM ubuntu MAINTAINER Victor Coisne victor.coisne@dotcloud.com # make sure the package repository is up to date RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list RUN apt-get update # install memcached RUN apt-get install -y memcached # Launch memcached when launching the container ENTRYPOINT ["memcached"] # run memcached as the daemon user USER daemon # expose memcached port EXPOSE 11211
格式
Dockerfile 中所有的命令都是以下格式:INSTRUCTION argument指令(INSTRUCTION)不分大小写,但是推荐大写。
FROM 命令 :添加基础镜像
FROM <image name>,例如
FROM ubuntu所有的 Dockerfile 都用该以
FROM开头,
FROM命令指明Dockerfile 所创建的镜像文件以什么镜像为基础,
FROM以后的所有指令都会在
FROM的基础上进行创建镜像;可以在同一个Dockerfile 中多次使用
FROM命令用于创建多个镜像。
MAINTAINER 命令 :记录维护者
MAINTAINER <author name>用于指定镜像创建者和联系方式。例如
MAINTAINER Victor Coisne victor.coisne@dotcloud.com
RUN 命令 :生成镜像要执行的命令
RUN <command>用于容器内部执行命令。每个
RUN命令相当于在原有的镜像基础上添加了一个改动层,原有的镜像不会有变化。
ADD 命令:向容器内添加文件或者目录
ADD <src> <dst>用于将
<src>文件复制到
<dst>文件:
<src>是相对被构建的源目录的相对路径,可以是文件或目录的路径,也可以是一个远程的文件url,
<dst>是容器中的绝对路径。
CMD 命令 : 启动容器默认执行的命令 ,和run 不同,run 是生成镜像要执行的命令,cmd 是 启动生成后的镜像的时候自动执行的命令。
CMD命令有三种格式:
CMD ["executable","param1","param2"]:推荐使用的exec 形式。
CMD ["param1","param2"]:无可执行程序形式
CMD command param1 param2:shell 形式。
CMD命令用于启动容器时默认执行的命令,
CMD命令可以包含可执行文件,也可以不包含可执行文件:不包含可执行文件的情况下就要用
ENTRYPOINT指定一个,然后
CMD命令的参数就会作为
ENTRYPOINT的参数。一个 Dockerfile 中只能有一个
CMD,如果有多个,则最后一个生效。
CMD的 shell 形式默认调用
/bin/sh -c执行命令。
CMD命令会被 Docker 命令行传入的参数覆盖:
dockerrun busybox /bin/echo Hello Docker会把
CMD里的命令覆盖。命令行传入的算是最后一个cmd 命令,最后一个有效。如果传入的参数里,没有可执行文件,则这些参数就作为entrypoint的参数,如果传入的参数里,包含可执行文件,则entrypoint 不执行。docker run busybox 不加启动命令的时候,会执行镜像busybox 默认的启动命令(busybox 也是通过dockerFile 创建的,也可以指定启动执行的命令),这个时候,如果 如果默认命令不包含可执行文件,需要找到entrypoint 作为可执行文件,默认命令作为 参数。
ENTRYPOINT 命令
ENTRYPOINT命令的字面意思是进入点,而功能也恰如其意:他可以让你的容器表现得像一个可执行程序一样。
ENTRYPOINT命令也有两种格式:
ENTRYPOINT ["executable", "param1", "param2"]:推荐使用的exec 形式
ENTRYPOINT command param1 param2:shell形式一个 Dockerfile 中只能有一个
ENTRYPOINT,如果有多个,则最后一个生效。关于
CMD和
ENTRYPOINT的联系请看下面的例子仅仅使用
ENTRYPOINT:
FROM ubuntuENTRYPOINT ls -l执行
docker run 306cd7e8408b /etc/fstab和
dockerrun 306cd7e8408b结果并不会有什么差别:
命令 # docker run 306cd7e8408b /etc/fstabtotal 64drwxr-xr-x 2 root root 4096 Mar 20 05:22 bindrwxr-xr-x 2 root root 4096 Apr 10 2014 bootdrwxr-xr-x 5 root root 360 Apr 24 02:52 devdrwxr-xr-x 64 root root 4096 Apr 24 02:52 etcdrwxr-xr-x 2 root root 4096 Apr 10 2014 home……但是我们通常使用
ENTRYPOINT作为容器的入口,使用
CMD给
ENTRYPOINT增加默认选项:
FROM ubuntuCMD ["-l"]ENTRYPOINT ["ls"]然后执行这个容器:不加参数便会默认有
-l参数:
命令 # docker run 89dc7e6d0ac1total 64drwxr-xr-x 2 root root 4096 Mar 20 05:22 bindrwxr-xr-x 2 root root 4096 Apr 10 2014 bootdrwxr-xr-x 5 root root 360 Apr 24 02:47 devdrwxr-xr-x 64 root root 4096 Apr 24 02:47 etcdrwxr-xr-x 2 root root 4096 Apr 10 2014 homedrwxr-xr-x 12 root root 4096 Mar 20 05:21 libdrwxr-xr-x 2 root root 4096 Mar 20 05:20 lib64drwxr-xr-x 2 root root 4096 Mar 20 05:19 mediadrwxr-xr-x 2 root root 4096 Apr 10 2014 mntdrwxr-xr-x 2 root root 4096 Mar 20 05:19 optdr-xr-xr-x 386 root root 0 Apr 24 02:47 procdrwx------ 2 root root 4096 Mar 20 05:22 rootdrwxr-xr-x 7 root root 4096 Mar 20 05:21 rundrwxr-xr-x 2 root root 4096 Apr 21 22:18 sbindrwxr-xr-x 2 root root 4096 Mar 20 05:19 srvdr-xr-xr-x 13 root root 0 Apr 24 02:47 sysdrwxrwxrwt 2 root root 4096 Mar 20 05:22 tmpdrwxr-xr-x 11 root root 4096 Apr 21 22:18 usrdrwxr-xr-x 12 root root 4096 Apr 21 22:18 var加了
/etc/fstab参数便会覆盖原有的
-l参数:
命令 # docker run 89dc7e6d0ac1 /etc/fstab/etc/fstab
EXPOSE 命令
EXPOSE <port> [<port>...]命令用来指定对外开放的端口。例如
EXPOSE 80 3306,开放
80和
3306端口。
WORKDIR命令 : 设置执行 Run,cmd entrypoint 命令的工作路径。
WORKDIR /path/to/work/dir配合
RUN,
CMD,
ENTRYPOINT命令设置当前工作路径。可以设置多次,如果是相对路径,则相对前一个
WORKDIR命令。默认路径为
/。例如:
FROM ubuntuWORKDIR /etcWORKDIR ..WORKDIR usrWORKDIR libENTRYPOINT pwd
docker run ID得到的结果为:
/usr/lib
USER命令 : 指定执行命令的用户
USER <UID/Username>为容器内指定
CMD
RUN
ENTRYPOINT命令运行时的用户名或UID。
VLOUME 命令 : 暴露某个目录给其他容器访问
VOLUME ['/data']允许容器访问容器的目录、允许容器之间互相访问目录。
VOLUME仅仅是允许将某一个目录暴露在外面,更多的操作还需要依赖
Docker命令实现。更多的内容可以参考 深入理解 Docker Volume(一)
ENV 命令 : 设置环境变量
参考export的用法咧:
ENV LC_ALL en_US.UTF-8
onbuild 命令:ONBUILD [INSTRUCTION]配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。例如,Dockerfile 使用如下的内容创建了镜像 image-A:
[...]ONBUILD ADD . /app/srcONBUILD RUN /usr/local/bin/python-build --dir /app/src[...]如果基于 image-A 创建新的镜像时,新的 Dockerfile 中使用 FROM image-A 指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令。
# Automatically run the followingADD . /app/srcRUN /usr/local/bin/python-build --dir /app/src
实例
Dockerfile 的写法已经讲述完毕,这儿有一个示例的 Dockerfile:#DockerfileFROM centos6-base#指定centos6系统MAINTAINER zhou_mfk <zhou_mfk@163.com>#我抄的他的 DockerfileRUN ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_keyRUN ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key#创建私钥RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd#修复SSH登录,否则登陆后的用户会被秒退。RUN mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh#创建root用户的ssh文件夹EXPOSE 22#开放端口RUN echo 'root:redhat' | chpasswd#root用户改密码为redhatRUN yum install -y yum-priorities && rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6RUN yum install tar gzip gcc vim wget screen -y#安装epel和安装一些软件ENV LANG en_US.UTF-8ENV LC_ALL en_US.UTF-8#系统环境变量CMD ["/usr/sbin/sshd", "-D"]#启动sshd#End
最佳实践
所有应用都会有个最佳的方式,Dockerfile 也不例外,下面是我们总结出的最佳实现方式:把维护者和更新系统的命令依次写在最上方使用标签管理 Dockerfile避免映射公共端口,映射端口并不属于 Dockerfile 的工作范围使用类似array形式的
CMD和
ENTRYPOINT
使用.dockerignore文件为了在docker build过程中更快上传和更加高效,应该使用一个.dockerignore文件用来排除构建镜像时不需要的文件或目录。例如,除非.git在构建过程中需要用到,否则你应该将它添加到.dockerignore文件中,这样可以节省很多时间。6 避免安装不必要的软件包为了降低复杂性、依赖性、文件大小以及构建时间,应该避免安装额外的或不必要的包。例如,不需要在一个数据库镜像中安装一个文本编辑器。7 每个容器都跑一个进程在大多数情况下,一个容器应该只单独跑一个程序。解耦应用到多个容器使其更容易横向扩展和重用。如果一个服务依赖另外一个服务,可以参考 Linking Containers Together。8 最小化层我们知道每执行一个指令,都会有一次镜像的提交,镜像是分层的结构,对于Dockerfile,应该找到可读性和最小化层之间的平衡。9 多行参数排序如果可能,通过字母顺序来排序,这样可以避免安装包的重复并且更容易更新列表,另外可读性也会更强,添加一个空行使用\换行:RUN apt-get update && apt-get install -y \ bzr \ cvs \ git \ mercurial \ subversion10 创建缓存镜像构建过程中会按照Dockerfile的顺序依次执行,每执行一次指令 Docker 会寻找是否有存在的镜像缓存可复用,如果没有则创建新的镜像。如果不想使用缓存,则可以在docker build时添加--no-cache=true选项。从基础镜像开始就已经在缓存中了,下一个指令会对比所有的子镜像寻找是否执行相同的指令,如果没有则缓存失效。在大多数情况下只对比Dockerfile指令和子镜像就足够了。ADD和COPY指令除外,执行ADD和COPY时存放到镜像的文件也是需要检查的,完成一个文件的校验之后再利用这个校验在缓存中查找,如果检测的文件改变则缓存失效。RUN apt-get -y update命令只检查命令是否匹配,如果匹配就不会再执行更新了。为了有效地利用缓存,你需要保持你的 Dockerfile 一致,并且尽量在末尾修改。FROM: 只要可能就使用官方镜像库作为基础镜像RUN: 为保持可读性、方便理解、可维护性,把长或者复杂的RUN语句使用\分隔符分成多行不建议RUN apt-get update独立成行,否则如果后续包有更新,那么也不会再执行更新避免使用RUN apt-get upgrade或者dist-upgrade,很多必要的包在一个非privileged权限的容器里是无法升级的。如果知道某个包更新,使用apt-get install -y xxx标准写法RUN apt-get update && apt-get install -y package-bar package-foo例子:
RUN apt-get update && apt-get install -y \aufs-tools \automake \btrfs-tools \build-essential \curl \dpkg-sig \git \iptables \libapparmor-dev \libcap-dev \libsqlite3-dev \lxc=1.0* \mercurial \parallel \reprepro \ruby1.9.1 \ruby1.9.1-dev \s3cmd=1.1.0*CMD: 推荐使用CMD [“executable”, “param1”, “param2”…]这种格式,CMD [“param”, “param”]则配合ENTRYPOINT使用EXPOSE: Dockerfile 指定要公开的端口,使用docker run时指定映射到宿主机的端口即可ENV: 为了使新的软件更容易运行,可以使用ENV更新PATH变量。如ENV PATH /usr/local/nginx/bin:$PATH确保CMD ["nginx"]即可运行ENV也可以这样定义变量:
ENV PG_MAJOR 9.3ENV PG_VERSION 9.3.4RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATHADDorCOPY:ADD比COPY多一些特性「tar 文件自动解包和支持远程 URL」,不推荐添加远程 URL如不推荐这种方式:
ADD http://example.com/big.tar.xz /usr/src/things/RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/thingsRUN make -C /usr/src/things all推荐使用 curl 或者 wget 替换,使用如下方式:
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如果不需要添加 tar 文件,推荐使用COPY
相关文章推荐
- 使用dockerfile创建支持ssh远程的镜像
- docker学习笔记(六)使用Dockerfile构建镜像
- Docker学习笔记(3)-- 如何使用Dockerfile构建镜像
- docker 使用Dockerfile 创建带jdk 和tomcat的镜像
- 如何使用Dockerfile构建镜像
- 使用dockerfile创建支持ssh远程的镜像
- 「二」创建一个带 ssh 服务的基础镜像(修订版)--使用 Dockerfile 创建
- 使用Dockerfile创建简单java应用镜像
- 使用Dockerfile创建自己的镜像
- Docker学习笔记(3)-- 如何使用Dockerfile构建镜像
- Docker学习笔记(3)-- 如何使用Dockerfile构建镜像
- Docker--使用Dockerfile创建镜像--RHEL7.2
- 使用Dockerfile创建支持ssh服务自启动的容器镜像
- Centos上Docker 使用dockerfile构建容器实现ssh
- [基础] Mac OS下使用docker 之使用docker file创建镜像
- Docker--使用Dockerfile构建新镜像--RHEL7.2
- 使用Dockerfile构建镜像
- 使用Dockerfile构建镜像
- Docker使用Dockerfile创建支持ssh服务自启动的容器镜像
- docker学习笔记4.1-使用Dockerfile文件构建镜像