wwqdrh

构建企业级镜像仓库

镜像

镜像安全与操作系统安全是动态的。需要容器云平台中设置准入控制,以及对正在运行的镜像进行监控,以限制或容忍存在安全漏洞的镜像运行在平台

客户端在拉取镜像时需要指定镜像仓库(默认为docker hub),仓库名,标签,作为三元组唯一标识

镜像仓库的核心API需要遵守OCI规范中的镜像仓库规范。所有的客户端与镜像仓库交互的命令均是通过这组API进行的。

  • 镜像manifest管理
  • blob管理
  • 镜像上传

镜像由元数据(镜像仓库、仓库名、标签、校验码、文件层、镜像构建描述等)和块文件(每一个块文件是一个文件层,内部包含对应文件层的变更)两部分组成,镜像仓库的核心职能就是管理这两项数据

镜像仓库根据文件层的校验码来管理每个块文件。当多个镜像基于同一个基础镜像构建时,这些基础镜像拥有相同的基础块文件并且会进行共享。在删除镜像时不能直接删除镜像引用的所有镜像块文件,而是由专门的垃圾回收器来清理没有被引用的块文件。

镜像仓库

公有镜像仓库与私有镜像的考量: 隐私性,网络连通性,安全性,吞吐量,团队权限控制,稳定性,易用性,易扩展,运维复杂度

这些考量的具体细节就不展开,毕竟就是字面意思

主流的开源镜像仓库包括: Harbor, Quay, 都提供了完善的UI、认证、授权支持,可配置的主流的块存储引擎,支持外接镜像扫描服务等

每个镜像仓库实例实质上是无状态的服务实例,接收客户端请求,从数据库获取元数据,从对象存储读取块文件

使用数据库存储镜像仓库的用户、用户组、守群、仓库、镜像manifest、块文件索引等。为了保证高可用,必须以集群的方式进行部署

块存储,块文件通常使用文件系统或对象存储引擎来存储。

  • 文件系统: 通过网络磁盘挂载到镜像仓库实例中来操作块文件,可以有效地降低由单个实例或磁盘的故障而导致镜像仓库不可用的风险。若通过网络文件系统NFS提供块存储,则可以将NFS挂载到多个镜像仓库实例中,从而提高镜像仓库的并发能力。
  • 对象存储引擎: 将镜像仓库实例与文件系统剥离开,使得多镜像仓库实例同时提供读写服务

镜像仓库所要解决的挑战: 如何应对海量镜像分发的压力,如何用合适的成本提高镜像分发的性能。可以根据镜像是否是常用镜像(热镜像)引入块文件缓存(相当于mysql与redis的关系,并且只缓存块文件不缓存镜像元数据,避免镜像已经失效了),需要考虑过期时间以及缓存大小,一般使用LRU淘汰最近最少使用的块文件。另外不要使用跨数据中心的缓存服务,一个是全局使用一个缓存频繁换入换出缓存命中率低,二一个是如果在缓存中,但是并不是在一个数据中心的部分,可能不如直接拉取当前数据中心的镜像。所以需要将缓存服务分散到每个数据中心或者集群中。

可以通过in-cluster类型服务充当缓存服务的访问入口,添加特定的路由规则来拦截流向镜像仓库的流量,从而将请求直接转向集群内的缓存服务

对访问镜像仓库的流量进行拦截,将其转向内部的缓存服务,可以分为两个阶段

1、节点启动阶段: 新节点加入集群,不存在集群服务的路由表和路由规则。此时节点只能访问镜像仓库服务 2、节点运行阶段: kube-proxy已经将集群中的路由表和路由规则配置完成。

可以使用DaemonSet(保证每一个节点都会运行一个)管理一个后台pod,用于在hosts文件(将主机的hosts文件挂载到镜像中)中注入缓存服务的集群地址

缓存服务则是一个Service(ClusterIP类型)+若干缓存服务实例Pod,缓存服务实例之间通过p2p的方式共享块文件,使每个块文件分享到所有的缓存实例,从而提供稳定的缓存服务性能,当kubernetes节点因为硬件故障宕机而重新调度新的节点时,新的节点会即使从其他缓存服务实例中同步并加在已有的缓存块文件然后提供服务。

安全准入

镜像安全,对于静态镜像与运行镜像,是否使用了高危软件包,是否存放了秘钥,端口扫描,协议扫描。分析构建指令,应用,文件,依赖包等,查询CVE库,安全策略

  • 镜像扫描服务从镜像仓库拉取镜像
  • 解析镜像的元数据
  • 解压镜像的每一个文件层
  • 提取每一层所包含的依赖包,可运行程序、文件列表、文件内容扫描
  • 将扫描结果与CVE字典、安全策略字典进行匹配,以确认最终镜像是否安全

例如构建编写dockerfile时不要直接把秘钥之类的copy进去,而是仅在运行时才加入,避免泄漏。而且不要想着追加删除命令就完事大吉了,因为所有的操作都会在中间的文件系统中表现出来,只需要解压就能看到了。还有.git之类包含代码仓库认证信息的千万也不要加入到镜像中,要写.dockerignore!!!

配置的安全准入策略同样会带来一些麻烦,例如CVE的变更导致某个pod由安全变为不安全,在更新pod或者扩容的时候都会被拦截。为了解决这种问题,就需要为镜像准入控制器进行扩展,引入带有效期支持的白名单机制,以容忍不安全或不符合安全策略的镜像,并且这个白名单最好是自动的,一个集群哨兵周期性的与镜像扫描服务进行交互监控集群中所有pod的镜像,获取镜像的安全状态,当发现新的不安全镜像时,自动为新的不安全镜像创建包含默认有效期的白名单

另外,由于每天CVE字典都会产生变化,如果对所有的重新扫描一遍是非常难以完成的事,所以会有优先级,并且针对每种优先级使用不同的扫描策略

  • 正在运行的高优先级,每周重新扫描
  • 最近推送的镜像处于中优先级,每月对这些镜像进行重新扫描
  • 推送超过一定时间而未被使用的低优先级,每三个月进行扫描

多租户生产集群

当多个用户共用一个平台时,平台运维会面临如何区分、管理不同的用户和用户组,控制权限,公平合理的分配资源,隔离不同用户的工作负载,数据,保证数据安全,确保应用的安全稳定,防止其他用户或匿名流量访问。

租户

kubernetes可以管理ServiceAccount(用来标识和管理系统组件的),另一类是外部用户认证

通过Role和RoleBinding来管理每个命名空间中的访问权限,通过PodSecurityPolicy和RoleBinding来完成用户与用户、用户与控制平面的特殊权限隔离

对于授权管理,kubernetes可以精确到每个用户对资源的具体管理

  • 集群级: 在这里授权的对象,可以操作非命名空间的资源与所有命名空间的资源(例如Node、PodSecurityPolicy这些非命名空间的资源只能通过集群集授权访问)
  • 命名空间级: 只能操作具体的某个命名空间中的资源

除了认证授权,还需要从权限、网络、数据三个方面对不同用户进行隔离,减少用户工作负载之间的影响

资源限额则可以通过kubernetes原生的资源配额管理-Resource-Quota, 基于命名空间的配额管理,通过定义ResourceQuota对象来限制每个命名空间中可支配的资源

但是原生的租户管理支持有限,运维复杂,可以使用三方租户服务来扩展支持。也可以使用聚合器扩展(需要直接对接已有数据)或者使用CRD扩展,通过扩展kubernetes的资源模型,对接企业已有平台的数据,完成无感支持

在租户的定义中,有租户的基本信息,工作组,租户的命名空间。在工作组信息中定义了该租户的成员信息。通过租户和工作组的定义,自顶向下完成租户的管理: 命名空间、权限、配额、应用、服务、访问控制等

认证

kubernetes原生支持三类认证对象: 用户(ServiceAccount)、组、服务账户。原生提供了多种认证方式(ServiceAccount、ServiceAccount TokenRequest、客户端证书认证、用户名密码认证、known Token认证、Bootstrap Token认证、Webhook Token认证),都是通过插件的形式添加到API Server中

授权

Kubernetes授权验证的时候,会审查: user、group、API request verb(资源操作, 包括get、list、create、update、patch、watch、proxy、redirect、delete、deletecollection)、Resource、Subresource(子资源)、Namespace、API group、非资源相关的api、(/api /apis /healthz)

新创建的命名空间是匿名的,role以及rolebingding都是空白的,任何用户、ServiceAccount都无法访问其中资源,必须由拥有集群级别管理员来进行授权。例如租户创建了一个新的命名空间,集群租户控制器性需要自动将租户的管理员组作为命名空间的管理员添加到RoleBinding

节点授权

kubelet作为一个Agent,拥有对Kubernetes资源完整的访问权限,从API Server获取Pod相关的所有资源(定义、Secret、ConfigMap、PersistentVolume-Claim等)并且将这些组装起来成为一个真正运行的实例。因此为了保证kubelettoken泄漏从而导致整个集群不安全的可能性,具有专门定制的授权方案: 节点授权,通过节点授权动态地根据调度到节点上的Pod来授权kubelet访问的资源,使得其只能访问一定范围的资源,而不能访问整个集群的资源

一个节点的kubelet无权访问其他节点上的资源

ABAC

基于属性的访问控制,通过定制user、group、APIGroup、Namespace,静态策略,通过配置文件将策略预先定义好,在API Server启动时载入策略,并根据后续的请求进行策略匹配及授权验证,启动之后无法再进行更改

RBAC

基于角色的访问控制。动态授权策略,通过使用rbac.authorization.k8s.io API组来定义授权策略

核心是通过定义Role、RoleBinding来完成授权策略定义。role定义具体的操作,rolebinding则将这个绑定到具体的对象上(user、group、ServiceAccount)

角色可以分为集群级别(ClusterRole)和命名空间级别(Role, 只对定义的单命名空间可见)

webhook

webhook认证是与企业认证平台集成的重要手段

通过认证可以获取当前请求的详细信息: 发起者的名字、唯一ID、属于哪些组、其他附属信息。只需要在API Server配置中添加--authentication-to-ken-webhook-config-file指向认证系统服务,认证系统服务实现一个RESTAPI,接收APIServer包装的TokenReview然后进行认证并发返回结果。

在webhook模式下,海量用户的认证请求很容器成为瓶颈,为了减少压力可以考虑加入离线认证,通过将Token认证从中心认证服务分散到所有的Kubernetes集群中,每个集群有一个认证服务来承担当前集群的用户认证,离线认证的token要保证合法性,并且包含用户的基本信息和组信息等基本信息,离线认证的去诶单就是无法体现出在签发Token后的用户和组的变动

隔离

节点隔离

指单个Kubernetes集群中未某个租户配置特定的节点池,这个租户独享这个节点池中的所有计算、存储资源,以及部分网络资源。在节点隔离中,允许住户拥有特定的特殊权限(例如hostNetwork、hostPath等)

使用Taints(一个等级是NoSchedule、一个等级是NoExecute)表示节点上有瑕疵,当调度器选择pod的调度节点时,如果这个节点有瑕疵,则这个节点在调度时会被过滤掉。

使用Toleration表示能够容忍什么样的瑕疵,pod创建完成之后不可更改,也有两个级别(容忍不允许调度、容忍不允许执行)

在添加了Toleration之后无法保证Pod会被调度器调度到特定的节点,还需在Pod上添加NodeSelector,从而保证Pod会被调度到特定的节点

为了防止用户刻意添加特定的Toleration将工作负载强行运行在特定的节点上,Kubernetes提供了scheduler.alpha.kubernetes.io/defaultTolerationsscheduler.alpha.kubernets.io/tolerationsWhitelist两个Annotation。当用户创建Pod时,Kubernetes会将用户Pod的Toleration和Namespace的默认Toleration合并,只有当合并后的Toleration均属于白名单是才允许创建

容器隔离

指运行在同一个节点上的不同工作负载之间的隔离。与节点隔离相比,容器隔离的颗粒度更小。在容器隔离中,由于工作负载共享了宿主机的操作系统内核,因此需要使用多种隔离机制,使得同一个宿主机上的不同工作负载之间共享计算、存储、网络I/O、硬盘I/O等资源,同时保证它们之间的隔离与公平竞争

Linux提供的多个层次隔离: UTS、IPC、PID、Network、Mount、User

  • Mount Namespace隔离: 无法看到其他容器的目录信息,即使是一个pod下,最多只能通过/prod/mounts看到容器的mount信息
  • PID Namespace隔离: 容器内执行ps或者/proc目录下,只能看到本容器的进程信息。无法获得主机和其他容器的运行进程和及进程的相关信息(启动参数、运行的环境变量),当然也就无法发送信号
  • IPC Namespace隔离: 无法与其他容器进行IPC通信
  • Network Namespace隔离: 维护独立的网络设备,IP地址,路由表,/proc/net目录等
  • UTS Namespace: 获取自己独立的主机名和域名

为了让同一个pod之间的容器能够进行通信,Kubernetes对容器的隔离做了特殊的定制,同一个Pod的不同容器之间共享IPC(使得可以通过SystemV信号量或POSIX共享内存进行进程间通信)和Network(可以通过localhost、127.0.0.1进行荣期间网络通信)

除此之外还支持在Pod上添加特定的声明来打破容器间的隔离

除了shareProcessNamespace外,其他都为特殊权限,由Pod安全策略(PSP)来管理

  • spec.hsotNetwork: 共享主机IPC Namespace,从而查看和监控主机上的网络流量
  • spec.volumes: 定义hostPath类型的卷来使用主机上的文件系统,从而可以在多个Pod中使用同一个目录来实现跨Pod文件共享
  • spec.shareProcessNamespace: 使得同一个pod的不同容器之间可以看到彼此的进程,跨容器协作例如一个包含shell工具的容器对一个不包含shell工具的容器进行debug
  • spec.containers[].securityContext.privileged: 容器以privilege模式运行,相当于宿主机上的root用户,可对宿主机进行各种操作

除了命名空间的隔离,资源的限额也是依赖于cgroups

网络策略隔离

默认情况下所有的Pod网络互相连通,但是在多租户集群中,需要做更细粒度的管控,例如禁止不同租户的Pod彼此访问。除了应用层进行隔离,更重要的直接在网络层面进行隔离,Kubernetes引入的网络策略(NetworkPolicy)对象定义隔离需求

  • podSelector: 通过一组键值对,选取与之相匹配的Pod,如果为空则选择当前Namespace下的所有Pod
  • policyTypes: 用于定义目标策略是入站还是出站或者双向流量生效,默认为入站
  • Ingress: 一组入站流量的白名单规则
  • Egress: 一组出站流量的白名单规则

不过这些规则只是从语义层面定义了网络隔离的模型,具体的实现需要相应的网络插件支持,主流的比如Calico、Kube Router等

Calico在部署时可以将数据源配置为Kubernetes,与其共用etcd(也可以部署独立的etcd,减少对核心组件的影响)

calico的核心组件包括: Calico kube-controllers和Calico-Node

如果是配置的独立etcd组件,当向API Server创建NetworkPolicy对象以后,kube-controllers(非独立etcd则这些不会运行)会将Namespace、Endpoint、NetworkPolicy和ServiceAccount中的相关信息同步到Calico etcd中

calico-node部署在每个节点上,其中Felix组件负责监控NetworkPolicy的变化, 并负责配置本机的iptables filter规则。并且calico仅管理以cali开头的网络端口,从而避免对非Kubernetes创建的端口的影响

配额

这一章节的资源限额水平切分与垂直切分之类的看得云里雾里,没看懂 😹

Kubernetes通过配额(ResourceQuota)来管理每个命名空间的资源,包括CPU、Memory、Storage、Load Balancer、Pod、Secrets、ConfigMap

不同的资源类型有不同的分类以及管理规则

BestEffort仅对Pod生效,NotBestEffort、Terminating、Not-Terminating仅针对Pod、CPU、memory等资源

  • Storage、Secrets、ConfigMap、Service: 以单位命名空间的资源分配个数为限制,例如一个Namespace分配100个Secret
  • CPU、Memory、Storage: 包括BestEffort(资源需求部分为空的Pod)、NotBestEffort(定义了资源需求而的)两种
  • 根据Pod是否定义了有限生存时间(pod定义了activeDeadlineSeconds, 超过时长会自动被kubelet删除)将配额分为Terminating、NotTerminating

guaranteed类型: 当资源的request和limits相同,直接调度并找到拥有请求数量的资源 Burstable类型: 调度时根据request调度,并且运行时最大的就是limit。

当资源紧张是,根据优先级从低到高驱逐pod,优先驱逐BestEffort,然后Burstable。也可以通过添加PriorityClass的scopeSelector的方式来定义有优先级的Pod

考虑到集群中应用优先级、节点多样性、多租户配额管理、不同类型配合划分等因素,可以将集群资源从多尔衮维度进行切分

通过使用PriorityClass设计与NodeSelector设计,解决Taints+Toleration模式无法阻止用户刻意抢占资源的问题,同时可以为用户提供更友好的配额管理

通过租户配额可以更有效地管理命名空间、管理各命名空间的配额,与云平台的配额管理解耦,当租户需要更多配额时,可以购买更多的配额。云平台只需保障租户得到应有的配额,而无须介入命名空间的管理,也无须介入命名空间级别的配额的管理。

  • 资源横向切分: 将资源划分出不同的优先级,保证高优先级,超售低优先级的方式进行配额管理,在集群资源紧张时,高优先级应用可以抢占低优先级,没有申请对应优先级的ResourceQuota的Namespace,不允许部署对应优先级的Pod
  • 资源纵向切分: 指将集群中具有相同类型、相同目的的节点提供的资源进行归类。

通过对Kubernetes集群计算资源进行横向、纵向切分,可以将集群中的资源从平面单优先级转为立体多优先级,将集群的不同类型的节点实名化,并绑定在不同优先级上

一般分为集群基础设施层(核心组件运行层,不对租户开放)、高优先级层、低优先级层(绑定在特定的节点类型中,以提供不同优先级的资源)、通用层(优先级最低,可以在部署被部署到任意类型的节点,当这些节点上有更高优先级的应用有需求时,原来的会被驱逐)