8.3

查看英文版

8.3 容器、编排与云原生

概述与动机

容器(container)把一个应用程序连同它的依赖项一起打包成一个单一、可移植、隔离的单元。它在笔记本电脑上、在测试环境中,以及在生产环境中都以同样的方式运行。编排(orchestration)平台,最著名的是 Kubernetes,负责在成群的机器上调度和管理大量容器。它们处理放置、扩缩容、健康状况、网络和恢复。云原生(cloud-native)是建立在这些基础之上、范围更广的架构风格:把应用程序设计为松耦合、可独立部署、可水平扩展的服务,并假定其运行在一个动态的、能自我修复的基础设施之上。

对大型团队而言,容器和编排解决的是一个棘手的问题。你需要让由众多团队构建的众多服务,在共享基础设施上可靠而高效地运行。容器为每个团队提供了一份一致的打包和运行时契约,从而消除了“在我机器上能跑”这一整类故障。编排把单台机器隐藏在一个共同的基底之下,这样团队部署的对象是一个平台,而不是一台台服务器。正是这种标准化,让你能够在不需要每个团队各自重新发明部署、扩缩容和弹性方案的情况下,运营成百上千个服务。

采用这些技术的企业和政府机构获得了可移植性、弹性,以及一条摆脱供应商锁定的路径。作为回报,它们也继承了实实在在的复杂性和新的安全责任。一个容器平台之所以强大,正是因为它是可编程且动态的,这也意味着你必须对它加以谨慎治理。镜像溯源、多租户隔离、网络策略和成本,都变成了平台层面的关切事项。公共部门的采用者越来越多地增加了主权要求:控制数据存放的位置以及谁能访问它。这使得在选定的环境中运行一致工作负载的能力,成为一种战略能力,而不仅仅是一个技术细节。

关键原则

  • 把应用程序打包为小型、单一用途、不可变的容器镜像。
  • 践行镜像卫生原则:最小化基础镜像、版本锁定、扫描漏洞,并进行签名。
  • 在可能的情况下,把应用程序设计为无状态且可水平扩展,把状态外部化。
  • 把编排平台的期望状态模型当作真相来源,并让它自我修复。
  • 在租户、工作负载和命名空间之间强制执行隔离和最小权限原则。
  • 遵循十二要素(twelve-factor)原则()一套用于构建可随时丢弃、配置外部化、可水平扩展应用的方法论,并针对分布式系统的现实情况对其加以扩展。
  • 把成本当作一项一流的、可见的工程关切事项,而不是事后才想起的东西。
  • 优先选择可移植的、基于标准的抽象,以保留战略上的灵活性。

建议

践行严谨的镜像卫生原则

容器镜像是你信任和部署的基本单元,所以要这样对待它。从最小化的、可信的基础镜像开始,以缩小攻击面。锁定依赖项和基础镜像的版本,以保证可复现性。在构建流水线中扫描每一个镜像的已知漏洞,并阻止那些存在严重问题的镜像。为镜像签名,并在部署时验证签名,使只有经过批准、未被修改的镜像才能运行。维护一个经过整理的内部注册表,存放团队据以构建的加固基础镜像。这样能自动把良好的安全默认设置推广开来。

使用 Kubernetes 的既有模式,而不是重新发明它们

Kubernetes 会奖励那些采纳其既有模式的团队,也会惩罚那些与其模型对抗的团队。使用声明式清单来定义期望状态。添加健康探针,使平台能够检测并替换不健康的实例。设置资源请求和限制,让调度器能够安全地打包工作负载。对弹性需求使用水平自动扩缩容。对于必须持续运行的操作性逻辑,例如管理一个数据库、轮换证书,或协调自定义资源,使用操作器(operator)模式,它把人类的运维知识编码进软件中,由软件持续观察状态并采取行动。抵制在平台之上构建定制编排逻辑的冲动。优先选择原生构造。

有意识地设计多租户方案

当许多团队共享一个集群时,隔离是一项安全和可靠性要求,而不是锦上添花之举。用命名空间作为租户边界。强制执行资源配额,使任何一个租户都无法让其他租户陷入资源匮乏。应用网络策略,把流量限制在明确允许的范围内。使用基于角色的访问控制(RBAC)来限制每个团队能做什么。对于有更强隔离需求的工作负载,考虑使用独立的集群或更强的沙箱机制。及早决定你的模型是软多租户(相互信任的内部团队)还是硬多租户(相互不信任的工作负载),因为这两者需要截然不同的控制手段。

构建云原生应用,遵循十二要素并更进一步

十二要素方法论()包括明确的依赖关系、把配置放在环境中、无状态进程、可随时丢弃性等等()依然是那些在动态平台上蓬勃发展的服务的绝佳基线。要针对分布式系统所增加的现实情况对其加以扩展。为部分失败而设计。让操作具备幂等性和可重试性。暴露健康状况和遥测数据。把可观测性当作一项内建特性,而不是附加功能。把所有状态外部化到受管理的数据服务中,使应用实例保持可随时丢弃、可水平扩展。

务实地规划多云、混合和主权策略

可移植性是有价值的,但要清醒地去追求它。在容器、Kubernetes 和开放 API 等可移植的抽象上实现标准化,以便工作负载在需要时能够迁移。但要避免拒绝一切托管服务的陷阱,那样做是用实实在在的生产力去交换一种假设性的可移植性。对于混合和主权方面的要求,要设计得让相同的工作负载和流水线,能够在选定的地区、私有数据中心,或满足司法辖区和数据驻留规则的主权云中运行。在架构和策略中明确表达主权和数据驻留边界。

用 FinOps 让成本可见

在弹性的云环境中,成本是工程决策的直接后果,所以要给工程师提供可见性和问责机制。为资源打上标签以进行成本分摊。把支出归属到具体团队和服务。把成本数据与性能指标并排展示。合理调整工作负载规模,使用自动扩缩容来匹配需求,并回收闲置资源。建立一套 FinOps 实践,把工程、财务和产品聚集到一起,使云端支出成为一项共同的、持续的责任,而不是季度末的意外惊吓。

权衡取舍:利与弊

选择优点缺点最适合
Kubernetes强大、可移植、生态系统庞大复杂度陡峭;运维负担重大规模的众多服务
托管容器服务运维负担更少;启动更快存在一定锁定;控制力较弱追求简单性的团队
单一共享集群资源利用高效隔离更难;影响半径大相互信任的内部租户
每租户一集群隔离性强成本和开销更高相互不信任或受监管的工作负载
多云可移植性灵活性;避免锁定服务功能受限于最低公约数战略性风险缓释
深度使用单一云的托管服务生产力最高依赖单一供应商追求速度的团队

这里最重要的权衡是能力与复杂性之间的取舍。Kubernetes 和云原生架构带来弹性、韧性和速度。但它们也施加了一种小团队常常低估的、实实在在的运维和认知负担。同样地,追求完全的多云可移植性,是在用生产力交换可选性。正确的答案取决于规模和风险。拥有众多团队和强治理需求的大型组织,通常能证明这项投入是值得的。规模较小的项目,则往往更适合用能隐藏复杂性的托管服务来完成。

与团队讨论的问题

  1. 你们是否为镜像签名并在部署时验证签名?一个严重漏洞是否真的会阻止构建通过? 镜像是你信任的基本单元,因此围绕它的供应链值得设置硬性的关卡,而不仅仅是警告。决定是否只有经过签名、验证的镜像才能运行,决定扫描是阻止严重问题还是仅仅记录它们,并决定谁来维护团队据以构建的加固基础镜像的整理注册表。对企业和政府工作负载而言,这常常是一项合规要求,也是你抵御被投毒的依赖项进入生产环境的最佳防线。带上当前状态:正在运行的镜像中有多大比例来自你的加固基础镜像、有多少携带未打补丁的严重 CVE,以及当前是否有任何未签名的镜像能够被调度运行。如果一个严重问题不能阻止一次部署,你的扫描器就只是摆设。

  2. 资源请求、限制和配额如何在不让昂贵的容量闲置的前提下,防止一个工作负载让它的邻居陷入资源匮乏? 在一个共享集群上,一个没有设置限制的工作负载可能会拖垮或压制周围的一切,而设置得过于宽松的配额则会浪费掉证明该平台合理性的利用率收益。决定合理的默认值、由谁来调优它们,以及如何发现那些根本没有设置请求量的工作负载。在规模化场景下,这既是一项可靠性控制,也是一项成本控制,因为合理调整规模正是 FinOps 节省效果的主要来源。带上数据:当前的集群利用率、工作负载被驱逐或被限流的频率,以及哪些命名空间没有配额。目标是密集而安全的装箱式调度,所以要把缺失的限制当作平台会拒绝的一个缺陷来对待。

  3. 哪些状态允许存在于容器内部?其他所有状态又该放在哪里? 云原生的韧性依赖于平台可以随意重新调度的可随时丢弃实例,而这一点只有在重要状态存放于受管理的数据服务中、而非容器本地磁盘上时才成立。明确地决定这条规则,因为意外存储在容器中的状态,会在下一次重新调度时变成数据丢失。对于正在迁移旧应用的团队而言,这往往是最困难的部分,因为遗留服务假定存在一个稳定的本地文件系统。带上一份清单:哪些服务在写入本地状态、哪些依赖粘性会话或节点亲和性,以及把每一个外部化需要做些什么。在状态被外部化之前,你拥有的只是看起来有弹性、实际上却无法迁移的容器。

  4. 当许多团队共享一个集群时,你们的隔离模型是有意选择为软多租户还是硬多租户?控制手段是否与这个选择相匹配? 命名空间能分隔相互信任的内部团队,但无法遏制一个主动怀有敌意或已被攻破的工作负载,而把软租户当作硬租户来对待,是一场蓄势待发的安全事故。针对每个工作负载决定:租户是否只需要公平共享,还是必须被假定为彼此不信任,然后匹配相应的控制手段:软租户情形下使用命名空间、配额、网络策略和 RBAC,硬租户情形下使用独立集群或更强的沙箱机制。对大型组织而言,这个决定直接驱动成本,因为每租户一集群的成本远高于共享命名空间,所以你希望只在威胁模型确实需要的地方花费隔离预算。带上租户清单:今天有哪些工作负载共享一个集群、哪些处理受监管或面向外部的流量,以及哪里的网络策略仍是默认放行。在企业和政府场景中,在软租户之下混合相互不信任的工作负载,正是审计人员会标记出来的发现,所以要在他们发现之前先明确这条边界。

  5. 你们为多云可移植性付出了多少代价?你们真的会用到它吗? 在容器、Kubernetes 和开放 API 上实现标准化能让工作负载保持可迁移性,但为了保留这个选项而拒绝一切托管服务,是在用实实在在的日常生产力去交换一种组织可能永远不会行使的可移植性。决定在哪些地方可移植性是真实的要求,比如你已签署的主权或退出义务,与哪些地方它只是一条拖慢每个团队的安慰毯。与之相竞争的考量是速度:深度托管服务能更快地交付功能,而最低公约数式的架构则是压在每个团队身上的一项常设税负。带上证据:你们回避了哪些托管服务,这在工程时间上付出了什么代价,你们是否曾经真的在供应商之间迁移过工作负载,以及你们的合同实际要求了什么。对政府和受监管的采用者而言,数据驻留和主权云规则可能使可移植性成为不容商量的事项,所以要设计得让相同的清单和流水线能够在一个主权地区和一个私有飞地中运行,但要诚实地承认这是一项合规成本,而不是免费的保险。

  6. 每个团队能否看到自己的支出?在账单变成意外惊吓之前,是否有人负责它? 在一个弹性平台上,成本是工程决策的直接产物,然而如果没有成本分摊标签和可见的仪表盘,支出就会累积到一个共享的池子里,没人会觉得自己该为此负责,直到财务部门升级问题。决定你们如何把成本归属到团队和服务、由谁来审视它,以及工程师是把成本和性能指标并排看到,还是每季度才听说一次。这里的张力在于问责与摩擦之间:把成本抓得太紧,每个决定都会变成一场预算谈判;放任不管,闲置、规模过大的工作负载就会悄悄累积。带上数字:各团队当前的支出、有多少容量处于闲置或规模过大的状态,以及一个失控的工作负载会被多快发现。对企业和政府预算而言,无法归属的云端支出既是一种治理失败,也是一项实实在在的财务风险,所以要建立一套 FinOps 实践,把工程、财务和产品纳入同一场对话,而不是事后再去对账。

行业视角

初创企业。 选择托管容器服务,而不是自建 Kubernetes 集群:只有几个服务、又没有平台工程师的情况下,控制平面是你负担不起的分心之事。从最小化的基础镜像打包出小型镜像,锁定版本,在构建中加入一次漏洞扫描,并把所有状态推送到一个托管数据库中,以保持实例的可随时丢弃性。在你真正拥有足以证明其合理性的服务数量和人手之前,跳过命名空间、操作器和多云可移植性。

小型企业。 没有专职的平台专家,预算也紧张,应大力依靠托管服务,让供应商来运行那些你原本需要自己配备人手才能运营的编排系统。把容器的基本要素当作你的安全底线:最小化镜像、版本锁定,以及流水线中的一次扫描,能用很小的努力换来大部分保护。倾向于购买一个受支持的平台,而不是自己构建一个,并保留足够的可移植性()标准容器和开放 API()使自己不会因为定价或条款变化而被困住。

企业。 任务是跨众多团队的平台治理:一个中央平台团队提供加固的基础镜像、签名和扫描关卡、带配额的命名空间租户方案、网络策略和 RBAC,再加上成本分摊标签和一个 FinOps 仪表盘。把部署契约标准化,使成百上千个服务以同样的方式运作,并集中管理安全、多租户和成本,同时让各团队能够自助完成部署。为平台团队提供充足的资金,因为一个资源不足的平台会变成整个组织都要等待的瓶颈。

政府。 主权、数据驻留和公共问责制塑造着这种架构。在标准容器和 Kubernetes 上运行工作负载,使相同的流水线能够在一个主权地区和一个经认证的本地飞地中运行,并把数据驻留和访问边界编码为策略,而不是约定俗成的惯例。从一个内部加固注册表中获取镜像,对最敏感的数据应用硬多租户,并保留能给你带来韧性和谈判筹码的可移植性,因为采购规则常常禁止单一供应商锁定。

示例

初创企业。 一家六人规模的初创公司把它的两个服务打包成从最小化基础镜像构建出的小型容器镜像,并将其运行在一个托管容器服务上,而非自建的 Kubernetes 集群,这样就没有人需要照看控制平面。他们锁定基础镜像的版本,并在构建中加入一次漏洞扫描,但他们有意跳过更重的编排功能,直到他们真正拥有超过寥寥数个的服务。状态存放在一个托管的 Postgres 数据库中,这使容器保持可随时丢弃,并让平台能够重启或扩缩容它们而不造成任何数据丢失。

企业。 一家电信公司在共享的 Kubernetes 集群上运行数百个微服务。一个平台团队提供加固的基础镜像,强制执行镜像签名和漏洞关卡,并通过带配额、网络策略和 RBAC 的命名空间,把各业务单元隔离开来。成本分摊标签和一个 FinOps 仪表盘,把支出归属到每一条产品线,自动扩缩容则根据需求合理调整容量。产品团队每天多次部署到一个一致的平台上,而无需管理服务器。该公司在安全和成本方面保持着中央控制。

政府。 一家国家卫生服务机构必须把公民数据保留在国境之内,并置于国家法律的控制之下。它使用标准容器和 Kubernetes,在一个主权云地区上运行其工作负载,这样相同的流水线和清单也能在一个经认证的本地环境中运行,用于处理最敏感的数据。数据驻留和访问边界被编码为策略,镜像取自一个内部加固注册表,硬多租户则隔离出敏感的工作负载。跨主权地区和私有飞地的可移植性,在不牺牲合规性的前提下,为该服务带来了韧性和谈判筹码。

商业理由:动机、投资回报率与总拥有成本

容器和编排的投资回报,来自更高的资源利用率、更快更可靠的部署、能让支出匹配需求的弹性扩缩容,以及通过自我修复获得的更强韧性。在一个共同平台上实现标准化,能减少各团队之间的重复劳动,并加快新成员的融入速度,因为每个服务都遵循相同的部署和运维契约。

总拥有成本分析必须诚实面对运维负担。采纳成本包括平台工程人员、培训、针对镜像和集群的安全工具,以及运行平台本身所需的持续投入。不采纳的成本则包括各团队之间不一致的定制化部署、昂贵基础设施的低效利用、脆弱的手动扩缩容,以及难以满足韧性和主权方面的要求。对领导层而言,这个论证要立足于规模。在服务数量低于某个临界点时,这种复杂性可能得不偿失,一个托管服务会是更明智的选择。但在企业和政府规模上,一个受治理的云原生平台通常是性价比最高、韧性最强的基础,前提是你为平台团队提供了足够的资金,使其能够妥善运营。

反模式与陷阱

  • 臃肿、未经扫描的镜像。 从不受信任的基础镜像构建出的臃肿镜像,携带着不必要的漏洞,并拖慢一切。
  • 一切都用 Kubernetes。 为寥寥几个简单服务采用一个复杂的编排器,买来了复杂性却没有相应的回报。
  • 忽视资源限制。 没有请求量和限制,一个工作负载可能会让它的邻居陷入资源匮乏,甚至崩溃。
  • 对怀有敌意的工作负载使用软租户。 仅靠命名空间来隔离相互不信任的租户,是一场蓄势待发的安全事故。
  • 意外产生的有状态容器。 把重要状态存储在可随时丢弃的容器内部,会在重新调度时导致数据丢失。
  • 对成本视而不见。 把云端支出当作固定开销、而非工程产出来对待,会导致账单失控。
  • 可移植性作秀。 为了保留组织实际上永远不会用到的可移植性,而拒绝一切托管服务。

成熟度模型

第 1 级:启动(Initiate)。 容器即便被使用,也是临时性的。镜像是手工构建、未经扫描的,部署是手动且被动反应式的,也没有共享的平台、成本可见性或隔离模型。

第 2 级:发展(Develop)。 团队将应用程序容器化,并采用了一个编排器,但各团队的实践参差不齐。镜像扫描、资源限制和签名做法不一致,成本和多租户也没有得到系统性的治理。

第 3 级:标准化(Standardize)。 一个标准化的平台在整个组织中被记录并强制执行:加固的基础镜像、签名和扫描关卡、带配额和网络策略的基于命名空间的租户方案、RBAC,以及成本分摊。云原生和十二要素模式是被期望的常态,而不是各自为政的选择。

第 4 级:管理(Manage)。 平台被度量并对照基准进行控制。你追踪集群利用率、正在运行的镜像中构建自加固基础的占比、未打补丁的严重漏洞、部署频率和变更失败率、驱逐和限流率,以及各团队和服务的成本相对于预算的情况。关卡的强制执行依据这些证据:缺失资源限制和未签名的镜像会被自动拒绝,偏离标准会触发行动而不仅仅是警告。

第 5 级:协同(Orchestrate)。 平台实现自助服务和自我修复,在整个组织范围内相互整合并具有适应性。FinOps 持续地合理调整规模并回收容量,可移植的架构支持混合和主权方面的要求,平台根据度量到的使用情况持续改进,随着工作负载、成本和风险状况的变化,淘汰并替换相应组件。

讨论要点

  • 在什么规模上,采用 Kubernetes 会不再只是为了复杂而复杂,并开始真正带来回报?
  • 对你们的工作负载而言,软多租户和硬多租户之间正确的边界在哪里?
  • 你们应该在多云可移植性上投入多少,与深度托管服务所带来的生产力相权衡?
  • 你们如何在不把每个决定都变成预算谈判的前提下,给工程师提供真正的成本问责?
  • 你们对基础镜像的治理模型是什么?谁来维护加固注册表?
  • 主权和数据驻留方面的要求,如何塑造你们的平台架构?

关键要点

  • 容器把打包和运行时标准化;编排把大规模的运维标准化。
  • 镜像卫生,即最小化、版本锁定、经过扫描、经过签名的镜像,是安全的基础。
  • 使用原生的 Kubernetes 模式和操作器,而不是构建定制化的编排逻辑。
  • 根据工作负载之间的信任程度,有意识地选择一种多租户模型。
  • 遵循十二要素,并针对分布式系统的现实情况(如部分失败和可观测性)对其加以扩展。
  • 把成本当作一项工程产出来对待,并通过 FinOps 持续地管理它。

参考文献与延伸阅读

  • Adam Wiggins, The Twelve-Factor App (methodology).
  • Brendan Burns, Joe Beda, and Kelsey Hightower, Kubernetes Up & Running.
  • Bilgin Ibryam and Roland Huß, Kubernetes Patterns.
  • Cornelia Davis, Cloud Native Patterns.
  • J.R. Storment and Mike Fuller, Cloud FinOps.
  • Liz Rice, Container Security.
  • Cloud Native Computing Foundation (CNCF), cloud-native definition and landscape.