文章目录

  • 一、关于制作Docker镜像?🐋
  • 1. Docker 镜像的构建原理和方式
  • 1.1 Docker 镜像的构建方式使用场景和选型
  • 二、Dockerfile快速开始
  • 什么是Dockerfile
  • Dockerfile的基本结构
  • Dockerfile 常用的指令
  • FROM命令
  • LABEL
  • WORKDIR
  • RUN
  • COPY
  • CMD
  • EXPOSE
  • ENTRYPOINT
  • CMD与ENTRYPOINT的关系
  • 工作中常用技巧总结
  • Dockerfile中使用变量
  • 如果现在我想在BUILD的时候,改变我的环境变量,而不是每次RUN的时候更改,需要怎么做?
  • 三、如何制作Docker镜像?
  • docker build 命令制作docker镜像
  • go build工作中遇到的常见问题
  • "docker build" requires exactly 1 argument.
  • 报错:file not found in build context or excluded by .dockerignore
  • 构建 Go 应用 docker 镜像
  • docker镜像存储位置
  • 个人demo:go 项目docker镜像Dockerfile


一、关于制作Docker镜像?🐋

46 | 如何制作Docker镜像?
参考URL: https://time.geekbang.org/column/article/417216
要落地云原生架构,其中的一个核心点是通过容器来部署我们的应用。如果要使用容器来部署应用,那么制作应用的 Docker 镜像就是我们绕不开的关键一步。

1. Docker 镜像的构建原理和方式

构建一个 Docker 镜像,最常用的有两种:

  • 通过docker commit命令,基于一个已存在的容器构建出镜像。
  • 编写 Dockerfile 文件,并使用docker build命令来构建镜像。

上面这两种方法中,镜像构建的底层原理是相同的,都是通过下面 3 个步骤来构建镜像:

  1. 基于原镜像,启动一个 Docker 容器。
  2. 在容器中进行一些操作,例如执行命令、安装文件等。由这些操作产生的文件变更都会被记录在容器的存储层中。
  3. 将容器存储层的变更 commit 到新的镜像层中,并添加到原镜像上。

1.1 Docker 镜像的构建方式使用场景和选型

docker commit这种镜像构建方式通常用在下面两个场景中:

  • 构建临时的测试镜像;
  • 容器被入侵后,使用docker commit,基于被入侵的容器构建镜像,从而保留现场,方便以后追溯。

除了这两种场景,我不建议你使用docker commit来构建生产现网环境的镜像。

原因如下:

  • 使用docker commit构建的镜像包含了编译构建、安装软件,以及程序运行产生的大量无用文件,这会导致镜像体积很大,非常臃肿。
  • 使用docker commit构建的镜像会丢失掉所有对该镜像的操作历史,无法还原镜像的构建过程,不利于镜像的维护。
  • Dockerfile 的操作流程可以通过docker image history [镜像名称]查询,方便开发者查看变更记录。

在实际开发中,使用Dockerfile来构建是最常用,也最标准的镜像构建方法.

使用 Dockerfile 构建镜像,本质上也是通过镜像创建容器,并在容器中执行相应的指令,然后停止容器,提交存储层的文件变更。和用docker commit构建镜像的方式相比,它有三个好处:

  • Dockerfile 包含了镜像制作的完整操作流程,其他开发者可以通过 Dockerfile 了解并复现制作过程。
  • Dockerfile 中的每一条指令都会创建新的镜像层,这些镜像可以被 Docker Daemnon 缓存。再次制作镜像时,Docker 会尽量复用缓存的镜像层(using cache),而不是重新逐层构建,这样可以节省时间和磁盘空间。
  • 把这一切都放到一个 Dockerfile 里,既没有源码泄漏,又不需要用脚本去跨平台编译,还获得了最小的镜像。

在实际生产环境中,标准的做法是通过 Dockerfile 来构建镜像。使用 Dockerfile 构建镜像,就需要你编写 Dockerfile 文件。

二、Dockerfile快速开始

什么是Dockerfile

Dockerfile是一个包含用于组合映像的命令的文本文档。可以使用在命令行中调用任何命令。 Docker通过读取Dockerfile中的指令自动生成映像。

  • dockerfile是自定义镜像的一套规则
  • dockerfile由多条指令构成,Dockerfile中的每一条指令都会对应于Docker镜像中的每一层。

docker build命令用于从Dockerfile构建映像。可以在docker build命令中使用-f标志指向文件系统中任何位置的Dockerfile。

docker build命令会读取Dockerfile的内容,并将Dockerfile的内容发送给 Docker 引擎,最终 Docker 引擎会解析Dockerfile中的每一条指令,构建出需要的镜像。

Dockerfile的基本结构

Dockerfile 一般分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令,’#’ 为 Dockerfile 中的注释。

Dockerfile 常用的指令

Docker以从上到下的顺序运行Dockerfile的指令。为了指定基本映像,第一条指令必须是FROM。一个声明以#字符开头则被视为注释。可以在Docker文件中使用RUN,CMD,FROM,EXPOSE,ENV等指令。

FROM命令

FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
  • FROM:指定基础镜像,必须为第一个命令
  • FROM指令用于指定基础镜像,ARG是唯一可以位于FROM指令之前的指令,一般用于声明基础镜像的版本。
  • –platform选项可用在FROM多平台镜像的情况下指定平台。例如,linux/amd64、lunux/arm64、windows/amd64。
  • AS name表示为构建阶段命令,在后续FROM和COPY --from=name说明中可以使用这个名词,引用此阶段构建的映像。
  • tag或digest值是可选的。如果您省略其中任何一个,构建器默认使用latest标签。如果找不到指定tag,构建起将返回错误。
ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app
FROM extras:${CODE_VERSION}
CMD  /code/run-extras

LABEL

LABEL 指令将元数据添加到镜像。LABEL 是键值对。要在 LABEL 值中包含空格,请像在命令行中一样使用引号和反斜杠。

WORKDIR

用来指定当前工作目录(或者称为当前目录)

当使用相对目录的情况下,采用上一个WORKDIR指定的目录作为基准

相当与cd 命令,但不同的是指定了WORKDIR后,容器启动时执行的命令会在该目录下执行

RUN

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

RUN 主要用于在Image里执行指令,比如安装软件,下载文件等。
COPY指令从复制文件、目录到镜像文件系统的。

COPY

编写Dockerfile的时候copy宿主机文件到镜像中。

CMD

Dockerfile只允许使用一次CMD命令。使用多个CMD会抵消之前所有的命令,只有最后一个命令生效。一般来说,这是整个Dockerfile脚本的最后一个命令。

FROM ubuntu
CMD ["/usr/bin/wc","--help"]

CMD有三种形式:

  • CMD [“exec”,“param1”,“param2”]:使用exec执行,推荐方式。
  • CMD command param1 param2:在/bin/sh中执行,可以提供交互操作。
  • CMD [“param1”,“param2”]:提供给ENTRYPOINT的默认参数(极少这样使用)。

EXPOSE

EXPOSE指令通知容器在运行时监听某个端口,可以指定TCP或UDP,如果不指定协议,默认为TCP端口。但是为了安全,docker run命令如果没有带上相应的端口映射参数,Docker并不会将端口映射出去。

EXPOSE 80/tcp
EXPOSE 80/udp

指定映射端口方式:

docker run -P:将所有端口发布到主机接口,每个公开端口绑定到主机上的随机端口,端口范围在/proc/sys/net/ipv4/ip_local_port_range定义的临时端口范围内。

docker run -p :显式映射单个端口或端口范围。

ENTRYPOINT

ENTRYPOINT 指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有其他传入值作为该命令的参数。

一个Dockerfile中只能有一个ENTRYPOINT命令。如果有多条,只有最后一条有效。

无参的方式:
ENTRYPOINT [“/usr/sbin/nginx"]

指定参数的方式:
ENTRYPOINT [“/usr/sbin/nginx”, “-g”, “deamon off"]

docker run 的–entrypoint 标志可以覆盖原Dockerfile中的ENTRYPOINT 指令。

注意理解该命令, 该命令 是指定你每次 docker run启动容器的时候,都会自己执行的一个程序!!!。

CMD与ENTRYPOINT的关系
  • CMD可以为ENTRYPOINT提供参数,ENTRYPOINT本身也可以包含参数,但是可以把需要变动的参数写到CMD里面,而不需要变动的参数写到ENTRYPOINT里面;
  • ENTRYPOINT使用第二种shell方式会屏蔽掉CMD里面的命令参数和docker run后面加的命令。
  • 在Dockerfile中,ENTRYPOINT指定的参数比运行docker run时指定的参数更靠前。

ENTRYPOINT/CMD最后一条命令为无限运行的命令:
这句话才是使用ENTRYPOINT的精髓。
在Docker Daemon模式下,entrypoint、cmd命令的最后一个命令,一定是要当前容器需要一直运行的,才能防止容器退出。

当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令。换句话说实际执行时,会变成 “”

工作中常用技巧总结

Dockerfile中使用变量

通过关键字ARG,ENV设置变量

ARG arg1=test
ENV env1=production

注意:

  • 不能通过表达如$(uname -a)进行设置,只能设置为常量
  • ARG设置的变量在构建完成后,就会丢失。即在Docker中无法引用该变量
  • ENV设置的变量在Docker中可以通过如${env1}访问

在RUN中设置变量
在RUN通过arg=someValue中设置变量,以下脚本先获取Debain的系统版本号,并设置到了os_release变量中,在后续的命令中可以通过${os_release}进行引用

RUN os_release="$(cat /etc/os-release | grep VERSION_CODENAME | awk -F '=' '{print $2}')" &&\
    echo "deb http://mirrors.aliyun.com/debian/ ${os_release} main non-free contrib\n\
    deb http://mirrors.aliyun.com/debian-security ${os_release}/updates main\n\
    deb http://mirrors.aliyun.com/debian/ ${os_release}-updates main non-free contrib\n\
    deb http://mirrors.aliyun.com/debian/ ${os_release}-backports main non-free contrib\n"\
    > /etc/apt/sources.list

一个RUN命令,相当于新打开一个Shell。所以上一个RUN设置的变量无法在下一个RUN中使用。

因此如果你需要在build期间使用某些变量,那么ARG是最好的选择。如果你是想在运行期间使用,那么ENV是唯一的选择。

ENV主要是定义环境变量,在docker run的时候ENV的配置会加载到容易内部,但ARG的参数在内部是没法看到的。同时也可以通过下面命令更改ENV的默认值:

docker run -e var=yyy
如果现在我想在BUILD的时候,改变我的环境变量,而不是每次RUN的时候更改,需要怎么做?

ARG和ENV 两者结合使用

ARG var
ENV var=${var}

在dockerfile内部可以这样控制命令的参数。

ARG protocal
ARG address
ARG port

ENV protocal=${protocal} \
    address=${address} \
    port=${port}

CMD /usr/bin/lightweightservicediscovery --listen=${PROTOCAL:-ipv4}:${ADDRESS:-0.0.0.0}:${port:-49188}

//如果读取环境变量失败再采用后面的默认值。

这样既可以在build的时候通过docker build --build-arg var=xxx 来传递参数,也可以通过在运行的时候通过docker run -e var=yyy来传递参数。

实战小demo:

docker build --build-arg INSTALL_ZIP=myinstall.zip   -t centos-test:v4 .
ARG INSTALL_ZIP
COPY ./${INSTALL_ZIP} /root/
RUN chmod 755 ${INSTALL_ZIP}
RUN unzip ${INSTALL_ZIP}

三、如何制作Docker镜像?

docker build 命令制作docker镜像

docker build 命令用于使用 Dockerfile 创建镜像。

docker build [OPTIONS] PATH | URL | -

OPTIONS说明:

  • –build-arg=[] :设置镜像创建时的变量;
  • –cpu-shares :设置 cpu 使用权重;
  • –cpu-period :限制 CPU CFS周期;
  • –cpu-quota :限制 CPU CFS配额;
  • –cpuset-cpus :指定使用的CPU id;
  • –cpuset-mems :指定使用的内存 id;
  • –disable-content-trust :忽略校验,默认开启;
  • -f :指定要使用的Dockerfile路径;
  • –force-rm :设置镜像过程中删除中间容器;
  • –isolation :使用容器隔离技术;
  • –label=[] :设置镜像使用的元数据;
  • -m :设置内存最大值;
  • –memory-swap :设置Swap的最大值为内存+swap,"-1"表示不限swap;
  • –no-cache :创建镜像的过程不使用缓存;
  • –pull :尝试去更新镜像的新版本
  • –quiet, -q :安静模式,成功后只输出镜像 ID;
  • –rm :设置镜像成功后删除中间容器
  • –shm-size :设置/dev/shm的大小,默认值是64M;
  • –ulimit :Ulimit配置。
  • –squash :将 Dockerfile 中所有的操作压缩为一层。
  • –tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
  • –network: 默认 default。在构建期间设置RUN指令的网络模式

使用当前目录的 Dockerfile 创建镜像,标签为 runoob/ubuntu:v1。

docker build -t runoob/ubuntu:v1 .

也可以通过 -f Dockerfile 文件的位置:

$ docker build -f /path/to/a/Dockerfile .

go build工作中遇到的常见问题

“docker build” requires exactly 1 argument.

问题描述:
在参照这docker官网教程学习构建镜像的时候。提示错误:“docker build” requires exactly 1 argument.

问题分析:
原因是因为(少了一个 ‘.’ , ‘.’ 代表当前路径):

docker build --tag=xxx .

报错:file not found in build context or excluded by .dockerignore

问题描述:
dockerfile中 执行copy 把主机上的文件往容器中copy时,报错file not found in build context or excluded by .dockerignore

问题分析:
我的dockerfile中 宿主机的文件,写的绝对路径。而 dockerfile 不能获取 父目录。

解决方案:
文件放置在当前路径下,dockerfile中 写成 ./文件名 即可(将文件copy到当前目录)。

构建 Go 应用 docker 镜像

构建 Go 应用 docker 镜像
https://zhuanlan.zhihu.com/p/476921483

之前我们的演示的是 centos:centos8 基础镜像,查看原文,作者使用了 golang:alpine 镜像,非常小。

原作者的 dockerfile demo:

FROM golang:alpine AS builder

LABEL stage=gobuilder

ENV CGO_ENABLED 0
ENV GOPROXY https://goproxy.cn,direct

RUN apk update --no-cache && apk add --no-cache tzdata

WORKDIR /build

ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
RUN go build -ldflags="-s -w" -o /app/hello ./hello.go


FROM alpine

RUN apk update --no-cache && apk add --no-cache ca-certificates
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
ENV TZ Asia/Shanghai

WORKDIR /app
COPY --from=builder /app/hello /app/hello

CMD ["./hello"]
  • 默认禁用了 cgo
  • 启用了 GOPROXY 加速 go mod download
  • 去掉了调试信息 -ldflags=“-s -w” 以减小镜像尺寸
  • 安装了 ca-certificates,这样使用 TLS证书就没问题了
  • tzdata 在 builder 镜像安装,并在最终镜像只拷贝了需要的时区
  • 自动设置了本地时区,这样我们在日志里看到的是北京时间了

Dockerfile 构建出的镜像大小和 v1 centos对比结果如下:

$ docker images | grep hello
hello     v4    94ba3ece3071   4 hours ago     6.66MB
hello     v3    f51e1116be11   8 hours ago     6.61MB
hello     v2    0dd53f016c93   8 hours ago     6.61MB
hello     v1    ac0e37173b85   9 hours ago     314MB

docker镜像存储位置

docker info | grep "Docker Root Dir"
root@cka-k8s-master:~/code/mygoapp# cd /data/docker
root@cka-k8s-master:/data/docker# ls
buildkit  containers  image  network  overlay2  plugins  runtimes  swarm  tmp  trust  volumes
root@cka-k8s-master:/data/docker# du -sh * | sort -h
4.0K    runtimes
4.0K    swarm
4.0K    tmp
4.0K    trust
16K     plugins
28K     volumes
80K     network
88K     buildkit
4.6M    image
8.1M    containers
3.1G    overlay2
root@cka-k8s-master:/data/docker#

个人demo:go 项目docker镜像Dockerfile

原始镜像的选择?
centos:centos8作为基础镜像,是因为centos:centos8镜像中包含了基本的排障工具,例如vi、cat、curl、mkdir、cp等工具。

FROM centos:centos8
LABEL maintainer="<xxx@yeah.net>"

WORKDIR /opt/mygoapp

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
      echo "Asia/Shanghai" > /etc/timezone && \
      mkdir -p /var/log/mygoapp

COPY file-boom /opt/mygoapp/bin/

ENTRYPOINT ["/opt/mygoapp/bin/file-boom"]
CMD ["-c", "/etc/mygoapp/file-boom.yaml"]