因为 go 最终是编译为一个二进制可执行文件,没有运行时依赖,也不须要管理库,丢到服务器上就能够直接运行。因此,若是你有一个二进制文件,那么在容器中打包二进制文件的要点是什么?若是使用 docker 的话,还得在服务器上装 docker,那么把最终程序打包成 docker 有什么好处呢?html
我想有这么几个好处:node
依赖打包
若是你的应用程序(二进制文件)依赖配置文件或一些静态文件,那使用docker就很方便的把这些文件一块儿打包进容器里。python
版本控制
启动Docker就和运行一个进程同样快,咱们能够在几秒钟的时间内运行整个服务器集群。除此以外,Docker 镜像的注册中心使Docker容器还能够像git仓库同样,可让你提交变动到Docker镜像中,并经过不一样的版原本管理它们。设想若是你由于完成了一个组件的升级而致使你整个环境都损坏了,Docker可让你轻松地回滚到这个镜像的前一个版本。这整个过程能够在几分钟内完成。mysql
隔离性
容器包含了应用程序的代码、运行环境、依赖库、配置文件等必需的资源。容器之间达到进程级别的隔离,在容器中的操做,不会影响道宿主机和其余容器,这样就不会出现应用之间相互影响的情形!linux
可移植性
能够实现开发、测试和生产环境的统一化和标准化。镜像做为标准的交付件,可在开发、测试和生产环境上以容器来运行,最终实现三套环境上的应用以及运行所依赖内容的彻底一致。在如今微服务的架构中,一个应用拆成几十个微服务,每一个微服务都对应有开发、测试、生产三套环境须要搭建。本身算算,若是采用传统的部署方式,有多少环境须要部署。nginx
轻量和高效
和虚拟机相比,容器仅须要封装应用和应用须要的依赖文件,实现轻量的应用运行环境,且拥有比虚拟机更高的硬件资源利用率。在微服务架构中,有些服务负载压力大,须要以集群部署,可能要部署几十台机器上,对于某些中小型公司来讲,使用虚拟机,代价太大。若是用容器,一样的物理机则能支持上千个容器,对中小型公司来讲,省钱!git
安全性
Docker容器不能窥视运行在其余容器中的进程。从体系结构角度来看,每一个容器只使用着本身的资源(从进程到网络堆栈)。做为紧固安全的一种手段,Docker将宿主机操做系统上的敏感挂载点(例如/proc和/sys)做为只读挂载点,而且使用一种写时复制系统来确保容器不能读取其余容器的数据。Docker也限制了宿主机操做系统上的一些系统调用,而且和SELinux与AppArmor一块儿运行的很好。此外,在Docker Hub上可使用的Docker镜像都经过数字签名来确保其可靠性。因为Docker容器是隔离的,而且资源是受限制的,因此即便你其中一个应用程序被黑,也不会影响运行在其它Docker容器上的应用程序。github
对于许多编程语言(包括 Go ),有几个很好的官方和社区支持的容器。咱们在容器化Go apps的时候,能够选择基于 Golang 官方镜像构建,如:golang:onbuild,golang:latest。可是这有一个很大的缺点:这些容器可能很大,因此基于它们的镜像建立的镜像文件将会很是大。golang
这是由于咱们的应用程序是在容器内编译的。这意味着该容器须要安装 Go ,以及 Go 的依赖关系,同时这也意味着咱们须要一个程序包管理器和整个操做系统。实际上,若是您查看 Golang 的 Dockerfile,它将以 Debian Jessie 开头,安装 GCC 编译器和一些构建工具,压缩 Go 并安装它。所以,咱们几乎有一个完整的 Debian 服务器和 Go 工具包来运行咱们的小型应用程序。redis
因此咱们应该使用一种静态构建 Go 容器化应用的方法,这种方法生成的镜像文件很是小。
我以 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
首先,在项目的根目录下,新建一个文本文件.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是一个文本文档,Docker能够经过阅读Dockerfile中的指令来自动构建映像。
该文档内的指令不区分大小写。可是,习惯是大写,以便更轻松地将它们与参数区分开。
Docker 按顺序在 Dockerfile 中运行指令。 Dockerfile 必须以 “FROM” 指令开头。固然,FROM 前面能够有一个或多个 ARG 指令或注释,ARG 指令声明 Dockerfile 中 FROM 行中使用的参数。
在开始以前咱们应该想清楚到底使用哪一个基础镜像?通常状况下,都会从如下三个基础镜像开始。
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 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 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指令初始化一个新的构建阶段,并为后续指令设置基本映像。所以,有效的 Dockerfile 必须以 FROM 指令开头。
格式:
FROM <image> [AS <name>] Or FROM <image>[:<tag>] [AS <name>] Or FROM <image>[@<digest>] [AS <name>]
MAINTAINER指令设置生成图像的“做者”字段。 LABEL指令是此指令的更为灵活的版本,您应该使用它,由于它能够设置所需的任何元数据,而且能够轻松查看,例如使用docker inspect。 要设置与MAINTAINER字段相对应的标签,可使用:
LABEL maintainer="SvenDowideit@home.org.au"
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命令。
设置环境变量。将环境变量<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>更改它们。
环境变量(用ENV语句声明)也能够在某些指令中用做Dockerfile解释的变量。经过在字面上将相似变量的语法包含到语句中来处理。
在 Dockerfile 文档中,可使用 $variable_name 或 ${variable_name} 引用环境变量,它们是等同的。其中大括号的变量是用在没有空格的变量名中的,如${foo}_bar。
${variable_name}变量也支持一些标准的bash修饰符,如:
word能够是任意的字符,包括额外的环境变量。
转义符(Escaping)能够添加在变量前面:$foo or ${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中的如下指令列表支持环境变量:
官方说明见: Environment replacement
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指令。
默认状况下,这些预约义变量从docker history记录的输出中排除。排除它们能够下降意外泄漏HTTP_PROXY变量中的敏感身份验证信息的风险。
--build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com
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遵照如下规则:
官方说明见:COPY
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遵照如下规则:
其它规则与COPY相同,不重复描述。
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指令将在当前映像顶部的新层中执行任何命令,并提交结果。生成的提交映像将用于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指令通知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命令支持建立用于容器之间通讯的网络,而无需暴露或发布特定端口,由于链接到网络的容器能够经过任何端口相互通讯。有关详细信息,请参阅此功能的概述。
用于指定默认的容器主进程的启动命令。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,一般翻译为数据卷,用于保存持久化数据。当咱们将数据库例如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 中数据持久化的最佳方式。
volume 在容器中止或删除的时候会继续存在,如需删除须要显示声明。
$ docker rm -v <container_id> $ docker volume rm <volume_name>
格式:
VOLUME ["<路径1>", "<路径2>"...] VOLUME <路径>
能够经过如下两种方式建立 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中的卷,请记住如下几点。
基于Windows的容器上的卷:使用基于Windows的容器时,容器内的卷的目的地必须是如下之一:
完成 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 引擎收到这个上下文包后,展开就会得到构建镜像所需的一切文件。
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 在后台运行的标准操做包括:
到此,完整的docker容器制做流程讲完了!
容器运行成功后,就确认了 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]
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 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 |
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 |
Building Docker Containers for Go Applications
Deploying a containerized Go app on Kubernetes
Building Minimal Docker Containers for Go Applications
Docker ARG, ENV and .env - a Complete Guide
How To Pass Environment Info During Docker Builds
跟我一块儿学Docker——Volume