Go在docker环境下部署
给大家分享一个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来引用,即第一个from
从0
开始。其实我们还可以在FROM
指令中添加AS <NAME>
来命名构建阶段,接着在COPY
指令中通过<NAME>
引用。
- 只构建某个阶段
构建镜像时,您不一定需要构建整个 Dockerfile
,我们可以通过--target
参数指定某个目标阶段构建,比如我们开发阶段我们只构建builder
阶段进行测试。
#docker build --target builder -t builder_app:v2 .
- 使用外部镜像
使用多阶段构建时,我们局限于从之前在Dockerfile
中创建的阶段进行复制。还可以使用COPY --from
指令从单独的镜像复制,如本地镜像名称、本地或Dockerhub
上可用的标签或标签ID
。Docker
客户端在必要时会拉取需要的镜像到本地。
COPY --from httpd:latest /usr/local/apache2/conf/httpd.conf ./httpd.conf
- 从上一阶段创建新的阶段
我们可以通过FROM指令来引用上一阶段作为新阶段的开始。
Docker
中 COPY
和 ADD
的区别是:
COPY
指令不支持从远程URL获取资源,只能从执行docker build
所在的主机上读取资源并复制到镜像中;而ADD
指令支持从远程URL
获取资源,可以通过URL
从远程服务器读取资源并复制到镜像中。
ldflags在golang编译中的2个作用
- ldflags用于链接过程。 主要是控制打包过程。
- 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"]
文件内容简要说明:
- 第一个
FROM
开始的部分是构建一个builder
镜像,目的是在其中编译出可执行文件main
,第二个From
开始的部分是从第一个镜像里copy
出来可执行文件main
,并且用尽可能小的基础镜像alpine
以保障最终镜像尽可能小,alpine
大概是5MB,对我们的服务不会构成多少影响。 - 默认禁用了
cgo
- 启用了
GOPROXY
加速go mod download
- 去掉了调试信息
-ldflags="-s -w"
以减小镜像尺寸 - 安装了
ca-certificates
,这样使用TLS
证书就没问题了 tzdata
在builder
镜像安装,并在最终镜像只拷贝了需要的时区- 自动设置了本地时区,这样我们在日志里看到的是北京时间了
执行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
看到正确的响应,表示服务部署成功。