Go在docker环境下部署

459 阅读6分钟

给大家分享一个Go如何在docker环境下部署Go应用程序,例如:已经写好一个简单的Go应用程序,下面是目录结构。

.
├── go.mod
└── main.go

main.go 中的代码如下:

package main

// 导入gin包
import "github.com/gin-gonic/gin"

// 入口函数
func main() {
    // 初始化一个http服务对象
    r := gin.Default()

    // 设置一个get请求的路由,url为/ping, 处理函数(或者叫控制器函数)是一个闭包函数。
    r.GET("/ping", func(c *gin.Context) {
        // 通过请求上下文对象Context, 直接往客户端返回一个json
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })

    r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

Go 代码构建出 docker 镜像,其中分为三步:

  • 本机编译 Go 代码,如果牵涉到 cgo 跨平台编译就会比较麻烦了。
  • 用编译出的可执行文件构建 docker 镜像。
  • 编写 shell 脚本或者 makefile 让这几步通过一个命令可以获得

多阶段构建就是把这一切都放到一个 Dockerfile 里,既没有源码泄漏,又不需要用脚本去跨平台编译,还获得了最小的镜像。

什么是多阶段构建?

在Docker Engine 17.05 中引入了多阶段构建,以此降低构建复杂度,同时使缩小镜像尺寸更为简单。

在一个Dockerfile中使用多个FROM指令,每个FROM都可以使用不同的基镜像,并且每条指令都将开始新阶段构建。在多阶段构建中,我们可以将资源从一个阶段复制到另一个阶段,在最终镜像中只保留我们所需要的内容。

默认情况下构建阶段没有名称,我们可以通过整数0~N来引用,即第一个from0开始。其实我们还可以在FROM指令中添加AS <NAME> 来命名构建阶段,接着在COPY指令中通过<NAME>引用。

  • 只构建某个阶段

构建镜像时,您不一定需要构建整个 Dockerfile,我们可以通过--target参数指定某个目标阶段构建,比如我们开发阶段我们只构建builder阶段进行测试。

#docker build --target builder -t builder_app:v2 .
  • 使用外部镜像 使用多阶段构建时,我们局限于从之前在 Dockerfile 中创建的阶段进行复制。还可以使用COPY --from指令从单独的镜像复制,如本地镜像名称、本地或 Dockerhub上可用的标签或标签 IDDocker 客户端在必要时会拉取需要的镜像到本地。
COPY --from  httpd:latest /usr/local/apache2/conf/httpd.conf ./httpd.conf
  • 从上一阶段创建新的阶段 我们可以通过FROM指令来引用上一阶段作为新阶段的开始。

DockerCOPYADD的区别是:

COPY指令不支持从远程URL获取资源,只能从执行docker build所在的主机上读取资源并复制到镜像中;而ADD指令支持从远程URL获取资源,可以通过URL从远程服务器读取资源并复制到镜像中。

ldflags在golang编译中的2个作用

  1. ldflags用于链接过程。 主要是控制打包过程。
  2. ldflags在编译golang的时候,可以传入一些值用来配置golang的应用。

本章主要是讲解Go的打包,更深入的可以参考: ldflags在golang编译中的2个作用

docker访问外部https数字证书问题

一般构建 docker 镜像使用的都是 alpine linux 系统,默认是不带 ca-certificates 根证书的,导致无法识别外部 https 携带的数字证书。

在访问的时候,会抛出509:certificate signed by unknown authority错误,导致 docker 容器的接口服务返回报错。

为了解决证书验证的问题,我们需要在构建 docker 镜像的时候将 ca-certificates 根证书装上。 在 Dockerfile 中加入如下内容:

RUN apk --no-cache add ca-certificates && update-ca-certificates

了解了上面关于Docker的基本知识后,看如下的 Dockerfile 文件用于构建镜像,里面已经包含了详细的注释。

# 阶段1命名为builder
FROM golang:alpine AS builder 

## Labels允许你为Docker对象指定metadata。 可以通过 docker image inspect main:v
LABEL stage=gobuilder

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

# RUN apk --no-cache add tzdata 
# 因为alpine 基础镜像中没有包含时区信息文件,当代码中有调用类似下面这样的通过名称获取时区信息的时候

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

# 使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。

WORKDIR /build

ADD go.mod .
ADD go.sum .
RUN go mod download

# 把当前宿主机中的文件复制进来
COPY . .
RUN go build -ldflags="-s -w" -o /app/main ./main.go


FROM alpine

# 解决证书 和 时区问题,这里时区文件直接复制过来的
RUN apk update --no-cache && apk add --no-cache ca-certificates
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
ENV TZ Asia/Shanghai

# 从builder阶段的镜像中复制过来
WORKDIR /app
COPY --from=builder /app/main /app/main

# run起来后启动服务
CMD ["./main"]

文件内容简要说明:

  1. 第一个 FROM 开始的部分是构建一个 builder 镜像,目的是在其中编译出可执行文件 main,第二个 From 开始的部分是从第一个镜像里 copy 出来可执行文件 main,并且用尽可能小的基础镜像 alpine 以保障最终镜像尽可能小,alpine 大概是5MB,对我们的服务不会构成多少影响。
  2. 默认禁用了 cgo
  3. 启用了 GOPROXY 加速 go mod download
  4. 去掉了调试信息 -ldflags="-s -w" 以减小镜像尺寸
  5. 安装了 ca-certificates,这样使用 TLS 证书就没问题了
  6. tzdatabuilder 镜像安装,并在最终镜像只拷贝了需要的时区
  7. 自动设置了本地时区,这样我们在日志里看到的是北京时间了

执行docker images 查看镜像编译结果。

> docker images
REPOSITORY       TAG       IMAGE ID       CREATED         SIZE
main             v1        746f285b01f7   24 hours ago    14.9MB

下面我们启动一个容器实验一下

docker run -it --rm -p 8080:8080 --name main_v1 -d main:v1

docker run 加上--rm退出容器以后,这个容器就被删除了,方便在临时测试使用。 不加--rm 退出容器后,容器只是停止运行,数据任然被保留。

--name main_v1 对容器的命名

最后本地执行: curl http://localhost:8080/ping

看到正确的响应,表示服务部署成功。