乐趣区

Golang-容器部署

Go 使用 Docker 部署的意义(优势)在哪?

由于 go 最终是编译为一个二进制可执行文件,没有运行时依赖,也不需要管理库,丢到服务器上就可以直接运行。所以,如果你有一个二进制文件,那么在容器中打包二进制文件的要点是什么?如果使用 docker 的话,还得在服务器上装 docker,那么把最终程序打包成 docker 有什么好处呢?

我想有这么几个好处:

依赖打包
如果你的应用程序(二进制文件)依赖配置文件或一些静态文件,那使用 docker 就很方便的把这些文件一起打包进容器里。

版本控制
启动 Docker 就和运行一个进程一样快,我们可以在几秒钟的时间内运行整个服务器集群。除此之外,Docker 镜像的注册中心使 Docker 容器还可以像 git 仓库一样,可以让你提交变更到 Docker 镜像中,并通过不同的版本来管理它们。设想如果你因为完成了一个组件的升级而导致你整个环境都损坏了,Docker 可以让你轻松地回滚到这个镜像的前一个版本。这整个过程可以在几分钟内完成。

隔离性
容器包含了应用程序的代码、运行环境、依赖库、配置文件等必需的资源。容器之间达到进程级别的隔离,在容器中的操作,不会影响道宿主机和其他容器,这样就不会出现应用之间相互影响的情形!

可移植性
可以实现开发、测试和生产环境的统一化和标准化。镜像作为标准的交付件,可在开发、测试和生产环境上以容器来运行,最终实现三套环境上的应用以及运行所依赖内容的完全一致。在现在微服务的架构中,一个应用拆成几十个微服务,每个微服务都对应有开发、测试、生产三套环境需要搭建。自己算算,如果采用传统的部署方式,有多少环境需要部署。

轻量和高效
和虚拟机相比,容器仅需要封装应用和应用需要的依赖文件,实现轻量的应用运行环境,且拥有比虚拟机更高的硬件资源利用率。在微服务架构中,有些服务负载压力大,需要以集群部署,可能要部署几十台机器上,对于某些中小型公司来说,使用虚拟机,代价太大。如果用容器,同样的物理机则能支持上千个容器,对中小型公司来说,省钱!

安全性
Docker 容器不能窥视运行在其他容器中的进程。从体系结构角度来看,每个容器只使用着自己的资源(从进程到网络堆栈)。作为紧固安全的一种手段,Docker 将宿主机操作系统上的敏感挂载点(例如 /proc 和 /sys)作为只读挂载点,并且使用一种写时复制系统来确保容器不能读取其他容器的数据。Docker 也限制了宿主机操作系统上的一些系统调用,并且和 SELinux 与 AppArmor 一起运行的很好。此外,在 Docker Hub 上可以使用的 Docker 镜像都通过数字签名来确保其可靠性。由于 Docker 容器是隔离的,并且资源是受限制的,所以即使你其中一个应用程序被黑,也不会影响运行在其它 Docker 容器上的应用程序。

Part 1: Dockerize

对于许多编程语言(包括 Go),有几个很好的官方和社区支持的容器。我们在容器化 Go apps 的时候,可以选择基于 Golang 官方镜像构建,如:golang:onbuild,golang:latest。但是这有一个很大的缺点:这些容器可能很大,所以基于它们的镜像创建的镜像文件将会非常大。

这是因为我们的应用程序是在容器内编译的。这意味着该容器需要安装 Go,以及 Go 的依赖关系,同时这也意味着我们需要一个程序包管理器和整个操作系统。实际上,如果您查看 Golang 的 Dockerfile,它将以 Debian Jessie 开头,安装 GCC 编译器和一些构建工具,压缩 Go 并安装它。因此,我们几乎有一个完整的 Debian 服务器和 Go 工具包来运行我们的小型应用程序。

所以我们应该使用一种静态构建 Go 容器化应用的方法,这种方法生成的镜像文件非常小。

Part 2: Our“app”

我以 Passport 应用为例,下面是应用程序结构:

$ tree -L 2
.
├── Makefile
├── control
├── app
│   ├── boot
│   ├── kernel
│   ├── lib
│   ├── main.go
│   ├── server
├── output
│   └── bin
└── vendor
    ├── appengine
    ├── cloud.google.com
    ├── github.com
    ├── go.etcd.io
    ├── go.uber.org
    ├── golang.org
    ├── golang_org
    ├── google.golang.org
    ├── gopkg.in
    └── vendor.json

我们要做的是在工作目录中编译 Go,然后将二进制文件添加到容器中。这种方式比直接使用官方镜像麻烦一些,不过体积要小很多,所以建议这么做。

go build -o output/bin/go_service_passport ./app

Part 3: Builder

.dockerignore file

首先,在项目的根目录下,新建一个文本文件.dockerignore,写入下面的内容。

# comment
.git
.gitignore

output
*.out
*.log

*/temp*
*/*/temp*
temp?

*.md
!README.md

在 Docker CLI 将上下文发送到 Docker 守护程序之前,它将在上下文的根目录中查找名为.dockerignore 的文件。如果此文件存在,则 CLI 会修改上下文以排除与其中的模式匹配的文件和目录。这有助于避免不必要地将大型文件或敏感文件和目录发送到守护程序,并避免使用 ADD 或 COPY 将它们添加到映像中。

如果.dockerignore 文件中的行以第 1 列中的#开头,则该行将被视为注释,并且在 CLI 解释之前将被忽略。

官方说明见:.dockerignore file

Dockerfile

接下来就是编写 Dockerfile 文件了。Dockerfile 是一个文本文档,Docker 可以通过阅读 Dockerfile 中的指令来自动构建映像。

该文档内的指令不区分大小写。但是,习惯是大写,以便更轻松地将它们与参数区分开。

Docker 按顺序在 Dockerfile 中运行指令。Dockerfile 必须以“FROM”指令开头。当然,FROM 前面可以有一个或多个 ARG 指令或注释,ARG 指令声明 Dockerfile 中 FROM 行中使用的参数。

在开始之前我们应该想清楚到底使用哪个基础镜像?一般情况下,都会从以下三个基础镜像开始。

  • 镜像 scratch(空镜像), 大小 0B
  • 镜像 busybox(空镜像 + busybox), 大小 1.4MB
  • 镜像 alpine (空镜像 + busybox + apk), 大小 3.98MB

So what’s scratch? Scratch is a special docker image that’s empty. It’s truly 0B:

REPOSITORY          TAG         IMAGE ID            CREATED              VIRTUAL SIZE
scratch             latest      511136ea3c5a        22 months ago        0 B

目前 Docker 官方已开始推荐使用 Alpine 替代之前的 Ubuntu 做为基础镜像环境。这样会带来多个好处。包括镜像下载速度加快,镜像安全性提高,主机之间的切换更方便,占用更少磁盘空间等。

下面,不如以上三个镜像我们都尝试一下吧。在项目的根目录下,新建一个文本文件 Dockerfile,写入下面的内容。

FROM scratch

FROM scratch
LABEL maintainer="tobeabme@gmail.com" version="1.0"

ENV RUNMODE dev
ENV CONSUL_ADDR 127.0.0.1:8500
ENV ETCD_ADDR 127.0.0.1:2379

ADD output/bin/go_service_passport /
EXPOSE 8080 9080
CMD ["/go_service_passport"]

下面这种写法是错误的,为什么呢?

FROM scratch
MAINTAINER weizi

ENV APP_RUN_DIR /data/app/go/work
ENV APP_LOG_DIR /data/app/go/log

RUN mkdir -p ${APP_RUN_DIR} \
    && mkdir -p ${APP_LOG_DIR}
   
COPY output/bin/go_service_passport ${APP_RUN_DIR}

WORKDIR ${APP_RUN_DIR}
EXPOSE 8080 9080
CMD ["${APP_RUN_DIR}/go_service_passport"]

这是由于 scratch 镜像几乎不包含任何东西,不支持环境变量,也没有 shell 命令。因此,基于 scratch 的镜像通过 ADD 指令进行添加,以此绕过目录创建。更完整的原因说明见如下:

FROM scratch is a completely empty filesystem. You have no installed libraries, and no shell (like /bin/sh) included in there. To use this as your base, you’d need a statically linked binary, or you’ll need to install all of the normal tools that are included with a linux distribution.

The latter is what is prepackaged in the various busybox, debian, ubuntu, centos, etc images on the docker hub. The fast way to make your image work with a minimal base image is to change the from to FROM busybox and change your /bin/bash to /bin/sh.

FROM busybox

FROM busybox
MAINTAINER weizi

ENV APP_RUN_DIR /data/app/go/work
ENV RUNMODE dev
ENV CONSUL_ADDR 127.0.0.1:8500
ENV ETCD_ADDR 127.0.0.1:2379

#RUN  mkdir -p /data/app/go/work

WORKDIR $APP_RUN_DIR
ADD output/bin/go_service_passport .

EXPOSE 8080 9080
CMD ["./go_service_passport","-g","daemon off;"]

多阶构建

ARG GO_VERSION=1.10.3
  
FROM golang:${GO_VERSION} AS builder
MAINTAINER weizi
LABEL author="name@gmail.com"

ENV GO111MODULE=off
ENV GO15VENDOREXPERIMENT=1

WORKDIR $GOPATH/src/code.qschou.com/peduli/go_service_passport
COPY . .

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o output/bin/go_service_passport ./app

#------------------------------------------

FROM alpine
MAINTAINER weizi
LABEL author="name@gmail.com"

ENV APP_RUN_DIR /data/app/go/work
ENV RUNMODE dev
ENV CONSUL_ADDR 127.0.0.1:8500
ENV ETCD_ADDR 127.0.0.1:2379

RUN apk update \
        && apk --no-cache add wget ca-certificates \
        && apk add -f --no-cache git \
        && apk add -U tzdata \
        && ln -sf /usr/share/zoneinfo/Asia/Shanghai  /etc/localtime

WORKDIR $APP_RUN_DIR

COPY --from=builder /go/src/code.qschou.com/peduli/go_service_passport/output/bin/go_service_passport .

EXPOSE 8080 9080
CMD ["./go_service_passport","-g","daemon off;"]

最后,让我们看下上面三种镜像生成后的大小:

$ docker images
REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
passport-busybox       1.0.1               1028fbd88847        32 seconds ago      36.3MB
passport-scratch       1.0.1               aa407fee8d95        33 minutes ago      35.1MB
passport-multi-stage   1.0.9               dd8a070d96e9        2 days ago          59.4MB

Go 应用本身的二进制文件为 33MB

$ ls -lh   output/bin/go_service_passport
-rwxr-xr-x  1 will  staff    33M Nov 18 11:09 output/bin/go_service_passport

scratch size = 35.1-33 = 2.1M
busybox size = 36.3-33 = 3.3M
alpine size = 59.4-33 = 26.1M

指令说明:

FROM

FROM 指令初始化一个新的构建阶段,并为后续指令设置基本映像。因此,有效的 Dockerfile 必须以 FROM 指令开头。

格式:

FROM <image> [AS <name>]
Or
FROM <image>[:<tag>] [AS <name>]
Or
FROM <image>[@<digest>] [AS <name>]
  • ARG 是 Dockerfile 中唯一可以出现在 FROM 指令之前的指令。
  • FROM 可以在单个 Dockerfile 中多次出现,以创建多个映像或将一个构建阶段用作对另一个构建阶段的依赖。只需在每个新的 FROM 指令之前记录一次提交输出的最后一个图像 ID。每个 FROM 指令清除由先前指令创建的任何状态。
  • 通过将 AS 名称添加到 FROM 指令中,可以选择为新的构建阶段指定名称。该名称可以在后续的 FROM 和 COPY –from = < 名称 | 索引 > 指令中使用,以引用在此阶段构建的映像。
  • tag or digest 值是可选的。如果您忽略其中任何一个,那么缺省情况下构建器都会采用 latest 标签。如果构建器找不到标签值,则返回错误。

MAINTAINER

MAINTAINER 指令设置生成图像的“作者”字段。LABEL 指令是此指令的更为灵活的版本,您应该使用它,因为它可以设置所需的任何元数据,并且可以轻松查看,例如使用 docker inspect。要设置与 MAINTAINER 字段相对应的标签,可以使用:

LABEL maintainer="SvenDowideit@home.org.au"

LABEL

LABEL 指令将元数据添加到图像。标签是键值对。要在 LABEL 值中包含空格,请使用引号和反斜杠。一些用法示例:

LABEL "com.example.vendor"="ACME Incorporated"

LABEL description="This text illustrates \
that label-values can span multiple lines."

一个镜像可以有多个标签。以下两种方式之一在一条指令中指定多个标签:

基本或父图像(FROM 行中的图像)中包含的标签由您的图像继承。如果标签已经存在但具有不同的值,则最近应用的值将覆盖任何先前设置的值。

LABEL multi.label1="value1" multi.label2="value2" other="value3"

OR

LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

被包含在基础镜像或父镜像(images in the FROM line)的 Labels 是被你的镜像继承的。如果一个标签已经存在,但具有不同的值,则最近应用的值将覆盖任何先前设置的值。

去查看一个镜像的 Labels,请使用 docker inspect 命令。

ENV

设置环境变量。将环境变量 <key> 设置为值 <value>。此值将在构建阶段中所有后续指令(RUN、ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD)的环境中使用。

格式:

ENV <key> <value>
ENV <key>=<value> ...

第一种形式,ENV < 键 > < 值 >,将单个变量设置为一个值。第一个空格之后的整个字符串将被视为 <value>- 包括空格字符。该值可以被其他环境变量解释,因此如果不对引号字符进行转义,则将其删除。

第二种格式,ENV <key> = <value> …,允许一次设置多个变量。请注意,第二种形式在语法中使用等号(=),而第一种形式则不使用等号(=)。与命令行解析一样,引号和反斜杠可用于在值中包含空格。

For example:

ENV myName="John Doe" myDog=Rex\ The\ Dog \
    myCat=fluffy
and

ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

当一个容器是从产生的镜像运行时,使用 ENV 设置的环境变量将持续存在。您可以使用 docker inspect 查看值,并使用 docker run –env <key> = <value> 更改它们。

Environment replacement

环境变量(用 ENV 语句声明)也可以在某些指令中用作 Dockerfile 解释的变量。通过在字面上将类似变量的语法包含到语句中来处理。

在 Dockerfile 文档中,可以使用 $variable_name 或 ${variable_name} 引用环境变量,它们是等同的。其中大括号的变量是用在没有空格的变量名中的,如 ${foo}_bar。

${variable_name}变量也支持一些标准的 bash 修饰符,如:

  • ${variable:-word} 表示如果 variable 设置了,那么结果就是设置的值。否则设置值为 word
  • ${variable:+word} 表示如果 variable 设置了,那么结果是 word 值,否则为空值。

word 可以是任意的字符,包括额外的环境变量。

转义符(Escaping)可以添加在变量前面:&dollar;foo or &dollar;{foo},例如,会分别转换为 $foor 和 ${foo}。示例:

FROM busybox
ENV foo /bar
WORKDIR ${foo}   # WORKDIR /bar
ADD . $foo       # ADD . /bar
COPY \$foo /quux # COPY $foo /quux

在此实例中:

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

将导致 def 的值为 hello,而不是 bye。但是,ghi 的值是 bye。

Dockerfile 中的以下指令列表支持环境变量:

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR
  • ONBUILD(在 1.4 之前,ONBUILD 指令不支持环境变量)

官方说明见: Environment replacement

ARG

ARG 指令定义了一个变量,用户可以在构建时使用 –build-arg <varname> = <value> 标志使用 docker build 命令将其传递给构建器。如果用户指定了未在 Dockerfile 中定义的构建参数,则构建会输出警告。

[Warning] One or more build-args [foo] were not consumed.

格式:

ARG <name>[=<default value>]

Dockerfile 可能包含一个或多个 ARG 指令。例如,

FROM busybox
ARG user1
ARG buildno
...

警告:不建议使用构建时变量来传递诸如 github 密钥,用户凭据等机密。构建时变量值对于使用 docker history 命令的映像的任何用户都是可见的。

默认值

ARG 指令可以选择包含默认值:

FROM busybox
ARG user1=someuser
ARG buildno=1
...

如果 ARG 指令具有默认值,并且在构建时未传递任何值,则构建器将使用默认值。

ARG 指令在定义它的构建阶段结束时超出范围。要在多个阶段中使用 arg,每个阶段都必须包含 ARG 指令。

FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS

FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS

使用 ARG 变量

您可以使用 ARG 或 ENV 指令来指定 RUN 指令可用的变量。使用 ENV 指令定义的环境变量始终会覆盖同名的 ARG 指令。

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER v1.0.0
4 RUN echo $CONT_IMG_VER

然后,假定此映像是使用以下命令构建的:

$ docker build --build-arg CONT_IMG_VER=v2.0.1 .

在这种情况下,RUN 指令使用 v1.0.0 而不是用户传递的 ARG 设置:v2.0.1 此行为类似于 shell 脚本,其中局部作用域的变量会覆盖从参数传递过来的作为参数。

使用上面的示例,但使用不同的 ENV 规范,您可以在 ARG 和 ENV 指令之间创建更有用的交互:

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
4 RUN echo $CONT_IMG_VER

与 ARG 指令不同,ENV 值始终保留在生成的映像中。考虑不带 –build-arg 标志的 Docker 构建:

$ docker build .

使用此 Dockerfile 示例,CONT_IMG_VER 仍保留在映像中,但其值为 v1.0.0,因为它是 ENV 指令在第 3 行中设置的默认值。

在此示例中,变量扩展技术使您可以从命令行传递参数,并利用 ENV 指令将其保留在最终映像中。

预定义的 ARG

Docker 具有一组预定义的 ARG 变量,您可以在 Dockerfile 中使用它们而无需相应的 ARG 指令。

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy

默认情况下,这些预定义变量从 docker history 记录的输出中排除。排除它们可以降低意外泄漏 HTTP_PROXY 变量中的敏感身份验证信息的风险。

--build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com

COPY

COPY 指令从 <src> 复制新文件或目录,并将它们添加到容器的文件系统中,路径为 <dest>。由于我们这里是拷贝 Go 构建好的二进制文件,所以不用将当前目录下的所有文件(除了.dockerignore 排除的路径),都拷贝进入 image 文件的 $WORKPATH 目录。如果需要拷贝后自动解压,用 ADD 指令。

COPY 有两种形式:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] (this form is required for paths containing whitespace)

每个 <src> 都可以包含通配符,并且将使用 Go 的 filepath.Match 规则进行匹配。例如:

COPY hom* /mydir/        # adds all files starting with "hom"
COPY hom?.txt /mydir/    # ? is replaced with any single character, e.g., "home.txt"

<dest> 是绝对路径,或相对于 WORKDIR 的路径,源将在目标容器内复制到该路径。

COPY test relativeDir/   # adds "test" to `WORKDIR`/relativeDir/
COPY test /absoluteDir/  # adds "test" to /absoluteDir/

复制包含特殊字符 (such as [ and]), 的文件或目录时,需要遵循 Golang 规则转义那些路径,以防止将它们视为匹配模式。例如,要复制名为 arr[0].txt 的文件,请使用以下命令:

COPY arr[[]0].txt /mydir/    # copy a file named "arr[0].txt" to /mydir/

可选地,COPY 接受 –from=<name|index> 标志,该标志可用于将源位置设置为先前的构建阶段 (created with FROM .. AS <name>),该阶段将用于代替由发送的构建上下文用户。如果找不到具有指定名称的构建阶段,则尝试改用具有相同名称的图像。

COPY 遵守以下规则:

  • <src> 路径必须在构建的上下文中。您无法 COPY ../something /something,因为 Docker 构建的第一步是将上下文目录(和子目录)发送到 docker 守护程序。
  • 如果 <src> 是目录,则将复制目录的整个内容,包括文件系统元数据。注意:目录本身不会被复制,只是其内容被复制。
  • 如果 <src> 是任何其他类型的文件,则将其与其元数据一起单独复制。在这种情况下,如果 <dest> 以尾斜杠 / 结束,则它将被视为目录,并且 <src> 的内容将写入 <dest>/base(<src>)
  • 如果直接或由于使用通配符而指定了多个 <src> 资源,则 <dest> 必须是目录,并且必须以斜杠 / 结尾。
  • 如果 <dest> 不以斜杠结尾,它将被视为常规文件,并且 <src> 的内容将写入 <dest>。
  • 如果 <dest> 不存在,它将与路径中所有缺少的目录一起创建。

官方说明见:COPY

ADD

ADD 指令从 <src> 复制新文件,目录或远程文件 URL,并将它们添加到镜像的文件系统中的路径 <dest>。

ADD 有两种形式:

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] (this form is required for paths containing whitespace)

ADD 遵守以下规则:

  • 如果 <src> 是 URL,并且 <dest> 不以斜杠结尾,则从 URL 下载文件并将其复制到 <dest>。
  • 如果 <src> 是 URL,并且 <dest> 确实以斜杠结尾,则从 URL 推断文件名,并将文件下载到 <dest>/<filename>。例如,ADD http://example.com/foobar / 将创建文件 /foobar。该 URL 必须具有不平凡的路径,以便在这种情况下可以找到适当的文件名(http://example.com 将不起作用)。
  • 如果 <src> 是采用公认压缩格式(身份,gzip,bzip2 或 xz)的本地 tar 归档文件,则将其解压缩为目录。来自远程 URL 的资源不会被解压缩。注意:是否将文件识别为可识别的压缩格式仅根据文件的内容而不是文件的名称来完成。例如,如果一个空文件碰巧以.tar.gz 结尾,则该文件将不会被识别为压缩文件,并且不会生成任何类型的解压缩错误消息,而是会将文件简单地复制到目标位置。
  • 如果 <dest> 不存在,它将与路径中所有缺少的目录一起创建。

其它规则与 COPY 相同,不重复描述。

WORKDIR

WORKDIR 指令为 Dockerfile 中跟在其后的所有 RUN,CMD,ENTRYPOINT,COPY 和 ADD 指令设置工作目录。相当于 cd。如该目录不存在,WORKDIR 会帮你建立目录,即使随后的 Dockerfile 指令未使用它。

WORKDIR 指令可在 Dockerfile 中多次使用。如果提供了相对路径,则它将相对于上一个 WORKDIR 指令的路径。例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

该 Dockerfile 中最后一个 pwd 命令的输出为 /a/b/c。

WORKDIR 指令可以解析以前使用 ENV 设置的环境变量。您只能使用在 Dockerfile 中显式设置的环境变量。例如:

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

该 Dockerfile 中最后一个 pwd 命令的输出为 /path/$DIRNAME

RUN

RUN 指令将在当前映像顶部的新层中执行任何命令,并提交结果。生成的提交映像将用于 Dockerfile 中的下一步。

RUN <command> (shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows)
RUN ["executable", "param1", "param2"] (exec form)

分层运行 RUN 指令并生成提交符合 Docker 的核心概念,在 Docker 上,提交很便宜,并且可以从映像历史记录的任何位置创建容器,就像源代码控制一样。

在 shell 形式中,可以使用(反斜杠)将一条 RUN 指令继续到下一行。

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

Together they are equivalent to this single line:

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

注意:要使用‘/bin/sh’以外的其他 shell,请使用 exec 形式传入所需的 shell。例如,RUN [“/bin/bash”, “-c”, “echo hello”]

注意:不同与 shell 形式,exec 形式不会调用命令 shell。这意味着正常的外壳处理不会发生。例如,RUN [“echo”, “$HOME”] 不会在 $HOME 上进行变量替换。如果要进行 shell 处理,则可以使用 shell 形式或直接执行 shell,例如:RUN [“sh”, “-c”, “echo $HOME”]。

注意:在 JSON 格式中,必须转义反斜杠。在 Windows 中,反斜杠是路径分隔符,这一点尤其重要。由于无效的 JSON,以下行将被视为 shell 形式,并以意外的方式失败:RUN [“c:windowssystem32tasklist.exe”] 此示例的正确语法是:RUN [“c:\windows\system32\tasklist.exe”]

在下一个构建时,RUN 指令的缓存不会自动失效。诸如 RUN apt-get dist-upgrade - y 之类的指令的缓存将在下一次构建中重用。可以使用 –no-cache 标志使 RUN 指令的缓存无效,例如 docker build –no-cache。

EXPOSE

EXPOSE 指令通知 Docker 运行时容器在指定的网络端口上进行侦听。您可以指定端口是侦听 TCP 还是 UDP,如果未指定协议,则默认值为 TCP。

EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

要将 EXPOSE 和在运行时使用 -p < 宿主端口 >:< 容器端口 > 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。格式为 EXPOSE < 端口 1 > [< 端口 2 >…]。

默认情况下,EXPOSE 假定使用 TCP。您还可以指定 UDP:

EXPOSE 80/udp

要同时在 TCP 和 UDP 上公开,请包括以下两行:

EXPOSE 80/tcp
EXPOSE 80/udp

无论 EXPOSE 设置如何,都可以在运行时使用 - p 标志覆盖它们。例如

docker run -p 80:80/tcp -p 80:80/udp ...

docker network 命令支持创建用于容器之间通信的网络,而无需暴露或发布特定端口,因为连接到网络的容器可以通过任何端口相互通信。有关详细信息,请参阅此功能的概述。

CMD

用于指定默认的容器主进程的启动命令。Dockerfile 中只能有一条 CMD 指令。如果您列出多个 CMD,则只有最后一个 CMD 才会生效。

CMD 的主要目的是为执行中的容器提供默认值。这些默认值可以包含一个可执行文件,也可以忽略该可执行文件,在这种情况下,您还必须指定 ENTRYPOINT 指令。

注意:如果使用 CMD 为 ENTRYPOINT 指令提供默认参数,则 CMD 和 ENTRYPOINT 指令均应使用 JSON 数组格式指定。

CMD 指令具有三种形式:

CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。)
CMD command param1 param2 (shell form)

If you use the shell form of the CMD, then the <command> will execute in /bin/sh -c:

FROM ubuntu
CMD echo "This is a test." | wc -

如果要在没有 shell 的情况下运行 <command>,则必须将命令表示为 JSON 数组,并提供可执行文件的完整路径。此数组形式是 CMD 的首选格式。任何其他参数必须在数组中分别表示为字符串:

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

注意,指定了 CMD 命令以后,docker container run 命令就不能附加命令了(比如 /bin/bash),否则它会覆盖 CMD 命令。

RUN 命令与 CMD 命令的区别在哪里?

简单说,RUN 命令在 image 文件的构建阶段执行,执行结果都会打包进入 image 文件;CMD 命令则是在容器启动后执行。另外,一个 Dockerfile 可以包含多个 RUN 命令,但是只能有一个 CMD 命令。

VOLUME

Volume,通常翻译为数据卷,用于保存持久化数据。当我们将数据库例如 MySQL 运行在 Docker 容器中时,一般将数据通过 Docker Volume 保存在主机上,这样即使删除 MySQL 容器,数据依然保存在主机上,有效保证了数据的安全性。

VOLUME 指令创建具有指定名称的挂载点,并将其标记为保存来自本地主机或其他容器的外部安装的卷。该值可以是 JSON 数组,VOLUME [“/var/log/”], 或具有多个参数的纯字符串,例如 VOLUME /var/log or VOLUME /var/log /var/db。

我们知道,镜像的每一层都是 ReadOnly 只读的。只有在我们运行容器的时候才会创建读写层。文件系统的隔离使得:

  • 容器不再运行时,数据将不会持续存在,数据很难从容器中取出。
  • 无法在不同主机之间很好的进行数据迁移。
  • 数据写入容器的读写层需要内核提供联合文件系统,这会额外的降低性能。

docker 为我们提供了三种不同的方式将数据挂载到容器中:volume、bind mount、tmpfs。

volume 方式是 docker 中数据持久化的最佳方式。

  • docker 默认在主机上会有一个特定的区域(/var/lib/docker/volumes/ Linux),该区域用来存放 volume。
  • 非 docker 进程不应该去修改该区域。
  • volume 可以通过 docker volume 进行管理,如创建、删除等操作。
  • volume 在生成的时候如果不指定名称,便会随机生成。

volume 在容器停止或删除的时候会继续存在,如需删除需要显示声明。

$ docker rm -v <container_id>
$ docker volume rm <volume_name>

格式:

VOLUME ["< 路径 1 >", "< 路径 2 >"...]
VOLUME < 路径 >

可以通过以下两种方式创建 VOLUME:

  • 在 Dockerfile 中指定 VOLUME
  • 使用 docker run –volume 命令来指定

这两种方式有区别吗?

根据官方文档,Dockerfile 生成目标镜像的过程就是不断 docker run + docker commit 的过程,当 Dockerfile 执行到 VOLUME /some/dir(这里为 /var/lib/mysql)这一行时,输出:

Step 6 : VOLUME /var/lib/mysql
 ---> Running in 0c842ec90849
 ---> 214e3dccd0f2

在这一步,docker 生成了临时容器 0c842ec90849,然后 commit 容器得到镜像 214e3dccd0f2。因此 VOLUME /var/lib/mysql 是通过 docker run -v /var/lib/mysql,即第二种方式来实现的,随后由于容器的提交,该配置被保存到了镜像 214e3dccd0f2 中,通过 inspcet 可以查看到:

"Volumes": {"/var/lib/mysql": {},
}

使用 docker inspect 命令,可以查看 Docker 容器的详细信息:

docker inspect --format='{{json .Mounts}}' test | python -m json.tool

"Mounts": [
    {
        "Name": "8827c361d103c1272907da0b82268310415f8b075b67854f27dbca0b59a31a1a",
        "Source": "/mnt/sda1/var/lib/docker/volumes/8827c361d103c1272907da0b82268310415f8b075b67854f27dbca0b59a31a1a/data",
        "Destination": "/var/lib/mysql",
        "Driver": "local",
        "Mode": "","RW": true,"Propagation":""
    }
]

Source 表示主机上的目录,Destination 为容器中的目录。

由于没有指定挂载到的宿主机目录,因此会默认挂载到宿主机的 /var/lib/docker/volumes 下的一个随机名称的目录下,在这为 /mnt/sda1/var/lib/docker/volumes/8827c361d103c1272907da0b82268310415f8b075b67854f27dbca0b59a31a1a/data。因此 Dockerfile 中使用 VOLUME 指令挂载目录和 docker run 时通过 - v 参数指定挂载目录的区别在于,run 的 - v 可以指定挂载到宿主机的哪个目录,而 Dockerfile 的 VOLUME 不能,其挂载目录由 docker 随机生成。

若指定了宿主机目录,比如:

docker run --name mysql -v ~/volume/mysql/data:/var/lib/mysql -d mysql:5.7

那么 inspect 如下:

"Mounts": [
    {
        "Source": "/Users/weizi/volume/mysql/data",
        "Destination": "/var/lib/mysql",
        "Mode": "","RW": true,"Propagation":"rprivate"
    }
]

这里将 /var/lib/mysql 挂载到宿主机的 /Users/weizi/volume/mysql/data 目录下,而不再是默认的 /var/lib/docker/volumes 目录。这样做有什么好处呢?我们知道,将该目录挂载到宿主机,可以使数据在容器被移除时得以保留,而不会随着容器 go die。下次新建 mysql 容器,只需同样挂载到 /Users/weizi/volume/mysql/data,即可复用原有数据。

在宿主机 /Users/weizi/volume/mysql/data 目录中新建 hello.txt 文件,在容器 /var/lib/mysql 目录中可见。
在容器 /var/lib/mysql 目录中新建 world.txt 文件,在宿主机 /Users/weizi/volume/mysql/data 目录中可见。

通过 VOLUME,我们得以绕过 docker 的 Union File System,从而直接对宿主机的目录进行直接读写,实现了容器内数据的持久化和共享化。

关于 Dockerfile 中的卷,请记住以下几点。

  • 从 Dockerfile 内更改卷:如果在声明了卷后,有任何构建步骤更改了卷内的数据,则这些更改将被丢弃。
  • JSON 格式:列表被解析为 JSON 数组。您必须用双引号(”)而不是单引号(’)括起单词。
  • 主机目录(host directory)在容器运行时(container run-time)声明:主机目录(挂载点)从本质上说是依赖于主机的。这是为了保留镜像的可移植性,因为不能保证给定的主机目录在所有主机上都可用。因此,您无法从 Dockerfile 中挂载主机目录。VOLUME 指令不支持指定 host-dir 参数。创建或运行容器时,必须指定安装点。
  • 基于 Windows 的容器上的卷:使用基于 Windows 的容器时,容器内的卷的目的地必须是以下之一:

    • 不存在 或 空目录
    • C: 以外的驱动器

Part 4: image

完成 Dockerfile 文档之后,接下来我们可以基于 Dockerfile 文档生成镜像文件了。

docker build -t passport-scratch:1.0.1 -f Dockerfile .

以上,如果运行成功,就可以看到新生成的 image 文件了。

$ docker image ls

REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
passport-busybox       1.0.1               1028fbd88847        10 minutes ago      36.3MB
passport-scratch       1.0.1               aa407fee8d95        42 minutes ago      35.1MB
passport-multi-stage   1.0.9               dd8a070d96e9        2 days ago          59.4MB
passport-busybox       1.0.0               d56a21693694        3 days ago          36.3MB
<none>                 <none>              492f4b83ea2d        16 minutes ago      34.9MB
nginx                  v1                  75b671fe9af3        9 days ago          126MB
busybox                latest              020584afccce        2 weeks ago         1.22MB
nginx                  latest              540a289bab6c        3 weeks ago         126MB
alpine                 latest              965ea09ff2eb        3 weeks ago         5.55MB
ubuntu                 19.10               09604a62a001        4 weeks ago         72.9MB
kong                   latest              03f9bc1cd4f7        2 months ago        130MB
postgres               9.6                 f5548544c480        3 months ago        230MB
pantsel/konga          latest              dc0af5db6ce9        7 months ago        389MB
pgbi/kong-dashboard    latest              f9e2977207e3        8 months ago        96.4MB
golang                 1.10                6fd1f7edb6ab        9 months ago        760MB
golang                 1.10-alpine         7b53e4a31d21        9 months ago        259MB
golang                 1.10.3-alpine       cace225819dc        15 months ago       259MB
golang                 1.10.3              d0e7a411e3da        16 months ago       794MB
soyking/e3w            latest              a123f3eeaad2        23 months ago       24.2MB
soyking/etcd-goreman   3.2.7               4c0139e55ed5        2 years ago         121MB

上面代码中,- t 参数用来指定 image 文件的名字(名字中不能报考下划线_),后面还可以用冒号指定标签。如果不指定,默认的标签就是 latest。最后的那个点表示 Dockerfile 文件所在的路径,上例是当前路径,所以是一个点。

docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端 / 服务端的架构中,如何才能让服务端获得本地文件呢?

这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径(也就是上面命令中最后的那个圆点 “.”),docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。

Part 5: container

Ok, 接下来我们来生成容器。docker container run 命令会基于 image 文件生成容器。

$ docker run -p 80:8080 -it passport-scratch:1.0.1
no such file or directory

从上面可以看出,运行容器时报错了。这是为什么呢?

Go 二进制文件正在其运行的操作系统上寻找一些库。我们编译了应用,但它仍动态链接到需要运行的库(即,它绑定到的所有 C 库)。不幸的是,scratch 是空的,因此没有库。我们要做的是修改构建脚本,以使用所有内置库静态编译我们的应用程序。

CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o output/bin/go_service_passport ./app

OR

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o output/bin/go_service_passport ./app

上面的命令,我们禁用了 cgo,它为我们提供了静态二进制文件。我们还将操作系统设置为 Linux(以防有人在 Mac 或 Windows 上构建),- a 标志意味着可以重建我们正在使用的所有软件包,这意味着所有导入将在禁用 cgo 的情况下进行重建。

重新生成二进制文件后,让我们再次试一下:

# 后台运行
$ docker container run -it -p 80:8080 -d passport-scratch:1.0.1

OR
# 前台运行并删除
$ docker container run --rm -p 8080:8080 -it passport-scratch:1.0.1

2019-11-15T17:25:33.747+0800    DEBUG    setting/etcd.go:35    setting.etcd: Backend config    {"backend": "etcdv3", "machines": ["127.0.0.1:2379"], "keyspace": "","traceId":""}

这时发现仍然有问题,容器内的主程序是可以运行了,不过很快就退出了。什么原因呢?

这是因为 passport 程序内部有连接 etcd,consul,mysql 等服务,而这些基础服务在宿主机器上默认监听地址是 127.0.0.1。而容器内也确实存在 127.0.0.1/localhost 地址。不过这和宿主机上的 127.0.0.1 是不一样的,所以容器内的程序就无法访问宿主机上的 127.0.0.1/localhost。解决方法是把宿主机器上的 etcd, consul 等服务的监听地址修改为 0.0.0.0。如:

#consul
$ nohup /usr/local/opt/consul/bin/consul agent -dev -client 0.0.0.0
$ lsof -nP -iTCP -sTCP:LISTEN | grep consul
consul    79872 will    5u  IPv4 0x9c21088b2c0aa1b      0t0  TCP 127.0.0.1:8300 (LISTEN)
consul    79872 will    6u  IPv4 0x9c210888fd5863b      0t0  TCP 127.0.0.1:8302 (LISTEN)
consul    79872 will    8u  IPv4 0x9c21088b2c1137b      0t0  TCP 127.0.0.1:8301 (LISTEN)
consul    79872 will   11u  IPv6 0x9c21088832ff0d3      0t0  TCP *:8600 (LISTEN)
consul    79872 will   12u  IPv6 0x9c21088832fd413      0t0  TCP *:8500 (LISTEN)

#etcd
$ nohup ./etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379

$ lsof -nP -iTCP -sTCP:LISTEN | grep etcd
etcd      80114 will    4u  IPv4 0x9c21088b0d6463b      0t0  TCP 127.0.0.1:2380 (LISTEN)
etcd      80114 will    6u  IPv6 0x9c21088832ff693      0t0  TCP *:2379 (LISTEN)

好,让我们再重启下容器并加上环境变量

# 后台运行
$ docker container run -it -p 80:8080 -d -e ETCD_ADDR=172.16.60.88:2379 -e CONSUL_ADDR=172.16.60.88:8500 passport-scratch:1.0.1

OR
# 前台运行并删除
$ docker container run --rm -p 8080:8080 -it -e ETCD_ADDR=172.16.60.88:2379 -e CONSUL_ADDR=172.16.60.88:8500 passport-scratch:1.0.1

2019-11-15T17:25:33.747+0800    DEBUG    setting/etcd.go:35    setting.etcd: Backend config    {"backend": "etcdv3", "machines": ["127.0.0.1:2379"], "keyspace": "","traceId":""}

### 以下是启动信息
2019-11-15T22:46:12.278+0800    DEBUG    setting/etcd.go:35    setting.etcd: Backend config    {"backend": "etcdv3", "machines": ["10.10.1.29:2379"], "keyspace": "","traceId":""}
2019-11-15T22:46:12.290+0800    DEBUG    setting/etcd.go:65    setting.etcd: Retrieved mysql-key-val from etcd store    {"key": "root/config/common/database/mysql/passport", "config": {"master":{"dsn":"","user":"root","pass":"GL@c*Nm#dkaLH!FNe","host":"rm-j6cli54dhwo5ski2quo.mysql.rds.aliyuncs.com","port":3306,"dbname":"peduli","max_open":100,"max_idle":10},"slave":{"dsn":"","user":"root","pass":"GL@c*Nm#dkaLH!FNe","host":"rm-j6cli54dhwo5ski2quo.mysql.rds.aliyuncs.com","port":3306,"dbname":"peduli","max_open":100,"max_idle":10}}, "traceId": ""}
DEBU[0001] new passport mysql store                      MasterDB="&{<nil> <nil> 0 0xc42022ac80 false 2 {0xc42034a280} <nil> map[] 0xc4200e05a0 0x16c7aa0 0xc4201e39a0 false}" SlaveDB="&{<nil> <nil> 0 0xc42022ae60 false 2 {0xc42034a280} <nil> map[] 0xc4200e06c0 0x16c7aa0 0xc4201e3b60 false}"
2019-11-15T22:46:13.888+0800    DEBUG    setting/etcd.go:35    setting.etcd: Backend config    {"backend": "etcdv3", "machines": ["10.10.1.29:2379"], "keyspace": "","traceId":""}
2019-11-15T22:46:13.894+0800    DEBUG    setting/etcd.go:102    setting.etcd: Retrieved redis-key-val from etcd store    {"key": "root/config/common/database/redis", "config": {"master":{"addr":"127.0.0.1:6379","password":"","db":1},"slave":{"addr":"127.0.0.1:6379","password":"","db":1}}, "traceId": ""}
2019-11-15T22:46:13.895+0800    DEBUG    setting/etcd.go:35    setting.etcd: Backend config    {"backend": "etcdv3", "machines": ["10.10.1.29:2379"], "keyspace": "","traceId":""}
2019-11-15T22:46:13.902+0800    DEBUG    setting/etcd.go:102    setting.etcd: Retrieved redis-key-val from etcd store    {"key": "root/config/common/database/redis", "config": {"master":{"addr":"127.0.0.1:6379","password":"","db":1},"slave":{"addr":"127.0.0.1:6379","password":"","db":1}}, "traceId": ""}
2019-11-15 22:46:13.905270 I | Initializing logging reporter
INFO[0001] new command                                   Command="&{<nil> <nil>}"
2019-11-15T22:46:13.907+0800    DEBUG    setting/etcd.go:35    setting.etcd: Backend config    {"backend": "etcdv3", "machines": ["10.10.1.29:2379"], "keyspace": "","traceId":""}
2019-11-15T22:46:13.916+0800    DEBUG    setting/etcd.go:65    setting.etcd: Retrieved mysql-key-val from etcd store    {"key": "root/config/common/database/mysql/passport", "config": {"master":{"dsn":"","user":"root","pass":"GL@c*Nm#dkaLH!FNe","host":"rm-j6cli54dhwo5ski2quo.mysql.rds.aliyuncs.com","port":3306,"dbname":"peduli","max_open":100,"max_idle":10},"slave":{"dsn":"","user":"root","pass":"GL@c*Nm#dkaLH!FNe","host":"rm-j6cli54dhwo5ski2quo.mysql.rds.aliyuncs.com","port":3306,"dbname":"peduli","max_open":100,"max_idle":10}}, "traceId": ""}
DEBU[0003] new passport mysql store                      MasterDB="&{<nil> <nil> 0 0xc4200ba6e0 false 2 {0xc42034a280} <nil> map[] 0xc4200e0a20 0x16c7aa0 0xc42019bee0 false}" SlaveDB="&{<nil> <nil> 0 0xc4201c3ea0 false 2 {0xc42034a280} <nil> map[] 0xc420376480 0x16c7aa0 0xc4201e2780 false}"
DEBU[0003] new store                                     MySQL="&{0xc4200e0a20 0xc420376480 0xc4200831e0}" config="&{dev [10.10.1.29:2379]    0 0               }"
2019-11-15T22:46:15.736+0800    DEBUG    setting/etcd.go:35    setting.etcd: Backend config    {"backend": "etcdv3", "machines": ["10.10.1.29:2379"], "keyspace": "","traceId":""}
2019-11-15T22:46:15.742+0800    DEBUG    setting/etcd.go:83    setting.etcd: Retrieved service-key-val from etcd store    {"key": "root/config/custom/go_service_passport", "config": {"Runmode":"","EtcdEndpoints":null,"AppConfigPath":"","ServiceName":"","ServiceIP":"","ServiceHttpPort":0,"ServiceRpcPort":0,"log_level":"debug","log_path":"","domain_www":"https://www.pedulisehat.id","domain_api":"","domain_passport":"https://passport-qa.pedulisehat.id","domain_project":"https://project-qa.pedulisehat.id","domain_trade":"https://trade-qa.pedulisehat.id","domain_static_avatar":"https://static-qa.pedulisehat.id/img/avatar","url_share_project":"","url_ico":"","host_passport":"","host_project":"","host_trade":"","url_share":"","domain_gtry":""},"traceId":""}
DEBU[0003] setting.NewConfig                             Config="&{dev [10.10.1.29:2379]  go_service_passport 0.0.0.0 8080 9080 debug  https://www.pedulisehat.id  https://passport-qa.pedulisehat.id https://project-qa.pedulisehat.id https://trade-qa.pedulisehat.id https://static-qa.pedulisehat.id/img/avatar       }"
INFO[0003] command run ...                               Command="&{<nil> 0xc4201e21c0}"
2019-11-15 22:46:15.744629 I | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:    export GIN_MODE=release
 - using code:    gin.SetMode(gin.ReleaseMode)

2019-11-15 22:46:15.745561 I | RPC Server has been started up. 172.17.0.2:9080

可以发现我们的 passport 服务可以正常启动了,查看下容器的运行状态:

$ docker container ls --all
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                            NAMES
b58ee39088dd        passport-multi-stage:1.0.9   "./go_service_passpo…"   5 seconds ago       Up 4 seconds        9080/tcp, 0.0.0.0:80->8080/tcp   recursing_poitras

参数说明:

-p 参数: 容器的 8080 端口映射到本机的 80 端口。
-it 参数: 容器的 Shell 映射到当前的 Shell,然后你在本机窗口输入的命令,就会传入容器。其中,-t 选项让 Docker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上,-i 则让容器的标准输入保持打开。
passport-scratch:1.0.1: image 文件的名字(如果有标签,还需要提供标签,默认是 latest 标签)。

当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:

  • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个 ip 地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

到此,完整的 docker 容器制作流程讲完了!

发布 image 文件

容器运行成功后,就确认了 image 文件的有效性。这时,我们就可以考虑把 image 文件分享到网上,让其他人使用。image 文件制作完成后,可以上传到网上的仓库。

首先,去 Docker 的官方仓库 Docker Hub 注册一个账户,这是最重要、最常用的 image 仓库。

$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: isgiker
Password: 
Login Succeeded

接着,为本地的 image 标注用户名和版本。

$ docker image tag [imageName] [username]/[repository]:[tag]
# 实例
$ docker image tag passport-multi-stage:1.0.9 isgiker/passport-multi-stage:1.0.9

最后,发布 image 文件。

$ docker image push isgiker/passport-multi-stage:1.0.9
The push refers to repository [docker.io/isgiker/passport-multi-stage]
e22072d3470d: Pushed 
9136612a4372: Pushed 
dac53910d311: Pushed 
77cae8ab23bf: Mounted from library/alpine 
1.0.9: digest: sha256:b5e9f0db2bd3e9ba684c8c359b087aa097adbb6a7426732b6d9246ca1b3dd6dc size: 1158

可以通过 docker search 命令来查找官方仓库中的镜像,

$ docker search keywords[username/image name]

命令

image 命令

docker image COMMAND

Child commands

Command Description
docker image build Build an image from a Dockerfile
docker image history Show the history of an image
docker image import Import the contents from a tarball to create a filesystem image
docker image inspect Display detailed information on one or more images
docker image load Load an image from a tar archive or STDIN
docker image ls List images
docker image prune Remove unused images
docker image pull Pull an image or a repository from a registry
docker image push Push an image or a repository to a registry
docker image rm Remove one or more images
docker image save Save one or more images to a tar archive (streamed to STDOUT by default)
docker image tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE

docker image 命令

container 命令

docker container COMMAND

Child commands

Command Description
docker container attach Attach local standard input, output, and error streams to a running container
docker container commit Create a new image from a container’s changes
docker container cp Copy files/folders between a container and the local filesystem
docker container create Create a new container
docker container diff Inspect changes to files or directories on a container’s filesystem
docker container exec Run a command in a running container
docker container export Export a container’s filesystem as a tar archive
docker container inspect Display detailed information on one or more containers
docker container kill Kill one or more running containers
docker container logs Fetch the logs of a container
docker container ls List containers
docker container pause Pause all processes within one or more containers
docker container port List port mappings or a specific mapping for the container
docker container prune Remove all stopped containers
docker container rename Rename a container
docker container restart Restart one or more containers
docker container rm Remove one or more containers
docker container run Run a command in a new container
docker container start Start one or more stopped containers
docker container stats Display a live stream of container(s) resource usage statistics
docker container stop Stop one or more running containers
docker container top Display the running processes of a container
docker container unpause Unpause all processes within one or more containers
docker container update Update configuration of one or more containers
docker container wait Block until one or more containers stop, then print their exit codes

docker container 命令

docker 命令

Child commands

Command Description
docker attach Attach local standard input, output, and error streams to a running container
docker build Build an image from a Dockerfile
docker builder Manage builds
docker checkpoint Manage checkpoints
docker commit Create a new image from a container’s changes
docker config Manage Docker configs
docker container Manage containers
docker context Manage contexts
docker cp Copy files/folders between a container and the local filesystem
docker create Create a new container
docker deploy Deploy a new stack or update an existing stack
docker diff Inspect changes to files or directories on a container’s filesystem
docker engine Manage the docker engine
docker events Get real time events from the server
docker exec Run a command in a running container
docker export Export a container’s filesystem as a tar archive
docker history Show the history of an image
docker image Manage images
docker images List images
docker import Import the contents from a tarball to create a filesystem image
docker info Display system-wide information
docker inspect Return low-level information on Docker objects
docker kill Kill one or more running containers
docker load Load an image from a tar archive or STDIN
docker login Log in to a Docker registry
docker logout Log out from a Docker registry
docker logs Fetch the logs of a container
docker manifest Manage Docker image manifests and manifest lists
docker network Manage networks
docker node Manage Swarm nodes
docker pause Pause all processes within one or more containers
docker plugin Manage plugins
docker port List port mappings or a specific mapping for the container
docker ps List containers
docker pull Pull an image or a repository from a registry
docker push Push an image or a repository to a registry
docker rename Rename a container
docker restart Restart one or more containers
docker rm Remove one or more containers
docker rmi Remove one or more images
docker run Run a command in a new container
docker save Save one or more images to a tar archive (streamed to STDOUT by default)
docker search Search the Docker Hub for images
docker secret Manage Docker secrets
docker service Manage services
docker stack Manage Docker stacks
docker start Start one or more stopped containers
docker stats Display a live stream of container(s) resource usage statistics
docker stop Stop one or more running containers
docker swarm Manage Swarm
docker system Manage Docker
docker tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
docker top Display the running processes of a container
docker trust Manage trust on Docker images
docker unpause Unpause all processes within one or more containers
docker update Update configuration of one or more containers
docker version Show the Docker version information
docker volume Manage volumes
docker wait Block until one or more containers stop, then print their exit codes

docker container 命令

参考

Go 语言应用 Docker 化部署

Building Docker Containers for Go Applications

Deploying a containerized Go app on Kubernetes

Building Minimal Docker Containers for Go Applications

构建安全可靠、最小化的 Docker 镜像

Docker ARG, ENV and .env – a Complete Guide

How To Pass Environment Info During Docker Builds
跟我一起学 Docker——Volume

退出移动版