Golang 中的微服务-第二部分-Docker和go-micro

简介: Docker 和 go-micro

Docker简介

随着云计算的到来和微服务的诞生,服务在部署的时候有更多的压力,但是一次一小段代码就产生了一些有趣的新思想和新技术,其中之一就是容器的概念。

在早些的时候,团队部署一个庞大的服务到静态服务器,运行一套操作系统,需要使用一组预定义的依赖来跟踪。例如,可能是由管理员提供的 虚拟机或者 。伸缩是昂贵的并且不一定有效,最常见的是垂直缩放,例如在静态服务器上投入越来越多资源。

针对虚拟机的配置,伴随着像 vagrant 这样的工具越来越常使用。但是运行一个虚拟机仍然是一个相当大的操作。它相当于在你的主机上运行一个完整的操作系统(包括内核,各种应用等)。在资源方面,这是相当昂贵的。所以当微服务出现时,让每个微服务独立跑在自己的虚拟机中变得不可行了。

容器的诞生

容器是精简版的操作系统。容器不包含内核、用户操作系统或通常构成操作系统的较低级别组件。

容器只包含顶层库及其运行组件,内核在主机上共享。所以主机运行一个 内核,然后由 个容器共享,运行非常不同的运行时集合。

在底层,容器使用各种内核工具。以便跨容器空间共享资源和网络功能。

进一步阅读

这意味着您可以运行代码所需的运行时和依赖关系,无需启动几个完整的操作系统。这改变了游戏规划,因为一个容器和虚拟机比较体积是比较小的。例如 ,它通常 小一点,而 镜像只有 。

你会注意到我在这个介绍中更广泛的谈到容器,而不是 容器。尽管人们通常认为 Docker 和容器是一回事。但是,容器在 Linux 中更多是一个概念或一组功能。

Docker 只是其中的一种,只是因为好用而变得流行,还有其他的。但是我们会专注于 ,因为在我看来它是目前支持度最高且对新手最友好的容器技术。。

所以希望你看到容器的价值。我们开始使用 来运行我们的第一个服务。我们来创建一个 。(译注: Docker 容器的创建一般都使用 Dockerfile,容器会根据这个文件创建相对应的运行环境)

在该文件中添加以下内容:

如果你在 Linux 上运行,你可能会遇到使用 Alpine 的问题。所以,如果你在 Linux 机器上关注这篇文章,只需用 替换 即可,你应该能够正常运行。 稍后我们将会介绍一个更好的方法来构建我们的二进制文件。

首先,我们下载最新的 Linux Alpine 镜像。Linux Alpine 是一个轻量级 Linux 发行版,为运行 Dockerised Web 应用程序而开发和优化。换一种说法,Linux Alpine 具有足够的依赖和运行时功能来运行大多数应用程序。这意味着它的镜像大小只有 8M 左右。与之相比,大约 1GB 的 Ubuntu 虚拟机,你可以开始看到为什么 Docker 镜像更适合微服务和云计算。

接下来我们创建一个新的目录来存放我们的应用程序,并将上下文目录设置到我们的新目录中。这时我们的应用程序目录是默认的目录。然后,我们将编译后的二进制文件添加到我们的 Docker 容器中,并运行它。

现在我们来更新 文件来构建我们的 Docker 镜像。

我们在这里增加了两个步骤,我想详细解释一下。首先,我们正在构建我们的二进制文件。你会注意到在运行命令 之前,设置了两个环境变量。GOOS 和 GOARCH 允许您为另一个操作系统交叉编译您的二进制文件,由于我在 Macbook上开发,所以无法编译出二进制文件,让它在 Docker 容器中运行它,而该容器使用的是 Linux。这个二进制在你的 Docker 容器中将是完全没有意义的,它会抛出一个错误。第二步是添加 Docker 构建过程。这将读取你的 Dockerfile 文件,并通过一个名称 构建镜像。句号表示一个目录路径,在这里我们只是希望构建过程在当前目录中查找。

我将在我们的 Makefile 中添加一个新条目:

在这里,我们运行 Docker 镜像,并暴露 50051 端口。由于 Docker 在单独的网络层上运行,因此您需要将 Docker 容器中使用的端口转发给主机。您可以通过更改第一个段将内部端口转发到主机上的新端口。例如,如果要在端口 8080 上运行此服务,则需要将 -p 参数更改为 。您也可以通过包含 标志在后台运行容器。例如,。

您可以阅读更多关于 Docker 网络如何工作的信息。

当您运行 时,您正在将代码和运行时环境构建到镜像中。Docker 镜像是您的环境及其依赖关系的可移植快照。你可以将它分享到 Docker Hub 来共享你的 Docker 镜像。Docker 镜像就像一个 npm 或 yum repo。当你在你的 Dockerfile 里面定义了 ,你就告诉了 docker 从 docker hub 下载哪个镜像来作为运行环境。然后,您可以扩展并覆盖该基本文件的某些部分,方法是自行重新定义它们。我们不会公开我们的 Docker 镜像,但是可以随时仔细阅读 Docker hub,并且注意到有多少功能被容器化。一些非常显著的事情已经被 Docker 化了。

Dockerfile 中的每个声明在第一次构建时都被缓存。这样可以节省每次更改时重新构建整个运行时的时间。 Docker 非常聪明,可以确定哪些部分发生了变化,哪些部分需要重新构建。这使得构建过程非常快速。

我们已经介绍了很多容器的部分了。让我们回到我们的代码。

在创建 gRPC 服务时,创建连接的代码有很多,并且必须将服务地址的位置硬编码到客户端或其他服务中,以便连接到它。这很棘手,因为当您在云中运行服务时,它们可能不共享相同的主机,或者重新部署服务后地址或 IP 可能会更改。

这是服务发现的起点。服务发现保持所有服务及其位置的最新目录。每个服务在运行时注册自己,并在关闭时自行注销。 每个服务都有一个名字或编号分配给它。 因此,即使可能有新的 IP 地址或主机地址,只要服务名称保持不变,您就不需要从其他服务更新对此服务的调用。

通常情况下,解决这个问题的方法有很多,但是像编程中的大多数情况一样,如果已经有人解决了这个问题,那么重新发明轮子就没有意义了。 Go-micro 的创始人是 @chuhnk(Asim Aslam),他以一种非常清晰和易用的方式解决了这些问题。

Go-micro

Go-micro 是一个用 Go 编写的强大的微服务框架,大部分用于 Go。但是,您也可以使用 Sidecar 以便与其他语言进行交互。

Go-micro 有一些有用的功能,可以用来制作微型服务。但是,我们将从可能解决的最常见问题开始,那就是服务发现。

我们需要对我们的服务进行一些更新,让它与 go-micro 一起工作。Go-micro 作为 protoc 插件集成,在这种情况下,替换我们当前使用的标准 gRPC 插件。所以让我们开始在我们的 Makefile 中替换它。

确保安装 go-micro 依赖:

我们已经更新了我们的 Makefile 来使用 go-micro 插件,而不是 gRPC 插件。现在需要更新我们的 文件来使用 go-micro。这将抽象我们以前的 gRPC 代码,它将处理注册和轻松启动我们的服务

这里的主要变化是我们实例化我们的 gRPC 服务器的方式,它处理注册我们的服务,已经被整齐地抽象到一个 方法中。最后,处理连接本身的 函数。和以前一样,我们注册了我们的实现,但这次使用了一个稍微不同的方法。第二个最大的变化是服务方法本身,参数和响应类型略有变化,把请求和响应结构作为参数,现在只返回一个错误。在我们的方法中,设置了由 处理响应。

最后,我们不再对端口进行硬编码。go-micro 应该使用环境变量或命令行参数进行配置。设置地址, 使用 。我们还需要告诉我们的服务使用 mdns(多播DNS)作为我们本地使用的服务代理。

您通常不会在生产环境中使用 mdns 进行服务发现,但我们希望避免在本地运行诸如 Consul 或 etcd 这样的测试。更多我们将在后面介绍。

让我们更新我们的 Makefile 来实现这一点。

是一个环境变量标志,它允许你将环境变量传递到你的 Docker 容器中。

每个变量必须有一个标志,例如 等。

现在如果你运行 ,您将拥有一个 Dockerised 服务,并具有服务发现功能。所以让我们更新我们的 cli 工具来利用它。

完整文件看这里

在这里,我们导入了用于创建客户端的 go-micro 库,并用 go-micro 客户端代码取代了现有的连接代码,该客户端代码使用服务解析而不是直接连接到地址。

但是,如果你运行它,这是行不通的。这是因为我们现在正在 Docker 容器中运行我们的服务,它有自己的 mdns,独立于我们使用中的主机 mdns 。解决这个问题的最简单的方法是确保服务和客户端都在 “dockerland” 中运行,以便它们都在相同的主机上运行,并使用相同的网络层。让我们创建一个 Makefile ,并创建一些条目。

与之前类似,我们要为 Linux 构建我们的二进制文件。 当我们运行我们的 docker 镜像时,我们想传递一个环境变量来指示 go-micro 使用 mdns。

现在让我们为我们的 工具创建一个 Dockerfile :

它除了引入了我们的 json 数据文件,其余与之前服务的 Dockerfile 非常相似。如果你在你的 目录,运行 命令,你应该和以前一样,看见 。

之前,我提到那些使用 Linux 的人应该切换到使用 Debian 作为基础映像。现在看起来是一个很好的时机来看看 Docker 的一个新功能:多阶段构建。这使我们可以在一个 Dockerfile 中使用多个 Docker 镜像。

这在我们的例子中尤其有用,因为我们可以使用一个镜像来构建我们的二进制文件,具有所有正确的依赖关系等,然后使用第二个镜像来运行它。让我们试试看,我会在代码中留下详细的注释:

这种方法的唯一问题,我想回来并在某些时候改善这一点,是 Docker 不能从父目录中读取文件。它只能读取 Dockerfile 所在目录或子目录中的文件。

这意味着为了运行 或 ,你需要确保你的代码被推到 Git 上,这样它就可以提取 vessel-service。就像其他 Go 包一样。这种方法不理想,但足够满足我们现在的需求。

现在我将会在其他 Docker 文件中应用这种新方法。

噢,记住要记得从 Makefiles 中删除 。

更多的多阶段编译在这里

Vessel 服务

让我们创建第二个服务。我们有一个代销服务,这将处理将一批货物与一批最适合该批货物的 vessel 进行匹配。为了配合我们的货物,我们需要将集装箱的重量和数量发送到我们的新 vessel 服务,然后将找到一个能够处理该货物的船只。

在你的根目录创建一个新的目录 。现在为我们的新服务 protobuf 定义创建了一个子目录, 。现在让我们来创建新的 文件, 。

由于 protobuf 的定义确实是我们领域设计的核心,所以我们从这里开始。

正如你所看到的,这和我们的第一个服务非常相似。我们用一个 方法 创建了一个服务。。这需要一个 Specification 类型并返回一个 Response 类型。Response 类型使用重复字段返回 Vessel 类型或多个 Vesse。

现在我们需要创建一个 Makefile 来处理我们的构建逻辑和运行脚本。 。打开该文件并添加以下内容:

这与我们为托管服务创建的第一个 Makefile 几乎相同,但注意服务名称和端口已经改变了一点。我们不能在同一个端口上运行两个 Docker 容器,所以我们在这里利用 Docker 端口转发来确保服务上的 50051 端口映射到主机网络上的 50052 端口。

现在我们需要一个 Dockerfile,使用我们新的多阶段格式:

最后,我们可以开始我们的实现。

我留下了一些注释,但是非常简单。另外,我想提下,一个 Reddit 用户 /r/jerky_lodash46 指出,我曾经使用 IRepository 作为我的接口名称。我想在这里纠正一下,在我的接口名前面加上 Java 和 C# 等语言的约定,但 Go 并没有真正鼓励这一点,因为 Go 把接口当作一等公民。所以我把 IRepository 更名为 Repository ,并且把我的具体结构重命名为 ConsignmentRepository。

这个系列中,我会留下任何错误,并在以后的文章中予以纠正,以便我能够解释这些改进。我们可以更多地学习。

现在让我们来看看有趣的部分。当我们创建一个托运货物时,我们需要改变我们的托运服务来呼叫我们的新 vessel 服务,找到一艘船,并更新创建的托运中的 vessel_id:

更新 文件,删除硬编码的 vessel_id ,我们要确认我们自己正在工作。让我们再添加一些容器,增加权重。例如

现在在 中运行 。您应该看到一个响应,并创建货物清单。在您的货物中,您现在应该看到 vessel_id 已经设置好了。所以现在有了我们两个互联的微服务和一个命令行界面!在这个系列的下一部分,我们将看看使用 MongoDB 来保存这些数据。我们还将添加第三个服务,并使用 来管理我们在本地不断增长的容器生态系统。

如果你发现这个系列很有用,而且你使用了一个广告拦截器(谁可以责怪你)。请考虑一下我的时间和精力。干杯! https://monzo.me/ewanvalentine

Docker 简报(2017 年 11 月 22 日)

via: https://ewanvalentine.io/microservices-in-golang-part-2/

作者:Ewan Valentine

译者:guoxiaopang

校对:QueShengyao polaris1119

本文由 GCTT 原创编译,Go 中文网 荣誉推出

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180119G0769R00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

同媒体快讯

扫码关注云+社区

领取腾讯云代金券