这是我参与「第三届青训营 -后端场」笔记创作活动的的第四篇笔记
前言
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows操作系统的机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口
官方文档:Build your Go image | Docker Documentation
示例程序源码:github.com/zzyzzyzzyzz…
一、文件命名:Dockerfile
用于 Dockerfile 的默认文件名是:Dockerfile(不带文件扩展名)。使用默认的名称允许你运行命令:docker build,而无需指定其他命令标志。
出于某些特定目的,某些项目可能需要不同的Dockerfiles。常见的约定是将这些Dockerfile命名Dockerfile.something or something.Dockerfile。然后可以通过docker build命令加上--file(或-f)选项使用此类Dockerfiles。具体请参阅docker build reference中的“指定Dockerfile”部分,以了解--file选项。
推荐使用第一种命名方式
二、编写Dockerfile
1、语法解析
指示 Docker 构建器在解析 Dockerfile 时要使用的语法,并允许启用了 BuildKit 的较旧 Docker 版本在开始构建之前升级解析器。解析器指令必须出现在 Dockerfile 中的任何其他注释、空格或 Dockerfile 指令之前,并且应该是 Dockerfile 中的第一行。
# syntax=docker/dockerfile:1
推荐使用:docker/dockerfile:1(指向最新版本)
2、指明使用镜像
告诉 Docker 我们要为应用程序使用哪个基本镜像,在我们的镜像中包含该镜像中的所有功能
FROM golang:1.16-alpine
Docker 镜像可以从其他镜像继承。因此,我们将使用已经具有所有工具和包的官方 Go镜像来编译和运行 Go 应用程序,而不是创建我们自己的基本镜像。您可以像考虑面向对象编程中的类继承或函数式编程中的函数组合一样考虑这一点。
如果要了解有关创建自己的基本映像的详细信息,请参阅本指南的创建基本映像部分。
3、创建工作目录
为了在运行其余命令时更轻松,让我们在正在构建的映像内创建一个目录。这也指示 Docker 将此目录用作所有后续命令的默认目标。这样,我们不必键入完整的文件路径,而是可以使用基于此目录的相对路径。
WORKDIR /app
4、复制源文件
通常,一旦我们下载了一个用Go编写的项目,要做的第一件事就是安装编译它所需的模块。但是,在我们可以在映像中运行之前,我们需要将和文件复制到其中。我们使用一下命令来执行此操作。
#复制所有文件
COPY . /app
以上命令是将所有的文件复制到镜像目录中也可已采用以下命令分步处理
#复制项目所需依赖模块
COPY go.mod ./
COPY go.sum ./
#复制所有源文件(扩展名为go的文件)
COPY *.go ./
5、设置代理
源网站对咱们国内不太友好,访问速度较慢,有些依赖包可能会因为网络问题拉取失败。所有通过设置代理来提高下载速度。
ENV GOPROXY=https://goproxy.cn,direct
6、下载依赖
现在,我们已经在正在构建的 Docker 镜像中拥有了模块文件,因此也可以使用该命令在那里执行该命令。这与我们在计算机上本地运行的工作方式完全相同,但这次这些Go模块将安装到镜像内的目录中。
RUN go mod download
7、编译
此时,我们有一个基于 Go 环境版本 1.16(或更高版本的次要版本,因为我们在命令中指定为标记)的镜像,并且已安装依赖项。
现在,我们想编译我们的应用程序。为此,我们使用熟悉的命令:RUN
RUN go build -o /docker-demo
这应该很熟悉。该命令的结果将是一个静态应用程序二进制文件,该二进制文件命名并位于我们正在构建的镜像的文件系统的根目录中。我们可以将二进制文件放入该镜像中所需的任何其他位置,根目录在这方面没有特殊含义。使用它来保持文件路径简短以提高可读性非常方便。
8、CMD命令
现在,剩下要做的就是告诉 Docker 当我们的映像用于启动容器时要执行的命令。
我们用以下命令执行此操作:CMD
CMD ["/docker-demo"]
9、完整Dockerfile
# syntax=docker/dockerfile:1
#使用golang:1.16-alpine作为基本镜像
FROM golang:1.16-alpine
#为了在运行其余命令时更轻松,让我们在正在构建的镜像内创建一个目录。
#这也指示 Docker 将此目录用作所有后续命令的默认目标。
#这样,我们不必键入完整的文件路径,而是可以使用基于此目录的相对路径
WORKDIR /app
#把所有的文件复制到镜像目录中
COPY . /app
#为映像设置代理,方便依赖包的下载
ENV GOPROXY=https://goproxy.cn,direct
#下载依赖包
RUN go mod download
#编译我们的应用程序
RUN go build -o /docker-demo
#这里要与你的项目名字一致
CMD ["/docker-demo"]
三、构建镜像
现在我们已经创建了Dockerfile,让我们从中构建一个图像。docker build命令从Dockerfile和“上下文”创建docker图像。生成上下文是位于指定路径或URL中的一组文件。Docker构建过程可以访问上下文中的任何文件。 build命令可以选择使用--tag标志。此标志用于用字符串值标记图像,这便于人们阅读和识别。如果未传递--标记,Docker将使用latest作为默认值。
#进入Dockerfile文件所在目录
#不要忘记后面那个“.”
docker build --tag docker-demo .
查看镜像:
docker image ls
标记镜像:
镜像名称由斜杠分隔的名称组件组成。名称组件可以包含小写字母、数字和分隔符。分隔符定义为句点、一个或两个下划线或一个或多个短划线。名称组件不能以分隔符开头或结尾。
镜像由清单和层列表组成。简单来说,“标签”指向这些工件的组合。您可以为镜像设置多个标记,实际上,大多数镜像都具有多个标记。让我们为构建的镜像创建第二个标记,并查看其图层。
使用(或速记)命令为我们的镜像创建新标记。此命令采用两个参数;第一个参数是“源”镜像,第二个参数是要创建的新标记。以下命令为我们上面构建的 创建一个新标记:
docker image tag docker-demo:latest docker-demo:v1.0
四、多阶段构建
#此步可以忽略
我们使用一个镜像来制作一些伪镜像,然后将其放入另一个更小的镜像中,仅包含运行我们构建的所需的部件。
# syntax=docker/dockerfile:1
##
## Build
##
FROM golang:1.16-buster AS build
WORKDIR /app
#为映像设置代理,方便依赖包的下载
ENV GOPROXY=https://goproxy.cn,direct
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY *.go ./
RUN go build -o /docker-demo
##
## Deploy
##
FROM gcr.io/distroless/base-debian10
WORKDIR /
COPY --from=build /docker-demo /docker-demo
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/docker-demo"]
由于我们现在有两个 dockerfile,我们必须告诉 Docker 我们要使用新的 Dockerfile 进行构建。我们也用multistage标记新镜像,但这个词没有特殊含义,我们这样做只是为了将这个新镜像与我们之前构建的镜像进行比较,这是我们标记的那个latest:
docker build -t docker-demo:multistage -f Dockerfile.multistage .
比较两者大小,我们看到一个数量级的差异
五、将映像作为容器运行
1、容器内运行
若要在容器内运行镜像,请使用docker run。它需要一个参数,即镜像名称。让我们启动镜像并确保它正常运行。在终端中执行以下命令。
docker run docker-demo
当运行此命令时,你会注意到你没有返回到命令提示符。这是因为我们的应用程序是一个 REST 服务器,它将在循环中运行,等待传入的请求,而不会将控制权返回给操作系统,直到我们停止容器。
注意:此时我们无法在容器外部访问
curl http://localhost:8080/hello
2、映射到外部端口
上一步中我们的 curl 命令失败,因为与服务器的连接被拒绝。这意味着我们无法连接到端口8080上的本地主机。这是预料之中的,因为我们的容器在包括网络在内的隔离运行中运行。让我们停止容器,并使用本地网络上发布的端口 8080 重新启动。
若要停止容器,请按 Ctrl-c。这将返回到终端提示符。
若要为容器发布端口,我们将在 docker run 命令上使用--publish标志(-p简称)。该--publish 命令的格式为[host_port]:[container_port]。因此,如果我们想将容器内的8080端口公开给容器外部的3000端口,我们将传递3000:8080给--publish标志。
启动容器并把容器8080端口映射到主机8080端口:
docker run --publish 8080:8080 docker-demo
现在,让我们从上面重新运行 curl 命令。
curl http://localhost:8080/hello
成功!我们能够连接到端口 8080 上容器内运行的应用程序。切换回运行容器的终端,您应该会看到请求已记录到控制台。GET
按 ctrl-c 停止容器或者直接关闭终端窗口。
3、分离模式下运行
到目前为止,这很好,但是我们的示例应用程序是一个Web服务器,我们不应该将终端连接到容器。Docker 可以在分离模式下运行容器,即在后台运行。为此,我们可以使用--detach 或 -d。Docker 将像以前一样启动容器,但这次将从容器中“分离”,并将你返回到终端提示符。
docker run -d -p 8080:8080 docker-demo
Docker 在后台启动了我们的容器,并在终端上打印了容器 ID。
同样,让我们确保容器正常运行。运行相同的curl命令或者直接用浏览器访问:http://localhost:8080/hello
4、容器相关常用命令
4.1 查看容器
# 查看正在运行的容器
docker ps
# 查看所有容 包括停止的容器
docker ps -a
# -q参数,只显示container id(容器id)
docker ps -q
# 查看容器详细信息
docker inspect demo1
4.2 容器启动与停止
#新建并启动容器,参数:-i 以交互模式运行容器;-t 为容器重新分配一个伪输入终端;--name 为容器指定一个名称
docker run -i -t --name 容器名称 镜像名称/镜像ID
#后台启动容器,参数:-d 以分离模式启动容器
docker run -d 容器名称
#启动止容器
docker start 容器id
# 重启容器
docker restart 容器id
# 关闭容器
docker kill 容器id
docker stop 容器id
-t 参数让Docker分配一个伪终端并绑定到容器的标准输入上
-i 参数则让容器的标准输入保持打开。
-c 参数用于给运行的容器分配cpu的shares值
-m 参数用于限制为容器的内存信息,以 B、K、M、G 为单位
-v 参数用于挂载一个volume,可以用多个-v参数同时挂载多个volume
-p 参数用于将容器的端口暴露给宿主机端口 格式:host_port:container_port 或者 host_ip:host_port:container_port
--name 容器名称
--net 容器使用的网络
-d 创建一个后台运行容器
4.3 容器进入与退出
#使用run方式在创建时进入
docker run -it 容器名称 /bin/bash
#关闭容器并退出
exit
#仅退出容器,不关闭
快捷键:Ctrl + P + Q
快捷键:Ctrl + Shift + P + Q
4.4 容器进程
#top支持 ps 命令参数,格式:docker top [OPTIONS] CONTAINER [ps OPTIONS]
#列出docker-demo容器中运行进程
docker top docker-demo
#查看所有运行容器的进程信息
for i in `docker ps |grep Up|awk '{print $1}'`;do echo \ &&docker top $i; done
4.5 容器日志
# 查看docker-demo容器日志,默认参数
docker logs docker-demo
# 查看docker-demo容器日志,参数:-f 跟踪日志输出;-t 显示时间戳;--tail 仅列出最新N条容器日志;
docker logs -f -t --tail=20 docker-demo
# 查看容器docker-demo从2022年05月20日后的最新10条日志。
docker logs --since="2022-05-20" --tail=10 docker-demo
#如有不足,欢迎指正