# Dockerfile

# 一、镜像

镜像可以看成是由多个镜像层叠加起来的一个文件系统(通过 UnionFSAUFS 文件联合系统实现),镜像层也可以简单理解为一个基本的镜像,而每个镜像层之间通过指针的形式进行叠加。

image-20210621184500330

根据上图,镜像层的主要组成部分包括:

  • 镜像层 ID
  • 镜像层指针 「指向父层」
  • 元数据「 Layer Metadata,包含了 Docker 构建和运行的信息和父层的层次信息」。

只读层和读写层「Top Layer」的组成部分基本一致,同时读写层可以转换成只读层「 通过docker commit 操作实现」。

**元数据(metadata)**就是关于这个层的额外信息,它不仅能够让 Docker 获取运行和构建时的信息,还包括父层的层次信息。需要注意,只读层和读写层都包含元数据。

image-20210621184550221

每一层都包括了一个指向父层的指针。如果一个层没有这个指针,说明它处于最底层。

image-20210621184607447

在 Docker 主机中镜像层(image layer)的元数据被保存在名为 “json” 的文件中,一个容器的元数据好像是被分成了很多文件,但或多或少能够在 /var/lib/docker/containers/ 目录下找到,就是一个可读层的 id。这个目录下的文件大多是运行时的数据,比如说网络,日志等等。

镜像是一堆只读层的统一视角,除了最底层没有指向外,每一层都指向它的父层。统一文件系统**(Union File System)**技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在。在用户的角度看来,只存在一个文件系统。镜像每一层都是不可写的,都是只读层。

image-20210621184704773

我们可以看到镜像包含多个只读层,它们重叠在一起。除了最下面一层,其它层都会有一个指针指向下一层。这些层是 Docker 内部的实现细节,并且能够在 Docker 主机的文件系统上访问到。统一文件系统(Union File System,升级版为 AUFS)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。

# 二、Dockerfile 介绍

Dockerfile 是由一系列命令和参数构成的脚本,这些命令应用于基础镜像并最终创建一个新的镜像。它们简化了从头到尾的流程并极大的简化了部署工作。Dockerfile 从 FROM 命令开始,紧接着跟随着各种方法,命令和参数。其产出为一个新的可以用于创建容器的镜像。

Dockerfile 语法由两部分构成

  • 注释
  • 命令+参数

# 三、Dockerfile 命令

# FROM

指定基础镜像,并且必须是第一条指令。

如果不以任何镜像为基础,写法为:FROM scratch 。同时意味着接下来所写的指令将作为镜像的第一层开始。

  • 语法:

    FROM 基础镜像:tag    #其中 :tag 是可选的,如果不写,那么默认值是 latest
    

# MAINTAINER

指定作者。

  • 语法:

    MAINTAINER  作者名
    

# LABEL

为镜像设置标签。

  • 语法:

    LABEL key1=value1 key2=value2 ...
    

一个 Dockerfile 可以有多个 LABLE,如下:

LABEL "com.example.vendor"="ACME Incorporated"LABEL com.example.label-with-value="foo"LABEL version="1.0"LABEL description="This text illustrates \that label-values can span multiple lines."# 但是并不建议这样写,最好就写成一行,如太长需要换行的话则使用 \符号# 如下:LABEL multi.label1="value1" \multi.label2="value2" \other="value3"# 注意:LABEL会继承基础镜像种的 LABEL,如遇到 key 相同,则值覆盖

# RUN

运行指定的命令。

  • 语法一:

    RUN shell命令
    
  • 语法二:

    RUN [“executable”, “param1”, “param2”]
    # 可将executable理解成为可执行文件,后面就是两个参数。
    

示例:

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOMERUN ["/bin/bash", “-c”, “echo hello”]

注意:

  • 多行命令不要写多个 RUN,原因是 Dockerfile 中每一个指令都会建立一层。RUN 书写时的换行符是 \ ,多少个 RUN 就构建了多少层镜像,会造成镜像的臃肿、多层,不仅仅增加了构件部署的时间,还容易出错。

# ADD

一个复制命令,把文件复制到镜像中

如果把虚拟机与容器想象成两台 Linux 服务器的话,那么这个命令就类似于 scp,只是 scp 需要加用户名和密码的权限验证,而 ADD 不用。

  • 语法:

    ADD <src> <desc>
    
    # <src>可以是一个本地文件或者是一个本地压缩文件,还可以是一个url
    # <dest>路径的填写可以是容器内的绝对路径,也可以是相对于工作目录的相对路径
    

示例:

ADD test1.txt test1.txtADD test1.txt test1.txt.bakADD test1.txt /mydir/ADD data1 data1ADD zip.tar /myzip

注意:

  1. 如果源路径是个文件,且目标路径是以 / 结尾, 则 Docker 会把目标路径当作一个目录,会把源文件拷贝到该目录下。如果目标路径不存在,则会自动创建目标路径。
  2. 如果源路径是个文件,且目标路径不是以 / 结尾,则 Docker 会把目标路径当作一个文件。
  3. 如果目标路径不存在,会以目标路径为名创建一个文件,内容同源文件;
  4. 如果目标文件是个存在的文件,会用源文件覆盖它,当然只是内容覆盖,文件名还是目标文件名。
  5. 如果目标文件实际是个存在的目录,则会源文件拷贝到该目录下。 注意,这种情况下,最好显示的以 / 结尾,以避免混淆。
  6. 如果源路径是个目录,且目标路径不存在,则 Docker 会自动以目标路径创建一个目录,把源路径目录下的文件拷贝进来。如果目标路径是个已经存在的目录,则 Docker 会把源路径目录下的文件拷贝到该目录下。
  7. 如果源文件是个归档文件(压缩文件),则 Docker 会自动帮解压。尽量不要把 <scr> 写成一个文件夹,如果 <src> 是一个文件夹了,复制整个目录的内容,包括文件系统元数据。

# COPY

复制命令。只能用在本地文件上。

# VOLUME

可实现挂载功能,可以将本地文件夹或者其他容器中的文件夹挂在到这个容器中。

  • 语法:

    VOLUME ["/data"]
    
  • 说明:["/data"]可以是一个JsonArray ,也可以是多个值。所以如下几种写法都是正确的

    • VOLUME ["/var/log/"]
    • VOLUME /var/log
    • VOLUME /var/log /var/db

一般的使用场景为需要持久化存储数据时, 容器使用的是 AUFS,这种文件系统不能持久化数据,当容器关闭后,所有的更改都会丢失,所以当数据需要持久化时用这个命令。

# EXPOSE

功能为暴漏容器运行时的监听端口给外部。

但是 EXPOSE 并不会使容器访问主机的端口。如果想使得容器与主机的端口有映射关系,必须在容器启动的时候加上 -P 参数。

# WORKDIR

设置容器的工作目录。

  • 语法:

    WORKDIR 工作目录
    

# ENV

设置环境变量。

  • 语法:

    ENV              # 1 次设置 1 个
    ENV = ...        # 1 次设置多个
    

# CMD

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

  • 语法:

    CMD [“executable”,“param1”,“param2”]
    
    CMD [“param1”,“param2”]
    
    CMD command param1 param2
    
  • 示例:

    CMD [ “sh”, “-c”, “echo $HOME”
    
    CMD [ “echo”, “$HOME” ]
    

注意:

  1. 这里边包括参数的一定要用双引号,就是 " 不能是单引号,原因是参数传递后,Docker 解析的是一个JSON Array。

  2. 不要把 RUN 和 CMD 搞混了。

    RUN:是构件容器时就运行的命令以及提交运行结果。

    CMD:是容器启动时执行的命令,在构件时并不运行。

# ENTRYPOINT

设置启动时的默认命令。

  • 语法:

    ENTRYPOINT [“executable”, “param1”, “param2”]
    
    ENTRYPOINT command param1 param2
    

注意:

# 与 CMD 比较说明:

1. 相同点:
只能写一条,如果写了多条,那么只有最后一条生效,容器启动时才运行,运行时机相同。

2. 不同点:
ENTRYPOINT 不会被运行的 command 覆盖,而 CMD 则会被覆盖


# 举例 1
如果我们在 Dockerfile 时同时写了 ENTRYPOINT 和 CMD ,并且 CMD 指令不是一个完整的可执行命令,那么 CMD 指定的内容将会作为 ENTRYPOINT 的参数, 如下:

FROM centos
ENTRYPOINT ["top", "-b"]
CMD ["-c"]


# 举例 2
如果我们在 Dockerfile 种同时写了 ENTRYPOINT 和 CMD ,并且 CMD 是一个完整的指令,那么它们两个会互相覆盖,谁在最后谁生效, 如下:

FROM centos
ENTRYPOINT ["top", "-b"]
CMD ls -al

那么将执行 ls -al , top -b 不会执行

# 四、Dockerfile 案例

基本步骤:

  1. 创建目录,用于存放 dockerfile 所使用的文件
  2. 在此目录中创建 dockerfile 文件
  3. 在此目录中使用 docker build 创建镜像
  4. 使用创建的镜像启动容器

# 1. 在 Docker 中部署 Go Web 项目

1. 先写一个简单的 go web 项目

进入到目录:/usr/local/gopath/src/docker_go

mkdir -p /usr/local/gopath/src/docker_go
cd /usr/local/gopath/src/docker_go

写一个 main.go

package main

import (
    "fmt"
    "net/http"
)

func main(){
    http.HandleFunc("/", hello)
    server := &http.Server{
      Addr: ":8888",
    }
    fmt.Println("server startup ....")
    if err := server.ListenAndServe(); err != nil {
      panic(err)
    }
}

func hello (w http.ResponseWriter, _ *http.Request){
	w.Write([]byte("hello hedon."))
}

2. 本地测试 go web 项目是否没问题

go build .
/docker_go
image-20210622104642709

3. 编写 Dockerfile

vim Dockerfile

写入:

FROM golang

#设置环境变量
ENV GO111MODULE=on \
    CGO_ENABLED=0 \
    GOOS=linux \
    GOARCH=amd64


#设置容器工作目录
WORKDIR /build

#将代码从本机复制到容器中
ADD . .

#先删除到之前的 go.mod 文件,不然会报错
RUN rm -rf ./*.mod
#执行 go mod init
RUN go mod init docker_go
#执行 go mod tidy 更新依赖
RUN go build -o app .

#容器中:移动到用于存放生成的二进制文件的 /dist 目录
WORKDIR /dist

#容器中:将二进制文件 app 从 /build 目录复制到 /dist 目录下
RUN cp /build/app .

#声明服务端口
EXPOSE 8888

#启动容器时运行 go web 项目
CMD ["/dist/app"]

4. 构建镜像

docker build -t goweb_app .

出现下面信息就表示构建成功了:

....
....
Successfully built 62879d6fbd56

5. 查看镜像

[root@VM-12-10-centos docker_go]docker imagesREPOSITORY           TAG                 IMAGE ID            CREATED             SIZEgoweb_app            latest              62879d6fbd56        2 minutes ago       894 MB

6. 运行镜像

docker run -p 8888:8888 goweb_app

7. 访问项目

image-20210622104642709
上次更新: 6/24/2021, 6:09:13 PM