在 Docker 和 Kubernetes 时代,软件开发的世界发生了怎样的变化?有可能使用这些技术一劳永逸地构建一个放之四海而皆准的架构吗?当所有东西都“打包”在容器中时,有可能统一开发和集成的过程吗?
在 Docker 和 Kubernetes 时代,软件开发的世界发生了怎样的变化?有可能使用这些技术一劳永逸地构建一个放之四海而皆准的架构吗?当所有东西都“打包”在容器中时,有可能统一开发和集成的过程吗?这些决策有什么要求?它们会带来什么限制?它们会让开发人员的生活变得更轻松,还是会增加不必要的复杂性?
是时候讨论和阐明这些(以及其它一些)问题了!(在本文和原创插图中)
本文将带你踏上从现实到开发过程到架构再回到现实的旅程,在沿途的每一站回答最重要的问题。我们将尝试确定一些应该成为体系架构一部分的组件和原则,并在不过多深入实现细节的情况下展示一些示例。
这篇文章的结论可能会让你不高兴或高兴。这完全取决于你的个人经验,你对这个分为三章的故事的看法,甚至可能取决于你阅读时的心情。请在文末发表评论或提出问题,让我知道你的想法!
通过适当的方法,你可以自动化和统一以下序列中的所有内容,并在未来几个月内忘掉它。
建立开发环境
关于“黑盒”你只需要知道,所有该有的东西都已经打包到里面了。是或否,1 或 0。更多关于我为什么将容器称为黑盒的信息,将在本文的后面介绍。有了数量有限的可以在容器内执行的命令,以及描述其所有依赖关系的 docker-compose.yml,你就可以轻松地自动化和统一测试工作,而无需过多关注实现细节。
例如, 像这样 !
在这里,测试不仅意味着单元测试,还意味着功能测试、集成测试、检查代码风格和重复代码、检查过时的依赖关系、可能使用违反许可证的软件包以及许多其他事情。关键是所有这些都应该封装在你的 Docker 镜像中。
如果你有一个统一的测试方法对 Docker 镜像(或“黑盒”)进行测试,你就可以假设这样的测试结果将允许你无缝地(并且问心无愧地)将 特性分支(feature-branch)集成到 git 存储库的 上游(upstream)或 主分支(master)中。
或许,这里唯一的障碍是集成和交付的顺序。当没有发布时,你如何在一个拥有一组并行 功能分支(feature-branches)的系统上防止“竞争条件”的发生?
因此,只有在没有竞争的情况下,这个过程才应该开始,否则“竞争条件”会一直萦绕在你的脑海中:
任何步骤中发生的任何失败都应该终止交付过程,并将任务返回给开发人员来修复错误,不管是测试失败还是代码合并冲突。
你可以使用此流程来在多个代码库上工作。只需一次对所有代码库执行每个步骤(在 A 库和 B 库上执行步骤 1,在 A 库和 B 库上执行步骤 2,如此类推),而不是在每个单独的代码库上重复执行整个过程(在 A 库上执行步骤 1-6,在 B 库上执行步骤 1-6,如此类推)。
此外,Kubernetes 还允许你进行部分更新,以便执行各种 A/B 测试和风险分析。Kubernetes 在内部通过分离服务(访问点)和应用程序来实现这一点。你总是可以按期望的比例平衡组件的新版本和旧版本,以便于问题分析并为潜在的回滚提供可能。
架构框架的强制性要求之一是回滚任何部署过程的能力。这反过来又会带来一些显而易见和隐含的细微差别。以下是其中一些最重要的:
在 Kubernes 集群中的回滚状态非常简单(运行 kubectl rollout undo deployment/some-deployment,Kubernes 将恢复到以前的“快照”),但是为了有效地做到这一点,你的元项目应该包含关于该快照的信息。非常不鼓励使用更复杂的交付回滚算法,尽管它们有时是必要的。
以下是触发回滚机制的因素:
没有一个工作流可以神奇地“构建”防弹车级别的安全并保护你的生态系统免受外部和内部威胁,因此你需要确保你的架构框架在执行时着眼于公司在每个级别和所有子系统中的标准和安全策略。
稍后,我将在关于监控和告警的章节中讨论建议的所有三个级别解决方案,它们对系统完整性也至关重要。
Kubernetes 有一套良好的内置 访问控制机制 、 网络策略 、 事件审计 和其它与信息安全相关的强大工具,可用于构建出色的 周界保护(perimeter of protection)、抵御和防止攻击以及数据泄漏。
应该认真对待在开发流程和生态系统之间建立紧密集成的尝试。将这种集成的需求添加到传统架构需求集(灵活性、可伸缩性、可用性、可靠性、防威胁等)中,可以极大地增加架构框架的价值。这是非常关键的一个方面,它导致了DevOps(Development Operation)这个概念的出现。DevOps 是实现基础设施完全自动化和优化的一个合乎逻辑的步骤。然而,一个设计良好的架构架构和可靠的子系统,可以让 DevOps 任务最小化。
没有必要赘述面向服务的架构(SOA)的好处,包括为什么服务应该是 “微”(micro)粒度的。我只想说,如果你已经决定使用 Docker 和 Kubernetes,那么你很可能会理解(并接受)这样的观点:维护一个 单体(monolithic)架构是困难的,甚至在意识形态上是错误的。Docker 被设计成运行 单进程(single process)应用,并和持久层一起工作。它迫使我们在 DDD( 领域驱动开发(Domain-Driven Development))框架内思考。在 Docker 中,被打包的代码被视为一个暴露部分访问端口的黑盒。
根据我设计可用性和可靠性更高的系统的经验,有几个组件对微服务的运行至关重要。稍后我将列出并讨论这些组件,即使以下我只是在 Kubernetes 环境中引用它们,你也可以将我的这个列表作为任何其他平台的检查表使用。
如果你(和我一样)能得出这样的结论,也即将这些组件作为一个常规 Kubernetes 服务来管理是非常好的,那么我建议你在不同于 “生产集群”(production)的单独集群中运行它们。例如, “预生产/准生产”(staging)集群可以在生产环境不稳定时挽救你的生命,因为这种情况下你会迫切需要一个能提供镜像、代码或监控工具的环境。可以说,这解决了鸡和蛋的问题。
虽然 Kubernetes 只要求在物理机器/云虚拟机上的少量组件(docker、kubelet、kube proxy、etcd 集群),但你仍然需要能够自动化完成添加新机器和对集群进行管理。以下是几个简单的方法:
就我个人而言,我更喜欢第 3 个选项(加上一个 Kubernetes 集成模块 ),因为它允许我同时使用服务器和 k8s 对象,并实现任何类型的自动化。然而,没有什么能阻止你使用 Teraform 及其 Kubernetes 模块 。KOPS 不能很好地使用“裸机”,但是它仍然是一个很好的使用 AWS/GCE 的工具!
应特别注意 Docker 镜像管理系统,因为它对于存储和交付服务至关重要。此外,该系统应该支持用户和用户组的访问,能够删除旧的和不必要的镜像,提供图形用户界面和 RESTful 应用编程接口。
你可以使用云解决方案(例如, hub.docker.com )或私有托管服务,甚至可以安装在你的 Kubernetes 集群中。作为 Docker 注册表的企业解决方案, Vmware Harbor 就是一个很好的例子。最坏的情况是,如果你只想存储镜像,而不需要复杂的系统,你就直接使用 Docker Registry 好了。
任何 Docker 容器使其日志可访问的唯一方法是将它们写入容器中运行的根进程的标准输出或标准错误设备(STDOUT 或 STDERR)。微服务的开发人员并不真正关心日志数据接下来会发生什么,只需要保证它们应该在必要时可用,并且最好包含过去某个时刻的记录。Kubernetes 和支持这个生态的工程师负责这一切的实现。
在 官方文档 中,你可以找到处理日志的基本(也是一个好的)策略的描述,这将有助于你选择用于聚合和存储大量文本数据的服务。
在日志记录系统的推荐服务中,相同的文档提到了用于收集数据的 fluentd (作为代理在集群的每个节点上启动)和存储和索引数据的 Elasticsearch 。即使你可能不同意这种解决方案的效率,但它是可靠且易于使用的,所以我认为这至少是一个好的开始。
Elasticsearch 是一个资源密集型解决方案,但是它可以很好地扩展,并且有现成的 Docker 镜像来运行单个节点或所需大小的集群。
Prometheus 已经成为现代系统中事实上的监控和报警标准,更重要的是,它在 Kubernetes 中几乎 开箱即用 。你可以参考 官方 Kubernetes 文档 ,了解更多有关监控和报警的信息。
监控是必须安装在群集内的少数辅助系统之一。集群是一个受监控的实体。但是监控系统的监控(请原谅重复)只能从外部执行(例如,从相同的 “预生产”(Staging)环境)。在这种情况下,交叉检查对于任何分布式环境来说都是一个方便的解决方案,不会使高度统一的生态系统的架构复杂化。
整个监控范围分为三个完全逻辑隔离的级别。以下是我认为在每个级别跟踪点的最重要的例子:
至于每个级别的报警通知,我建议你使用无数外部服务之一,这些服务可以通过电子邮件、短信发送通知或拨打手机号码。我还要提到另一个系统 — OpsGenie ,它可以 与 Prometheus 的报警管理器紧密集成 。
OpsGenie 是一种灵活的报警工具,有助于处理故障升级报告、全天候工作、通知渠道选择等。在团队之间分发报警信息也很容易。例如,不同级别的监控应该向不同的团队/部门发送通知:物理资源 —> 基础设施 + Devops,集群 —> Devops,应用 — > 向相关团队发送通知。
如果你的生态系统包含在一个宏域中工作的数百个服务,你将不得不处理数以千计的服务通信方式。为了简化数据流,你应该考虑在某些事件发生时将消息分发到大量收件人的能力,而不管事件的上下文如何。换句话说,你需要一个事件总线来发布基于标准协议的事件并订阅它们。
对于事件总线,你可以使用任何能够操作所谓代理的系统: RabbitMQ 、 Kafka 、 ActiveMQ 和其它类似的系统。一般来说,数据的高可用性和一致性对于微服务至关重要,但是由于 CAP 定理 ,你仍然需要牺牲一些东西来实现总线的正确分布和集群管理。
很自然,事件总线应该能够解决各种服务间通信的问题,但是随着服务数量从成百上千增加到数万,即使是最好的基于事件总线的架构也可能会失败,因此你需要寻找另一种解决方案。一个很好的例子是集成总线方法,它可以扩展上述 “愚蠢的管道 —智能的消费者”(Dumb pipe — Smart consumer)策略的能力。
有很多原因决定了需要使用“ 企业集成/服务总线 ”方法,该方法旨在降低面向服务架构的复杂性。下面列出了部分原因:
作为企业集成总线的开源软件,你可能需要考虑 Apache ServiceMix ,它包含了设计和开发这种 SOA 所必需的几个组件。
如果你还没有遇到你需要的包或依赖项已经从公共服务器上删除或暂时不可用的情况,请不要认为这种情况永远不会发生。为了避免任何不必要的不可用性并为内部系统提供安全性,请确保你的服务的构建和交付都不需要互联网连接。在内部网络中配置所有镜像和拷贝:Docker 镜像、rpm 包、源代码库、python/go/js/php 模块。
这些和任何其它类型的依赖都有自己的解决方案。最常见的方法可以通过在搜索引擎查询“ X 的私有依赖镜像 ”找到。