切换导航
{{systemName}}
{{ info.Title }}
{{info.Title}}
{{ menu.Title }}
{{menu.Title}}
登录
|
退出
搜索
golang 打包到docker运行,最小镜像
作者:ych
go项目想运行在docker中,需要先制作镜像。主要有两种方式! 1.在 https://hub.docker.com/ 中 搜索 golang ,Dockerfile 中 依赖 golang 镜像 (大概100M左右) 这种方式 如下,是在容器里,将 golang 程序编译的。所以需要依赖 golang sdk 进行编译 ``` FROM golang:alpine WORKDIR $GOPATH/src/gin_docker ADD . ./ ENV GO111MODULE=on ENV GOPROXY="https://goproxy.io" RUN go build -o gin_docker . EXPOSE 8080 ENTRYPOINT ["./gin_docker"] ``` 这种方式,我不太喜欢,打包的镜像文件太大了 。 最少也是100M,也浪费了golang 的交叉编译的功能。 2.在本地使用交叉编译,将go程序编译成 Linux 可以执行文件,然后只需要依赖一个非常小的操作系统就可以了。如: alpine 只有 3M 左右的大小。再加上go可执行文件的大小。也就是十几MB 随便编写一个 go 程序,如下 ``` package main import ( "fmt" "github.com/gin-gonic/gin" "github.com/spf13/viper" "strconv" "strings" ) //项目配置 type AppConfig struct { AppName string Port int Description string } var conf AppConfig // 初始化配置文件 func init() { viper.SetConfigName("config") viper.SetConfigType("toml") viper.AddConfigPath("/etc/appname/") // 查找配置文件所在路径 viper.AddConfigPath("$HOME/.appname") // 多次调用AddConfigPath,可以添加多个搜索路径 viper.AddConfigPath(".") // 还可以在工作目录中搜索配置文件 viper.AddConfigPath("./conf") // 还可以在工作目录中搜索配置文件 if err := viper.ReadInConfig(); err != nil { fmt.Printf("read config failed: %v \r", err) } if err := viper.Unmarshal(&conf); err != nil { fmt.Println(err) } } // 使用 gin 创建一个 http 服务 func main() { r := gin.Default() r.Any("/", func(c *gin.Context) { message := strings.Join([]string{"Hello", conf.AppName, conf.Description}, " ") c.JSON(200, gin.H{ "message": message, }) }) appPort := strings.Join([]string{":", strconv.Itoa(conf.Port)}, "") r.Run(appPort) } ``` config.toml 配置文件,如下 ``` appName = "go docker demo1" port = 8089 description = "这是一个非常牛逼的app" ``` 最小化打包 ``` FROM golang:1.7.3 as build RUN go build . FROM alpine:latest COPY —from=build app . CMD [“./app”] ``` >dockerfile先from golang编译好,然后from alpine 拷贝二进制进去 ### 进一步优化 对于dockerfile而言,何为完美? 我认为应该满足以下三点: >体积小 构建快 够安全 ``` FROM golang:1.13.5-alpine3.10 AS builder WORKDIR /build RUN adduser -u 10001 -D app-runner ENV GOPROXY https://goproxy.cn COPY go.mod . COPY go.sum . RUN go mod download COPY . . RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -a -o your-application . FROM alpine:3.10 AS final WORKDIR /app COPY --from=builder /build/your-application /app/ #COPY --from=builder /build/config /app/config COPY --from=builder /etc/passwd /etc/passwd COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ USER app-runner ENTRYPOINT ["/app/your-application"] ``` #### 逐行拆解 首先,这个dockerfile分为builder和final两部分。 builder选择了golang:1.13.5-alpine3.10作为编译的基础镜像,相比于golang:1.13, 一方面是因为它体积更小,另一方面是我发现golang:1.13的编译结果,在alpine:3.10中会报not found的错误,虽说有人提供了其它的[解决方案](https://cloud.tencent.com/developer/article/1419659 "解决方案"),但是能直接避免,为啥不避免呢。 ``` RUN adduser -u 10001 -D app-runner ``` 接着是创建了一个app-runner的用户, -D表示无密码。 此用户的信息是是需要拷到final中,作为应用程序的启动用户。这是为了避免使用container中的默认用户root,那可是有安全漏洞的,详细解释,可以参考这篇medium上的文章[Processes In Containers Should Not Run As Root](https://medium.com/@mccode/processes-in-containers-should-not-run-as-root-2feae3f0df3b "Processes In Containers Should Not Run As Root") 再下面的四行, ``` ENV GOPROXY https://goproxy.cn COPY go.mod . COPY go.sum . RUN go mod download ``` 是配置了国内的代理,安装依赖包了。这里用`go mod download`的好处是下次构建镜像文件时,当`go.mod`和`go.sum`没有改变时,它是有缓存的,可以避免重复下载依赖包,加快构建。 builder的最后,就是把当前目录的文件拷过去,编译代码了。 ``` COPY . . RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -a -o your-application . ``` `final`选择了`alpine:3.10`,一方面是体积小,只有5m;另一方面也是和构建镜像的`alpine`版本保持一致。 接下来几行没啥说的,就是把构建结果、配置文件(有的话)和用户的相关文件拷过去。 下面的这步一定不要忘记了, ``` USER app-runner ``` 没有它,`container`启动时就是用`root`用户启动了!!! 如果被攻击了,那黑客可是就有`root`权限了(不要问我为啥会被攻击)。 最后,设置一个`ENTRYPOINT`,完事! 如果你程序的启动过程比较复杂,或者是要在启动时根据环境变量的值做不同的操作,那还是写个shell文件吧。
评论区
先去登录
版权所有:机遇屋在线 Copyright © 2021-2025 jiyuwu Co., Ltd.
鲁ICP备16042261号-1