云计算与云原生 — OpenShift 部署实践

Posted 范桂飓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了云计算与云原生 — OpenShift 部署实践相关的知识,希望对你有一定的参考价值。

目录

文章目录

OpenShift 核心概念

容器(Container)

Container 的实现基于 Linux Kernel 的 chroot、namespace、cgroups 技术

  • chroot:每个容器具有独立的文件系统。
  • namespace:每个容器具有独立的操作系统资源视图(隔离)。
  • cgroups:每个容器具有独立的操作系统资源配额(限制)。

当使用 Docker 创建 Container 时,它会为每个 Container 创建 Namespace 和 Cgroups。

镜像(Image)

而 Image 的本质就是一个包含了 Application Container 运行所需要的文件集合,如:代码、运行时环境、系统工具、系统库和设置等。

容器镜像仓库(Container Image Registry)是一种集中的存储和分发容器镜像的服务。一个 Registry 中可包含多个仓库(Repository),每个仓库可以包含多个标签(Tag),每个标签对应一个 Image。通常情况下,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是软件哪个版本的镜像。

用户(User)

OpenShif 主要有以下 3 类 User:

  1. Regular User(常规用户):可通过 API 创建,以 User 对象表示。
  2. System User(系统用户):大部分 System User 在 Cluter 部署完成后自动被创建,主要用于基础架构和 API 服务之间的安全通信。比如:一个集群管理员、每个节点上的一个系统用户等。
  3. Service Account(服务账户):这是 Project 内的特殊系统用户。某些 Service Account 在 Project 创建完成后自动创建,项目管理员也可以创建 Service Account。

项目(Project)

Kubernetes Namespace 为 Cluster 中的资源划分了范围。OpenShift Project 基于 Kubernetes Namespace 概念新增了一些功能,用于对相关对象进行分组和隔离。每个 OpenShift Project 对象对应一个 Kubernetes Namespace 对象。集群管理员可授予用户对某些项目的访问权限、允许用户创建项目,以及授予用户在项目中的权限。

容器沙箱(Pod)

OpenShift 引入了 Kubernetes 中的 Pod 概念。Pod 是 OpenShift 应用的最小可执行和可调度单元,即应用的一个实例。Pod 定义中包含应用的一个或多个容器、存储资源、唯一的网络 IP,以及其他定义容器如何运行的选项。OpenShift 容器云平台使用 Docker 来运行 Pod 中的容器。每个 Pod 都被分配了独立的 IP 地址,Pod 中的所有容器共享本地存储和网络,容器使用 localhost 互相通信。Pod 可拥有共享的存储卷,Pod 中的所有容器都能访问这些卷。

  • Pod 是有生命周期的,从被定义开始,到被分配到某个节点上运行,再到被释放。
  • Pod 是不可以修改的,也就是说一个运行中的 Pod 的定义无法修改。
  • Pod 是临时性的,用完即丢弃,当 Pod 中的进程结束、所在节点故障,或者资源短缺时,Pod 即会被终止。

静态 Pod(Static Pod)是一类特殊的 Pod。这种 Pod 由 Kubelet 创建和管理,仅运行在 kubelet 运行的 Node 上,不能通过 API Server 进行管理,无法与 ReplicationController 等关联。OpenShift 控制平面组件(e.g. etcd、API Server、Scheduler、Controller)会以 Static Pod 的形式运行在 Master 上,由其上的 kubelet 创建和管理。

另一类特殊的 Pod 为守护 Pod(Daemon Pod),一个 Node 上只有一个 Daemon Pod 的副本。OpenShift 的 openshift-sdn和 openvswitch 组件以 Daemon Pod 的形式运行在所有 Node 上。

正是由于 Pod 具有临时性、不可修改、无法自愈等特性,用户很少直接创建独立的 Pod,而会通过 ReplicationController 这样的控制器来对它进行控制。如果需要,可通过 oc create –f <file> 命令来创建 Pod 实例。

一个 OpenShift Pod 可能会包括以下几种容器:

  1. Infra 容器(基础设施容器):是一种特殊的容器,每个 Pod 都会运行一个 Infra 容器,它负责创建和初始化 Pod 的各种 Namespace,后续创建到 Pod 中的所有 Containers 会被加入到这些 Namespace 中。这种容器的名字以 k8s_POD_<pod名称>_<project名称> 为前缀,该容器使用的镜像通过宿主机上的 kubelet 程序的启动参数 --pod-infra-container-image 来指定。

  2. Init 容器(初始容器):也是一种特殊的容器,一个 Pod 可以没有,也可以有一个或多个 Init 容器。Init 容器在 Pod 中的主容器(应用容器)运行前运行。如果有一个 Init 容器运行失败,那么 Pod 中的主容器就不会启动。因此,可利用 Init 容器来检查是否满足主容器启动所需的前提条件。比如一个应用 Pod 中的主容器要求 mysql 服务就绪后才能运行,那么可以在 Init 容器中检查 MySQL 服务是否就绪。例如:定义了两个 Init 容器,第一个 Init 容器会检查 MyService 服务是否就绪,第二个 Init 容器会检查 MyDB 服务是否就绪。只有在这两个服务都就绪了之后,Pod 的主容器 myapp-container 才会运行。

  3. 主容器(应用容器):通常一个 Pod 中运行一个应用程序的主容器。在某些场景下,一个 Pod 中会运行多个具有强耦合关系的主容器。比如:在一个 Pod 中以 Sidecar(边车)形式运行一个日志采集容器,用于采集该 Pod 主容器中的应用写到日志文件中的日志,并将它们输出到标准输出。

因此,Pod 是一个或多个容器组成的集合,这些容器共享同一个运行环境。OpenShift 默认利用 Docker 作为容器运行时来创建和管理容器,Pod 内的所有容器共享命名空间。Docker 首先为 Pod 创建 Infra 容器,为该容器创建命名空间和控制组,然后依次创建和运行 Init 容器,等到所有 Init 容器都运行后,再创建和运行主容器。这些容器都共享 Infra 容器的命名空间。

下图是 Pod 中的容器示意图。实际上,一个 Pod 中的所有容器中的进程都仿佛运行在同一台 “机器” 上。Pod 中的所有容器共享网络空间,因此可以通过 localhost 互相直接通信;它们还使用同样的主机名(hostname),以及共享 Pod 的存储卷。

Pod 具有其生命周期,其声明中的 “phase” 字段表示其当前所处的运行阶段。Pod 的主要运行阶段包括:

  1. Pending:OpenShift API Server 已经创建好了 Pod 对象,但还未被调度到某个节点上,或者还在下载 Pod 所需镜像。
  2. Running:Pod 被调度到了 OpenShift 集群的某个节点上,Pod 中所有的主容器都已经被创建出来,而且至少有一个在运行中。
  3. Failed:Pod 中所有容器都已被终止,而且至少有一个容器终止失败。
  4. Succeeded:Pod 中所有容器都已被终止,而且都终止成功了。

Pod 的状态(status)和 Pod 的阶段(phase)不是一一对应的:

  • 在 Pending 阶段,Pod 的状态通常为 “ContainerCreating”。
  • 在 Running 阶段,Pod 的状态可能为 “Running”,表示它在正常运行;也可能为 “Error”,比如某个容器失败了。
  • 在 Succeeded 阶段,Pod 的状态通常为 “Completed”。

通过 oc get pod 命令可查询当前项目中所有 Pod 的状态。下图显示了一个具有两个 Init 容器和两个主容器的 Pod 启动过程中,各个容器的启动顺序和对应 Pod 的状态,以及 Pod 终止时和终止后的状态。

部署(Deployment)

为了更好地管理应用开发和部署生命周期,OpenShift 在 Kubernetes 的 Replication Controller 的基础上增加了 DeploymentConfig 的概念。DeploymentConfig 对象定义了部署的元数据,包括 ReplicationController 的定义、自动进行新部署的触发器、在部署之间进行状态转换的方法(Rolling Strategy),以及生命周期钩子(Life Cycle Hook)。

通过 oc get dc 命令可查看当前 Project 中的 DeploymentConfig 对象列表。通过 oc rollout latest dc/XXX 命令可手动触发该应用的一次部署过程。部署成功后,会创建一个新的 ReplicationController 对象。每次部署时都会创建一个 ReplicationController 对象,并由它创建所需 Pod。Replication Controller 确保在任何时间上运行 Pod 的 “replicas” 数为定义中的数量。

  • 如果 Pod 超过指定的数量,ReplicationController 会终止多余的 Pod;
  • 如果 Pod 少于指定数量,它将启动更多Pod。

与手动创建的 Pod 不同,如果有 Pod 失败、被删除或被终止,ReplicationController 会自动维护并替代这些 Pod。通过 oc get rc 命令可查看当前项目中的 ReplicationController 对象列表。

还可以在 DeploymentConfig 配置中定义部署触发器,在指定条件发生时即进行一次新的部署。下面是某 DeploymentConfig 定义的 Trigger 部分,设置了 ImageChange 触发器,使得 mywebapp 镜像流的 latest 标签被监控,一旦该标签值发生改变(意味着有新的镜像被推送进来),即会触发一次新的部署过程。

triggers:
  - type: "ImageChange"
    imageChangeParams:
      automatic: true
      from:
        kind: "ImageStreamTag"
        name: "mywebapp:latest"
        namespace: "myproject

服务(Service)

由于 Pod 是临时性的,因此它的 IP:Port 也是动态变化的。这将导致以下问题:如果一组后端 Pod 作为服务提供方,供一组前端 Pod 调用,那么服务调用方怎么使用不断变化的后端 Pod 的 IP 呢?

为了解决此问题,OpenShift 引入了 Kubernetes 中的 Service 概念。一个 Service 可被看作 OpenShift 的一个内部负载均衡器。它定位一组 Pod,并将网络流量导入其中。可以通过 oc get svc 命令来获取当前项目中的 Service 实例。

Service 的后端 Pods 通过 Selector(筛选器)筛选出来,例如:后端 Pod 是包含 “deploymentconfig=mywebapp” 的所有 Pods,其 IP: Port 分别为 10.129.0.108:8080 和 10.130.0.142:8080,而该 Service 则被分配了 IP 地址 172.30.151.210,端口号为 8080。有了此 Service 之后,客户端就就可以使用该 Service 的 IP:Port 来访问后端 Pods 提供的应用业务了。

那 Service 是如何将网络流量导入后端 Pods 的呢?OpenShift 支持两种服务路由实现:

  1. 默认是基于 iptables 的,使用 iptables 规则将发送到 Service 的 Cluster IP 的请求转发到服务的后端 Pods;
  2. 较早期的实现是基于用户空间进程,它将收到的请求转发给一个可用后端 Pods。

相比之下,基于 iptables 的实现效率更高,但要求每个 Pods 都能接收请求;用户空间进程(userspace)实现方式的速度较慢一些,但会尝试后端 Pods 直到找到一个可用 Pod 为止。因此,如果有完善的 Pod 可用性检查机制(Readiness Check),那基于 iptables 的方案是最佳选择;否则,基于用户空间代理进程的方案会比较安全。

Service 的后端服务器被称为端点,以 Endpoints 对象表示,其名称和服务相同。当服务的后端是 Pod 时,通常在 Service 的定义中指定 Label 选择器来指定将哪些 Pod 作为 Service 的后端,然后 OpenShift 会自动创建一个 Endpoints 指向这些 Pods。通过 oc get ep 命令查询当前项目中的 Endpoints 对象。

路由(Router)

Service 提供了一个通往 Backend Pods 的稳定入口,但是 Service 的 IP 地址只是集群内部节点及容器可见。对于外部的应用或者用户来说,这个地址是不可达的。

为了从集群外部能访问到部署在 OpenShift 内部的应用,OpenShift 提供了 Router(路由器)组件。Router 是一个重要组件,是从集群外部访问集群内的容器应用的入口。集群外部请求都会到达 Router,再由它分发到具体应用容器中。Router 组件由集群管理员负责部署和配置,以插件形式实现,OpenShift 支持多种 Router 插件,默认采用了 HAProxy 实现。

Router 组件就绪之后,用户可创建 Route(路由规则)。每个 Route 对象会将关联的 Service 以域名的形式暴露到集群外部,使得从集群外部能通过域名访问到该 Service。每个 Route 对象包含 Name、DomainName、Service Selector 和可选的安全参数等配置。Route 被创建后会被 Router 加载,Router 通过 Route 的 Service Selector 定位到该 Route 的所有 Backend Services,并将其所有后端更新到自身的配置之中。同时,Router 还能动态地跟踪该服务后端的变化,并直接更新自己的配置。

当用户访问 Domain 时,首先会被 DNS 服务器解析并指向 Router 所在 Node 的 IP 地址。Router 获取该请求后,根据 Route 规则,将请求转发给该 Backend Service,最终再转发到 Service 所关联的 Pods 容器实例。通过 oc get route 命令可获取当前项目中的所有路由规则。

注意,OpenShfit 中,Route 和 Service 都提供了负载均衡功能,但使用场景和作用不同。Router(对外)负责将集群外的访问请求转发给 Backend Services,而 Service(对内)则负责将集群内的访问请求转发给 Backend Pods。

Pod、Service、Deployment 和 Router 的关系如上图所示:

  • DeploymenetConfig:是部署的静态定义,每次部署操作都会产生一个 Replication Controller 对象。
  • ReplicationController:对象负责维护在 DeploymenetConfig 中定义的 Pod 副本数。Pod 是 OpenShift 中最小的可调度单元,在其中运行应用容器。
  • Service:是集群内部负载均衡器,本身带有 IP 地址和端口,以 Pod 作为其后端,将对自身的请求转发至这些后端 Pod。
  • Router:中包含多个 Route,每个 Route 对应一个 Service,将其以域名形式暴露到集群外。

持久化存储(Persistent Storage)

容器默认是非持久化的,所有的修改在容器销毁时都会丢失。但现实是传统的应用大多都是有状态的,因此要求某些容器内的数据必须持久化,容器云平台必须为容器提供持久化存储(persistent storage)。

Docker 本身提供了持久化卷挂载的能力。OpenShift 除了支持 Docker 持久化的挂载方式外,还提供了一种持久化供给模型,即 Persistent Volume(持久化卷,PV)及 Persistent Volume Claim(持久化卷请求,PVC)模型。在 PV 和 PVC 模型中,集群管理员会创建大量大小不同和不同特性的 PV。

用户在部署应用时,先是声明对持久化的需求,创建 PVC,用户在 PVC 中定义所需存储的大小、访问方式(只读或可读可写、独占或是共享)。OpenShift Cluster 会自动寻找符合要求的 PV(数据的存储目录)和 PVC(数据需求的请求)自动对接。通过 PV 和 PVC 模型,OpenShift 为用户提供了一种灵活的方式来消费存储资源。

OpenShift 对持久化后端的支持比较广泛,除了 NFS、iSCSI 外,还支持 Ceph、GluterFS 等分布式存储,以及 Amazon Web Service 和 Google Compute Engine 的云硬盘。

模板(Template)

一个 Template 对象定义了一组对象,这些对象可被参数化,经 OpenShift 处理后会生成一组对象。这些对象可以是该用户在项目中有权创建的所有类型的对象,比如:Service(服务)、BuildConfiguraiton(构建配置)、DeploymentConfig(部署配置)等。

用户可以通过 JSON/YAML 文件来定义一个 Template,再通过 oc create –f <filename> 命令在 OpenShif 创建该 Template 对象。通过 oc get template 命令可查看当前 Project 中的 Template 对象列表。

默认情况下,OpenShfit 会在 Project 中创建一些 Template 供用户使用。通过 oc get templates -n openshift 命令可查看这些模板。

构建(Build)和镜像流(ImageStream)

Build 表示根据输入参数构建出目标对象的过程。在 OpenShift 上,该过程用于将源代码转化为可运行的容器镜像。OpenShift 支持 4 种构建方式:

  1. Docker 构建
  2. S2I 构建:是 OpenShift 的原创,它根据指定的构建镜像(Builder Image)和源代码(Source Code),构建生成可部署 Docker 镜像,并推送到 OpenShift 内部集成镜像库中。
  3. Pipeline 构建:允许开发者定义 Jenkins Pipeline。在项目首次使用该构建方式时,OpenShift 会启动一个 Jenkins 服务,然后再将该 Pipeline 交由它来执行,并负责启动、监控和管理该构建。BuildConfig 对象中可以直接包含 Jenkins Pipeline 的内容,或者包含其 Git 仓库地址。
  4. 自定义构建

Build 建的配置由一个 BuildConfig 对象表示,其定义了构建策略和各种参数,以及触发一次新构建的触发器(Trigger)。通过 oc get bc 命令可获取当前项目中的构建配置列表。

使用 Docker 或 S2I 策略的构建配置的一次成功构建会创建一个容器镜像。镜像会被推送到 BuildConfig 定义的 output 部分所指定的容器镜像仓库中:

  • 如果目标仓库的类型为 ImageStreamTag,那么镜像会被推送到 OpenShift 的内置镜像仓库中;
  • 如果类型为 DockerImage,那么镜像会被推送到指定的镜像仓库或 Docker Hub 中。

一个镜像流标签对象(ImageStreamTag)指向一个镜像,可以是本地镜像或者远程镜像。例如:名为 “python” 的镜像流包含两个 Label,标签 34 指向 Python v3.4 镜像,标签 35 指向 Python v3.5 镜像。通 过oc get istag 命令可查询当前 Project 中的镜像流标签。

使用 ImageStream 的目的是方便将一组相关联的镜像进行整合管理和使用,比如:可在新镜像被创建后自动执行指定构建或部署操作。构建和部署可以监视 ImageStream,在新镜像被添加后会收到通知,并分别通过执行构建或部署来作出反应。例如:某 DeploymentConfig 使用一个 ImageStream,当该镜像版本被更新时,应用会自动进行重新部署。

默认情况下,部署完成后,OpenShift 在 Project 中创建一些镜像流供用户直接使用。通过 oc get is -n openshift 命令可查看这些镜像流。每次向 OpenShift 内置镜像仓库中推送镜像时,会自动创建一个指向该镜像的 ImageSteam 对象。

OpenShift Build 和 Deployment 的关系如上图:

  • BuildConfig 是 Build 的静态定义,每次运行后会启动一次 Build。
  • Build 完成后产生的镜像会被推送到镜像仓库中,并产生 ImageStream 和 ImageStream-Tag。
  • DeploymentConfig 是 Deployment 的静态定义,它关联某个 ImageStreamTag。每当 Image-StreamTag 所指向的镜像发生变化,都会自动触发一次部署动作,生成一个 ReplicationController 对象。

OpenShift 核心业务流程

以应用为中心

从图中可以看出,相对于 Kubernetes,OpenShift 新增的全部内容几乎都是围绕着 “以应用为中心” 的主题来展开的。

  • Souce to Image(S2I,源代码到镜像):OpenShift 新增的一种构建方式,直接从项目源代码和基础镜像自动构建出应用镜像。
  • 内置镜像仓库:用于保存 S2I 生成的镜像。
  • 构建配置(BuildConfig):构建的静态定义,定义构建的源代码来源、基础镜像、生产镜像等。每次执行即开始一次构建过程。
  • 镜像流(ImageStream):镜像流中包括一个或多个标签,每个标签指向一个镜像。镜像流可用于自动执行某些操作,比如将设定 DeploymentConfig 的触发器为某镜像流标签,当该标签所指镜像发生变化时,即可自动触发一次部署过程。
  • 部署配置(DeploymentConfig):部署的静态定义,除了定义待部署的 Pod 外,还定义了自动触发部署的触发器、更新部署的策略等。
  • 路由规则(Route):将部署好的应用服务通过域名发布到集群外供用户访问。

应用构建流程

基于上述新增功能,OpenShift 支持如下图所示的应用从构建到发布的全自动化的过程:

在 OpenShift 平台上创建应用的简要步骤:

  1. 创建应用:OpenShift 用户通过 Web 控制台或命令行 oc new-app 来创建应用,根据用户提供的源代码仓库地址及 Builder 镜像,平台将生成构建配置(Build Config)、部署配置(Deployment Config)、镜像流(Image Stream)、服务(Service)和路由器(Router)等对象。

  2. 触发构建:平台实例化 BuildConfig 的一次构建,生成一个 Build 对象。Build 对象生成后,平台将执行具体的 S2I 构建操作,包括下载源代码、实例化 Builder 镜像、执行编译和构建脚本等。

  3. 生成镜像:构建成功后将生成一个可部署的应用容器镜像,平台将把此镜像推送到内部的镜像仓库中。

  4. 更新镜像流(ImageStream):镜像推送至内部的镜像仓库后,平台将更新应用的 ImageStream 中的镜像流标签,使之指向最新的镜像。

应用部署流程

  1. 触发部署:当 ImageStream 的镜像信息更新后,将触发 DeploymentConfig 对象进行一次部署操作。

  2. 实例化镜像部署:Deployment Config 对象记录了对象部署的定义,平台依据此配置实例化一次部署,生成一个 Deploy 对象来控制和跟踪所需部署的 Pod 的状态。

  3. 部署容器:平台部署将实例化一个Replication Controller,用以调度应用容器的部署。部署操作生成的 ReplicationController 对象会负责调度应用容器的部署,将 Pod 及应用容器部署到集群的计算节点中。

发布/更新应用流程

  1. 发布应用:运行 oc expose svc/mywebapp4 命令,生成用户通过浏览器可访问的应用域名。之后用户即可通过该域名访问应用。

  2. 请求处理并返回:客户端请求到 Router 组件后,Router 根据 Route 定义的规则,找到请求所含域名相关联的 Service 的容器,并将请求转发给应用容器。容器实例除了请求后返回数据,还会通过 Router 将数据返回给调用的客户端。

  3. 应用更新:当更新应用时,平台将重复上述步骤。平台将用下载更新后的代码构建应用,生成新的镜像,并将镜像部署至集群中。OpenShit 支持滚动更新,以保证在进行新旧实例交替时应用服务不会间断。

Pod 启动流程

  1. 客户端使用 HTTP/HTTPS 通过 API Server 发送(POST)YAML 格式的Pod Spec。

  2. API Server 在 etcd 中创建 Pod Object 并将 Spec 保存到其中。然后,API Server 向客户端返回创建结果。

  3. Scheduler 监控到这个 Pod Object 的创建事件,它根据调度算法决定把这个 Pod 绑定到 Node1,然后调用 API Server 在 etcd 中写入该 Pod Object 与 Node1 的绑定关系。

  4. Node1 上的 kubelet 监控到有一个 Pod 被分配到它所在的节点上,于是调用 Docker 创建并运行一个 Pod 实例,然后调用 API 更新 etcd 中 Pod Object 的状态。

部署 OKD 4.5

文档资料

  • 官方文档:https://docs.okd.io/

  • Github:https://github.com/openshift/okd

  • OKD 镜像地址:

    • https://quay.io/repository/openshift/okd
    • https://quay.io/repository/openshift/okd-content
  • openshift-client、openshift-install 下载地址:https://github.com/openshift/okd/releases

  • 官方裸金属部署文档:https://docs.okd.io/latest/installing/installing_bare_metal/installing-bare-metal.html

裸机拓扑

服务器环境

机器系统地址/hostname服务
Bastion NodeCentOS7.7192.168.120.101 / bastion.okd.example.comCoreDNS、HAProxy、nginx、Container Image Registry
Bootstrap NodeFedora coreos192.168.120.102 / bootstrap.okd.example.combootstrap
Master NodeFedora coreos192.168.120.103 / master.okd.example.comopenshift-master
Worker NodeFedora coreos192.168.120.104 / worker.okd.example.comopenshift-worker

前期准备

  • 时间同步:各节点的 Bios 硬件时间(hwclock)必须一致。
  • U 盘启动:各节点的 BIOS 配置成可以通过 U 盘安装操作系统。
  • 节点间 SSH 免密登录
# 创建 SSH 密钥
$ ssh-keygen -t rsa -b 4096 -N '' -f ~/.ssh/id_rsa

# 启动 ssh-agent 进程为后台任务
$ eval "$(ssh-agent -s)"

# 将 SSH 私钥添加到 ssh-agent
$ ssh-add ~/.ssh/id_rsa

NOTE:OpenShift 部署支持通过 “裸金属接管” 的方式进行部署,但由于需要使用到 TFTP/FTP + DHCP + IPMI 等方式,考虑到复杂度和对现有环境的侵入性,所以本次部署使用 U 盘启动的方式完成。

部署 Bastion Node(堡垒机节点)

Bastion Node 作为我们的 “部署堡垒机”,采用常见的 CentOS 操作系统即可,我们会在 Bastion Node 上安装部署 OpenShift 集群时所需要的 oc CLI 等软件工具支撑,建议连接办公网,作为 Remote SSH 的入口。

基础配置

# 主机名配置
$ hostnamectl set-hostname bastion.okd.example.com

# Selinux 配置
$ sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
$ setenforce 0

# 关闭防火墙
$ systemctl disable firewalld
$ systemctl stop firewalld

安装 OpenShift CLI

OpenShift CLI,简称 oc(openshift-client),下载地址:https://github.com/openshift/okd/releases,本次使用 OKD 4.5 版本,最新 GA tag 为 4.5.0-0.okd-2020-07-14-153706-ga。

$ wget https://github.com/openshift/okd/releases/download/4.5.0-0.okd-2020-07-14-153706-ga/openshift-client-linux-4.5.0-0.okd-2020-07-14-153706-ga.tar.gz

$ tar -zxvf openshift-client-linux-4.5.0-0.okd-2020-07-14-153706-ga.tar.gz

$ cp oc /usr/local/bin/

# 检查版本
$ oc version 

安装 openshift-install

$ wget https://github.com/openshift/okd/releases/download/4.5.0-0.okd-2020-07-14-153706-ga/openshift-install-linux-4.5.0-0.okd-2020-07-14-153706-ga.tar.gz

$ tar -zxvf openshift-install-linux-4.5.0-0.okd-2020-07-14-153706-ga.tar.gz

$ cp openshift-install /usr/local/bin/

# 检查版本
$ openshift-install version

安装 ETCD

$ yum install -y etcd

$ systemctl enable etcd --now

安装 CoreDNS

OpenShift 集群部署需要使用到 Domain Name 来完成 Node Discovery。


$ wget https://github.com/coredns/coredns/releases/download/v1.6.9/coredns_1.6.9_linux_amd64.tgz

$ tar zxvf coredns_1.6.9_linux_amd64.tgz

$ mv coredns /usr/local/bin

$ useradd coredns -s /sbin/nologin
  • 创建 systemd 配置:
$ vi /etc/systemd/system/coredns.service

[Unit]
Description=CoreDNS DNS server
Documentation=https://coredns.io
After=network.target
 
[Service]
PermissionsStartOnly=true
LimitNOFILE=1048576
LimitNPROC=512
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
User=coredns
WorkingDirectory=~
ExecStart=/usr/local/bin/coredns -conf=/etc/coredns/Corefile
ExecReload=/bin/kill -SIGUSR1 
Restart=on-failure
 
[Install]
WantedBy=multi-user.target
  • 编辑 CoreDNS 配置:
$ vi /etc/coredns/Corefile

.:53                                               # 监听 53 端口
    template IN A apps.okd.example.com 
        match .*apps\\.okd\\.example\\.com               # 匹配 DNS Query Request Domain Name 的正则表达式
        answer " .Name  60 IN A 172.18.22.243"  # 配置 Domain Name 应答的 IP 地址
        fallthrough
    
    etcd                               # 配置启用 etcd 插件,后面可以指定域名,例如:etcd test.com
        path /skydns                    # 默认路径为 /skydns,后续所有的 DNS 记录都存储在该路径下
        endpoint http://localhost:2379  # etcd 访问地址,使用空格分隔多个 endpoint
        fallthrough
    
    prometheus                          # 监控插件
    cache 160
    loadbalance                         # 负载均衡,开启 DNS 轮训查询测试
    forward . 114.114.114.114
    log                                 # 打印日志



$ systemctl enable coredns --now

# 验证
$ dig +short apps.okd.example.com @127.0.0.1
  • 添加 OpenShift Cluster Node 的 DNS 解析记录,每条记录都是部署过程中多需要的:
$ cat /etc/resolv.conf
# Generated by NetworkManager
search okd.example.com
nameserver 192.168.120.101

$ alias etcdctlv3='ETCDCTL_API=3 etcdctl'

# Bastion API Server HA
$ etcdctlv3 put /skydns/com/example/okd/api '"host":"192.168.120.101", "ttl":60'
$ etcdctlv3 put /skydns/com/example/okd/api-int '"host":"192.168.120.101", "ttl":60'

# Bastion Container Images Registry
$ etcdctlv3 put /skydns/com/example/okd/registry '"host":"192.168.120.101", "ttl":60'

# Master ETCD
$ etcdctlv3 put /skydns/com/example/okd/etcd-0 '"host":"192.168.120.103", "ttl":60'

$ etcdctlv3 put /skydns/com/example/okd/_tcp/_etcd-server-ssl/x1 '"host":"etcd-0.okd.example.com", "ttl":60, "priority":0, "weight":10, "port":2380'

$ etcdctlv3 put /skydns/com/example/okd/bastion '"host":"192.168.120.101", "ttl":60'
$ etcdctlv3 put /skydns/com/example/okd/bootstrap '"host":"192.168.120.102", "ttl":60'
$ etcdctlv3 put /skydns/com/example/okd/master '"host":"192.168.120.103", "ttl":60'
$ etcdctlv3 put /skydns/com/example/okd/worker '"host":"192.168.120.104", "ttl":60'

安装 HAProxy

$ yum install haproxy -y

$ vi /etc/haproxy/haproxy.cfg
...
listen stats
    bind :9000
    mode http
    stats enable
    stats uri /
    monitor-uri /healthz

frontend openshift-api-server                    # OpenShift API Server (HA)
    bind *:6443
    default_backend openshift-api-server
    mode tcp
    option tcplog

backend openshift-api-server
    balance source
    mode tcp
    server bootstrap 192.168.120.102:6443 check  # API Server in Bootstrap Node
    server master 192.168.120.103:6443 check     # API Server in Master Node

frontend machine-config-server                   # OpenShift Machine Config Server (HA)
    bind *:22623
    default_backend machine-config-server
    mode tcp
    option tcplog

backend machine-config-server
    balance source
    mode tcp
    server bootstrap 192.168.120.102:22623 check # Machine Config Server in Bootstrap Node
    server master 192.168.120.103:22623 check    # Machine Config Server in Master Node
    
$ systemctl enable haproxy && systemctl restart haproxy

安装 Registry

  • 自签发证书,确保客户端安全接入镜像仓库:
# 自建 CA 中心
$ mkdir -p /opt/registry/auth,certs,data
$ cd /opt/registry/certs

# 自签发证书,域名为 registry.okd.example.com
$ openssl req -subj '/CN=registry.okd.example.com/O=My Company Name LTD./C=US' -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout domain.key -out domain.crt

# 将自签名的证书复制到默认信任证书路径
$ cp /opt/registry/certs/domain.crt /etc/pki/ca-trust/source/anchors/
$ update-ca-trust extract
  • 生成镜像仓库密钥:
# 为镜像仓库生成密钥
$ echo -n 'admin:admin' | base64 -w0    # YWRtaW46YWRtaW4=

$ vi pull-secret.json

	"auths": 
		"registry.okd.example.com:5000":   # 本地仓库访问地址
			"auth": "YWRtaW46YWRtaW4=",     # 密钥信息
			"email": ""
		
	


# 检查 Registry 是否正常工作
$ curl -u admin:admin -k https://registry.okd.example.com:5000/v2/_catalog

NOTE:OCP 需要从 Redhat 官网下载密钥,地址:https://cloud.redhat.com/openshift/install/pull-secret;而 OKD 则可以自己生成。

  • 下载官方镜像:
$ yum -y install podman httpd httpd-tools vim

# 设置变量
export OKD_RELEASE="4.5.0-0.okd-2020-07-14-153706-ga"
export LOCAL_REGISTRY='registry.okd.example.com:5000'
export LOCAL_REPOSITORY='openshift/okd'
export PRODUCT_REPO='openshift'
export LOCAL_SECRET_JSON='/root/pull-secret.json'
export RELEASE_NAME="okd"

# 拉取镜像
$ oc adm -a $LOCAL_SECRET_JSON release mirror \\
     --from=quay.io/$PRODUCT_REPO/$RELEASE_NAME:$OKD_RELEASE \\
     --to=$LOCAL_REGISTRY/$LOCAL_REPOSITORY \\
     --to-release-image=$LOCAL_REGISTRY/$LOCAL_REPOSITORY:$OKD_RELEASE

# 加载证书、密钥,启动 regsitry 服务。
$ podman run --name example-registry -p 5000:5000 \\
     -v /opt/registry/data:/var/lib/registry:z \\
     -v /opt/registry/auth:/auth:z \\
     -v /opt/registry/certs:/certs:z \\
     -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \\
     -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \\
     -d docker.io/library/registry:2

安装 Nginx

OpenShift 集群部署采用类似 “boot from Network” 的方式,需要从 NFS(网络文件服务)服务器上下载 CoreOS Image 和 Ignition 文件。

$ yum install -y nginx

# 配置服务端口为 8080
$ vi /etc/nginx/nginx.conf

$ systemctl enable nginx --now

准备 OpenShift Nodes 部署所需的配置文件

OpenShift 的部署期间需要用到多个文件:

  • 安装配置文件:install-config.yaml
  • Kubernetes 部署清单:Manifest
  • Ignition 配置文件(包含了 Machine Types):该文件描述了如何创建 OpenShift Cluster。

install-config.yaml 将被转换为 Manifest 文件,然后再将 Manifest 文件包装到 Ignition 配置文件中。最终,安装程序使用这些 Ignition 配置文件来创建 Openshift 集群。运行安装程序时,所有原始安装配置文件都会修改,因此在安装之前应该先备份文件。

NOTE:安装程序生成的 Ignition 配置文件包含 24 小时后过期的证书,所以必须在证书过期之前完成集群安装。

$ mkdir /okdinstall

# 根据需要修改 OKD 部署配置
$ vi /okdinstall/install-config.yaml

apiVersion: v1
baseDomain: example.com         # 配置基础域名,OpenShift 内部所有的 DNS 记录必须是此基础域名的子域,并包含集群名称。
compute:
- hyperthreading: Enabled 
  name: worker
  replicas: 0                   # 配置 Worker Node 数量,因为我们要手动创建 Worker Node,所以这里设置为 0。
controlPlane:
  hyperthreading: Enabled
  name: master
  replicas: 1                   # 配置 Master Node 数量,我们部署单 Master,所以写 1,注意:Master Node 数量必须和 etcd 节点数量一致。
metadata:
  name: okd                     # 集群名称。
networking:
  clusterNetwork:
  - cidr: 10.128.0.0/14         # 配置 Pod IP Pool。注意:Pod IP Pool 不能与物理网络冲突。
    hostPrefix: 23              # 分配给每个节点的子网前缀长度。
  networkType: OVNKubernetes    # 配置集群网络类型
  serviceNetwork: 
  - 172.30.0.0/16               # 配置 Service IP Pool。
platform:
  none:                       # 平台类型,因为我们使用裸金属安装类别,所有不填。
fips: false

# 配置 Bastion SSH pub key
sshKey: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD1zXmIde90FOxXQIgNZgECZux0pwwMQ3CpRnvYozTqIP6jI//VNZJq0rKyOVfq8hvzYtJDGqJvUJwwXPS3jaI1X0eajRYCIEJ6fu71nLGhAd8igPp4AsP9TFzyIpReQXuGL990PJl4uFtGhCcr3PMLbLNC5qTt+rDKdYWexD74IpOZe3z14hRgHitcBsPmoKeMxTJlr+dYDW5n3tyBw/4cOpcdTSQVO4SWlmDzjrmCDGh13J7C60BB2PrLnHkrYjud66Pwcr6HcSTjq8Y/amx5aI+hp7wq2iWInGYsPW/6REIOm83pos5s+x9SOkM17TW/qmbqKivngyW5qNzXgzJnfiEg4YEGMoTcPfvYzaonRYhe01nf887eXZi5bemJoGyuTE92Ex/hokLxaZNVWIMxBLEVh9BJiDGALqvuPDmoDJIkIpqX0E2gL1Nz0iYZogRkdifnZpfVUrww9PS0xrUd0AwiFDn6Ng09qx8K8lJ/o2RgQJvVCqOeeXXrRWtdv6zS9IirmiqmQT5w8ywnQ2yi6wXrWG2BxMSJgV6pr2mwsg5GTEPMoM4PlUBExM8ZmlbvbDzqauZ2bDoFZGGFs4LqImf0zdS7nRx81q3i96Qqx8UCuArh/81YefLpLtYcl95e+7M9E8/mXTf0t9yeZYkDw0Iskn9TXMQ1JCW77Xvhvw== root@bastion.okd.example.com'

# 配置 Image Registry 地址
imageContentSources:
- mirrors:
  - registry.okd.example.com:5000/openshift/okd
  source: quay.io/openshift/okd
- mirrors:
  - registry.okd.example.com:5000/openshift/okd
  source: quay.io/openshift/okd-content

# 配置 Image Registry 的密钥信息
pullSecret: '"auths":"registry.okd.example.com:5000": "auth": "YWRtaW46YWRtaW4=","email": ""'

# 配置 Image Registry 的信任证书。
# cat /opt/registry/certs/domain.crt
# 注意:前面要保持两个空格作为缩进。
additionalTrustBundle: |
  -----BEGIN CERTIFICATE-----
  MIIDbTCCAlWgAwIBAgIJAP6IHiEew+eNMA0GCSqGSIb3DQEBCwUAME0xHzAdBgNV
  BAMMFnJlZ2lzdHJ5Lm9rZC5pbnRlbC5jb20xHTAbBgNVBAoMFE15IENvbXBhbnkg
  TmFtZSBMVEQuMQswCQYDVQQGEwJVUzAeFw0yMDEyMjIxMTUwMzVaFw0yMTEyMjIx
  MTUwMzVaME0xHzAdBgNVBAMMFnJlZ2lzdHJ5Lm9rZC5pbnRlbC5jb20xHTAbBgNV
  BAoMFE15IENvbXBhbnkgTmFtZSBMVEQuMQswCQYDVQQGEwJVUzCCASIwDQYJKoZI
  hvcNAQEBBQADggEPADCCAQoCggEBAN4jbffKnGVz8U6gcWIA/ug5kQH1lVtaNmcS
  sHVeqLwjfVlTSkK+yyhY4AyN6YWWhznaM9zqb9ffQXyP1zuwj2UgYfiZCFD4MDEd
  vHbMYLjkQ3w4LivmW/+4zZJmn8LZsQp4RpfliBDT2bc6ZuXnbL3Z6cF++/pA935J
  pxFHdmKekfa+foKZrEx2u+7F9JazrfkSJVsc3lPmqVyCBK4Eak0VWcxInAUh+ajh
  MCOwY+bepDpvpZLal2OVQBt8XiE0Aw0prhWtHbhLBgNMPhUf5idQYOuJKM0JAoAf
  GPtWXF1WZJ18gsMY6JiKLQg3AM5qVTPgNjNBL/XecY+QyihyFScCAwEAAaNQME4w
  HQYDVR0OBBYEFG6+XH3QEWpHvDUp32m9zaomGnH4MB8GA1UdIwQYMBaAFG6+XH3Q
  EWpHvDUp32m9zaomGnH4MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
  AGN5XTlZEvvxNdt/PyLAb+K0RK2kkitfM6c4Von7+QZSqerrtXGPce+NqIrCD7Me
  Gp9gmExlOLuVcduwEX1BdkAgASQEDtWBWR3lXO0pgM8LDQuSI59ztuoiuIYi6pFP
  wrjLND5R/15TMpc2mlFUz2Jv484HTmfQPJVAvdwGDGTJEom4mrytmSEBq0v8Gtdc
  A9Fi6XQZj9PW98kdPi+Emhbp9srNIBnyP/NzXGLz90jwmFWxPfLnwJ1wPNYqvw30
  i+B+jcfvVTpmX5JJTmPXGLNLgrROUjOE0S45BJFYim1ACu7ZDAluFlxHdOUAylzR
  TJcpgeuhQ/Aoj+SbFr0BjEg=
  -----END CERTIFICATE-----

NOTE:建议先备份

$ cp /okdinstall/install-config.yaml /tmp/install-config.yaml.bak 
  • 生产 manifests 部署 YAML 文件:
$ openshift-install create manifests --dir=/okdinstall

INFO Consuming Install Config from target directory 
WARNING Making control-plane schedulable by setting MastersSchedulable to true for Scheduler cluster settings 
WARNING Discarding the Openshift Manifests that was provided in the target directory because its dependencies are dirty and it needs to be regenerated 
  • 编辑 manifests/cluster-scheduler-02-config.yml 文件,将 mastersSchedulable 的值设为 flase,以防止将 Pod 调度到 Master Node:
$ sed -i 's/mastersSchedulable: true/mastersSchedulable: False/' /okdinstall/manifests/cluster-scheduler-02-config.yml
  • 创建 Ignition 文件,作为 OpenShift Nodes 的配置文件:
$ openshift-install create ignition-configs --dir=/okdinstall

# 生成的文件目录树
├── auth
│   ├── kubeadmin-password
│   └── kubeconfig
├── bootstrap.ign
├── master.ign
├── metadata.json
└── worker.ign


NOTE:其中,auth 目录下是 Kubernetes 的认证信息,拷贝到环境后才能使用 oc 或 kubectl 指令:

$ mkdir /root/.kube/

$ cp /okdinstall/auth/kubeconfig ~/.kube/config
  • 将 Ignition 文件拷贝到 Nginx 中,供各个节点在执行安装时进行下载:
$ mkdir /usr/share/nginx/html/ignition

$ chmod -R 755 /okdinstall/*

$ cp -rp /okdinstall/* /usr/share/nginx/html/ignition/
  • 下载 Fedora CoreOS 镜像文件并拷贝到 Nginx 中,供各个节点在执行安装时进行下载:
$ mkdir /usr/share/nginx/html/install

$ wget https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20200907.3.0/x86_64/fedora-coreos-32.20200907.3.0-metal.x86_64.raw.xz

$ mv fedora-coreos-32.20200907.3.0-metal.x86_64.raw.xz /usr/share/nginx/html/install/coreos.raw.xz

部署 Bootstrap Node(引导节点)

OpenShift 集群部署采用 “通过 Kubernetes 来安装升级 Kubernetes” 的思路,所以首先会在 Bootstrap 启动一套 “Micro-Kubernetes” 来部署 OpenShift Cluster。

Bootstrap Node 应该可以访问所有 Nodes,它会启动一个临时的 Kubernetes Control Plane(控制平面),以此来完成 OpenShift Cluster 的部署。

在自动化引导部署的场景中,Bootstrap Node 会完成以下步骤:

  1. Bootstrap Node 启动并开始托管 Master Node 启动所需的资源。
  2. Master Node 从 Bootstrap Node 远程获取资源并完成引导。
  3. Master Node 通过 Bootstrap Node 构建 ETCD 集群。
  4. Bootstrap Node 使用 ETCD 集群启动临时 Kubernetes Control Plane。
  5. 临时控制平面在 Master Node 启动生产控制平面。
  6. 临时控制平面关闭并将控制权传递给生产控制平面。
  7. Bootstrap Node 将 OpenShift Cluster 组件注入生产控制平面。
  8. 安装程序关闭 Bootstrap Node。
  9. 引导安装过程完成以后,OpenShift Cluster 部署完毕。然后集群开始下载并配置日常操作所需的其余组件,包括创建计算节点、通过 Operator 安装其他服务等。
  10. 以上是关于云计算与云原生 — OpenShift 部署实践的主要内容,如果未能解决你的问题,请参考以下文章

    新书《OpenShift云原生架构:原理与实践》第一章第二节:PaaS赋能企业数字化转型

    云原生技术分享 | 玩转OpenShift系列:不懂OpenShift,不足以谈容器云平台

    新书《OpenShift云原生架构:原理与实践》第一章第三节:企业级PaaS平台OpenShift

    新书《OpenShift云原生架构:原理与实践》第一章第三节:企业级PaaS平台OpenShift

    云计算与云原生 — ETCD 数据库完全解析

    申通的云原生实践之路:如何实现应用基于容器的微服务改造?