当前位置: 首页 > 科技观察

如何构建GolangDockerfiles?

时间:2023-03-19 02:07:44 科技观察

Docker提供了一些很棒的构建时特性和基础镜像,我们可以使用它们来实现轻量级、安全和高效的应用程序构建。本文将解释为什么Golang可以很好地展现这些特性,因为Golang可以编译成单个二进制文件(或一组二进制文件)。这篇文章中的示例侧重于极简主义。这些示例虽然很基础,但非常重要,您可以在这些概念的基础上为大型Golang项目引入更多最佳实践,以提高安全性和效率。我们将使用这个简单的main.go来演示本文的概念:packagemainimport“fmt”funcmain(){fmt.Println("HelloCloudreach!")}Dockerfile文档,强调的是尽量减少层数是最佳实践!这是一个重要的概念,必须从一开始就完成。编写具有多层的Dockerfile非常容易-它的语法倾向于这样做-并且您最终会在不知情的情况下编写很多低效的文件。最佳做法是将构建的相关阶段分组并链接在一起,例如下载依赖项、集成供应商文件夹或使用RUN命令设置构建环境。您还需要考虑分组的哪些部分可以经常更改,然后将它们分组在Dockerfile中尽可能低的位置,同时将静态构建依赖项、构建环境配置或应用程序资产放在Dockerfile上级中尽可能高的位置。每一层,更具体地说是Dockerfile中以指令开头的每一行,都经过哈希处理并构建在另一层之上,最终图像由“堆叠”层组成。由于Dockerfile的每一层都继承自下一层,因此构建缓存提供了一种很好的机制来帮助您跳过已构建或静态的内容并转到您需要构建和重新散列的部分!保持尽可能短的构建时间很重要,因为高效的CI/CD系统每天多次运行这些构建。随着团队规模的扩大,这可能意味着大量的构建工作,可能需要很多Jenkins工作人员,有时还需要很长时间来集成开发人员的代码!Docker具有一些构建缓存功能,可以显着节省构建时间。更少的构建等待时间意味着更快的集成和自动化测试,并加快您的CI/CD流程,使您的流程能够与团队的规模相匹配。尽可能为应用程序设置单独的非根用户也很重要。只需要在linuxadduser命令中使用RUN命令,然后在Dockerfile中使用USER命令就可以使用这个用户来运行binary了。这是一个使用这些最佳实践构建的最小Dockerfilemain.go示例,只有一个基本的main函数,没有外部依赖项:FROMgolang:alpineRUNmkdir/appADD。/app/WORKDIR/appRUNgobuild-omain.RUNadduser-S-D-H-h/appappuserUSERappuserCMD["./main"]构建后,生成的图像大小为378MB:$dockerbuild-thellocloudreachmain:1.0。-fDockerfile.single...(省略构建输出)$dockerimages|grephellocloudreachmainhellocloudreachmain1.0d1c5090585bc不到一秒前378MB尽可能使用官方的基于Alpine的图像!它建立在busybox和musl之上,后者是最轻量级的Linux发行版之一。与较重的发行版相比,它的容器镜像很小,这是轻松提高生产力的好方法。但我们可以进一步简化!这些官方镜像构建(比如golang:alpine)包含了很多层,里面有一些安全组件,方便构建应用资产;但是如果我们的应用程序不需要这些层,那么就不要把它们放进去!我们需要使用其他一些Docker构建功能来进一步减小文件大小。下一步:多阶段当需要在生产中运行应用程序时,我们需要制作专为性能和安全性而设计的容器。我们还需要尽可能多的可移植性,以便轻松移动容器并使用DockerSwarm和Kubernetes等编排器大规模调度它们。将映像推送到注册表和从注册表中拉取映像所需的时间应该最小化。在为生产编写Dockerfile时要牢记的一件重要事情是在最终运行时映像中实践极简主义。如果您不需要运行某些东西,请不要将其放入!在开发环境中,有时需要一个“更重”的容器镜像,或许是一个开发者专用的Dockerfile,这样开发者可以随时投入一些工具,与容器一起进行调试等开发活动。也可以附加或保留一个或两个与活动容器交互的卷。这些当然是很常见的情况!然而,重要的是要记住在容器镜像在开发管道中移动时将这些东西拉出来。一个正式的安全软件供应链需要在管道的早期构建最终镜像,对镜像进行签名,并将正式签名的镜像推送到生产环境中的各个阶段。因此,您需要在供应链的早期构建、验证、集成和签署这个最小镜像;Dockerfile开发者、QA团队、安全工程师都必须熟悉的操作!换句话说,构建一次,然后让您的过程将生成的图像投入生产。多阶段构建是实现这一目标的好方法!多阶段涉及的基本原理涉及调用一个临时容器来简化应用程序的构建,然后将构建的资产从这个空间复制到一个容器镜像中,其中只有运行应用程序所需的组件。以前面的Dockerfile示例为例:FROMgolang:alpineasbuilderRUNmkdir/buildADD。/build/WORKDIR/buildRUNgobuild-omain.FROMalpineRUNadduser-S-D-H-h/appappuserUSERappuserCOPY--from=builder/build/main/app/WORKDIR/appCMD["./main"]注意这个Dockerfile中的两个FROM指令。我们将标记第一个“构建器”并使用它来构建应用程序。然后我们使用第二个FROM,这次是从基础“alpine”(非常轻量级!)中提取,并将我们构建的可执行文件从该环境复制到这个新环境。这使得图像的尺寸比以前小得多!此外,“builder”容器缓存在dockerbuilder上下文中,因此可以像前面的示例一样利用构建缓存来提高速度!$dockerbuild-thellocloudreachmain:1.1。-fDockerfile.multi...(省略构建输出)$dockerimages|grephellocloudreachmainhellocloudreachmain1.0d1c5090585bc8分钟前378MBhellocloudreachmain1.1ea737df5cc64不到一秒前6.16MB这个映像的大小是6.16MB。相比378MB,体积缩减效果明显!最小化整个运行时......从头开始构建它!事实上,我们可以在精简的道路上走得更远。Golang有许多有趣的特性,其中之一是您可以编译为单个二进制文件,并且在大多数情况下,您可以使用一些特殊的构建时参数将所有相关库静态编译到这个二进制文件中。这使我们能够以更少的额外运行时开销构建最小的Docker容器,以实现我们所追求的出色性能、可移植性和安全性!如果我们可以将Golang应用程序编译成单个二进制文件并将其静态链接到依赖项,我们就可以使用0KB容器来运行该应用程序。这是Docker提供的特殊基础镜像,称为“scratch”。在我们的Docker培训课上经常出现的一个问题:是不是所有的容器里面都有操作系统?答案是否定的,就是因为这个特殊的类型!内部没有与此映像关联的受支持操作系统环境。它有一些特殊的要求,最重要的是宿主机的架构必须支持编译二进制文件的架构(x86,x64等),然后,你实际得到的容器除了隔离和那些好的特性之外什么都没有Docker容器为应用程序提供任何功能或支持!尝试使用scratch作为基础镜像将为您的应用程序容器提供极高水平的简单性和安全性。从golang:alpine作为构建器运行mkdir/buildADD。/build/WORKDIR/buildRUNCGO_ENABLED=0GOOS=linuxgobuild-a-installsuffixcgo-ldflags'-extldflags"-static"'-omain。FROMscratchCOPY--from=builder/build/main/app/WORKDIR/appCMD["./main"]在第6行,FROMscratch告诉Docker从头开始??,就像我们在前面的多阶段示例中看到的那样,但是这次使用了0KB的临时图像。第一阶段与之前类似,但这次我们在构建阶段使用一些编译时参数来指示go编译器将运行时库静态链接到二进制文件本身:RUNCGO_ENABLED=0GOOS=linuxgobuild-a-安装后缀cgo-ldflags'-extldflags"-static"'-omain.在这个例子中,最终的Docker镜像将只包含这个可执行文件,而不使用容器操作系统。$dockerbuild-thellocloudreachmain:1.2。-fDockerfile.scratch...(省略构建输出)$dockerimages|grephellocloudreachmainhellocloudreachmain1.0d1c5090585bc8分钟前378MBhellocloudreachmain1.1ea737df5cc644分钟前6.16MBhellocloudreachmain1.2bda5c99404ae33秒前2.01MB太棒了,生成的容器大小只有2.01MB!这是对原始378MB图像的巨大改进!那么这些真的有用吗?$dockerrun-ithellocloudreachmain:1.0你好Cloudreach!$dockerrun-ithellocloudreachmain:1.1你好Cloudreach!$dockerrun-ithellocloudreachmain:1.2你好Cloudreach!小结下面我们以一个基本的Dockerfile为例,逐步缩小最终镜像的大小。通过这个简单的练习,很容易看出在构建Golang应用程序时,在Docker构建中实践极简主义有很多选择。我们有很多方法可以利用这种语言的特性以及它在编译期间为开发人员提供的特性来减小容器的大小。由于Golang可以被编译成静态链接的可执行文件,我们可以利用这些特性为运行时剥离所有不必要的组件。更复杂的应用程序和构建可能不会遵循与上述完全相同的设计模式,但这些原则可以应用于大多数GolangDockerfile!只需花时间确保您使用行业最佳实践正确构建Dockerfile,您就可以在容器中成功构建快速、安全且可扩展的应用程序!