镜像的定制实际上就是定制每一层所添加的配置、文件
如果我们可以把一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像
那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决
这个脚本就是dockerfile
以定制nginx镜像为例:
建立目录、文件文件,命名为Dockerfile:
$ mkdir mynginx $ cd mynginx $ touch Dockerfile
其内容为:
FROM nginx RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
FROM:指定基础镜像
在docker hub上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类镜像,如nginx、redis、mongo、mysql、httpd、php、tomcat等
也有一些方便开发、构建、运行各种语言应用的镜像,如:node、openjdk、python、ruby、golang等
如果没有找到对应服务的镜像,官方镜像还提供了一些更为基础的操作系统镜像,如ubuntu、debian、centos、fedora、alpine等
除了选择现有镜像为基础镜像外,docker还存在一个特殊的镜像,名为scrath。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
FROM scratch
以上意味着不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在
不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,对于Linux下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了
因此直接FROM SCRATCH 会让镜像提及更加小巧。
使用go语言开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为Go特别适合容器微服务架构的语言原因之一。
RUN命令:用来执行命令行命令的,有两种格式:
shell格式:RUN <命令>,就像直接在命令行中输入的命令一样。
goRUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
exec格式:RUN["可执行文件","参数1","参数2"],这更像是函数调用中的格式 既然RUN就像shell脚本一样可以执行命令,那么我们是否就可以像shell脚本一样把每个命令对应一个run呢?
例如:
FROM debian:stretch RUN apt-get update RUN apt-get install -y gcc libc6-dev make wget RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" RUN mkdir -p /usr/src/redis RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 RUN make -C /usr/src/redis RUN make -C /usr/src/redis install
过程:新建立一层,在其上执行这些命令,执行结束后,commit这一层的修改,构成新的镜像。
而上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。 这是很多初学 Docker 的人常犯的一个错误。
Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。
正确的写法是:
FROM debian:stretch RUN set -x; buildDeps='gcc libc6-dev make wget' \ && apt-get update \ && apt-get install -y $buildDeps \ && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \ && mkdir -p /usr/src/redis \ && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \ && make -C /usr/src/redis \ && make -C /usr/src/redis install \ && rm -rf /var/lib/apt/lists/* \ && rm redis.tar.gz \ && rm -r /usr/src/redis \ && apt-get purge -y --auto-remove $buildDeps
在Dockerfile文件所在目录执行:
$ docker build -t nginx:v3 . Sending build context to Docker daemon 2.048 kB Step 1 : FROM nginx ---> e43d811ce2f4 Step 2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html ---> Running in 9cdc27646c7b ---> 44aa4490ce2c Removing intermediate container 9cdc27646c7b Successfully built 44aa4490ce2c
从命令的结果中,我们可以清晰的看到镜像的构建过程。
在step2中,run指令启动了一个容器的9cdc27646c7b,执行了所要求的命令,并最后提交了这一层 44aa4490ce2c,随后删除了所用到的这个容器 9cdc27646c7b。
docker build 命令进行构建,格式为:
docker build [选项] <上下文路径/URL/->
上述命令中 docker build命令最后有一个. 。.表示当前目录,而Dockerfile就在当前目录。
首先要理解docker build的工作原理
docker在运行时分为Docker引擎(也就是服务端守护进程)和客户端工具
docker的引擎提供了一组REST API,被称为Docker Remote API,而如docker命令这样的客户端工具,则是通过这组API与Docker引擎交互,从而完成各种功能。
因此,虽然表面上我们好像是在本机执行各种docker功能,但实际上,一切都是使用的远程调用的形式在服务器端(Docker引擎)
也因为这种C/S设计,让操作远程服务器的docker引擎变得轻而易举
当我们进行镜像构建的时候,并非所有定制都会通过RUN指令完成,经常会需要将一些本地文件复制进镜像。
比如:通过COPY命令、ADD指令等,而docker build命令构建镜像,其实并非在本地构建,而是在服务端,也就是docker引擎中构建的
那么在这种客户端/服务端的架构中,如何才能让服务器获得本地文件呢?
这就引入了上下文的概念。
当构建的时候,用户会制定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给docker引擎。
这样说ddocker引擎收到这个上下包后,展开就会获得构建镜像所需的一切。
COPY ./package.json /app/
这并不是要复制执行 docker build 命令所在的目录下的 package.json,也不是复制 Dockerfile 所在目录下的 package.json,而是复制 上下文(context) 目录下的 package.json。
因此,COPY这类指令中的源文件的路径是相对路径。
这也是经常会问为什么COPY ../package.json/app 或者COPY /opt/xxxx/app 无法工作的原因。
因为这些路径超出了上下文范围,Docker引擎无法获得这些位置的文件。如果真的需要这些文件,应该将他们复制到上下文目录中去。
现在就可以理解刚才的命令docker build -t nginx
.中的这个.,实际上是在指定上下文的目录,docker build 命令会将该目录下的内容打包交给Docker引擎以帮助构建镜像。如果观察docker build 输出,我们可以看到这个发送上下文的过程:
$ docker build -t nginx:v3 . Sending build context to Docker daemon 2.048 kB ...
千万不要把Dockerfile放到硬盘根目录去构建,这样会导致docker build 将整个硬盘打包
一般来说,应该会将Dockerfile置于一个空目录西,或者项目根目录下。
如果该目录下没有所需文件,按么应该把所需文件复制一份过来
如果目录下这些东西不希望构建时,传给Docker引擎,那么可以用.gitignore一样的语法写一个.dockerignore,该文件是用于剔除不需要作为上下文传递给docker引擎的。
那么为什么会有人误以为.是指定Dockerfile所在目录呢?这是因为在默认情况下,如果不额外指定Dockerfile的话,会将上下文目录下的名为Dockerfile的文件作为Dockerfile。
当然,一般大家习惯性的会使用默认的文件名dockerfile,以及会将其置于镜像构建上下文目录中
其他用法: 直接用git repo进行构建
本文作者:Eric
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!