了解OpenShift的逻辑架构之后,接下来讲解在OpenShift中使用了哪些关键性技术。OpenShift的技术架构如图2-7所示。
图2-7 OpenShift的技术架构
按照层级,我们自下往上进行介绍。
·OpenShift的基础操作系统是Red Hat CoreOS。Red Hat CoreOS是一个精简的RHEL发行版,专用于容器执行的操作系统。
·CRI-O是Kubernetes CRI(容器运行时接口)的实现,以支持使用OCI(Open Container Initiative)兼容的运行时。CRI-O可以使用满足CRI的任何容器运行时,如runC、libpod或rkt。
·Kubernetes是容器调度编排和管理平台,关于它的具体功能我们不再赘述。
·Etcd是一个分布式键值存储,Kubernetes使用它来存储有关Kubernetes集群元数据和其他资源的配置及状态信息。
·自定义资源定义(CRD)是Kubernetes提供的用于扩展资源类型的接口,自定义对象同样存储在Etcd中并由Kubernete管理。
·容器化服务(Containerized Service)实现了PaaS功能组件以容器方式在OpenShift上运行。
·应用程序运行时和xPaaS(Runtime and xPaaS)是可供开发人员使用的基本容器镜像,每个镜像都预先配置了特定的运行时语言或数据库。xPaaS产品是红帽中间件产品(如JBoss EAP和ActiveMQ)的一组基础镜像。OpenShift应用程序运行时(RHOAR)是在OpenShift中运行云原生应用的程序运行时,包含Red Hat JBoss EAP、OpenJDK、Thorntail、Eclipse Vert.x、Spring Boot和Node.js。
·DevOps工具和用户体验:OpenShift提供用于管理用户应用程序和OpenShift服务的Web UI和CLI管理工具。OpenShift Web UI和CLI工具是使用REST API构建的,可以与IDE和CI平台等外部工具集成使用。
我们已经从技术架构图中了解到技术组件的概貌,接下来将深入介绍OpenShift中一些关键技术的细节。
CoreOS是一个容器化操作系统,由CoreOS公司开发。后来CoreOS被红帽收购,形成了Red Hat Enterprise Linux CoreOS(简称RHCOS)产品。
RHCOS代表了下一代单用途容器操作系统技术。RHCOS由创建了Red Hat Enterprise Linux Atomic Host和CoreOS Container Linux的同一开发团队打造,它将Red Hat Enterprise Linux(RHEL)的质量标准与Container Linux的自动化远程升级功能结合在一起。
OpenShift的Master节点必须使用RHCOS,Worker节点可以选择RHCOS或RHEL。目前RHCOS只能作为OpenShift的基础操作系统,不能独立使用。RHCOS的组件管理也由OpenShift进行,如图2-8所示。
图2-8 OpenShift基础架构的变化
RHCOS底层操作系统主要由RHEL组件构成。它支持RHEL的相同质量、安全性和控制措施,也支持RHCOS。例如,RHCOS软件位于RPM软件包中,并且每个RHCOS系统都以RHEL内核以及由Systemd初始化系统管理的一组服务启动。
RHCOS系统包含的组件如图2-9所示。
图2-9 RHCOS系统包含的组件
我们查看CoreOS的版本,它与OpenShift的版本匹配,如OpenShift 4.4使用RHCOS 4.4。
# cat /etc/redhat-release Red Hat Enterprise Linux CoreOS release 4.4
查看Kubelet、CRI-O,这两个是Systemd,如图2-10和图2-11所示。
图2-10 RHCOS系统中的Kubelet状态
图2-11 RHCOS系统中的CRI-O状态
对于RHCOS系统,rpm-ostree文件系统的布局如下:
·/usr是操作系统二进制文件和库的存储位置,并且是只读的。
·/etc、/boot和/var在系统上是可写的,但只能由Machine Config Operator更改。
·/var/lib/containers是用于存储容器镜像的Graph存储位置。
在OpenShift中,Machine Config Operator负责处理RHCOS操作系统升级,rpm-ostree以原子单元形式提供升级,不像Yum升级那样单独升级各个软件包。新的RHCOS部署在升级过程中进行,并在下次重启时才会生效。如果升级出现问题,则进行一次回滚并重启,就能使系统恢复到以前的状态。需要指出的是,RHCOS升级是在OpenShift集群更新期间执行的,不能单独升级RHCOS。
在OpenShift中大量使用了Operator。Operator是打包、运行和维护Kubernetes应用程序的一种方式,主要针对有状态应用集群,如etcd。Operator同样也是一种Kubernetes应用程序,它不仅部署在Kubernetes上,还要与Kubernetes的设施和工具协同工作,以实现软件自动化的整个生命周期。通过Operator,使用者可以更轻松地部署和运行基础应用程序所依赖的服务。对于基础架构工程师和供应商,Operator提供了一种在Kubernetes集群上分发软件的一致方法,并且能够在发现和纠正应用程序问题之前减少支持负担和异常出现。
Operator最初由CoreOS公司开发,逐步成为一种非常流行的打包、部署和管理Kubernetes/OpenShift应用的方法,如图2-12所示。
图2-12 Operator发展史
OpenShift中的Operator按作用不同,主要可分为Cluster Operator和Application Operator。下面分别说明OpenShift中的这两类Operator。
Cluster Operator用来对OpenShift集群组件进行生命周期管理,如OpenShift的版本升级实际上是升级Cluster Operator和CoreOS,在本章后面我们将详细说明升级过程。也就是说,OpenShift自身能力的提供是通过各类Cluster Operator实现的,如图2-13所示。
每个Cluster Operator相关的Namespace通常有两个,一个Namespace运行Operator本身的Pod,另一个Namespace运行由这个Operator控制的、负责具体工作的Pod,实际工作的Pod通常有多个,分别运行在OpenShift的不同节点上。
图2-13 OpenShift上的Cluster Operator
我们以dns的Cluster Operator为例,和dns相关的Namespace有两个:openshift-dns-operator Namespace和openshift-dns Namespace。其中openshift-dns-operator Namespace中运行dns Cluster Operator本身的Pod,Pod名称类似于dns-operator-5995b99c68-sf2b9;
openshift-dns Namespace中运行具体负责DNS解析的Coredns的Pod。我们查看该Namespace下的Pod,就可以看到每个OpenShift节点上都会运行一个Coredns的Pod。这些Coredns Pod是由dns Cluster Operator管理的Daemonset控制的,默认名称为dns-default。
综上所述,dns通过Operator的启动和控制流程为:dns Cluster Operator(openshift-dns-operator Namespace下)创建dns-default Daemonset(openshift-dns Namespace下),再由dns-default Deamonset在每个OpenShift节点创建Coredns Pod,负责集群中的DNS解析。这样我们就需要通过配置dns Cluster Operator的参数来改变dns-default Daemonset的参数,进而改变Coredns的行为。
在OpenShift中,有的Cluster Operator具体负责工作的Pod无须运行在所有的节点上,只需有多副本保证高可用即可。如Ingress,Ingress负责对外发布路由,同样由Cluster Operator控制,运行在openshift-ingress-operator和openshift-ingress两个Namespace上。同样,在openshift-ingress-operator Namespace中运行ingress operator Pod;openshift-ingress Namespace中运行两个负责具体路由功能的Router Pod。而Router Pod是由Ingress Operator管理的Deployments控制的。
默认安装完成后,Router Pod会运行在不固定的两个节点上,比如Master节点中的两个节点。通常在生产上我们需要专门规划节点来运行Router,在后文将介绍如何将Router迁移到Infra节点(特殊的Worker节点)上。
经过前面的介绍,相信读者已经了解了OpenShift中Cluster Operator的运作模式。限于篇幅,我们在此就不一一介绍了,OpenShift中所有Cluster Operator的整体概览性介绍如表2-1所示。
表2-1 OpenShift中的Cluster Operator清单
OpenShift推出OperatorHub,可以基于Operator模式管理应用,我们称这类Operator为Application Operator(与Cluster Operator相对应)。
Operator Framework也是红帽推出的,用于构建、测试和打包Operator的开源工具包。它提供了以下三个组件:
·Operator软件开发套件(Operator SDK):提供了一组Golang库和源代码示例,这些示例在Operator应用程序中实现了通用模式。它还提供了一个镜像和Playbook示例,我们可以使用Ansible开发Operator。
·Operator Metering:为提供专业服务的Operator启用使用情况报告。
·Operator生命周期管理器(Operator Lifecycle Manager,OLM):提供了一种陈述式的方式来安装、管理和升级Operator以及Operator在集群中所依赖的资源。
其中Operator Lifecycle Manager(OLM)可帮助用户安装、更新和管理跨集群运行的所有Operator及其关联服务的生命周期。OLM承载着OpenShift OperatorHub,目前可以提供近200个Operator,如图2-14所示。
图2-14 OLM中的Operator
在OLM生态系统中,以下资源负责Operator的安装和升级:
·ClusterServiceVersion
·CatalogSource
·Subscription
ClusterServiceVersion(CSV)是从Operator Metadata创建的YAML清单,可帮助OLM在集群中运行Operator。一个CSV包括Metadata(应用的name、version、icon、required resources、installation等)、Install strategy(包括ServiceAccount、Deployments)、CRDs。
不同Operator的元数据以CSV存储在CatalogSource中。在CatalogSource中,Operator被存储到Package中,然后以Channel来区分Update Stream。OLM使用CatalogSources(调用Operator Registry API)来查询可用的Operator以及已安装的Operator的升级,如图2-15所示。
图2-15 OLM调用CatalogSources查询版本
以Etcd Operator为例,它整体上是个Package,分为alpha和beta两个Channel,每个Channel中通过CSV来区分版本,如etcdoperator.v0.6.1、etcdoperator.v0.9.0、etcdoperator.v0.9.2。在Channel中也定义了通过CSV实现的升级途径。
用户在Subscription的特定CatalogSource中指示特定的软件包和Channel,例如etcd Package及其alpha Channel。如果在尚未安装该Package的Namespace中进行订阅,则会安装Package选择的Channel中最新版本的Operator。
在OpenShift中有四种CatalogSource,如图2-16所示。
图2-16 查看OCP中的CatalogSources
四种CatalogSource分别为certified-operators(红帽认证的Operator)、community-operators(开源社区的Operator)、redhat-marketplace(红帽认证过的由第三方销售的Operator)和redhat-operators(红帽公司提供的Operator)。这与图2-16 OpenShift OperatorHub显示的四类Provider Type是对应的(框线标记)。
接下来,我们通过在OpenShift安装Etcd Operator来说明OLM的工作原理。
首先在Operator中搜索etcd,如图2-17所示,我们看到etcd属于community-operators的CatalogSource。
图2-17 查找etcd Operator
我们可以看到Operator Version、Repository、Container Image、创建时间等关键信息,如图2-18所示,点击Install。
图2-18 etcd Operator的信息
我们看到需要指定etcd安装的Namespace、Update Channel、Approval Strategy(当本Channel有新版的软件时采用自动还是手动更新),我们根据需要做出选择,然后点击Install,如图2-19所示。
图2-19 etcd Operator安装时的选择
稍等一会儿,etcd Operator就可以安装成功,如图2-20所示。
图2-20 etcd Operator安装成功
此时,我们可以选择编辑CSV,如图2-21所示。
图2-21 编辑etcd Operator的CSV
如前文所示,CSV中包含大量的元数据信息,将其中的metadata.name字段由example修改为example-davidwei。修改完成后,点击Save,界面会提示需要Reload,如图2-22所示。
图2-22 编辑CSV的内容
接下来,我们使用etcd Operator提供的API创建etcd Cluster实例,如图2-23所示。
图2-23 使用etcd Operator API创建etcd Cluster
我们看到metadata中的name字段为example-davidwei,这是此前我们修改CSV时指定的,如图2-24所示,点击Create。
图2-24 创建EtcdCluster
稍等一会儿,EtcdCluster就会创建成功,此时通过命令行查看Pod。
# oc get pods NAME READY STATUS RESTARTS AGE etcd-operator-59dc995496-mdkjk 3/3 Running 0 6m example-davidwei-mzfl7nm566 1/1 Running 0 38s example-davidwei-nxphptkn95 1/1 Running 0 30s example-davidwei-qt98qwrm79 1/1 Running 0 14s
此时应用已经可以使用这个etcd集群存储数据了。
如果我们想对etcd Cluster进行扩容,例如增加一个实例,通过Operator操作即可,如图2-25所示,点击Update Size。
图2-25 增加etcd Cluster节点数量
etcd Cluster很快开始进行扩容,如图2-26所示。
扩容完成后,查看运行的Pod,已经变成4个节点的etcd集群。
# oc get pods NAME READY STATUS RESTARTS AGE etcd-operator-59dc995496-mdkjk 3/3 Running 0 9m14s example-davidwei-f6vpv8mzqv 1/1 Running 0 10s example-davidwei-mzfl7nm566 1/1 Running 0 3m52s example-davidwei-nxphptkn95 1/1 Running 0 3m44s example-davidwei-qt98qwrm79 1/1 Running 0 3m28s
图2-26 etcd Cluster扩容完毕
通过扩容这个示例可以看出,通过Application Operator管理的应用可以大幅度减少运维层面的操作。
值得一提的是,在etcd Operator安装后,我们仍然可以修改Channel,如图2-27所示。
图2-27 修改etcd Operator的Channel
至此,我们在OpenShift上通过etcd Operator安装并扩容了etcd Cluster。
为了更清楚地了解OpenShift各节点上运行的服务,我们先简单回顾原生Kubernetes各节点上运行的服务或进程。
原生Kubernetes中的核心进程架构如图2-28所示。
图2-28 Kubernetes中的核心进程
Kubernetes Master节点上有如下核心进程:
·kube-apiserver:Kubernetes API server验证并配置API对象的数据,这些对象包括Pod、Service、ReplicationController等。此外,API Server提供REST操作服务,并为集群的共享状态提供前端访问,所有其他组件都通过该前端进行交互。
·etcd:一个键值数据库,用于存放kube-apiserver的相关数据,如服务注册信息等。
·controller manager:kube-controller-manager只在Master节点运行,管理多个controller进程。kube-controller-manager监控对象包括Node、Workload(replication controller)、Namespace(namespace controller)、Service Account(service account controller)等,当这些被监控对象实际状态和期望状态不匹配时采取纠正措施。
·scheduler:即kube-scheduler。主要负责整个集群资源的调度功能,根据特定的调度算法和策略,将Pod调度到最优的Worker节点上。
Kubernetes Node节点上有如下核心进程:
·kubelet:kubelet是在Kubernetes节点上运行的Node Agent。kubelet可以通过hostname将Node注册到kube-apiserver。
·kube-proxy:即Kubernetes network proxy。kube-proxy部署在每个Node节点上,它是实现Kubernetes Service的通信与负载均衡机制的重要组件;kube-proxy负责为Pod创建代理服务,从kube-apiserver获取所有Service信息,并根据Service信息创建代理服务,实现Service到Pod的请求路由和转发,从而实现Kubernetes层级的虚拟转发网络。
·容器运行时:kubelet通过调用容器运行时管理节点上的容器。
在OpenShift中,同样将节点分为Master节点和Worker节点,下面先介绍Master节点。
OpenShift Master节点的进程结构如图2-29所示。
图2-29 OpenShift Master节点的进程结构
我们看到OpenShift Master中会有两个API Server:kube-apiserver和openshift-apiserver,后者是前者的API请求代理;两个controller manager:kube-controller-manager和openshift-controller-manager,后者是前者的代理,对前者进行了一定拓展。在OpenShift集群中,任何操作都不会直接调用kube-apiserver和kube-controller-manager,而是由openshift-apiserver和openshift-controller-manager完成。
同时从图2-29中可以看到,Master上进程的运行方式分以下三类:
·普通Pod(由Cluster Operator管理)
·静态Pod
·Systemd服务
首先我们先看静态Pod。静态Pod是不需要连接到API Server即可启动的容器,它们由特定节点上的Kubelet守护程序直接管理,Kubelet会监视每个静态Pod(并在崩溃时重新启动它),静态Pod始终绑定到特定节点上的一个Kubelet。
我们查看Master节点上运行的静态Pod,首先登录节点,如master-0。切换到/etc/kubernetes/static-pod-resources目录。
通过ls命令查看该目录下的文件,可以看到Master节点被Kubelet管理的静态Pod正是etcd、kube-controller-manager、kube-apiserver、kube-scheduler四个。这些Pod实际上是在OpenShift安装完成前,在Bootstrap阶段就已经启动了,第5章会详细介绍安装的各个阶段。
接下来,我们查看Master节点上运行的两个Systemd服务:kubelet和cri-o,我们看到它们处于正常运行的状态,如图2-30、图2-31所示。
图2-30 OpenShift Master节点的kubelet进程
图2-31 OpenShift Master节点的cri-o进程
除了Kubernetes Master的6个核心组件之外,OpenShift Master节点上还运行openshift-apiserver、openshift-controller-manager、openshift authentication,这些组件都由OpenShift Cluster Operator管理,并且都是普通Pod。
openshift-controller-manager用于管理OpenShift集群中的多个controller,我们通过命令行查看OpenShift中的controller,如图2-32所示。
图2-32 查看OpenShift中的controller
从图2-32中看到,有sdn-controller(负责管理SDN)、machine-config-controller(负责对底层CoreOS进行配置管理,第5章会进行详细介绍)、multus-admission-controller(负责OpenShift中多网络平面管理,本章后文详细介绍)、csi-snapshot-controller(用于实现第三方CSI的volume snapshot生命周期管理)。这些组件的特点是控制平面只运行在Master节点上,所有OpenShift计算节点都承担数据平面的职责。
以SDN为例查看相关的Pod,如图2-33所示。
图2-33 查看OpenShift中SDN相关的Pod
其中sdn-controller Pod只运行在Master节点上,OVS Pod则在每个节点上都会运行。其余的controller组件的结构都类似,不再一一列出。
当我们清楚了Master节点上的进程如何运行之后,接下来看Worker节点。在Worker节点上运行OpenShift自身核心的组件有crio、kubelet、kube-proxy。此外还有由openshift-controller-manager控制的controller对应的数据平面,如sdn、machine-config等,Worker节点上无静态Pod。
Worker节点上的crio、kubelet同样是Systemd服务。而Worker节点上的kube-proxy在OpenShift的每个节点上运行,并由Cluster Network Operator(CNO)管理。kube-proxy维护用于转发与Service关联的Endpoints的网络规则。
kube-proxy的安装方式取决于集群配置的SDN插件。如果使用SDN插件内置kube-proxy(如OpenShift SDN),则不需要独立安装kube-proxy;还有一些SDN插件需要独立安装kube-proxy,也有一些SDN插件(如ovn-kubernetes)根本就不需要kube-proxy。
在安装集群的时候,在文件manifests/cluster-network-03-config.yml中的deployKube-Proxy参数可用于指示CNO是否应部署独立的kube-proxy。对于OpenShift官方明确支持的SDN插件,将自动配置正确的值;当使用第三方插件的时候可以修改此值。
由于OpenShift SDN内置了kube-proxy进程,因此Worker节点上的kube-proxy进程由openshift-sdn项目下的Pod sdn-xxxxx启动,启动命令为/usr/bin/openshift-sdn-node--proxy-config=/config/kube-proxy-config.yaml。
我们可以通过Network Operator的参数来配置kube-proxy,以便控制OpenShift节点上的kube-proxy参数。
kube-proxy配置参数包含的内容如表2-2所示。
表2-2 kube-proxy配置参数
以调整kube-proxy参数iptables-min-sync-period为例,该参数控制刷新iptables规则之前的最短持续时间,此参数确保不会太频繁地发生刷新,默认值为30s。
Iptables的同步触发条件如下:
·events触发:是指Service或Endpoint对象发生变化,如创建Service。
·自上次同步以来的时间超过了kube-proxy定义的同步时间,默认30s。
通过修改Network.operator.openshift.io自定义资源(CR)实现修改kube-proxy的配置。默认配置中,没有任何与kube-proxy相关的配置。
# oc edit network.operator.openshift.io cluster
增加如图2-34中框选的内容。
图2-34 修改kube-proxy的配置
运行以下命令以确认配置更新,如图2-35所示。
# oc get networks.operator.openshift.io -o yaml
这样,我们就设置了Iptables的同步时间为30s,iptables刷新之前最短持续时间为30s。
图2-35 确保kube-proxy的配置修改成功
OpenShift认证体系包含认证和授权。认证层负责识别用户的身份,验证用户身份后,通过RBAC(Role-Based Access Control,基于角色的访问控制)策略定义该用户允许执行的操作,这属于授权。OpenShift通过RBAC实现权限控制。
OpenShift支持以下两种认证方式:
·OAuth Access Token:使用OpenShift内的OAuth Server颁发的Access Token认证,可以通过用户登录、ServiceAccount或者API获取。
·X.509 Client Certificate:通过证书认证,证书大多数用于集群组件向API Server认证。
任何具有无效Token或无效证书的请求都将被身份验证层拒绝,并返回401错误。OpenShift内置OAuth Server,由authentication operator提供,如图2-36所示。
图2-36 查看OpenShift内置OAuth Server
接下来简单介绍与认证相关的几个概念。
·User:OpenShift中的User是与API Server进行交互的实体。通过直接向User或User所属的Group添加角色来分配权限。OpenShift中的User分为三种:System User、Regular User和Service Account。System User是OpenShift自动生成的,主要用于基础组件与API交互。Regular User表示user对象,是用户和OpenShift API交互最活跃的部分。Service Account是与Project相关联的特殊的系统用户,可以看作OpenShift运行应用的用户代表,通过Service Account配置应用具有的OpenShift权限。
·Identity:OpenShift中的每个用户都有一个身份(identity)用于认证,本质上Identity是OpenShift与Identity Providers集成的产物。在使用Identity Providers中的用户登录后,会自动在OpenShift中创建User对象和Identity对象,Identity对象中记录User和Identity Providers的信息,实现唯一标识一个用户,尤其在多个Identity Providers中有重名时这种方式很有效。
当User尝试向API Server进行身份验证时,OAuth Server首先会通过Identity Providers来验证请求者的身份,只有身份验证通过,OAuth Server才会向用户提供OAuth Access Token。OpenShift提供了可以与多种Identity Providers对接的能力,主要包括:
·HTPasswd:使用HTPasswd生成的用户名和密码文件验证。
·Keystone:启用与OpenStack Keystone v3服务器的共享身份验证。
·LDAP:使用简单绑定身份验证,配置LDAP身份提供程序以针对LDAP v3服务器验证用户名和密码。
·GitHub或GitHub Enterprise:配置GitHub身份提供者以针对GitHub或GitHub Enterprises OAuth身份验证服务器验证用户名和密码。
·OpenID Connect:使用授权代码流与OpenID Connect身份提供者集成。
我们可以在同一个OpenShift集群中定义相同或不同种类的多个身份提供者。例如,既有通过HTPasswd认证的方式,又有通过OpenID Connect认证的方式。此外,我们既可以使用OpenShift内置的OAuth做认证,也可以对接外部的OAuth服务器,尤其是我们在实现微服务的Single Sign On时,例如使用Red Hat Single Sign On(Keycloack企业版)。在本章后面安装环节将介绍身份提供者的配置。
综上所述,OpenShift中的认证流程如图2-37所示。
图2-37 OpenShift的认证流程
在认证成功之后,访问Master API还需要经过鉴权来判断该用户对资源是否有操作权限,下面就介绍OpenShift中的RBAC权限模型。
在获取到OAuth Access Token之后,任何的操作都要经过鉴权。OpenShift中与RBAC相关的资源类型有User(前文已经介绍)、Group、Role等概念。接下来,我们简单介绍这几个概念。
·Group:代表一组特定的用户。用户被分配到一个或多个组。在实施授权策略后,可以利用组同时向多个用户分配权限。例如,如果要允许20个用户访问某个项目中的资源,更好的方式是使用组而不是单独授予每个用户访问权限。除了自定义组之外,OpenShift默认还提供了系统组或虚拟组,这些组由系统自动创建,常用的系统组如system:authenticated:oauth,所有通过OAuth Access Token认证的用户会自动加入该组,这个组被赋予self-provisioners Cluster Role,使得所有通过OAuth Access Token认证的用户具有新建Project的权限。
·Role:是一组权限,使用户可以对一种或多种资源类型执行API操作。按作用范围,可将Role分为Cluster Role和Local Role。Cluster Role是整个集群范围内的,任何Namespace都可以使用,但是Local Role是属于Namespace级别的资源,而且需要自行创建。你可以通过对User、Group分配Cluster Role或Local Role来为其授予权限。
OpenShift中的RBAC模型如图2-38所示。
图2-38 OpenShift中的RBAC模型
从图2-38中可以看出授权的对象可以是Service Account、Group和User。Role可以是Cluster Role,也可以是Local Role。授权后会生成相应的Role Binding对象,相当于记录授权对象与Role的授权关系。
很多企业对容器云的分权控制要求很高,这时候就需要借助OpenShift中的RBAC进行细致的权限划分。我们不再赘述RBAC的基本概念,这里列出几个常见的场景供读者参考。
场景1:只拥有审计权限,没有管理权限。预期:登录后只能查看审计日志,无法查看租户。
# oc login -u admin # oc create clusterrole audit-no-admin --verb=get --resource=nodes/log --verb=list --resource=nodes --verb=get --resource=nodes/proxy # oc adm policy add-cluster-role-to-user audit-no-admin auditnoadmin # oc login -u auditnoadmin # oc adm node-logs --role=master --path=openshift-apiserver/
场景2:只拥有管理权限,没有审计权限。预期:登录后无法查看审计日志,可以查看租户。
# oc login -u admin # oc adm policy add-cluster-role-to-user admin adminnoaudit # oc login -u adminnoaudit # oc adm node-logs --role=master --path=openshift-apiserver/
场景3:不能审计、不能管理的安全员。预期:登录后无法查看审计日志,无法查看租户。
# oc login -u admin # oc adm policy add-cluster-role-to-user cluster-status safetyofficer # oc login -u safetyofficer # oc adm node-logs --role=master --path=openshift-apiserver/ # oc get projects
读者在理解了RBAC的模型后,可以自行创建满足需求的Role,实现细粒度的权限控制。
除了通过RBAC来控制用户的权限之外,OpenShift还提供了安全上下文约束(SCC)来控制Pod的权限。这些权限包含Pod可以执行的操作和可以访问的资源,包含对主机环境的访问限制。SCC允许控制如下内容:
·运行特权容器。
·在容器中获取额外的能力。
·使用宿主机目录作为Pod Volume。
·容器的SELinux上下文。
·容器的用户ID。
·宿主机命名空间和网络的使用。
·Pod Volume的属组FSGroup。
·附加组的配置。
·根文件系统只读。
·可以使用的Volume类型。
·配置允许的seccomp策略。
OpenShift中默认提供了8个SCC策略,SCC需要授权User或Group后才能被使用,默认策略中允许使用User和Group如表2-3所示。
表2-3 OpenShift中SCC策略的说明
需要特别提醒的是,绝不要修改默认的8个SCC策略的内容,否则会导致集群升级出现问题,正确的做法是创建新的SCC策略。
上述8个SCC策略中,只有anyuid的优先级最高,会被优先匹配。我们最常用的是privileged和restricted,前者表示几乎无限制的特权容器,后者表示受严格限制的容器。大部分容器会匹配restricted SCC,我们以restricted SCC为例,查看策略内容如下所示。
Name: restricted Priority: <none> Access: Users: <none> Groups: system:authenticated Settings: Allow Privileged: false Allow Privilege Escalation: true Default Add Capabilities: <none> Required Drop Capabilities: KILL,MKNOD,SETUID,SETGID Allowed Capabilities: <none> Allowed Seccomp Profiles: <none> Allowed Volume Types:configMap,downwardAPI,emptyDir,persistentVolumeClaim, projected,secret Allowed Flexvolumes: <all> Allowed Unsafe Sysctls: <none> Forbidden Sysctls: <none> Allow Host Network: false Allow Host Ports: false Allow Host PID: false Allow Host IPC: false Read Only Root Filesystem: false Run As User Strategy: MustRunAsRange UID: <none> UID Range Min: <none> UID Range Max: <none> SELinux Context Strategy: MustRunAs User: <none> Role: <none> Type: <none> Level: <none> FSGroup Strategy: MustRunAs Ranges: <none> Supplemental Groups Strategy: RunAsAny Ranges: <none>
可以看到SCC由Setting和Strategy两部分来控制Pod的权限。这些配置的值分为以下三类:
·通过布尔值设置,只有True和False,如Allow Privileged:false。
·通过一组允许的值设置,如Allow Volume Types。
·通过策略控制,这类配置通过设置策略实现不同的权限控制。策略分为生成固定值的和指定允许范围的。
通过布尔值设置以及通过一组允许的值设置这两种方式大家都比较好理解,这里我们着重说明策略控制类型的SCC配置。有四个策略控制类型:Run As User、SELinux Context、Supplemental Groups、FSGroup。
·Run As User:该设置控制Pod启动后运行进程的用户。
允许配置四种策略:
1)MustRunAs:如果SCC中设定为此策略,则必须在Pod的定义中通过security-Context.runAsUser设定明确的Pod运行UID,否则Pod将无法启动。
2)MustRunAsRange:SCC Resricted的默认配置。此策略表示Pod运行的UID必须在一个范围内,如果在SCC中没有定义范围的最小最大值,则每个Project Pod运行的UID范围使用Project上Annotation openshift.io/sa.scc.uid-range定义的范围,如1000060000/10000,表示最小值为1000060000,向后步增,共10000个。如果不在Pod的定义中通过securityContext.runAsUser设定Pod运行UID,则使用范围的最小值作为默认值。设定runAsUser必须在MustRunAsRange设定的最大最小值范围里。比如某个Pod匹配到Resricted SCC,由于Resricted默认未设置MustRunAsRange的最大最小值,则会使用Project上定义的范围,假设为1000060000/10000。如果该Pod中也没有明确定义runAsUser,那么该Pod运行的UID为范围最小值1000060000;如果该Pod中有明确定义运行的UID,那么必须在1000060000~1000070000范围内。
3)MustRunAsNonRoot:该策略表示只要不是Root(UID=0)用户就可以。没有提供默认值,要求Pod必须提供一个非零的UID。可以在Pod定义中通过securityContext.runAsUser设定,也可以在Pod使用的镜像的Dockerfile中通过USER指令来定义非Root用户。
4)RunAsAny:该策略表示可以使用任何UID运行。没有提供默认值,可以在Pod定义中通过securityContext.runAsUser设定,也可以在Pod使用的镜像的Dockerfile中通过USER指令定义。
·SELinux Context:该设置控制Pod启动后的SELinux标签。
允许配置两种策略:
1)MustRunAs:SCC Resricted的默认配置。必须以指定的SELinux标签运行。可以通过在SCC策略中设定,也可以在Pod定义中通过securityContext.SELinuxOptions设定,如果上述两处都没有设定,那么将使用Project上Annotaions openshift.io/sa.scc.mcs定义的值。
2)RunAsAny:允许以任何SELinux标签运行,未提供默认值。在这种策略下,如果SCC和Pod定义中都未指定,则为空。
·Supplemental Groups:该设置控制Pod运行用户的附加组。
允许配置两种策略:
1)MustRunAs:必须以指定的附加组范围运行。可以在SCC中定义附加组范围,如果SCC中没有定义附加组范围,将使用Project上Annotation openshift.io/sa.scc.supplemental-groups定义的范围,如1000060000/10000。你可以在Pod的定义中通过securityContext.supplementalGroups设定附加组GID,但必须在定义的范围内。如果Pod定义中未明确设置附加组,将以生效范围的最小值作为Pod的附加组GID。
2)RunAsAny:SCC Resricted的默认配置。允许Pod设定任何的附加组。
·FSGroup:该设置控制Pod运行的FSGroup。
允许配置两种策略:
1)MustRunAs:SCC Resricted的默认配置。必须以指定的FSGroup范围运行。可以在SCC中定义FSGroup范围,如果SCC中没有定义FSGroup范围,将同样使用Project上Annotation openshift.io/sa.scc.supplemental-groups定义的范围。你可以在Pod的定义中通过securityContext.fsGroup设定FSGroup ID,但必须在定义的范围内。如果Pod定义中未明确设置FSGroup,将以生效范围的最小值作为Pod的FSGroup ID。
2)RunAsAny:允许任何的FSGroup设置。
可以看到,Pod的SCC权限受Pod的定义、SCC策略以及Project三者的相互作用,优先级顺序大致为Pod的定义>SCC策略>Project Annotations默认值。
我们在清楚了SCC中的配置内容之后,接下来看看SCC是如何匹配的。
在本章开始已提到,SCC是控制Pod权限的,所以SCC匹配也是与Pod匹配。SCC匹配大致经过过滤和匹配两个过程。过滤是指在集群中所有的SCC必须允许Pod使用,即Pod有权限使用SCC列表;匹配是指在过滤后的SCC中,逐个匹配Pod的需求与各项策略的定义,如果有满足Pod环境需求的,则匹配成功,否则Pod无法启动。如果有多个SCC满足,则需要评估SCC的优先级(SCC中的优先级参数设置,比如anyuid 10),优先级高的将被匹配;如果在优先级相同的情况下,则选择策略限制更加严格的SCC。
综上所述,SCC匹配将与以下四点相关:
1)启动Pod的用户的权限,注意权限有可能来源于User或Group。
2)Pod中securityContext的定义。
3)Pod使用的镜像中USER的定义,注意如果Dockerfile没有明确定义USER,则默认为Root。
4)SCC的优先级和策略配置,注意anyuid的优先级最高。
介绍完SCC匹配的原理之后,我们来看默认SCC策略的情况。默认情况下,项目中未经特殊授权的普通Pod只能使用restricted SCC,由于该SCC允许system:authenticated组匹配,其余的默认均无法被普通Pod使用,也就是说,普通Pod必须满足restricted SCC的策略和权限,否则就会启动失败。我们可以根据需要创建新的SCC使用,也可以对默认的SCC赋权后使用,但绝对不要修改默认SCC策略的内容!
通常使用SCC的做法是通过Pod绑定的Service Account实现赋权,然后在Pod中定义特殊需求(比如hostnetwork、anyuid等)以匹配合适的SCC。过程大致如下。
首先需要创建Pod运行的Service Account。
# oc create serviceaccount scc-demo serviceaccount/scc-demo created
然后将创建的Service Account赋权给特定的SCC。
# oc adm policy add-scc-to-user hostnetwork -z scc-demo securitycontextconstraints.security.openshift.io/hostnetwork added to: ["scc-demo"]
执行后可以去查看SCC中的Access.user会增加我们新建的Service Account。然后,就可以创建自己的Pod,Pod需要使用scc-demo Service Account。
注意,如果Pod中没有必要使用hostnetwork SCC的需求,根据优先级相同的匹配原则,依然会使用策略限制更加严格的restricted SCC,所以并不是赋权就一定使用hostnetwork SCC。
Pod调度程序用于根据特定的算法与策略将Pod调度到节点上。调度的主要过程是接受API Server创建新Pod的请求,并为其安排一个主机信息(也就是调度到节点上),最后将信息写入Etcd中。但实际上,调度过程远远没有这么简单,需要综合考虑很多决策因素。调度策略可分为默认调度策略和高级调度策略,下面我们分别介绍。
默认调度策略是OpenShift为kube-scheduler进程配置的策略,主要通过Predicates和Priorities(优先级)定义策略。
1)Predicates的概念
Predicates本质上是过滤掉不合格节点的规则。OpenShift提供了多个Predicates。我们可以通过参数自定义Predicates,也可以组合多个Predicates以提供节点的过滤。Predicates包含Static(静态)和General(普通)两类。
静态的Predicates不接受用户的任何配置参数或输入,这些在调度程序配置中使用其确切名称指定。静态的Predicates示例如下所示(完整列表参考Repo中“默认静态Predicates”)。
PodToleratesNodeTaints检查Pod是否可以容忍节点污点。
{"name" : "PodToleratesNodeTaints"}
普通的Predicates包含Non-critical predicates和Essential predicates两类。Non-critical predicates是只针对非关键Pod的节点过滤策略,Essential predicates是针对所有Pod的节点过滤策略。
Non-critical General Predicates示例如下:
PodFitsResources:根据资源可用性(CPU、内存、GPU等)确定适合度(根据Request资源)。
{"name" : "PodFitsResources"}
Essential General Predicates示例如下:
PodFitsHostPorts:确定节点是否具有用于请求的Pod端口的空闲端口(不存在端口冲突)。
{"name" : "PodFitsHostPorts"}
2)Priorities的概念
Priorities是根据优先级对节点进行排名的规则。我们可以指定一组定制的Priorities来配置调度程序。默认情况下,OpenShift容器平台中提供了Priorities,其他Priorities策略可以通过提供某些参数进行自定义。我们也可以组合多个Priorities,并且可以为每个Priorities赋予不同的权重,以设定节点优先级。Priorities包含Static(静态)和Configurable(可配置)两类。
Static类型除权重外不接受用户的任何配置参数。必须要指定权重,并且不能为0或负数。(除NodePreferAvoidPodsPriority为10000外,每个Priorities函数的权重均为1。)
默认使用的优先级都是Static类型的,如下所示(完整列表见Repo中“默认静态优先级策略”):
NodeAffinityPriority:根据节点相似性调度首选项对节点进行优先级排序。
{"name" : "NodeAffinityPriority", "weight" : 1}
Configurable类型是除了OpenShift提供之外自行配置的优先级。我们可以在openshift-config项目的调度程序中使用Configmap配置这些优先级,后文我们将演示如何实现。
默认的调度策略是在集群运行前就配置好的,而且是全局生效的。那么,如果需要更多控制以调整某些Pod的调度呢?为了满足多样化的需求,OpenShift还提供了高级调度策略,也被称为运行时调度策略,这种策略允许在创建应用时设置策略来影响Pod调度的位置。目前官方明确支持的高级调度策略有:
·使用node selectors规则。
node selectors是目前最为简单的一种节点约束规则。通过在Pod中定义nodeSelector,然后匹配节点上的键值对Labels。OpenShift也提供了一部分内置的节点标签,如kubernetes.io/hostname。
·使用affinity(亲和)与anti-affinity(反亲和)规则。
虽然nodeSelector提供了一种非常简单的方法来将Pod调度到预期节点上,但是缺少灵活性。亲和与反亲和规则相当于nodeSelector的扩展,增强了语法的多样化以及软/偏好规则。目前包含Node亲和、Pod亲和与Pod反亲和。
Node亲和:目前支持requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution两种类型的节点亲和,可以分别视为硬条件限制和软条件限制。在调度过程中,必须满足requiredDuringSchedulingIgnoredDuringExecution定义的规则,如果不匹配,则调度失败;preferredDuringSchedulingIgnoredDuringExecution中定义的规则为优先选择,即如果存在满足条件的节点,则优先使用,如果没有满足条件的节点,则忽略规则。
Pod亲和与Pod反亲和:Pod亲和与反亲和使得可以基于已经在节点上运行的Pod的标签来约束Pod可以调度到的节点,而不是基于节点上的标签。Pod亲和用于调度Pod可以和哪些Pod部署在同一拓扑结构下,Pod反亲和则相反。同样支持requiredDuringScheduling IgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution两种类型。需要注意的是,使用这种策略的所有节点上必须有适当的标签表示拓扑域,而且由于这种策略需要大量的计算,不建议在大规模集群中使用。
·使用taints(污点)和tolerations(容忍)规则。
节点亲和是将Pod调度到预期的节点上,而污点则是反过来将一个节点标记为污点,那么该节点默认就不会被调度Pod,除非Pod明确被标识为可以容忍污点节点。标识节点为污点的每个键值对有三种效果:NoSchedule、PreferNoSchedule和NoExecute。NoSchedule表示Pod不会被调度到标记为污点的节点,PreferNoSchedule相当于NoSchedule的软限制或偏好版本,NoExecute则意味着一旦污点被设置,该节点上所有正在运行的Pod只要没有容忍污点的设置都将被直接驱逐。
我们在了解了OpenShift中的调度策略后,接下来看看大部分(某些Pod会跳过调度程序,如Daemonsets、Static Pod)情况下Pod调度程序算法遵循的三个步骤。
第一步:过滤节点。
调度程序通过Predicate来过滤正在运行的节点的列表。此外,Pod可以定义与集群节点中的Label匹配的Node Selector,Label不匹配的节点不符合条件。
·Pod定义资源请求(例如CPU、内存和存储),可用资源不足的节点不符合条件。
·评估节点列表是否有污点,如果有,则Pod是否有容忍污点的设置可以接受污点。如果Pod无法接受污点节点,则该节点不符合条件。
我们查看默认设置在Master节点上的污点。
# oc describe nodes master-0 |grep -i taints Taints: node-role.kubernetes.io/master:NoSchedule
然后查看目前在Master节点上运行Pod设置的容忍污点。
# oc describe pods oauth-openshift-69bb54fb9f-4bf6w | grep -i toleration Tolerations: node-role.kubernetes.io/master:NoSchedule
我们看到,oauth-openshift Pod因为包含了容忍污点,所以可以在Master节点上运行,如图2-39所示。
图2-39 oauth-openshift Pod在Master节点上运行
过滤步骤的最终结果通常是有资格运行Pod的节点候选短列表。在某些情况下不会过滤掉任何节点,这意味着Pod可以在任何节点上运行。当然也可能所有节点都被过滤掉,这意味着没有合适的节点满足先决条件,调度将失败。
第二步:对节点候选短列表排优先级。
节点候选短列表是使用多个Priorities进行评估的,这些Priorities和权限计算得出的总和为加权得分,得分较高的节点更适合运行Pod。
亲和与反亲和是重要的判断标准。对Pod具有亲和性的节点得分较高,而具有反亲和性的节点得分较低。
第三步:选择最适合的节点。
根据这些得分对节点候选短列表进行排序,并选择得分最高的节点来运行Pod。如果多个节点具有相同的高分,则以循环方式选择一个。
通常默认调度策略和高级调度策略就能满足大部分场景的需求,但也不排除我们需要根据实际的基础设施拓扑自定义调度策略。接下来,我们将展示如何在OpenShift中修改Pod默认调度策略。
调度程序配置文件是一个JSON文件,必须命名为policy.cfg,用于指定调度程序将考虑的Predicates和Priorities。我们创建policy.cfg的JSON文件如下。
# cat policy.cfg { "kind" : "Policy", "apiVersion" : "v1", "predicates" : [ {"name" : "PodFitsHostPorts"}, {"name" : "PodFitsResources"}, {"name" : "NoDiskConflict"}, {"name" : "NoVolumeZoneConflict"}, {"name" : "MatchNodeSelector"}, {"name" : "MaxEBSVolumeCount"}, {"name" : "MaxAzureDiskVolumeCount"}, {"name" : "checkServiceAffinity"}, {"name" : "PodToleratesNodeNoExecuteTaints"}, {"name" : "MaxGCEPDVolumeCount"}, {"name" : "MatchInterPodAffinity"}, {"name" : "PodToleratesNodeTaints"}, {"name" : "HostName"} ], "priorities" : [ {"name" : "LeastRequestedPriority", "weight" : 1}, {"name" : "BalancedResourceAllocation", "weight" : 1}, {"name" : "ServiceSpreadingPriority", "weight" : 1}, {"name" : "EqualPriority", "weight" : 1} ] }
创建上述文件为Configmap。
# oc create configmap custom-scheduler-policy --from-file=policy.cfg -n openshift-config
编辑Scheduler Operator自定义资源(CR)以添加Configmap。
# oc patch scheduler cluster --type='merge' -p '{"spec":{"policy": {"name":"custom-scheduler-policy"}}}' --type=merge
对scheduler config资源进行更改后,请等待相关容器重新部署,这可能需要几分钟。在容器重新部署之前,新的调度程序不会生效。
在OpenShift中我们会大量使用CRD。为了方便读者理解,本小节对此展开介绍。
CRD的全称是Custom Resource Definition(自定义资源定义),CR的全称是Custom Resource(自定义资源)。CRD本质上是Kubernetes的一种资源。在Kubernetes中一切都可视为资源,Kubernetes 1.7之后增加了使用CRD进行二次开发来扩展Kubernetes API,通过CRD我们可以向Kubernetes API中增加新资源类型,而不需要修改Kubernetes源码。该功能大大提高了Kubernetes的扩展能力。当你创建一个新CRD时,Kubernetes API服务器将为你指定的每个版本创建一个新的RESTful资源路径。CRD根据其作用域,可以分为Cluster级别和Namespace级别。
CRD在OpenShift中被大量使用,以OpenShift 4.6为例,CRD的数量多达上百个,这些CRD都是Cluster级别的。
# oc get crd | wc -l 108
那么,站在OpenShift角度,我们该怎样使用CRD呢?下面举例说明。
安装OpenShift后内置的image registry是不会启动的(只有Operator),如图2-40所示。
图2-40 查看项目中Pod
如果我们想让OpenShift启动image registry Pod,就需要修改image registry Operator对应crd:configs.imageregistry.operator.openshift.io。
# oc get crd |grep -i configs.imageregistry.operator.openshift.io configs.imageregistry.operator.openshift.io 2020-10-09T00:48:20Z
查看CRD的RESTful资源路径(selfLink)。
# oc get configs.imageregistry.operator.openshift.io -o yaml |grep selfLink selfLink: /apis/imageregistry.operator.openshift.io/v1/configs/cluster
接下来,我们将CRD的managementState从Removed修改为Managed,同时也可以设置副本数,这里我们保持1不变,如图2-41所示。
# oc edit configs.imageregistry.operator.openshift.io cluster
修改后,我们发现image registry Pod被自动创建,如图2-42所示。
也就是说,我们通过修改image registry Operator的CRD,完成了image registry的状态设置和副本数修改。
在OpenShift中我们大量使用Cluster Operator(CO),每个Cluster Operator都有对应的CRD。例如我们查看ingress这个CO对应的CRD,如图2-43所示。
在OpenShift的运维中少不了修改Cluster Operator对应的CRD,这类CRD都是以operator.openshift.io为后缀。例如,我们想要在OpenShift中启用multus,就需要修改networks.operator.openshift.io这个CRD,增加如图2-44所示内容。
图2-41 修改CRD
图2-42 image registry Pod自动创建
图2-43 查看ingress CO对应的CRD
图2-44 修改CRD
截至目前,我们介绍了CRD的作用。那么CR的作用是什么?
CR是CRD中的一个字段。我们在上文修改networks.operator.openshift.io CRD增加的内容,其实就是增加了一个macvlan-network的CR,而且将这个CR作用在Tomcat的Namespace上(而不是Cluster级别)。