Go在docker环境下部署

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

Go 复制代码
.
├── go.mod
└── main.go

main.go 中的代码如下:

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阶段进行测试。

arduino 复制代码
#docker build --target builder -t builder_app:v2 .
  • 使用外部镜像 使用多阶段构建时,我们局限于从之前在 Dockerfile 中创建的阶段进行复制。还可以使用COPY --from指令从单独的镜像复制,如本地镜像名称、本地或 Dockerhub上可用的标签或标签 IDDocker 客户端在必要时会拉取需要的镜像到本地。
bash 复制代码
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 中加入如下内容:

Dockerfile 复制代码
RUN apk --no-cache add ca-certificates && update-ca-certificates

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

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 查看镜像编译结果。

shell 复制代码
> docker images
REPOSITORY       TAG       IMAGE ID       CREATED         SIZE
main             v1        746f285b01f7   24 hours ago    14.9MB

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

Dockerfile 复制代码
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

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

相关推荐
程序员爱钓鱼2 小时前
Go if 判断详解
前端·后端·go
名字还没想好☜15 小时前
Go 的 unsafe.Pointer 实战:零拷贝 []byte↔string 转换与三条铁律
开发语言·后端·golang·go·unsafe
阿里云云原生17 小时前
阿里云联合 Datadog,补齐 Go 可观测性最后短板
云原生·go
可观测性用观测云17 小时前
零码改造!Go 语言应用上报观测云完整最佳实践
go
斐波那契数列19 小时前
参考react 实现一个golang gui
go
程序员爱钓鱼1 天前
Go 运算符详解
后端·面试·go
angryshan2 天前
goLang配置断点流程
go·bug
名字还没想好☜2 天前
Go 的 sync.Cond 实战:用条件变量做等待/通知,比忙轮询省 CPU
开发语言·数据库·后端·golang·go
程序员爱钓鱼2 天前
Go 输入输出详解
后端·面试·go
朦胧之3 天前
Go 项目设计
go