在上一篇中我们简单介绍了Docker镜像的获取与使用,其中在镜像制作中提到在实际使用中一定要用Dockerfile方式去创建镜像而不要用docker commit方式,那么我们该如何编写Dockerfile呢,在写Dockerfile时又有那些注意点呢?今天我们就来一起学习Dockerfile的编写。

一、什么是Dockerfile?

Dockerfile 是一个用来构建镜像的文本文件,其内容包含了一条条构建镜像所需的指令和说明。

二、从一个简单的例子开始

1、制作一个JDK镜像

我们首先通过制作一个简单的JDK镜像来感受Dockerfile的魅力,既然要制作JDK镜像,我们首先需要准备好需要的安装的JDK安装包,这里我们使用jdk-8u231-linux-x64.tar.gz,接下来就是Dockerfile的编写了:

  1. # 声明所使用的基础镜像
  2. FROM ubuntu
  3. # 指定构建镜像时的工作目录,后续命令都是基于此目录的,如果不存在则创建
  4. WORKDIR /opt/soft/jdk
  5. # 将jdk包复制并解压到/opt/soft/jdk目录下
  6. ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
  7. # 设置环境变量
  8. ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
  9. ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
  10. ENV PATH=$JAVA_HOME/bin:$PATH

ok,这样我们Dockerfile就编写完毕了,其中涉及到的指令含义后续会专门讲解,大家暂时不需要纠结。我们通过docker build命令制作镜像。

  1. $ sudo docker build -t ubuntu-jdk .
  2. Sending build context to Docker daemon 194.2MB
  3. Step 1/6 : FROM ubuntu
  4. latest: Pulling from library/ubuntu
  5. 35807b77a593: Pull complete
  6. Digest: sha256:9d6a8699fb5c9c39cf08a0871bd6219f0400981c570894cd8cbea30d3424a31f
  7. Status: Downloaded newer image for ubuntu:latest
  8. ---> fb52e22af1b0
  9. Step 2/6 : WORKDIR /opt/soft/jdk
  10. ---> Running in 1526a2a25872
  11. Removing intermediate container 1526a2a25872
  12. ---> e5b5ee6e0f89
  13. Step 3/6 : ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
  14. ---> f22f968c43cd
  15. Step 4/6 : ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
  16. ---> Running in a71c57c44b12
  17. Removing intermediate container a71c57c44b12
  18. ---> 876227810405
  19. Step 5/6 : ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
  20. ---> Running in 8ff7aefbc820
  21. Removing intermediate container 8ff7aefbc820
  22. ---> f04605d6e9c2
  23. Step 6/6 : ENV PATH=$JAVA_HOME/bin:$PATH
  24. ---> Running in 88f21ce05cc4
  25. Removing intermediate container 88f21ce05cc4
  26. ---> cb70bf70e1e9
  27. Successfully built cb70bf70e1e9
  28. Successfully tagged ubuntu-jdk:latest

我们先来学习下上面执行的docker build命令,其中 -t 将镜像重新命名为ubuntu-jdk,命令末尾的 “.”指明docker context是当前目录,Docker默认是从docker context中查找Dockerfile,所以一般情况下Dockerfile文件名我们是不需要修改的,如果我们修改了Dockerfile文件名,那一定记得使用 -f 参数去指定要使用的Dockerfile。

接下来我们分析下Docker镜像的创建过程:

1)Step 1:执行FROM,将ubuntu作为基础镜像,这里将tag为latest的ubuntu镜像拉取下来,镜像ID为fb52e22af1b0

2)Step 2:执行WORKDIR,设置工作目录,这里其实是首先启动ID为fb52e22af1b0的临时容器,然后在其中创建/opt/soft/jdk目录,创建完毕后删除此临时容器,并将此容器保存为ID是e5b5ee6e0f89的镜像。

3)Step 3:执行ADD,将jdk-8u231-linux-x64.tar.gz拷贝并解压到/opt/soft/jdk目录下,并保存为ID是f22f968c43cd的镜像。

4)Step 4 ~ Step 6:执行ENV,先开启临时容器,然后设置环境变量,最后保存为镜像。

5)最终镜像构建成功,镜像ID为cb70bf70e1e9,tag为ubuntu-jdk:latest。

2、查看镜像分层结构

大家看到上面是不是很懵逼,我明明只要制作一个镜像,怎么在执行Dockerfile时还创建了那么多镜像呢?其实在Dockerfile中,它的每条指令都会创建一个镜像层,执行操作后再将此镜像层保存。我们通过docker history可以很清楚看到这一点:

  1. $ sudo docker history ubuntu-jdk
  2. IMAGE CREATED CREATED BY SIZE COMMENT
  3. cb70bf70e1e9 22 minutes ago /bin/sh -c #(nop) ENV PATH=/opt/soft/jdk/jd… 0B
  4. f04605d6e9c2 22 minutes ago /bin/sh -c #(nop) ENV CLASSPATH=.:/opt/soft… 0B
  5. 876227810405 22 minutes ago /bin/sh -c #(nop) ENV JAVA_HOME=/opt/soft/j… 0B
  6. f22f968c43cd 22 minutes ago /bin/sh -c #(nop) ADD file:610ae1ffb70fff692… 403MB
  7. e5b5ee6e0f89 22 minutes ago /bin/sh -c #(nop) WORKDIR /opt/soft/jdk 0B
  8. fb52e22af1b0 12 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
  9. <missing> 12 days ago /bin/sh -c #(nop) ADD file:d2abf27fe2e8b0b5f… 72.8MB
  1. $ sudo docker history ubuntu
  2. IMAGE CREATED CREATED BY SIZE COMMENT
  3. fb52e22af1b0 12 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
  4. <missing> 12 days ago /bin/sh -c #(nop) ADD file:d2abf27fe2e8b0b5f… 72.8MB

我们可以看到ubuntu-jdk相比于ubuntu镜像多了很多层,这些层就是我们在执行WORKDIR、ADD、ENV指令时产生的。细心的同学可能发现了:既然我们在构建jdk镜像时创建了那么多镜像,那为什么通过docker image ls 命令只能看到基础镜像和jdk镜像呢?

  1. $ sudo docker image ls
  2. REPOSITORY TAG IMAGE ID CREATED SIZE
  3. ubuntu-jdk latest cb70bf70e1e9 39 minutes ago 476MB
  4. ubuntu latest fb52e22af1b0 12 days ago 72.8MB
  5. ubuntu 18.04 39a8cfeef173 6 weeks ago 63.1MB
  6. nginx 1.21.1 08b152afcfae 7 weeks ago 133MB

这里需要大家注意我们构建jdk镜像时产生的那些镜像都是中间镜像,这类镜像会被别的镜像所依赖,如果想看到这些镜像,我们要使用docker image ls -a命令:

  1. $ sudo docker image ls -a
  2. REPOSITORY TAG IMAGE ID CREATED SIZE
  3. <none> <none> f04605d6e9c2 42 minutes ago 476MB
  4. ubuntu-jdk latest cb70bf70e1e9 42 minutes ago 476MB
  5. <none> <none> 876227810405 42 minutes ago 476MB
  6. <none> <none> f22f968c43cd 42 minutes ago 476MB
  7. <none> <none> e5b5ee6e0f89 42 minutes ago 72.8MB
  8. ubuntu latest fb52e22af1b0 12 days ago 72.8MB
  9. ubuntu 18.04 39a8cfeef173 6 weeks ago 63.1MB
  10. nginx 1.21.1 08b152afcfae 7 weeks ago 133MB

3、镜像缓存特性

我们在制作jdk镜像时docker创建了多个中间层镜像,这些镜像一般都是会被重复利用的,无需重新构建,这样便能提升镜像构建效率,为了验证这一点,我们简单修改下我们的Dockerfile,我们在最后加一句java文件生成的操作:

  1. FROM ubuntu
  2. WORKDIR /opt/soft/jdk
  3. ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
  4. ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
  5. ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
  6. ENV PATH=$JAVA_HOME/bin:$PATH
  7. RUN touch test.java

接着我们构建镜像:

  1. $ sudo docker build -t ubuntu-jdk-2 .
  2. Sending build context to Docker daemon 194.2MB
  3. Step 1/7 : FROM ubuntu
  4. ---> fb52e22af1b0
  5. Step 2/7 : WORKDIR /opt/soft/jdk
  6. ---> Using cache
  7. ---> e5b5ee6e0f89
  8. Step 3/7 : ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
  9. ---> Using cache
  10. ---> f22f968c43cd
  11. Step 4/7 : ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
  12. ---> Using cache
  13. ---> 876227810405
  14. Step 5/7 : ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
  15. ---> Using cache
  16. ---> f04605d6e9c2
  17. Step 6/7 : ENV PATH=$JAVA_HOME/bin:$PATH
  18. ---> Using cache
  19. ---> cb70bf70e1e9
  20. Step 7/7 : RUN touch test.java
  21. ---> Running in e5f462e9c451
  22. Removing intermediate container e5f462e9c451
  23. ---> 7dc9fe245160
  24. Successfully built 7dc9fe245160
  25. Successfully tagged ubuntu-jdk-2:latest

大家可以很明显看出来,在构建过程中第1~6步都是用了镜像缓存,那我们能不能不使用缓存呢?当然可以我们只需要在构建时加入参数--no-cache即可:

  1. $ sudo docker build --no-cache -t ubuntu-jdk-3 .
  2. Sending build context to Docker daemon 194.2MB
  3. Step 1/7 : FROM ubuntu
  4. ---> fb52e22af1b0
  5. Step 2/7 : WORKDIR /opt/soft/jdk
  6. ---> Running in 8560b572d2ac
  7. Removing intermediate container 8560b572d2ac
  8. ---> 7cecd9874b7c
  9. Step 3/7 : ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
  10. ---> 01d1539300e6
  11. Step 4/7 : ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
  12. ---> Running in daa99a7adfe0
  13. Removing intermediate container daa99a7adfe0
  14. ---> 16e8e58ff40b
  15. Step 5/7 : ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
  16. ---> Running in e9d598aa1cd3
  17. Removing intermediate container e9d598aa1cd3
  18. ---> 868daae44996
  19. Step 6/7 : ENV PATH=$JAVA_HOME/bin:$PATH
  20. ---> Running in e13199a0ba85
  21. Removing intermediate container e13199a0ba85
  22. ---> 0e9732a41c0e
  23. Step 7/7 : RUN touch test.java
  24. ---> Running in 90d76edd935d
  25. Removing intermediate container 90d76edd935d
  26. ---> 2c73ecf1af57
  27. Successfully built 2c73ecf1af57
  28. Successfully tagged ubuntu-jdk-3:latest

三、常用的Dockerfile指令:

Docker为了方便我们制作镜像,提供了多种Dockerfile指令,接下来我们一起看看这些指令都是什么含义:

  • FORM:指定基础镜像;

  • MAINTAINER:设置镜像作者;

  • WORKDIR:设置构建镜像的工作目录,如果目录不存在则自动创建;

  • COPY:将文件从docker context拷贝到镜像;

  • ADD:与COPY类似,都是将文件从docker context拷贝到镜像,不同的是当文件是归档类型(tar、tar.gz、zip等)时,会自动解压到目标路径;

  • ENV:设置环境变量,并可被后面的指令使用;

  • EXPOSE:指定容器中进程会监听的端口,docker可将该端口暴露出来;

  • VOLUME:将文件或目录设置为volume;

  • RUN:在容器中运行指定的指令;

  • CMD:设置容器启动时运行的指令,当设置多条CMD指令时,只有最后一条生效;

  • ENTRYPOINT:设置容器启动时运行的命令,当设置多条ENTRYPOINT时,只有最后一条生效。

PS:RUN、CMD和ENTRYPOINT的区别:

1、RUN

执行命令并创建新的镜像层,通常用于镜像构建中软件包安装等操作。

2、CMD

为容器指定默认的启动执行命令,此命令会在容器启动且docker run没有指定其他命令时运行,也就是说CMD中的命令是可以在docker run中被其他命令所覆盖的。

3、ENTRYPOINT

和CMD很像,不同的是ENTRYPOINT指定的命令一定会被执行,即使docker run中指定了其他命令,这也就意味着ENTRYPOINT可以用于让容器以应用程序或服务的形式运行。

好了,以上就是本文的内容,关于Dockerfile的其它指令后续会在各个实践案例中逐步使用。

Docker 与 K8S学习笔记(四)—— Dockerfile的编写的更多相关文章

  1. Docker 与 K8S学习笔记(二十四)—— 工作负载的使用

    我们前面讲了很多关于Pod的使用,但是在实际应用中,我们不会去直接创建Pod,我们一般通过Kubernetes提供的工作负载(Deployment.DeamonSet.StatefulSet.Job等 ...

  2. Docker 与 K8S学习笔记(二十三)—— Kubernetes集群搭建

    小伙伴们,好久不见,这几个月实在太忙,所以一直没有更新,今天刚好有空,咱们继续k8s的学习,由于我们后面需要深入学习Pod的调度,所以我们原先使用MiniKube搭建的实验环境就不能满足我们的需求了, ...

  3. Docker 与 K8S学习笔记(二)—— 容器核心知识梳理

    本篇主要对容器相关核心知识进行梳理,通过本篇的学习,我们可以对容器相关的概念有一个全面的了解,这样有利于后面的学习. 一.什么是容器? 容器是一种轻量级.可移植.自包含的软件打包技术,使应用程序可以在 ...

  4. Docker 与 K8S学习笔记(二十二)—— 高效使用kubectl的小技巧

    kubectl作为我们主要的操作K8S的工具,其具备非常丰富的功能,但是如果不经过打磨,使用起来还是存在诸多不便,今天我们来看看如何将我们的kubectl打磨的更加易用. 一.命令自动补全 kubec ...

  5. Docker 与 K8S学习笔记(九)—— 容器间通信

    容器之间可通过IP.Docker DNS Server或joined三种方式进行通信,今天我们来详细学习一下. 一.IP通信 IP通信很简单,前一篇中已经有所涉及了,只要容器使用相同网络,那么就可以使 ...

  6. Docker 与 K8S学习笔记(七)—— 容器的网络

    本节我们来看看Docker网络,我们这里主要讨论单机docker上的网络.当docker安装后,会自动在服务器中创建三种网络:none.host和bridge,接下来我们分别了解下这三种网络: $ s ...

  7. Docker 与 K8S学习笔记(五)—— 容器的操作(下篇)

    上一篇我们学习了容器的启动和常用的进入容器的方式,今天我们来看看如何控制容器起停以及容器删除操作. 一.stop.kill.start和restart stop.kill命令都可以停止运行的容器,二者 ...

  8. Docker 与 K8S学习笔记(五)—— 容器的操作(上篇)

    上一篇我们介绍了Dockerfile的基本编写方法,这一节我们来看看Docker容器的常用操作. 一.容器的运行方式 容器有两种运行方式,即daemon形式运行与非daemon形式运行,通俗地讲就是长 ...

  9. Docker 与 K8S学习笔记(三)—— 镜像的使用

    前面的文章介绍过镜像的三种获取方式: 下载并使用别人创建好的镜像: 在现有镜像上创建新的镜像: 从无到有创建镜像. 本文主要介绍前两种. 一.下载镜像 在Docker Hub上有大量优质镜像可以使用, ...

随机推荐

  1. MapReduce04 框架原理Shuffle

    目录 2 MapReduce工作流程 3 Shuffle机制(重点) 3.1 Shuffle机制 3.2 Partition分区 默认Partitioner分区 自定义Partitioner分区 自定 ...

  2. A Child's History of England.3

    So, Julius Caesar came sailing over to this Island of ours, with eighty vessels and twelve thousand ...

  3. acclaim

    欲学acclaim,先学claim.即使学会claim,未必记住acclaim. [LDOCE] claim的词源是cry out, shoutverb:1. state that sth is tr ...

  4. 【STM8】SPI通讯

    这篇内容有点长,如果有人想透过我的博客学习STM8的SPI,那是我的荣幸 首先我要先说大纲,这样大家心里比较有底,可以把精力都用在SPI理解上 [SPI初步介绍]:介绍SPI如何接线.名称解释.通讯注 ...

  5. NERD_commenter快捷键

    快捷键有点多,记不过来,做个备份 1. \cc 注释当前行和选中行 2. \cn 没有发现和\cc有区别 3. \c<空格> 如果被选区域有部分被注释,则对被选区域执行取消注释操作,其它情 ...

  6. Swift alert 倒计时

    let title: String = "您的开奖时间为" let time: String = "2017-10-23 12:23:18" let count ...

  7. 最新的Android Sdk 使用Ant多渠道批量打包

    实例工程.所需的文件都在最后的附件中.    今天花费了几个小时,参考网上的资料,期间遇到了好几个问题, 终于实现了使用Ant批量多渠道打包,现在,梳理一下思路,总结使用Ant批量多渠道打包的方法:1 ...

  8. 在隐藏导航栏的控制器中,调用UIIMagePickerController,出现导航栏变透明的问题

    在隐藏导航栏的控制器中,调用UIIMagePickerController,出现导航栏变透明的问题 解决办法 #pragma mark - UIImagePickerController Delega ...

  9. java实现链式线性表

    package ch9; public class LinkList <T>{ private class Node { //保存节点的数据 private T data; //指向下一个 ...

  10. Redis操作命令合集

    目录 一.客户端命令 二.sql命令 一.客户端命令 #读取配置文件启动 redis-server redis.conf #关闭 Redis,Redis服务器将断开与客户端链接,产生持久化文件,平滑关 ...