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

一、什么是Dockerfile?

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

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

1、制作一个JDK镜像

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

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

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

$ sudo docker build -t ubuntu-jdk .
Sending build context to Docker daemon 194.2MB
Step 1/6 : FROM ubuntu
latest: Pulling from library/ubuntu
35807b77a593: Pull complete
Digest: sha256:9d6a8699fb5c9c39cf08a0871bd6219f0400981c570894cd8cbea30d3424a31f
Status: Downloaded newer image for ubuntu:latest
---> fb52e22af1b0
Step 2/6 : WORKDIR /opt/soft/jdk
---> Running in 1526a2a25872
Removing intermediate container 1526a2a25872
---> e5b5ee6e0f89
Step 3/6 : ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
---> f22f968c43cd
Step 4/6 : ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
---> Running in a71c57c44b12
Removing intermediate container a71c57c44b12
---> 876227810405
Step 5/6 : ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
---> Running in 8ff7aefbc820
Removing intermediate container 8ff7aefbc820
---> f04605d6e9c2
Step 6/6 : ENV PATH=$JAVA_HOME/bin:$PATH
---> Running in 88f21ce05cc4
Removing intermediate container 88f21ce05cc4
---> cb70bf70e1e9
Successfully built cb70bf70e1e9
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可以很清楚看到这一点:

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

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

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

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

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

3、镜像缓存特性

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

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

接着我们构建镜像:

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

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

$ sudo docker build --no-cache -t ubuntu-jdk-3 .
Sending build context to Docker daemon 194.2MB
Step 1/7 : FROM ubuntu
---> fb52e22af1b0
Step 2/7 : WORKDIR /opt/soft/jdk
---> Running in 8560b572d2ac
Removing intermediate container 8560b572d2ac
---> 7cecd9874b7c
Step 3/7 : ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
---> 01d1539300e6
Step 4/7 : ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
---> Running in daa99a7adfe0
Removing intermediate container daa99a7adfe0
---> 16e8e58ff40b
Step 5/7 : ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
---> Running in e9d598aa1cd3
Removing intermediate container e9d598aa1cd3
---> 868daae44996
Step 6/7 : ENV PATH=$JAVA_HOME/bin:$PATH
---> Running in e13199a0ba85
Removing intermediate container e13199a0ba85
---> 0e9732a41c0e
Step 7/7 : RUN touch test.java
---> Running in 90d76edd935d
Removing intermediate container 90d76edd935d
---> 2c73ecf1af57
Successfully built 2c73ecf1af57
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. [云原生]Docker - 安装&卸载

    目录 系统要求 卸载旧版本 安装Docker 方法一:通过repo安装 设置Repository 安装Docker Engine 升级Docker Engine 方法二:通过package安装 方法三 ...

  2. JavaScript设计模式,单例模式!

    单例设计模式:保证一个类仅有一个实例,并且提供一个访问它的全局访问点.有些对象只需要一个,这时可用单例模式. 传统的单例模式 和new 创建对象的调用不一样 调用者要调用xxx.getInstance ...

  3. 3.5 Rust Generic Types, Traits, and Lifetimes

    Every programming language has tools for effectively handling the duplication of concepts. In Rust, ...

  4. HelloWorldMBean

    package mbeanTest; public interface HelloWorldMBean { public String getHello(); public void setHello ...

  5. ORACLE CACHE BUFFER CHAINS原理

    原理图如下: 一个cache buffer chains 管理多个hash bucket,受隐含参数:_db_block_hash_buckets(控制管理几个hash bucket)

  6. ORACEL 创建DIRECTORY

    oracle要直接对文件进行读写必须先创建一个DIRECTORY. 语法如下: CREATE DIRECTORY UTL_FILE_DIR AS '/home/oracle/oradir'; 可以通过 ...

  7. js实现点击不同按钮切换内容

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. FindUserByPageServlet

    package com.hopetesting.web.servlet;import com.hopetesting.domain.PageBean;import com.hopetesting.do ...

  9. 【C/C++】从矩阵左上角走到右下角

    tx的笔试,但是只过了10%,就离谱 #include <bits/stdc++.h> using namespace std; const int maxn = 1010; long d ...

  10. Delphi编译报错对照表

    ';' not allowed before 'ELSE' → ElSE前不允许有";" " clause not allowed in OLE automation s ...