【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

怎样构建 Golang Dockerfiles?

  • 2020-08-14
  • 本文字数:3861 字

    阅读完需:约 13 分钟

怎样构建Golang Dockerfiles?

Docker 提供了一些出色的构建时功能和基本映像,我们可以用它们来实现轻量、安全和高效的应用程序构建。


本文会介绍为什么 Golang 可以很好地展示这些特性,因为 Golang 可以编译为单个二进制文件(或一组二进制文件)。这篇文章的示例所关注的焦点是极简主义。尽管这些示例很基础,但它们非常重要,你可以在这些概念基础上为大型 Golang 项目引入更多最佳实践,以提高安全性和效率。


我们将使用这个简单的 main.go 来演示本文的概念:


package main import "fmt" func main() {  fmt.Println("Hello Cloudreach!") } 
复制代码

最少的层带来效率最大化:最佳实践

Docker 在 Dockerfile 文档中一上来就强调:尽量减少层数是一个最佳实践!这是一个重要的概念,必须从一开始就做好。


你很容易就能写一个包含很多层的 Dockerfile——它的语法就有这个倾向——结果你不知不觉中就会写出很多效率低下的内容。最佳实践是将构建的相关阶段分组和链接在一起,例如下载依赖项、供应商文件夹集成或使用 RUN 命令设置构建环境等阶段。


你还需要考虑分组的哪些部分是可以经常更改的,然后将它们分组到 Dockerfile 中尽可能低的层,同时把静态构建依赖项、构建环境配置或应用程序资产放到 Dockerfile 中尽可能上层的位置上。


每一层,更具体地说是 Dockerfile 中以指令开头的每一行,都经过哈希处理并建立在另一层之上,最后一个映像由“堆叠(stacked)”层构成。由于 Dockerfile 的每一层都是从下一层继承的,因此构建缓存提供了一种很好的机制,可以帮助你跳过已构建或静态的内容,然后转到你需要构建和重新哈希的部分!


尽量缩短构建时间是很重要的,因为高效的 CI/CD 系统每天都会运行这些构建很多次。当团队规模逐渐扩大后,这可能意味着大量的构建工作,可能会需要很多 Jenkins worker,有时甚至需要很长时间才能集成开发人员的代码!Docker 有一些构建缓存功能,它们可以显著节省构建时间。等待构建的时间越短,意味着集成和自动化测试的速度也就越快,也能提升 CI/CD 流程的速度,让你的流程足以和团队规模相匹配。


尽可能为应用程序安排单独的非 root 用户也是很重要的。你只需要在 linux adduser 命令中使用 RUN 指令,然后在 Dockerfile 中使用 USER 指令就可以使用这个用户来运行二进制文件了。


下面是使用这些最佳实践构造的一个最精简的 Dockerfile main.go 示例,它只有一个基本的 main 函数,没有外部依赖项:


FROM golang:alpine RUN mkdir /app  ADD . /app/ WORKDIR /app  RUN go build -o main . RUN adduser -S -D -H -h /app appuser USER appuser CMD ["./main"] 
复制代码


构建后,生成的映像大小为 378MB:


$  docker build -t hellocloudreachmain:1.0 . -f Dockerfile.single... (build output omitted)$  docker images | grep hellocloudreachmainhellocloudreachmain                1.0 d1c5090585bc  Less than a second ago 378MB
复制代码


尽可能使用基于 Alpine 的官方映像!它是基于 busybox 和 musl 构建的,最轻量级的 Linux 发行版之一。与较重的发行版相比,它的容器映像体积很小,这是一个轻松提升效率的好方法。


但我们还可以进一步简化!这些官方映像构建(比如golang:alpine)都包含很多层,里面含一些安全组件,用来构建应用程序资产时很方便;但是如果我们的应用程序不需要这些层,那就不要把它们放进去!我们需要使用其他一些 Docker 构建功能,进一步缩小文件体积。

下一步:多阶段

当需要在生产环境中运行应用程序时,我们需要让容器的设计可以确保性能和安全性。我们还需要尽可能多的可移植性,以便轻松地移动容器,并使用 DockerSwarm 和 Kubernetes 之类的编排器对其进行大规模调度。将映像推入和拉出注册表所需的时间应尽量缩短。编写用于生产的 Dockerfile 时,要记住的一个要点就是在最终运行时映像中实践极简主义。


如果运行的时候并不需要某样东西,请不要把它放进去!


在开发环境中,有时需要一个“较重”的容器映像,也许是一个开发人员专属的 Dockerfile,方便开发人员随时扔进来一些工具,和容器一起进行调试等开发活动。也可能会附加或保留一两个卷和活动容器交互。这些当然都是很常见的情况!


但是,随着容器映像沿开发管线向上移动,一定要记得把这些东西都取出来。一条正规的安全软件供应链会要求在管道中尽早构建最终映像,对映像签名,并将经过正式签名的映像推到生产环境的各个阶段。因此,你需要尽早在供应链中构建、验证、集成和签名这个最小化的映像;这是 Dockerfile 开发人员、QA 团队和安全工程师必须熟悉的操作!换句话说,只构建一次,然后让你的流程将生成的映像投入生产环境。


多阶段构建是实现这一目标的一个好方法!多阶段涉及的基本原理包括:调用一个临时容器以简化应用程序的构建,然后将构建的资产从这个空间复制到只有运行应用所需必要组件的容器映像中。拿之前的 Dockerfile 示例来说:


FROM golang:alpine as builder RUN mkdir /build  ADD . /build/ WORKDIR /build  RUN go build -o main . FROM alpine RUN adduser -S -D -H -h /app appuser USER appuser COPY --from=builder /build/main /app/ WORKDIR /app CMD ["./main"] 
复制代码


注意这个 Dockerfile 中的两个 FROM 指令。我们将第一个标记为“builder”,使用它来构建应用程序。然后,我们使用第二个 FROM,这一次是从基本的“alpine”(非常轻巧!)中提取的,并将我们构建的可执行文件从该环境复制到该新环境。这就让映像的体积比以前小了很多!另外,“builder”容器被缓存在 docker builder 上下文中,因此可以像前面的示例一样利用构建缓存来提升速度!


$  docker build -t hellocloudreachmain:1.1 . -f Dockerfile.multi... (build output omitted)$  docker images | grep hellocloudreachmainhellocloudreachmain                1.0 d1c5090585bc  8 minutes ago 378MBhellocloudreachmain                1.1 ea737df5cc64 Less than a second ago    6.16MB
复制代码


这个映像的大小是 6.16MB。相比 378MB 来说,体积减少的效果很明显!

最小化整个运行环境……从头开始构建!

实际上,我们在精简之路上还可以走得更远。Golang 有很多有趣的特性,其中之一是你可以编译为单个二进制文件,并且在大多数情况下,你可以使用某些特殊的构建时参数将所有相关的库静态编译进这个二进制文件。这样我们就能构建一个最小化的 Docker 容器,并减少额外的运行时开销,以实现我们所追求的出色性能、可移植性和安全性!


如果我们可以将 Golang 应用程序编译为单个二进制文件,并将它静态链接到依赖项上,就可以使用一个 0KB 容器来运行这个应用程序。这是 Docker 提供的一个特殊的基础映像,称为“scratch”。


在我们的 Docker 培训课程中总会遇到一个问题:所有容器内部都装有操作系统吗?答案是否定的,就是因为有这种特殊类型!这个映像内部没有关联受支持的操作系统环境。它有一些特殊要求,最重要的是,主机的架构必须支持编译好的二进制文件的架构(x86、x64 等),然后,你实际得到的容器除了隔离功能和 Docker 容器的那些优秀特性外,不向应用程序提供任何功能或支持!尽量使用 scratch 作为基础映像,将为你的应用程序容器提供极高的简约性和安全性水平。


FROM golang:alpine as builder RUN mkdir /build  ADD . /build/ WORKDIR /build  RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o main . FROM scratch COPY --from=builder /build/main /app/ WORKDIR /app CMD ["./main"] 
复制代码


在第 6 行,FROM scratch 告诉 Docker 从头开始,就像我们在上一个多阶段示例中看到的那样,但这次使用的是 0KB 临时映像。第一个阶段与之前类似,但这次我们在构建阶段使用一些编译时参数来指示 go 编译器将运行时库静态链接到二进制文件本身:


RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o main. 
复制代码


在此示例中,最终的 Docker 映像只会包含这一个可执行文件,而无需使用容器操作系统。


$ docker build -t hellocloudreachmain:1.2 . -f Dockerfile.scratch ... (build output omitted) $ docker images | grep hellocloudreachmain hellocloudreachmain 1.0 d1c5090585bc 8 minutes ago 378MB hellocloudreachmain 1.1 ea737df5cc64 4 minutes ago 6.16MB hellocloudreachmain 1.2 bda5c99404ae 33 seconds ago 2.01MB 
复制代码


太棒了,生成的容器大小只有 2.01MB!与最初的 378MB 映像相比,这是一个巨大的进步!


那么这些真的有用吗?


$  docker run -it hellocloudreachmain:1.0 Hello Cloudreach! $  docker run -it hellocloudreachmain:1.1 Hello Cloudreach! $  docker run -it hellocloudreachmain:1.2 Hello Cloudreach! 
复制代码


是的!

小结

我们用一个基本的 Dockerfile 举例,然后一步步缩减最终映像的体积。通过这个简单的练习,我们很容易看到在构建 Golang 应用程序时,有很多选择可以在 Docker 构建中实践极简主义。


我们有很多办法可以利用这种语言的特性,及其在编译期间提供给开发人员的特性来减小容器的体积。由于 Golang 可以编译为静态链接的可执行文件,因此我们能利用这类特性为运行时剥离所有不必要的组件。


更复杂的应用程序和构建可能无法遵循和上面完全相同的设计模式,但是这些原理可以应用在大多数 Golang Dockerfile 上!只需花一些时间来确保自己使用行业最佳实践正确构建 Dockerfile,就能成功地在容器中构建快速、安全和可扩展的应用程序!


英文原文:


https://www.cloudreach.com/en/resources/blog/cts-build-golang-dockerfiles


2020-08-14 13:432328
用户头像
王强 技术是文明进步的力量

发布了 783 篇内容, 共 368.2 次阅读, 收获喜欢 1705 次。

关注

评论

发布
暂无评论
发现更多内容

更新视图——基于函数的视图 Django

海拥(haiyong.site)

Python django 6月月更

Vue-16-表单绑定

Python研究所

6月月更

Ares阿瑞斯i质押LP挖矿众筹模式dapp智能合约定制

开发微hkkf5566

2022年中国手机银行年度专题分析

易观分析

手机银行

3M互助智能合约系统开发搭建技术

薇電13242772558

智能合约

不容错过的2大直播!Linux应用运行抖动的背后&身临其境体验Anolis OS|第25-26期

OpenAnolis小助手

Linux 开源 操作系统 直播 龙蜥大讲堂

AutoK3s v0.5.0 发布 延续简约和友好

Rancher

Kubernetes k8s rancher

大数据培训flink之电商用户行为项目整体介绍

@零度

flink 大数据开发

2022年Q1手机银行用户规模达6.5亿,加强ESG个人金融产品创新

易观分析

手机银行

依靠可信AI的鲁棒性有效识别深度伪造,帮助银行对抗身份欺诈

易观分析

AI

《网络是怎么样连接的》读书笔记 - ADSL

懒时小窝

网络编程

融云 x DiDO:中东热土上的语音社交「萌狮」

融云 RongCloud

为什么要做茶叶商城小程序app开发?

开源直播系统源码

软件开发 一对一源码 小程序商城

撰写有效帮助文档的7大秘诀

小炮

中国游戏的“外卷”大时代,中小厂商如何破解出海难题?

Geek_2d6073

智能制造的下一站:云原生+边缘计算双轮驱动

York

云原生 边缘计算 工业互联网 云边端协同

高效远程办公手册| 社区征文

程序员-小江

初夏征文

云原生监控系统·夜莺近期新功能一览,解决多个生产痛点

龙渊秦五

云原生 Prometheus Nightingale 运维监控

既不是研发顶尖高手,也不是销售大牛,为何偏偏获得 2 万 RMB 的首个涛思文化奖?

TDengine

数据库 tdengine 时序数据库

集成底座方案演示说明

agileai

集成底座 企业服务总线 统一身份管理平台 主数据管理平台 方案演示

MAUI与Blazor共享一套UI,媲美Flutter,实现Windows、macOS、Android、iOS、Web通用UI

沙漠尽头的狼

C# MAUI Blazor Blazor Server Blazor WebAssembly 跨平台UI

招募令|数据可视化开发平台“FlyFish”「超级体验官」招募啦!

云智慧AIOps社区

前端 前端开发 低代码 数据可视化 可视化开发

Linux下玩转nginx系列(六)---nginx实现cache(缓存)服务

anyRTC开发者

nginx Linux 缓存 音视频 服务器

《网络是怎么样连接的》读书笔记 - FTTH

懒时小窝

网络编程

进击的程序员,如何提升研发效能?|直播预告

万事ONES

百度交易中台之钱包系统架构浅析

百度Geek说

系统架构 百度app

浅谈德州扑克AI核心算法:CFR

行者AI

人工智能 AI 强化学习

容器云是什么意思?与堡垒机有什么区别?

行云管家

云计算 运维 容器云 堡垒机 IT运维

GraalVM 与 Spring Native 项目实现链路可观测

观测云

Apache ShardingSphere 5.1.2 发布|全新驱动 API + 云原生部署,打造高性能数据网关

SphereEx

云原生 ShardingSphere 版本更新

云堡垒机分布式集群部署优缺点简单说明-行云管家

行云管家

云计算 网络安全 堡垒机 云堡垒机

怎样构建Golang Dockerfiles?_语言 & 开发_Mike Zazon_InfoQ精选文章