我是Go的粉丝,而且我也对容器感兴趣。容器可以使得代码更容易部署也更容易扩展。 但并不是所有Go程序员都使用容器。 在本文中,我将探讨一些真正应该考虑使用Go和容器的原因 - 然后我们来看一些容器根本不会增加任何好处的情况。
首先,我们要确保我们都在相同的起点上。
对于使用这些容器的人来说,可能有许多不同的定义。对于许多人来说,尽管容器比Docker开源项目或Docker公司要久远得多,“容器”一词与Docker是同义词。如果您是容器的新使用者,Docker可能是您的最佳起点,其提供对开发人员友好的命令行支持,但依然还有其他可用的实现:
- Linux容器 - 容器实现,包括LXC和LXD
- 来自CoreOS的rkt-pod-native容器引擎
- runc - 按OCI规范运行容器
- Windows容器 - Windows Server容器和Hyper-V容器
容器是一种虚拟化技术 - 它们允许您隔离应用程序,使其认为自己在单独物理机器中运行。在这个意义上,容器类似于虚拟机,除了它使用主机操作系统而不是拥有自己的操作系统以外。
您可以从容器镜像启动容器,该容器会捆绑应用程序运行所需的所有内容,包括其所有运行时依赖关系。这些镜像使分发程序更容易。
由于依赖关系是容器镜像的一部分,因此当您在开发机,测试或生产环境中运行它时,您将获得完全相同的依赖关系。因为数据中心的机器和你的笔记本的环境差异导致的问题将不复存在。
但是Go的一个优势就是将编译成一个单独的二进制可执行文件。您必须在构建时处理依赖关系,而没有运行时依赖,也不需要管理库。如果您曾经在Python,JavaScript,Ruby或Java中工作过,您会发现Go的这个特性是其优势:您可以在Go编译过程中得到单个可执行文件,它可以在你需要的机器上运行。您不需要确保目标机器具有正确的版本库或执行环境。
呃,所以,如果你有一个二进制文件,那么在容器中打包二进制文件的要点是什么?
答案是可能还有其他的东西你想打包在你的二进制文件里。如果您正在构建一个网站,或者您的程序附带配置文件,则可能会将静态文件分开。您可以使用go-bindata或类似的方法将它们构建到可执行文件中。或者您可以使用包含一个包含二进制文件及其静态资源的容器镜像。无论您如何放置该容器镜像,它都具有程序运行所需的一切。
让我们假设你没有任何静态资源,只是一个二进制文件。您构建该可执行文件,然后将其移动到需要运行的计算机(您只需移动该文件)。 Go使得交叉编译变得容易,所以即使要运行代码的目标机器与您正在构建的代码不同,也没什么大不了的。您需要做的只是在运行go构建时指定目标机器的体系结构和操作系统。
在许多传统部署中,您将确切知道哪个(虚拟)机器将运行哪个可执行文件。您可能拥有多个主机(例如为了高可用性),但是现在我们知道为目标机器构建容器是很容易的事情,而将Go二进制文件部署到机器上并非是高深莫测的事情。
但是现代部署方式是运行一组机器,并使用诸如Kubernetes,ECS或Docker Swarm之类的协调者将容器放置在集群中的某个位置。
容器对此非常有用,因为镜像作为协调执行标准“部署单位”。管理员通过给它一个容器镜像的标识符告诉机器运行什么代码?如果机器还没有该镜像的副本,它可以从容器注册表中获取它。
当然也可以运行一个流程来部署没有打包在容器镜像中的代码。但是通过使用容器,您正在利用广泛使用并语言无关的部署方法,这种方法在行业中越来越多地被使用。即使你的公司今天是一个纯粹的Go语言栈,通过使用容器,您将有一个通用的机制来部署不同的代码组件。不管它们可能使用哪种语言,因此避免语言锁定。
当我说“部署代码的现代方法”时,您可能会非常正常地认为“无服务器”架构?无服务器架构就是在容器内运行某个可执行函数。今天部署到无服务器看起来有很大不同(跟其他方式比),但我不会惊讶地看待这些术语 - 在某些环境中,您可以用Docker容器镜像的形式部署无服务函数(不仅仅是它具有全部依赖关系)。
当您在Linux机器上运行Go(或任何其他)可执行文件时,您将启动一个进程。如果您在Linux容器中执行代码,您也将以几乎完全相同的方式启动一个进程。该进程实际上认为它运行在单独的机器上,并且具有所有的资源权限。
在容器内可以限制程序权限。虚拟机在同一硬件上运行多个不同应用程序具有许多相同的优点。例如,容器化进程无法访问其容器外的文件或设备,除非您明确允许它可以访问,因此它不能影响这些文件或设备(由于错误等)。它可能会认为可以使用所有CPU来进行CPU密集型的操作,但系统可能会限制其可以使用CPU的能力,以便其他应用程序和服务可以继续运行。
资源限制是通过使用命名空间和cgroups创建的。这些术语意味着什么是另一个时代的话题,但是人们告诉我,他们发现我在英国做的这个演讲是有帮助的。
如果要限制可执行文件并只访问有限的资源集合,容器会为您提供一个整洁,友好和可重复的方式。
可以用其他方式为可执行文件创建相同的限制,但是使用容器会更容易。例如,传统的系统管理员已经做了大量的工作,为文件,设备和网络端口等设置权限。在容器的世界中,开发人员很容易表达他们的意图,即代码应该能够使用某些端口或卷(而不是其他端口),并且默认情况下,容器内的所有内容都对于容器是私有的。您需要确保您的Dockerfiles遵循最佳安全做法,而每次开发团队部署新的应用程序或服务时,都不需要定制操作来获取权限设置。
许多应用程序需要访问其他组件,如数据库或排队服务(或其他无限的)。 当您想在本地运行程序进行测试时,您还需要安装这些组件。
但是如果不同应用程序需要不同版本的组件怎么办? 或者如果不同项目的配置有所不同? 例如,如果您是开发人员,您可以轻松地使用不同版本(例如Postgres)运行两个客户端。 可以在笔记本电脑上运行多个副本,但可能会很痛苦(您必须确保使用正确的版本)。
如果您使用所需服务的docker版本,那么生活可以简单得多。 您可以为每个项目设置一个docker-compose文件,以使用正确配置启动正确的组件集。
总之,docker容易:
- 将代码分发到可以在任何地方运行的程序包中
- 通过一个协调者部署您的软件
- 限制您(或其他人的)代码可以在主机上使用的资源
- 在本地运行和测试您的软件(包括所需的所有服务)
如果您是Go开发人员从事“后端”开发或将代码部署在云端的系统软件,这些都是可能使用容器的原因。 但是如果那些不适用于你,你是否应该使docker部署Go代码?
Docker有一个口号:“Build, Ship and Run Any App, Anywhere”。Go已经有一些内置的属性支持这一点。正如我前面提到的,比如交叉编译和生成单个没有依赖的可执行文件。除非您使用可执行文件打包其他文件(或新插件),否则除非“运行”是指“通过编排器进行部署”,容器不能提供便捷。
也许你是一个Go开发者,并不必担心将代码部署到一组机器上。如果你建立一个独立的桌面或移动应用程序作为下载分发奇,那么使用容器不会增加任何好处,反而会增加你的工作流和构建过程的不必要的复杂性。
同样,如果我正在编写一个独立的程序(我只打算在本地运行,也许是一个实验或演示,或者一个不需要与其他组件交互的小型实用程序),我就不会使用容器。和往常一样,使用正确的工具作业,如果不会增加价值,就不要使用容器。
有没有使用容器的其他原因?我很想听听你的经历。