构建镜像

前面我们使用各种镜像进行测试演示,很多情况下我们是需要自己的镜像,满足自己业务需要的镜像,这就需要我们能够定制自己需要的镜像,构建 Docker 镜像有以下两种方法。

  • 使用 docker commit 命令。
  • 使用 docker build 命令和 Dockerfile 构建文件。

现在我们不推荐使用 docker commit 命令,而应该使用更灵活、更强大的 Dockerfile 来构建 Docker 镜像。

1、使用 commit 命令构建

docker commit 命令是创建新镜像最直观的方法,其过程包含三个步骤:

  1. 运行容器;
  2. 修改容器;
  3. 将容器保存为新的镜像。

先从创建一个新容器开始,这个容器我们就使用很常见的 ubuntu 镜像,操作步骤如下

1.1 运行一个要进行修改的容器

root@ubuntu:~# docker run -ti ubuntu /bin/bash
root@733a4b080491:/#

1.2 安装 Apache 软件包

root@733a4b080491:/# apt-get update
... ...
root@733a4b080491:/# apt-get install -y apache2
... ...

我们启动了一个容器,并在里面安装了 Apache 。我们将会拿这个容器作为一个 Web 服务器来运行,我们需要把它保存下来,这样就不用每次都运行这个步骤了。

1.3 提交定制容器

root@ubuntu:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
733a4b080491 ubuntu "/bin/bash" 11 minutes ago Exited (0) 5 seconds ago suspicious_mestorf
root@ubuntu:~# docker commit 733a4b080491 wzlinux/ubuntu_with_apache
sha256:902ac2c87147fefc5b70c741ce9550dcda426cea9f824f442d5cc2744bdc90ae
root@ubuntu:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
wzlinux/ubuntu_with_apache latest 902ac2c87147 33 seconds ago 261MB
ubuntu latest 20c44cd7596f 10 days ago 123MB

可以看到,我们使用 docker commit 提交了修改过的容器,从 size 上可以看到镜像因为安装软件而变大了,docker commit 提交的只是创建容器的镜像与容器的当前状态之间有差异的部分,这使得该更新非常轻量。

以上演示了如何用 docker commit 创建新镜像。然而,Docker 并不建议用户通过这种方式构建镜像。因为这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 debian base 镜像中也加入 apache,还得重复前面的所有步骤。更重要的:使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。

不过,为了对 Docker 有一个更全面的了解,我们还是要了解一下如何使用 docker commit 构建 Docker 镜像。因为即便是用 Dockerfile(推荐方法)构建镜像,底层也 docker commit 一层一层构建新镜像的。学习 docker commit 能够帮助我们更加深入地理解构建过程和镜像的分层结构。

2、使用 Dockerfile 构建

Dockerfile 使用基本的基于DSL(Domain Specific Language)语法的指令来构建一个 Docker 镜像,我们推荐使用 Dockerfile 方法来代替 docker commit,因为通过前者构建镜像更具备可重复性、透明性以及幂等性。

一旦有了 Dockerfile,我们就可以使用 docker build 命令基于该 Dockerfile 中的指令构建一个新的镜像。

2.1 我们的第一个 Dockerfile

用 Dockerfile 创建上面的 ubuntu_with_apache,内容如下。

# Version 0.0.1
FROM ubuntu
RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
RUN sed -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
RUN apt-get -y update && apt-get -y install apache2
EXPOSE 80

执行 docker build 命令时,Dockerfile 中的所有指令都会被执行并且提交,并且在该命令成功结束后返回一个新镜像。

root@ubuntu:~/sample# docker build -t ubuntu_with_apache_dockerfile .      ①
Sending build context to Docker daemon 6.144kB ②
Step 1/5 : FROM ubuntu ③
---> 20c44cd7596f
Step 2/5 : RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
---> Running in bac6dc3b900f
---> c66ad94ad8a4
Removing intermediate container bac6dc3b900f
Step 3/5 : RUN sed -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
---> Running in 5158558b6403
---> 0a4c480147c5
Removing intermediate container 5158558b6403
Step 4/5 : RUN apt-get -y update && apt-get -y install apache2 ④
---> Running in f547ce7a1b39 ⑤
……
……
---> 118bde35120a ⑥
Removing intermediate container f547ce7a1b39 ⑦
Step 5/5 : EXPOSE 80
---> Running in e546786de05b
---> f55d7b07365b
Removing intermediate container e546786de05b
Successfully built f55d7b07365b ⑧
Successfully tagged ubuntu_with_apache_dockerfile:latest

① 运行 docker build 命令,-t 将新镜像命名为 ubuntu-with-apache-dockerfile,命令末尾的 . 指明 build context 为当前目录。Docker 默认会从 build context 中查找 Dockerfile 文件,我们也可以通过 -f 参数指定 Dockerfile 的位置。

② 从这步开始就是镜像真正的构建过程。 首先 Docker 将 build context 中的所有文件发送给 Docker daemon。build context 为镜像构建提供所需要的文件或目录。

Dockerfile 中的 ADD、COPY 等命令可以将 build context 中的文件添加到镜像。此例中,build context 为当前目录 /sample,该目录下的所有文件和子目录都会被发送给 Docker daemon。

所以,使用 build context 就得小心了,不要将多余文件放到 build context,特别不要把 /、/usr 作为 build context,否则构建过程会相当缓慢甚至失败。

③ Step 1:执行 FROM,将 ubuntu 作为 base 镜像。

ubuntu 镜像 ID 为 20c44cd7596f。

④ Step 4:执行 RUN,安装 apache,具体步骤为 ⑤ ⑥ ⑦。

⑤ 启动 ID 为 f547ce7a1b39 的临时容器,在容器中通过 apt-get 安装 apache。

⑥ 安装成功后,将容器保存为镜像,其 ID 为 118bde35120a。

这一步底层使用的是类似 docker commit 的命令。

⑦ 删除临时容器 f547ce7a1b39。

⑧ 镜像构建成功。

通过 docker images 查看镜像信息。

root@ubuntu:~/sample# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu_with_apache_dockerfile latest f55d7b07365b 27 minutes ago 261MB
wzlinux/ubuntu_with_apache latest 902ac2c87147 About an hour ago 261MB
ubuntu latest 20c44cd7596f 10 days ago 123MB

ubuntu_with_apache_dockerfile 的镜像 ID 为 f55d7b07365b,与构建时的输出一致。

2.2 查看镜像分成结构

ubuntu_with_apache_dockerfile 是通过在 base 镜像的顶部添加几个新的镜像层而得到的。



每条指令都会创建一个镜像层。这一点我们可以通过 docker history 命令验证。



docker history 会显示镜像的构建历史,也就是 Dockerfile 的执行过程。

ubuntu_with_apache_dockerfile 与 ubuntu 镜像相比,确实只是多了几层,Dockerfile 中的每个指令都会创建一层,docker history 也向我们展示了镜像的分层结构,每一层由上至下排列。

2.3 镜像的缓存特性

由于每一步的构建过程都会将结果提交为镜像,所以 Docker 的构建镜像过程就显得非常聪明。它会将之前的镜像层看作缓存。

比如我们把 EXPOSE 80 改为 EXPOSE 8080。

root@ubuntu:~/sample# docker build -t ubuntu_with_apache_8080 .
Sending build context to Docker daemon 6.144kB
Step 1/5 : FROM ubuntu
---> 20c44cd7596f
Step 2/5 : RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
---> Using cache
---> c66ad94ad8a4
Step 3/5 : RUN sed -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
---> Using cache
---> 0a4c480147c5
Step 4/5 : RUN apt-get -y update && apt-get -y install apache2
---> Using cache
---> 118bde35120a
Step 5/5 : EXPOSE 8080
---> Running in c89f8210c56a
---> ac88967e578e
Removing intermediate container c89f8210c56a
Successfully built ac88967e578e
Successfully tagged ubuntu_with_apache_8080:latest

我们可以看到,之前的指令都是一样的,所以 docker 直接利用之前的缓存,只构建我们更改的指令,新的镜像层如下。



如果我们希望在构建镜像时不使用缓存,可以在 docker build 命令中加上 --no-cache 参数。

Dockerfile 中每一个指令都会创建一个镜像层,上层是依赖于下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。

也就是说,如果我们改变 Dockerfile 指令的执行顺序,或者修改或添加指令,都会使缓存失效。

比如我们在前面添加指令 MAINTAINER wzlinux "admin@wzlinux.com"。如下:

# Version 0.0.1
FROM ubuntu
MAINTAINER wzlinux "admin@wzlinux.com"
RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
RUN sed -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
RUN apt-get -y update && apt-get -y install apache2
EXPOSE 80

然后使用docker进行构建,查看其过程。

root@ubuntu:~/sample# docker build -t ubuntu_with_apache_author .
Sending build context to Docker daemon 6.144kB
Step 1/6 : FROM ubuntu
---> 20c44cd7596f
Step 2/6 : MAINTAINER wzlinux "admin@wzlinux.com"
---> Running in 637bb3457407
---> 829b24531d69
Removing intermediate container 637bb3457407
Step 3/6 : RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
---> Running in 416ae8aefb61
---> 84643fe8447a
Removing intermediate container 416ae8aefb61
Step 4/6 : RUN sed -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
---> Running in 58d8375fd5c3
---> 1cb5776982d3
Removing intermediate container 58d8375fd5c3
Step 5/6 : RUN apt-get -y update && apt-get -y install apache2
---> Running in 0514a7d04814
……
……
Processing triggers for sgml-base (1.26+nmu4ubuntu1) ...
---> 30eb21527fee
Removing intermediate container 0514a7d04814
Step 6/6 : EXPOSE 80
---> Running in 476ca5f98886
---> 30672998f3d0
Removing intermediate container 476ca5f98886
Successfully built 30672998f3d0
Successfully tagged ubuntu_with_apache_author:latest



从输出的结果生成了很多新的镜像层,缓存已经失效。

2.4 调试 Dockerfile

包括 Dockerfile 在内的任何脚本和程序都会出错。有错并不可怕,但必须有办法排查,那我们测试一下在构建的过程中指令出现错误怎么办,比如我们把第二个sed指令写错了,写错了sd。

# Version 0.0.1
FROM ubuntu
MAINTAINER wzlinux "admin@wzlinux.com"
RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
RUN sd -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
RUN apt-get -y update && apt-get -y install apache2
EXPOSE 80

执行 docker build,如下。



Dockerfile 在执行第四步 RUN 指令时失败。我们可以利用第三步创建的镜像 84643fe8447a 进行调试,方式是通过 docker run -it 启动镜像的一个容器。

root@ubuntu:~/sample# docker run -ti 84643fe8447a /bin/bash
root@422ecce78664:/# sd
bash: sd: command not found

其实我们肯定不会傻到连 sd 不存在也不知道,我这里只是作为一个例子,其他更难的排错方法我们就使用这种方式。

2.5 Dockerfile 指令

  • FROM

    指定 base 镜像。
  • MAINTAINER

    设置镜像的作者,可以是任意字符串。
  • COPY

    将文件从 build context 复制到镜像。

    COPY 支持两种形式:

    COPY src dest

    COPY ["src", "dest"]

注意:src 只能指定 build context 中的文件或目录。

  • ADD

    与 COPY 类似,从 build context 复制文件到镜像。不同的是,如果 src 是归档文件(tar, zip, tgz, xz 等),文件会被自动解压到 dest。
  • ENV

    设置环境变量,环境变量可被后面的指令使用。例如:

    ENV MY_VERSION 1.3

    RUN apt-get install -y mypackage=$MY_VERSION
  • EXPOSE

    指定容器中的进程会监听某个端口,Docker 可以将该端口暴露出来。
  • VOLUME

    将文件或目录声明为 volume。
  • WORKDIR

    为后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令设置镜像中的当前工作目录。
  • RUN

    在容器中运行指定的命令。
  • CMD

    容器启动时运行指定的命令。

    Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。
  • ENTRYPOINT

    设置容器启动时运行的命令。

    Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT。

参考文档:https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/

Docker镜像的构建(五)的更多相关文章

  1. sqler sql 转rest api 的docker 镜像构建(续)使用源码编译

    sqler 在社区的响应还是很不错的,已经添加了好多数据库的连接,就在早上项目的包管理还没有写明确, 下午就已经有go mod 构建的支持了,同时也调整下docker 镜像的构建,直接使用git cl ...

  2. Docker镜像构建文件Dockerfile及相关命令介绍

    使用docker build命令或使用Docker Hub的自动构建功能构建Docker镜像时,都需要一个Dockerfile文件.Dockerfile文件是一个由一系列构建指令组成的文本文件,doc ...

  3. 使用Buildpacks高效构建Docker镜像

    1. 前言 Spring Boot 2.3.0.RELEASE 正式发布了几天了,其中有个新的特性:可以将Spring Boot应用代码直接打包为Docker镜像.这是什么科技?我赶紧去官网查了一番才 ...

  4. Docker学习(7) 构建镜像

    构建docker镜像 1 构建镜像的两种方式 1 通过容器构建镜像 2 通过Dockerfile构建镜像

  5. Spring Boot 2.6.0正式发布:默认禁止循环依赖、增强Docker镜像构建...

    昨天,Spring官方正式发布了Spring Boot今年最后一个特性版本:2.6.0 同时,也宣布了2.4.x版本的终结. 那么这个新版本又带来了哪些新特性呢?下面就一起跟着DD来看看吧! 重要特性 ...

  6. 【Docker】Maven打包SpringBoot项目成Docker镜像并上传到Harbor仓库(Eclipse、STS、IDEA、Maven通用)

    写在前面 最近,在研究如何使用Maven将SpringBoot项目打包成Docker镜像并发布到Harbor仓库,网上翻阅了很多博客和资料,发现大部分都是在复制粘贴别人的东西,没有经过实践的检验,根本 ...

  7. 二、docker 镜像容器常用操作(让我们用docker 溜得飞起)

    前言 上篇讲了我们如何安装docker,现在该我们一展拳脚的时候了.接下来让我们一起学习一下docker常见的操作,让我们能够会使用 docker. 基本概念 在讲使用之前,还是先将一下docker ...

  8. 管理2000+Docker镜像,Kolla是如何做到的

    根据 DockerHub 上的数据,整个 Kolla 项目管理的 镜像有 2000 多个,这么多的镜像,是怎么定义,又是如何构建的呢? 简介 我们一直在说的 Kolla,通常情况下泛指,包括了 Kol ...

  9. 前端 Docker 镜像体积优化

    如果 2019 年技术圈有十大流行词,容器化肯定占有一席之地,随着 Docker 的风靡,前端领域应用到 Docker 的场景也越来越多,本文主要来讲述下开源的分布式图数据库 Nebula Graph ...

随机推荐

  1. unordered_map初用

    unordered_map,顾名思义,就是无序map,STL内部实现了Hash 所以使用时可以当做STL的Hash表使用,时间复杂度可做到O(1)查询 在C++11前,使用unordered_map要 ...

  2. 搭建自己的博客(九):使用shell模式批量添加博客文章并增加分页功能

    想做个博客分页功能,但是没有太多的文章.所以使用shell命令行创建多篇文章. 1.打开pycharm下的terminal终端 python manage.py shell # 打开python终端 ...

  3. Set集合类

    1.1  Set.add方法——向Set集合添加对象 public static void main(String[] args) {  Set set = new HashSet();      / ...

  4. python 迷宫问题

    # -*- coding:utf- -*- from collections import deque # 引入队列 maze = [ [,,,,,,,,,], [,,,,,,,,,], [,,,,, ...

  5. CF1174B Ehab Is an Odd Person(排序+结论)

    做法 一个显然的结论就是如果至少有一个奇数和一个偶数,那么是可以随意调整的,也就是升序排序 否则不可以进行任何操作 Code #include<bits/stdc++.h> using n ...

  6. mysql5.7 源码安装步骤

    操作系统:centos 7 mysql版本:5.7  下载地址:https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.24-linux-gli ...

  7. centos6下安装docker

    安装docker对内核版本的要求很高,需要内核3.10以上. 一.docker卸载 查看内核版本: 如果不升级内核到3.10安装docker,后面会有很多奇怪的问题,像我就是拉取不到镜像. 以下我是r ...

  8. MQ Cannot convert from [[B] to [] for GenericMessage

    MQ消费端转换报错:主要错误信息:Caused by: org.springframework.messaging.converter.MessageConversionException: Cann ...

  9. GO语言反射

    反射可以作为了解,不必深入! 反射介绍 Go语音提供了一种机制在运行时更新变量和检查它们的值.调用它们的方法和它们支持的内在操作,但是在编译时并不知道这些变量的具体类型.这种机制被称为反射.反射也可以 ...

  10. webapi接口上传大文件

    通过WebApi或者MVC模式的接口上传文件时,总数报错 413 Request Entity Too Large IIS 404 服务未找到 解决方法: 1. 在web.config文件下找到sys ...