Golang 容器部署

维子 · · 723 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:维子

查看原文:Golang 容器部署

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

723 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传