分层命名空间基本概念

多租户

Posted by shmily on December 30, 2019

介绍

分层命名空间是Kubernetes命名空间的简单扩展,可以轻松管理共享通用所有权概念的命名空间组。它们在由多个团队共享的集群中特别有用,但是所有者不必是人。例如,您可能想让操作员成为一组名称空间的所有者。

动机

为什么要使用常规名称空间?

在深入研究分层名称空间之前,值得考虑一下为什么Kubernetes首先具有任何名称空间概念。

首先,也是最明显的是,名称空间是一种组织K8s对象的方法,可以防止您必须为给定种类的群集中的每个对象找到唯一的名称。虽然不同种类的两个对象(例如,一个服务和一个端点)可以共享相同的名称,但是同一种类的两个对象不能在名称空间中共享相同的名称。这使Kubernetes用户可以轻松使用短名称(例如“前端”或“数据库”),而不会与同一集群上的其他对象发生冲突。

更微妙但同样重要的是,名称空间是Kubernetes控制平面中安全性隔离和标识的主要单位。它们的排他性(每个命名空间的对象可能仅属于一个命名空间),并且分组功能使它们成为一组相关资源的自然目标。例如,名称空间是各种策略(例如角色绑定和网络策略)以及Kubernetes扩展(例如Istio策略)的默认目标。

当然,可以在命名空间中应用策略,但是Kubernetes本身通常对此支持不佳,容易出错,并且可能使人类用户以及K8s生态系统中的工具感到困惑。例如,RBAC策略可以使用单独的名称作为对象,但是这种策略很难维护。

再举一个例子,一个命名空间中的Pod可以作为来自同一命名空间的任何服务帐户运行,即使是一个特权远远超出Pod要求的特权;管理员不能不借助高级技术(例如,验证准入控制器或OPA Gatekeeper)来限制此操作。因此,除非将不同的服务帐户也来自不同的命名空间,否则将不同的策略应用于不同的服务帐户是一项较弱的策略。

总之,名称空间是用于组织,安全性和工作负载标识的有用构造。

为什么要使用分层名称空间?

由于名称空间非常有用,因此您可能会发现自己在每个集群中创建了许多名称空间。但是,如果对象本身没有名称空间,则会遇到许多相同的问题:

  • 您可能希望许多名称空间对其应用类似的策略,例如允许同一团队的成员进行访问。但是,由于角色绑定在各个名称空间级别上运行,因此您将被迫在每个名称空间中单独创建此类角色绑定,这可能既乏味又容易出错。这同样适用于其他策略,例如网络策略和限制范围。
  • 同样,您可能希望允许某些团队自己创建名称空间,作为其自身服务的隔离单元。但是,名称空间的创建是特权集群级别的操作,您通常希望非常紧密地控制此特权。
  • 最后,您可能希望避免为集群中的每个命名空间找到唯一的名称。

层次名称空间控制器通过允许您将名称空间组织到树中,并允许将策略应用于这些树(或其子树),包括在这些树中创建新名称空间的能力,解决了前两个问题。不幸的是,HNC无法解决每个名称空间必须唯一命名的要求,因为这是Kubernetes本身强加的。

但是,实际上,这通常没有您可能担心的那么严格。 Google的内部容器管理系统(Borg)的概念类似于名称空间,成千上万的团队经常使用它,而不会发生任何严重的冲突。

什么时候不应该使用分层名称空间?

层次结构是表达所有权和应用默认策略的理想选择,但它们是一维的-命名空间只能有一个父级。但是,现实世界是多维的。您可能希望根据其所有权将一组策略应用于名称空间,但根据其环境(例如,临时vs.产品)或关键程度(例如,批处理vs.实时)将其他(正交)策略应用到名称空间。

与层次结构不同,标签可用于实现这些类型的灵活策略,但是它们也有其自身的缺点:

  • K8中的标签通常没有权限。如果您有能力编辑对象,则可以应用所需的任何标签。除非您所有人都信任相关对象的所有可能的编辑器,否则这将使其不适用于策略应用程序。
  • 尽管标签比层次结构更灵活,但标签也更容易出错。 HNC有助于确保大多数名称空间具有父级,因此可以确保使用一组健全的默认值,而标签则更难以审核和验证。

如果层次结构不适合您的需求,则可以使用诸如名称空间配置操作符(基于标签)或Anthos Config Management(支持标签和层次结构)之类的工具。您还可以使用OPA Gatekeeper之类的工具来帮助确保正确应用标签,或直接表达和应用无法通过层次结构表达的策略。

基本概念

这些概念对于使用带有分层名称空间的群集的任何人都是有用的。

Parents,children,trees and forests

使用HNC时,每个名称空间可以有零个或一个父级。没有父级的名称空间称为根名称空间,而所有具有相同父级的名称空间均称为该名称空间的子级。非根名称空间可以拥有自己的子代;层次结构的数量没有硬性限制。

源自根名称空间的所有名称空间,连同根本身,都称为树。源自任何名称空间的所有名称空间以及该名称空间都称为子树。没有子级的命名空间称为叶子。请注意,叶子名称空间从技术上来说也是子树,而名称空间既是根又是叶子,从技术上来说也是树。群集中所有树的集合称为名称空间森林。

HNC包括验证准入控制器,这些控制器将阻止您创建关系周期,例如,两个名称空间可能不是彼此的父级。

在命令行中,您可以按如下所示使用hierarchical-namespaces插件设置名称空间的父级:kubectl hierarchical-namespaces set <child> --parent <parent>请注意,我们建议使用快捷方式hns为该插件安装别名。本文档的其余部分将使用此快捷方式。

您还可以通过命令kubectl hns tree <ns>查看以命名空间为根的子树。该名称空间的更详细的分层信息也可以通过kubectl hns describe <ns>获得。

自助服务名称空间

群集中的任何常规名称空间都可以具有其父集。也就是说,您可以通过kubectl create namespace foo创建名称空间,然后通过kubectl hns set foo --parent bar设置其父项。但是,第一步始终需要集群级别的特权,而集群级别的特权可能未得到广泛授予。

为解决此问题,HNC引入了自助名称空间的概念,该名称空间是作为另一个名称空间的子级创建的名称空间。无需拥有群集级别的权限,您只需成为父名称空间的管理员即可。命名空间管理员的概念将在下面进一步描述。

与常规名称空间相比,自助服务名称空间有一个重要的限制。虽然常规名称空间可以更改其父级,但自助服务名称空间通常不能更改。但是在所有其他方面,这些名称空间都是常规(分层)名称空间-例如,它们在集群中必须具有唯一的名称。

您可以通过kubectl hns create child -n parent从命令行创建分层名称空间。

策略继承和对象传播

HNC中的命名空间从其祖先继承策略。如果您熟悉诸如C ++或Java的面向对象的语言,这似乎很自然。同样,在许多公司中,您可能具有适用于整个组织的策略,有些策略仅适用于一个部门,有些策略仅适用于单个团队。这些级别中的每一个都继承其上一级的所有策略。这是HNC遵循的模型。

HNC实施此策略继承的主要方式只是复制它们。我们将此过程称为传播。例如,父名称空间team-a中的名称为team-members的角色绑定将传播到team-a的所有子级;也就是说team -a的所有子级将拥有自己的team-members副本。 HNC确保这些副本始终与原始副本保持同步。

这意味着任何子名称空间,例如service-1,都可能没有任何相同名称的Role Binding;如果是这样,HNC将覆盖它。我们正在考虑将异常的概念添加到HNC中,以允许子命名空间中的策略在有限的情况下覆盖来自父命名空间的策略;请告诉我们这是否对您有用

当前,HNC将以下类型的对象从父母传播到孩子:

  • Roles and Role Bindings:允许在祖先名称空间中具有权限的用户在子孙名称空间中声明相同的权限。
  • Network Policies:允许将子树中的所有工作负载限制为相同的入口/出口规则(但请参阅下文,了解使用层次结构的其他方式)。
  • Limit Ranges:防止子树中的任何一个Pod消耗过多的资源。
  • Resource Quotas:防止子树中的任何一个名称空间消耗过多的资源。但是,尽管正在努力限制这种行为,但具有创建子命名空间能力的用户可以通过简单地创建具有相同配额的新子命名空间来有效地解决此限制。
  • Secrets:允许在多个名称空间之间共享机密(例如凭据)。如果必须在不同的微服务或同一微服务的不同版本之间共享团队拥有的凭据,则每个凭据都在其自己的名称空间中。
  • Config Maps:允许在微服务名称空间之间共享Config Map。

当原始对象被更新或删除时,所有传播的副本也将尽快更新或删除。同样,如果您更改名称空间的父级,则将删除该名称空间祖先中不再存在的所有对象,并添加该祖先中的所有新对象。

HNC中每个传播的对象都被赋予了hnc.x-k8s.io/inheritedFrom标签。此标签的值指示包含原始对象的名称空间。 HNC准入控制器将阻止您添加或删除该标签,但是如果您设法添加它,则HNC可能会迅速删除该对象(认为源对象已被删除),而如果您设法将其删除,则HNC将删除该标签。仍然可以简单地覆盖对象。

在不久的将来,HNC将是可配置的,从而允许在群集或子树中传播不同的种类。也就是说,可以排除上面列出的所有种类(角色和角色绑定除外),而其他种类(包括CRD)也可以包括在内。

管理

这些概念对需要管理分层名称空间的人很有用。

分层配置

到目前为止,我们没有提到层次结构在集群中的实际表示方式。答案是每个命名空间都可以包含一个带有Kind HierarchicalConfiguration和名称层次结构的对象。此对象用于配置以命名空间为根的子树,以及报告层次结构中的任何问题。它也是RBAC的附加点(请参阅下面的“特权”)。

父级由.spec.parent字段定义。 kubectl hns set --parent命令仅编辑此字段,但是,如果您有足够的特权,也可以直接在yaml文件中对其进行编辑,并通过kubectl apply -f对其进行更新。

该规范还包含与子命名空间的创建相关的字段(尽管这些字段可能会在不久的将来更改/删除),并且还将包含其他配置,例如在子命名空间中传播的Kinds。

配置的状态更加有趣,因为它包含以下有用的属性:

  • 子级:此名称空间的所有子级的列表。
  • 条件:影响此名称空间的所有问题的列表(请参见下文)。

可以通过kubectl hns describe <ns>命令轻松检查分层配置。

命名空间管理员

能够更新名称空间的层次结构配置的任何用户(或服务帐户)都被称为该名称空间的管理员,即使他们在该名称空间中没有其他权限也是如此。通常,您可以通过两种方式成为名称空间的管理员:

  • 您具有群集角色绑定,该权限使您有权在整个群集中更新配置。
  • 拥有该权限的人已授予您通过角色绑定更新特定名称空间中的配置的权利。由于该角色绑定将传播到该名称空间的所有后代,因此通常这意味着您还将成为所有后代名称空间的管理员。

即使您创建了根名称空间(通过kubectl create namespace foo),也不能成为该名称空间的管理员(从HNC的角度来看),除非您还具有对分层配置的更新权限。相反,成为名称空间管理员并没有授予您删除该名称空间的权利。但是,与所有其他角色绑定一样,如果您是名称空间的管理员,则可以将该名称空间或其任何后代中的特权授予他人。

但是,作为名称空间的管理员并不能让您自由控制该名称空间的配置。例如,如果您是子名称空间的管理员而不是其父名称空间,则您不能更改名称空间的父名称,因为这将删除父名称空间的管理员的特权。同样,即使您的名称空间是根,也不能将其父级设置为当前不是管理员的任何名称空间,因为该名称空间可以继承敏感信息,例如Secrets。

通常,要将名称空间N的父级从A更改为B,您必须具有以下特权:

  • 您必须是最高名称空间的管理员,此更改之后,该名称空间将不再是名称空间N的祖先,以确认您很高兴失去名称空间N的特权。
  • 您必须是名称空间B的管理员,才能确认来自B的敏感对象可以复制到名称空间N中。

群集管理员通常将具有这些特权,并且如果A和B在同一棵树中,则其共同祖先的管理员将能够进行更改。 但是,如果没有一个单独的人具有这些特权,则可以要求A的根管理员将N设为根命名空间,然后将B的管理员特权手动授予N,然后让Admin使N成为B的孩子。

条件

每个条件都包含一个机器可读的字符串,一条人类可读的消息以及受该条件影响的对象列表。例如:

  • 如果您以某种方式绕过了验证webhook并创建了一个循环,则使用CRIT_INVALID_PARENT条件。
  • CANNOT_PROPAGATE条件表明该命名空间中的对象无法传播。此条件显示在源名称空间以及相关的后代名称空间中。

任何以“CRIT_”前缀开头的条件都是至关重要的条件,它表示名称空间存在严重问题,无法正常进行HNC操作。具有严重条件的命名空间具有以下属性:

  • 对象传播被禁用。也就是说,将不会复制新对象,也不会删除过时的对象。 所有名称空间标签都将被删除。
  • 解决条件后,将继续传播并恢复名称空间标签。

当HNC重新启动时,可能会有很短的时间,在此期间,随着HNC恢复其对群集层次结构的内部视图,虚假关键条件可能会出现在名称空间上。这些都是无害的,并且通常会在10到30秒内解决大小合理的层次结构。

在所有其他情况下,情况都需要人工干预才能解决。

命名空间标签和非传播策略

虽然层次结构是在“层次结构配置”对象中定义的,但是它通过HNC管理的标签反映在名称空间本身上。

例如,假设名称空间service-1的父团队为team-a,而祖父母的division-x。在这种情况下,名称空间service-1将具有以下三个标签:

  • service-1.tree.hnc.x-k8s.io/depth = 0
  • team-a.tree.hnc.x-k8s.io/depth = 1
  • division-x.tree.hnc.x-k8s.io/depth = 2

这些标签有两种用途。首先,任何使用名称空间标签选择器的策略都可以直接使用它们。例如,您不仅可以将网络策略放入将应用于该子树中任何命名空间的team-a中,还可以使用运算符team-a.tree.hnc.x-k8s.io/depth exists配置该策略以允许来自该子树中任何命名空间的流量。

其次,其他应用程序可以使用这些命名空间来检查层次结构的当前状态。例如,多租户工作组听取了关于分层资源配额的建议,以限制子树内的资源使用,而不仅仅是单个名称空间。这样的应用程序可以直接在HierarchyConfiguration中使用.spec.parent,但是标签通常更易于使用。更重要的是,如果命名空间存在严重问题,HNC将删除标签(如上所述),因此可以保证存在的所有标签都是正确的-至少与分布式系统中任何正确的标签一样(请参见“最终一致性”。

请注意,通常,出于策略目的,您不能总是信任标签的值,因为可以编辑K8s对象的任何人也可以应用他们喜欢的任何标签。但是,HNC将覆盖对这些标签所做的任何更改,因此其他应用程序可以信任这些标签以用于策略应用程序。

最佳做法和陷阱

Gitops整合

由于HNC由常规Kubernetes对象控制,因此您可以将YAML文件检入源代码管理,并通过kubectl apply -f将其应用于集群。但是,HNC确实对更改的应用顺序施加了两个限制:

    1、一个名称空间必须存在,然后才能被引用为另一个名称空间的父级。但是,潜在的父级的HierarchicalConfiguration不需要存在。

    2、您不能在名称空间之间创建任何循环。例如,假设命名空间A是B的父级,并且您希望颠倒这种关系,以便B成为A的父级。如果在B之前应用A的配置,则将导致一个循环。

在这两种情况下,HNC的有效准入控制器都会拒绝更改。但是,在许多情况下,只需重新运行应用程序即可解决此问题:

    1、如果在第一个应用程序期间创建了所有名称空间,则第二个应用程序将成功允许它们被引用为父级。     2、所有不会引起周期的配置都将成功应用-在上面的示例中,B的配置将成功应用,这意味着A不再是B的父级。因此,当重新应用A的配置时,将不会一个周期,应用程序将成功。

HNC不需要将整个群集的层次结构存储在单个存储库中。例如,顶级根可能存储在一个存储库中,使用该集群的团队的命名空间存储在另一个存储库中,每个团队也可能有自己的存储库。通常,这些存储库中的每一个都有自己的同步过程。

在这种情况下,我们建议仅在RBAC中为每个同步过程授予权限,以修改其相关子树。在上面给出的示例中,顶级根的同步器可能具有群集级名称空间创建特权,并且团队列表的同步器可能仅被允许在适当的根中创建子命名空间。每个团队的同步器仅应允许在其团队的子树中运行。

最终的一致性和微小的变化

与所有Kubernetes控制器一样,HNC强制执行最终的一致性。它不会尝试确保其控制下的所有属性或对象都以任何特定顺序进行同步。例如:

  • 如果已成功将A设置为B的父级,并且已应用所有名称空间标签,则并不意味着A的所有对象都已传播到B。
  • 相反,如果名称空间标签表明A是B的父对象,则B可能包含其他父对象。这可能是因为B最近是另一个父级的子级,或者是因为B正在成为另一个父级的子级,而名称空间标签只是尚未更新。
  • 如果名称空间B将其父项从A更改为C,并且A和C中都存在相同的对象,则不能保证此对象在过渡期间将继续存在于B中。
  • 如果需要更新两个相关的对象,例如“角色”和“角色绑定”,则不能保证它们按照您期望的顺序进行同步。例如,名称空间A可能包含名为“ admins”的角色,该角色具有非常高的特权,仅限于极少数的人,而名称空间C也可能包含名为“ admins”的角色,其角色具有更严格但广泛共享的特权。在将父级从A更改为C时,这可能导致大量人临时获得非常高的特权,直到角色绑定也被更新为止。
  • 当仅创建名称空间时,或者如果重新启动控制器,则许多名称空间可能具有可解决自身的暂时性紧急或非紧急条件。

因此,如果需要考虑这些中间状态,我们强烈建议您手动或通过Gitops或通过其他自动化系统对层次结构进行相对较小且易于理解的更改。请注意,对于有或没有HNC的任何类型的部署,这都是一个好习惯。

进行较小更改时,请确保在继续进行更改之前,先检查受影响的名称空间的条件; kubectl hns tree<ns>将显示子树内所有条件的摘要。