Openshift-学习指南-全-

Openshift 学习指南(全)

原文:annas-archive.org/md5/f70bdc41527e5b194096308fe2c0b67c

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

OpenShift 是一个应用管理平台,利用 Docker 作为隔离的运行时来运行应用程序,并使用 Kubernetes 进行容器编排。它首次发布于 2011 年 5 月 4 日,深受 Google 工程师开发的 Borg 启发,Borg 是一个用于管理数十万个容器的容器编排解决方案。2014 年 9 月,OpenShift 进行了重新设计,Docker 和 Kubernetes 成为其主要构建模块,从那时起,它经历了大量的改进,并拥有了日益壮大的用户和开发者社区。在撰写本文时,OpenShift 的最新版本是 3.9,发布于 2018 年 3 月 28 日,版本 3.10 正在开发中。3.9 版本是 3.7 版本之后的下一个版本,因此,从技术角度看,它也包含了 3.8 版本的预期更改,并代表了其生命周期中的一个重要步骤。

依赖于 Docker,OpenShift 将容器化的真正优势带给了企业,使他们能够快速响应客户日益增长的需求,并通过支持高可用性和多数据中心部署,保持良好的声誉。从商业角度来看,OpenShift 在五年内将投资相关的成本降低了 531%,年均效益为 129 万美元,回报周期为 8 个月——更多详情请参考 cdn2.hubspot.net/hubfs/4305976/s3-files/idc-business-value-of-openshift-snapshot.pdf

开发者会发现,OpenShift 的自助服务门户易于使用,提供了快速访问所有功能和部署策略的途径,支持未修改的源代码,以及 Docker 镜像和 Dockerfile,允许开发者集中精力进行开发,而无需管理环境。OpenShift 通过依赖 Jenkins 管道和与 SCM 的集成,能够自动化管理发布的各个方面。

在操作方面,OpenShift 提供了自动故障转移和恢复功能,以及高可用性和可扩展性,这意味着运维团队可以将时间用于更高层次的任务。它还可以与由其他厂商(而非 Red Hat)开发的各种 SDN 技术集成。而且,依赖于这些知名技术,使得学习曲线较为平缓。

从安全角度来看,OpenShift 可以与企业身份管理解决方案集成,用于用户管理和角色分配。它能够将应用暴露给企业安全基础设施,以实现细粒度的访问控制和审计,保护应用使用的敏感数据,并管理不同应用之间的访问权限。

本书中的示例基于 OpenShift Origin 3.9 演示,但由于技术上的等效性,它们同样适用于 Red Hat OpenShift 容器平台^(TM)。本书以模块化的方式编写,因此,如果你已经熟悉某些主题,可以随时跳到另一个章节。

本书读者群体

本书是为刚接触 OpenShift 的专业人士编写的,但也涉及一些高级主题,如 CI/CD 流水线、高可用性和多数据中心架构。读者不需要具备 Docker、Kubernetes 或 OpenShift 的背景知识,但对基本概念的了解会有所帮助。本书不涉及如何使用 Linux,因此至少需要一年以上的 Linux 使用经验。本书的主要目标并不是理论知识,而是实践经验,因此我们采用了实践导向的方法,在可能的情况下使用虚拟实验室。本书从介绍容器的概念和优势开始,帮助新手迅速掌握基本知识,随后逐步深入,带领读者了解 Kubernetes 和 OpenShift 的基本和高级概念。最后,本书提供了一个关于高可用性多数据中心架构的参考。在我们开始撰写本书之前,我们意识到关于如何在多个数据中心部署 OpenShift 以实现高可用性和容错性的信息非常少。正是由于这一点,我们决定汇聚我们的经验,共同编写这本书。

本书内容概述

第一章,容器与 Docker 概述,讨论了容器以及如何使用 Docker 构建镜像和运行容器。

第二章,Kubernetes 概述,解释了 Kubernetes 如何协调 Docker 容器,并介绍了如何使用其 CLI。

第三章,CRI-O 概述,介绍了 CRI-O 作为容器运行时接口,并解释了它与 Kubernetes 的集成。

第四章,OpenShift 概述,解释了 OpenShift 作为 PaaS 的角色,并涵盖了其提供的不同版本。

第五章,构建 OpenShift 实验室,展示了如何通过多种方法在 OpenShift 上设置你自己的虚拟实验室。

第六章,OpenShift 安装,通过使用 Ansible 进行 OpenShift 的高级安装,提供了实际操作经验。

第七章,管理持久存储,介绍了 OpenShift 如何为应用程序提供持久存储。

第八章,核心**OpenShift 概念,带你了解 OpenShift 背后最重要的概念和资源。

第九章,高级 OpenShift 概念,探讨了 OpenShift 的资源,并解释了如何进一步管理它们。

第十章,OpenShift 中的安全性,描述了 OpenShift 如何在多个层次上处理安全性。

第十一章,管理 OpenShift 网络,探讨了 OpenShift 中虚拟网络的每种网络配置的使用案例。

第十二章,在 OpenShift 中部署简单应用,展示了如何在 OpenShift 中部署一个单容器应用。

第十三章,使用模板部署多层应用,带你了解如何通过模板部署复杂应用。

第十四章,从 Dockerfile 构建应用镜像,解释了如何使用 OpenShift 从 Dockerfile 构建镜像。

第十五章,从源代码构建 PHP 应用,解释了如何在 OpenShift 中实现源代码到镜像的构建策略。

第十六章,从源代码构建多层应用,展示了如何在 OpenShift 上部署一个多层 PHP 应用。

第十七章,OpenShift 中的 CI/CD 管道,介绍了如何使用 Jenkins 和 Jenkinsfile 在 OpenShift 上实现 CI/CD。

第十八章,OpenShift 高可用架构概述,展示了如何为你的 OpenShift 集群的各个层级带来高可用性。

第十九章,OpenShift 高可用设计(单数据中心和多数据中心),解释了构建地理分布式 OpenShift 集群所需的条件。

第二十章,OpenShift 高可用网络设计,探讨了构建 HA OpenShift 解决方案所需的网络设备和协议。

第二十一章,OpenShift 3.9 的新特性,为你提供 OpenShift 3.9 的最新功能的洞察,并解释了为什么你可能想要使用它。

要充分利用本书

本书假设您具备 Linux 和开源的实际经验,能够熟练使用命令行界面(CLI),熟悉如 nano 或 vi/vim 等文本编辑器,并了解如何使用 SSH 访问正在运行的机器。由于 OpenShift 只能安装在基于 RHEL 的 Linux 发行版上,具有 RHEL/CentOS 7 的经验会更为合适,而不是基于 Debian 的变种。了解云技术和容器将是加分项,但不是必需的。

为确保顺利的体验,我们建议使用具有足够 RAM 的笔记本或台式电脑,因为这是 OpenShift 最关键的资源。您可以在 GitHub 仓库中找到“软件硬件清单”部分,其中列出了您的学习环境的所有要求。使用低于 8GB RAM 的系统可能会导致在安装 OpenShift 时出现偶尔的失败和整体不稳定,虽然这会提升您的故障排除技能,但也会让人分心。

另一个重要的方面涉及到您环境中的 DNS。一些网络服务提供商,如 Cox(www.cox.com),会将所有不存在的域名(那些导致上游 DNS 返回 NXDOMAIN 响应的域名)的请求重定向到一个包含合作伙伴搜索结果的自定义网页。通常,这不是问题,但在安装 OpenShift 时,这会导致安装失败。发生这种情况是因为您虚拟机和由 OpenShift 管理的容器的本地 DNS 查找设置包括几个域名,这些域名按照顺序进行联系,直到返回 NXDOMAIN 为止,下一域名只有在前一个返回 NXDOMAIN 后才会被尝试。因此,当您的服务提供商拦截这些请求时,它可能会返回自己的 IP 地址,这会导致 OpenShift 安装程序尝试通过该 IP 检查某个服务的健康状况。正如预期的那样,请求不会得到回应,安装将失败。对于 Cox 来说,这一功能被称为增强错误结果,因此我们建议您在账户中选择退出此功能。

下载示例代码文件

您可以从您的账户中下载本书的示例代码文件,网址为www.packtpub.com。如果您是从其他地方购买本书的,您可以访问www.packtpub.com/support,注册后可以将文件直接通过邮件发送给您。

您可以通过以下步骤下载代码文件:

  1. www.packtpub.com登录或注册。

  2. 选择“支持”标签。

  3. 点击“代码下载与勘误”。

  4. 在搜索框中输入书名并按照屏幕上的指示操作。

文件下载完成后,请确保使用最新版本的以下工具来解压或提取文件夹:

  • Windows 版的 WinRAR/7-Zip

  • Mac 版的 Zipeg/iZip/UnRarX

  • Linux 版的 7-Zip/PeaZip

本书的代码包也托管在 GitHub 上,网址为 github.com/PacktPublishing/Learn-OpenShift。如果代码有更新,它将在现有的 GitHub 仓库中进行更新。

我们还提供了来自我们丰富书籍和视频目录的其他代码包,网址为 github.com/PacktPublishing/。快来查看吧!

下载彩色图像

我们还提供了一份包含本书截图/图表的彩色图像 PDF 文件。你可以在此下载:www.packtpub.com/sites/default/files/downloads/LearnOpenShift_ColorImages.pdf

使用的规范

本书中使用了许多文本规范。

CodeInText:表示文本中的代码词汇、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。例如:“假设模板存储在本地,文件名为 wordpress.yaml。”

一段代码的格式如下所示:

...
node('nodejs') {
  stage('build') {
    openshiftBuild(buildConfig: 'nodejs-mongodb-example', showBuildLogs: 'true')
  }

当我们希望特别强调代码块中的某一部分时,相关的行或项目将用粗体显示:

openshiftBuild(buildConfig: 'nodejs-mongodb-example', showBuildLogs: 'true')
}
stage('approval') {
 input "Approve moving to deploy stage?"
}
stage('deploy') {
  openshiftDeploy(deploymentConfig: 'nodejs-mongodb-example')
}

任何命令行输入或输出的格式如下所示:

$ vagrant ssh

粗体:表示一个新术语、重要词汇或在屏幕上看到的词汇。例如,菜单或对话框中的词汇将以这种形式出现在文本中。以下是一个示例:“当你点击‘登录’按钮后,页面会显示如下。”

警告或重要提醒以这种形式出现。

小贴士和技巧以这种形式出现。

联系我们

我们始终欢迎读者的反馈。

一般反馈:请发送电子邮件至 feedback@packtpub.com,并在邮件主题中注明书名。如果您对本书的任何方面有疑问,请通过 questions@packtpub.com 与我们联系。

勘误表:虽然我们已经尽力确保内容的准确性,但难免会出现错误。如果您在本书中发现错误,请帮助我们报告。请访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误表提交链接,并输入详细信息。

盗版:如果你在互联网上发现我们作品的任何非法复制品,感谢你提供具体的地址或网站名称。请通过电子邮件 copyright@packtpub.com 联系我们,并附上相关链接。

如果你有兴趣成为作者:如果你在某个领域有专业知识,并且有意撰写或参与书籍的编写,请访问 authors.packtpub.com

评审

请留下评论。在阅读并使用本书后,为什么不在您购买该书的网站上留下评论呢?潜在读者可以看到并参考您的公正意见,从而做出购买决策,我们在 Packt 能了解您对我们产品的看法,作者也能看到您对他们书籍的反馈。谢谢!

有关 Packt 的更多信息,请访问 packtpub.com

第一章:容器和 Docker 概述

本书不仅仅是 OpenShift 基础知识的介绍。它讲述了微服务和容器的过去、现在与未来。我们将在本书中讨论 OpenShift 及其相关内容;这包括容器基础、Docker 基础等主题,并通过与 Kubernetes 和 OpenShift 的结合使用,帮助你更好地掌握它们。

在我们的 OpenShift 之旅中,我们将带领你了解 OpenShift 的所有主要组件以及大多数高级组件。我们将涵盖 OpenShift 的安全性、网络配置,以及使用 OpenShift 最受欢迎和内建的 DevOps 工具(如 Jenkins 的 CI/CD 和与 GitHub 配合使用的 Source-to-Image (S2I))进行应用开发。

我们还将学习每个希望在公司实际实现 OpenShift 的人最关键的一部分——设计部分。我们将展示如何正确设计和实现 OpenShift,分析那些刚开始使用 OpenShift 的人最常犯的错误。

本章重点介绍容器和 Docker 技术。我们将从架构到低层技术,介绍容器概念和 Docker 基础。在本章中,我们将学习如何使用 Docker CLI 管理 Docker 容器和 Docker 镜像。本章的一个重要部分是构建和运行 Docker 容器镜像。作为本章的一部分,你需要开发多个 Dockerfile,并将多个应用程序容器化。

本章内容包括以下几个部分:

  • 容器概述

  • Docker 容器架构

  • 理解 Docker 镜像和层

  • 理解 Docker Hub 和 Docker 注册表

  • 安装和配置 Docker 软件

  • 使用 Docker 命令行

  • 通过 Docker CLI 管理镜像

  • 通过 Docker CLI 管理容器

  • 理解环境变量在 Docker 容器中的重要性

  • 为 Docker 容器管理持久存储

  • 构建自定义 Docker 镜像

技术要求

在本章中,我们将使用以下技术和软件:

  • Vagrant

  • Bash Shell

  • GitHub

  • Docker

  • 推荐使用 Firefox 或任何其他浏览器

Vagrant 安装以及我们在本章使用的所有代码都可以在 GitHub 上找到:github.com/PacktPublishing/Learn-OpenShift

本章将提供如何安装和配置 Docker 的说明,随着学习的进展,我们将深入了解这些内容。

Bash Shell 将作为你基于 CentOS 7 的虚拟环境的一部分使用。

可以使用 Firefox 或其他任何浏览器来浏览 Docker Hub。

作为前提条件,你需要一台能够稳定连接互联网的笔记本电脑。

容器概述

传统上,软件应用的开发遵循单体架构方法,这意味着所有的服务或组件都紧密耦合在一起。你不能随意抽出一部分并替换成其他组件。随着时间的推移,这种方法发生了变化,演变成了 N 层架构。N 层应用架构是容器和微服务架构向前迈进的一步。

单体架构的主要缺点是其缺乏可靠性、可扩展性和高可用性。由于单体应用的特性,扩展这些应用变得非常困难。这些应用的可靠性也是一个问题,因为你几乎无法在不发生停机的情况下轻松地操作和升级它们。你无法有效地扩展单体应用,也就是说,你不能简单地再添加一个、五个或十个应用,并让它们相互共存。

过去我们有单体应用,但随着人们和公司开始关注应用的可扩展性、安全性、可靠性和高可用性HA),这种架构才逐渐发展起来。这就是 N 层设计的由来。N 层设计是一种标准的应用设计方式,比如三层 Web 应用,其中包括 Web 层、应用层和数据库后端。这是一个非常标准的设计。如今,它正向微服务架构演变。那么,为什么我们需要微服务?简短的回答是 为了更好的效果。它更便宜、扩展性更强、也更安全。容器化的应用将把你带到一个全新的层次,这也是你能从自动化和 DevOps 中受益的地方。

容器是新一代虚拟机,它将软件开发带入了一个全新的层次。容器是在单一操作系统内的一组隔离的不同规则和资源。这意味着容器能够提供与虚拟机相同的好处,但使用的 CPU、内存和存储却少得多。当前有几个流行的容器提供商,包括 LXC、Rockt 和 Docker,我们在本书中将重点讨论 Docker。

容器的特点和优势

这种架构为软件开发带来了诸多优势。

容器的主要优势包括以下几点:

  • 高效的硬件资源消耗

  • 应用与服务隔离

  • 更快速的部署

  • 微服务架构

  • 容器的无状态特性

高效的硬件资源消耗

无论你是在裸金属服务器上原生运行容器,还是使用虚拟化技术,容器都能让你更好、更高效地利用资源(CPU、内存和存储)。在裸金属服务器的情况下,容器允许你运行几十个甚至上百个相同或不同的容器,与通常只在专用服务器上运行一个应用程序相比,提供了更好的资源利用率。我们曾经看到过某些服务器在高峰期的利用率只有 3%,这是一种资源浪费。如果你要在同一台服务器上运行几个相同或不同的应用程序,它们会相互冲突。即使它们能正常工作,你在日常操作和故障排除中也会面临许多问题。

如果你打算通过引入流行的虚拟化技术(如 KVM、VMware、XEN 或 Hyper-V)来隔离这些应用程序,你会遇到另一个问题。由于为了虚拟化你的应用程序,你需要在虚拟机管理程序操作系统之上安装一个操作系统,而这个操作系统需要 CPU 和内存来运行。例如,每个虚拟机都有自己的内核和内核空间。与标准虚拟机相比,经过精心调优的容器平台可以让你运行最多四倍数量的容器。当你只有五个或十个虚拟机时,差异可能不显著,但当我们谈论上百或上千个虚拟机时,差距就非常大。

应用程序和服务隔离

假设我们有十个不同的应用程序托管在同一台服务器上。每个应用程序都有一些依赖项(如包、库等)。如果需要更新某个应用程序,通常涉及到更新该应用程序及其依赖项。如果你更新所有相关的依赖项,很可能会影响其他应用程序和服务,导致这些应用程序无法正常工作。当然,某种程度上,这些问题可以通过环境管理工具来解决,比如 Python 的virtualenv和 Ruby 的rbenv/rvm,而共享库的依赖项可以通过LD_LIBRARY_PATH进行隔离。但如果你需要同一个包的不同版本呢?容器和虚拟化技术解决了这个问题。虚拟机和容器都为你的应用程序提供了环境隔离。

但与裸金属应用程序部署相比,容器技术(例如 Docker)提供了一种高效的方式,将应用程序和其他计算资源库相互隔离。它不仅使这些应用程序能够在同一操作系统上共存,还提供了高效的安全性,这是每个面向客户和内容敏感的应用程序所必须的。它使你能够独立地更新和修补容器化的应用程序。

更快的部署

使用本书后续将讨论的容器镜像,可以加速容器部署。我们说的是几秒钟就能完全重启一个容器,而不像裸金属服务器和虚拟机那样需要几分钟甚至几十分钟。这主要是因为容器不需要重启整个操作系统,它只需要重启应用程序本身。

微服务架构

容器通过引入微服务架构,将应用程序部署提升到全新水平。其实质是,如果你有一个单体应用或多层应用,它通常有许多不同的服务相互通信。将服务容器化可以让你将应用程序拆分成多个部分,并独立操作每个部分。假设你有一个标准的应用程序,包含一个 Web 服务器、应用程序和数据库。你可能会将其部署到一台或三台不同的服务器、三台虚拟机或三个简单的容器中,每个容器运行应用程序的一个部分。这些选项所需的工作量、时间和资源各不相同。本书后续会展示使用容器进行这一操作是多么简单。

容器的无状态特性

容器是无状态的,这意味着你可以随时启动和关闭容器,创建或销毁容器,这不会影响你的应用性能。这是容器的最伟大特性之一。本书后续将深入探讨这一点。

Docker 容器架构

Docker 是当今最流行的应用容器化技术之一。那么,既然有其他容器选项,为什么我们要使用 Docker 呢?因为在开源时代,协作和贡献至关重要,而 Docker 在这一领域做到了其他技术无法做到的许多事情。

例如,Docker 与其他容器开发者(如 Red Hat、Google 和 Canonical)合作,共同开发其组件。Docker 还将其软件容器格式和运行时贡献给了 Linux 基金会的开放容器项目。Docker 使得容器的学习和使用变得非常简单。

Docker 架构

正如我们已经提到的,Docker 是最流行的容器平台。它允许在 Docker 容器中创建、共享和运行应用程序。Docker 将运行中的应用程序与基础设施分开。它可以大大加快应用程序交付的速度。Docker 还将应用程序开发提升到一个全新的水平。在下图中,你可以看到 Docker 架构的高级概览:

Docker 架构

Docker 使用的是客户端-服务器类型的架构:

  • Docker 服务器:这是一个作为守护进程在操作系统中运行的服务。该服务负责下载、构建和运行容器。

  • Docker 客户端:该命令行工具负责使用 REST API 与 Docker 服务器进行通信。

Docker 的主要组件

Docker 使用三个主要组件:

  • Docker 容器:隔离的用户空间环境,运行相同或不同的应用程序,并共享相同的主机操作系统。容器是由 Docker 镜像创建的。

  • Docker 镜像:包含应用程序库和应用程序的 Docker 模板。镜像用于创建容器,您可以立即启动容器。您可以创建和更新自己的自定义镜像,也可以从 Docker 的公共注册表中下载构建镜像。

  • Docker 注册表:这是一个镜像存储库。Docker 注册表可以是公共的或私有的,这意味着您可以使用互联网上可用的镜像,也可以为内部用途创建自己的注册表。一个流行的公共 Docker 注册表是 Docker Hub,本章稍后会讨论。

Linux 容器

如前一节所述,Docker 容器是安全的,并且彼此隔离。在 Linux 中,Docker 容器使用 Linux 内核的几个标准特性。这包括:

  • Linux 命名空间:这是 Linux 内核中的一个功能,用于将资源彼此隔离。这允许一组 Linux 进程看到一组资源,而另一组 Linux 进程则看到另一组资源。Linux 中有几种类型的命名空间:挂载mnt)、进程 IDPID)、网络net)、用户 IDuser)、控制组cgroup)和进程间通信IPC)。内核可以将通常对所有进程可见的特定系统资源放入命名空间中。在命名空间内,进程只能看到与同一命名空间中的其他进程相关联的资源。您可以将进程或一组进程与其自己的命名空间关联,或者如果使用网络命名空间,甚至可以将网络接口移动到网络命名空间。例如,位于两个不同挂载命名空间中的两个进程可能对挂载的根文件系统有不同的视图。每个容器都可以与一组特定的命名空间关联,这些命名空间仅在这些容器内部使用。

  • 控制组cgroups):这些提供了一种有效的资源限制机制。通过 cgroups,您可以按 Linux 进程控制和管理系统资源,提高整体资源利用效率。Cgroups 允许 Docker 按容器控制资源的使用。

  • SELinuxSecurity Enhanced LinuxSELinux)是强制访问控制MAC),用于细粒度的系统访问,最初由国家安全局NSA)开发。它是 Debian 和基于 RHEL 的发行版(如 Red Hat Enterprise Linux、CentOS 和 Fedora)的一层附加安全性。Docker 使用 SELinux 的两个主要原因:主机保护和将容器彼此隔离。容器进程使用特殊的 SELinux 规则,以限制对系统资源的访问。

Docker 的优势在于它利用了上述低级内核技术,但通过提供一种简便的方式来管理容器,隐藏了所有复杂性。

理解 Docker 镜像和层

Docker 镜像是一个只读模板,用于构建容器。一个镜像由多个层组成,这些层被组合成一个单一的虚拟文件系统,Docker 应用可以访问。通过使用一种特殊的技术,将多个层合并成一个单一视图,从而实现这一点。Docker 镜像是不可变的,但你可以添加一个额外的层并将其保存为一个新的镜像。基本上,你可以添加或更改 Docker 镜像的内容,而无需直接更改这些镜像。Docker 镜像是运送、存储和交付容器化应用的主要方式。容器是通过 Docker 镜像创建的;如果没有 Docker 镜像,你需要下载或构建一个。

容器文件系统

容器文件系统用于每个 Docker 镜像,它表现为一个堆叠在一起的只读层列表。这些层最终形成容器的基础根文件系统。为了实现这一点,使用了不同的存储驱动程序。对正在运行的容器文件系统的所有更改都会应用到容器的最上层镜像层,这一层称为容器层。基本上,这意味着多个容器可以共享对 Docker 镜像的相同底层访问权限,但会在本地独立且唯一地写入更改。这个过程在以下图示中展示:

Docker 层

Docker 存储驱动程序

Docker 存储驱动程序是启用和管理容器镜像的主要组件。为了实现这一点,使用了两种主要技术——写时复制和可堆叠的镜像层。存储驱动程序的设计目的是处理这些层的细节,使它们能够相互作用。有多种驱动程序可供选择。它们基本上执行相同的工作,但每一种方法都不同。最常见的存储驱动程序是 AUFS、Overlay/Overlay2、Devicemapper、Btrfs 和 ZFS。所有存储驱动程序可以分为三种不同类型:

存储驱动类别 存储驱动
联合文件系统 AUFS, Overlay, Overlay2
快照文件系统 Btrfs, ZFS
写时复制块设备 Devicemapper

容器镜像层

如前所述,Docker 镜像包含多个层,这些层通过存储驱动程序组合成一个单一的文件系统。层(也称为中间镜像)是在执行 Docker 镜像构建过程中的命令时生成的。通常,Docker 镜像是通过 Dockerfile 创建的,其语法将在后面描述。每一层代表镜像 Dockerfile 中的一个指令。

除了最后一层外,每一层都是只读的:

Docker 镜像层

一个 Docker 镜像通常由多个层组成,层与层之间是堆叠的。最上层具有读写权限,所有其他层都是只读权限。这个概念与写时复制技术非常相似。因此,当你从镜像启动容器时,所有更改都会应用到最上层的可写层。

Docker 注册表

如前所述,Docker 镜像是一种交付应用程序的方式。你可以创建一个 Docker 镜像并通过公共/私有注册表服务与其他用户共享。注册表是一个无状态的、高度可扩展的服务器端应用程序,你可以用它来存储和下载 Docker 镜像。Docker 注册表是一个开源项目,使用宽松的 Apache 许可证。一旦镜像在 Docker 注册表服务上可用,其他用户可以通过拉取镜像来下载它,并使用该镜像创建新的 Docker 镜像或从该镜像运行容器。

Docker 支持几种类型的 Docker 注册表:

  • 公共注册表

  • 私有注册表

公共注册表

你可以从存储在公共注册表中的镜像启动容器。默认情况下,Docker 守护进程会从 Docker Hub(一个由 Docker 提供的公共注册表)查找并下载 Docker 镜像。然而,许多厂商在安装时将自己的公共注册表添加到 Docker 配置中。例如,Red Hat 有自己的 经过验证且被推荐 的公共 Docker 注册表,你可以用它来拉取 Docker 镜像并构建容器。

私有注册表

一些组织或特定团队出于某种原因,不希望与所有人共享他们定制的容器镜像。它们仍然需要一个服务来共享 Docker 镜像,但仅供内部使用。在这种情况下,私有注册表服务可能会很有用。可以在专用服务器或网络内的虚拟机上安装并配置一个私有注册表服务。

你可以通过从公共注册表镜像启动一个 Docker 容器,轻松安装私有 Docker 注册表。私有 Docker 注册表的安装过程与使用附加选项运行常规 Docker 容器没有区别。

访问注册表

通过 Docker 客户端使用 Docker 守护进程服务访问 Docker 注册表。Docker 命令行使用 RESTful API 来请求守护进程执行处理。这些命令大多被转换为 HTTP 请求,并可以通过 curl 传输。

使用 Docker 注册表的过程将在下一个部分中展示。

开发者可以创建一个 Docker 镜像,并将其上传到私有或公共注册表。一旦镜像上传成功,就可以立即用来运行容器或构建其他镜像。

Docker Hub 概述

Docker Hub 是一个基于云的注册服务,它允许您构建镜像并进行测试,推送这些镜像,并连接到 Docker 云,以便将镜像部署到您的主机上。Docker Hub 提供了一个集中式资源,用于容器镜像的发现、分发和变更管理、用户和团队协作以及整个开发管道中的工作流自动化。

Docker Hub 是由 Docker 项目管理的公共注册中心,托管着大量容器镜像,包括由主要开源项目(如 MySQL、Nginx、Apache 等)提供的镜像,以及由社区开发的定制容器镜像。

Docker Hub 提供以下一些功能:

  • 镜像仓库:您可以查找并下载其他 Docker Hub 用户管理的镜像。您还可以推送或拉取您有权限访问的私人镜像库中的镜像。

  • 自动构建:当您对源代码仓库进行更改时,可以自动创建新的镜像。

  • Webhooks:当仓库发生推送时触发的操作,以便自动化构建。

  • 组织:创建小组并管理对镜像仓库的访问权限。

要开始使用 Docker Hub,您需要使用 Docker ID 登录 Docker Hub。如果您没有 Docker ID,可以通过简单的注册过程创建一个。它完全免费。如果您还没有 Docker ID,创建链接是 hub.docker.com/

您可以在不登录的情况下搜索和拉取 Docker 镜像;但是,要推送镜像,您必须先登录。Docker Hub 让您能够创建公共和私人仓库。公共仓库将对所有人开放,而私人仓库将仅限于特定用户或组织使用。

Docker Hub 包含多个官方仓库。这些是来自不同供应商和 Docker 贡献者的公共认证仓库,涵盖了像 Red Hat、Canonical 和 Oracle 等供应商。

Docker 安装和配置

Docker 软件有两个版本:社区版CE)和企业版EE)。

Docker CE 是学习 Docker 和使用容器化应用程序的一个很好的起点。它支持多个平台和操作系统。Docker CE 带有安装程序,您可以立即开始使用容器。Docker CE 集成并优化了基础设施,因此在开始使用 Docker 时,您可以保持原生应用体验。

Docker 企业版EE)是一个容器即服务CaaS)平台,面向 IT 管理并保护跨不同基础设施(无论是本地还是云端)的多样化应用程序。换句话说,Docker EE 与 Docker CE 类似,它由 Docker Inc. 提供支持。

Docker 软件支持多种平台和操作系统。大多数流行的操作系统都可以获得 Docker 的安装包,如 Red Hat Enterprise Linux、Fedora Linux、CentOS、Ubuntu Linux、Debian Linux、macOS 和 Microsoft Windows。

Docker 安装

Docker 安装过程取决于具体的操作系统。在大多数情况下,官方 Docker 门户网站上已经有很好的描述——docs.docker.com/install/。作为本书的一部分,我们将在 CentOS 7.x 上使用 Docker 软件。在其他平台上的 Docker 安装与配置不在本书范围内。如果您仍然需要在其他操作系统上安装 Docker,请访问官方 Docker 网站。

通常,Docker 节点的安装过程如下:

  1. 操作系统的安装与配置

  2. Docker 包安装

  3. 配置 Docker 设置

  4. 启动 Docker 服务

我们假设读者已经具备足够的知识来安装和配置基于 CentOS 的 虚拟机 (VM) 或裸金属主机。如果您不知道如何使用 Vagrant,请参阅以下指南:www.vagrantup.com/intro/getting-started/

一旦您在系统上正确安装了 Vagrant,只需运行 vagrant init centos/7,然后运行 vagrant up。您可以通过 vagrant status 命令验证 Vagrant 是否已启动,最后可以通过 vagrant ssh 命令进入虚拟机。

由于 Docker 支持包括最流行的操作系统在内的多种操作系统,您可以选择直接在桌面操作系统上安装 Docker。我们建议您使用 Vagrant 或其他虚拟化提供商,如 VMware 或 KVM,因为我们已经在 CentOS 7 的虚拟环境中完成了所有测试。如果您仍然希望在桌面操作系统上安装 Docker,请访问链接:docs.docker.com/install/

Docker CE 可以在 CentOS 7 上通过标准仓库进行安装。安装过程侧重于 docker 包的安装:

# yum install docker -y
...
output truncated for brevity
...
Installed:
docker.x86_64 2:1.12.6-71.git3e8e77d.el7.centos.1
Dependency Installed:
...
output truncated for brevity
...

安装完成后,您需要启动 Docker 守护进程,才能管理您的容器和镜像。在 RHEL7 和 CentOS 7 上,这仅仅意味着启动 Docker 服务,操作如下:

# systemctl start docker
# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.

您可以通过运行 docker info 命令来验证 Docker 守护进程是否正常工作,从而显示 Docker 信息:

# docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
...
output truncated for brevity
...
Registries: docker.io (secure)

Docker 配置

Docker 守护进程的配置由 Docker 配置文件(/etc/docker/daemon.json)管理,Docker 守护进程的启动选项通常由名为 Docker 的 systemd 单元控制。在基于 Red Hat 的操作系统中,一些配置选项可以在 /etc/sysconfig/docker/etc/sysconfig/docker-storage 中找到。修改上述文件将允许您更改 Docker 参数,如 UNIX 套接字路径、listen 在 TCP 套接字上的配置、注册表配置、存储后端等。

使用 Docker 命令行

为了开始使用 Docker CLI,你需要配置并启动一个 Vagrant 虚拟机。如果你使用的是 macOS,使用 Vagrant 进行配置的过程如下所示:

end
**$ vagrant up** 
**$ vagrant ssh**

**# 使用 Docker man、help、info

Docker 守护进程监听 unix:///var/run/docker.sock,但你可以将 Docker 绑定到其他主机/端口或 Unix 套接字。Docker 客户端(docker 工具)使用 Docker API 与 Docker 守护进程进行交互。

Docker 客户端支持数十个命令,每个命令都有众多选项,因此尝试列出它们会导致一份官方文档中的 CLI 参考副本。相反,我们将为你提供最有用的子集命令,帮助你快速上手。

你可以随时通过以下命令查看所有 Docker 子命令的手册页:

$ man -k docker

你将能够看到 Docker 及其所有子命令的手册页列表:

$ man docker
$ man docker-info
$ man Dockerfile

获取有关命令的另一个方法是使用 docker COMMAND --help

# docker info --help
Usage: docker info
Display system-wide information
--help             Print usage

docker 工具允许你管理容器基础设施。所有子命令可以按如下方式进行分组:

活动类型 相关子命令
管理镜像 search, pull, push, rmi, images, tag, export, import, load, save
管理容器 run, exec, ps, kill, stop, start
构建自定义镜像 build, commit
信息收集 info, inspect

使用 Docker CLI 管理镜像

在你的服务器或笔记本电脑上运行和使用容器的第一步是使用 docker search 命令从 Docker 注册表中搜索并拉取 Docker 镜像。

让我们搜索 Web 服务器容器。执行此操作的命令是:

$ docker search httpd
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
httpd ... 1569 [OK]
hypriot/rpi-busybox-httpd ... 40
centos/httpd 15 [OK]
centos/httpd-24-centos7 ... 9

或者,我们可以访问hub.docker.com/,在搜索窗口输入httpd,它会给我们类似于 docker search httpd 的结果:

Docker Hub 搜索结果

一旦找到容器镜像,我们可以从 Docker 注册表中拉取该镜像以开始使用它。要将容器镜像拉取到主机上,你需要使用 docker pull 命令:

$ docker pull httpd

上述命令的输出如下:

请注意,Docker 使用联合文件系统层的概念来构建 Docker 镜像。这就是为什么你可以看到从 Docker Hub 拉取的七个层级。一个层叠加在另一个之上,最终构建出一个完整的镜像。

默认情况下,Docker 会尝试拉取最新标签的镜像,但我们也可以使用不同的标签下载我们感兴趣的较旧版本镜像。快速找到可用标签的最佳方式是访问hub.docker.com/,搜索特定镜像并点击镜像详情:

Docker Hub 镜像详情

在那里我们可以看到从 Docker Hub 拉取的所有镜像标签。也有方法可以通过 docker search CLI 命令实现相同的目标,稍后我们将在本书中介绍。

$ docker pull httpd:2.2.29

上述代码的输出应如下所示:

你可能会注意到,第二个镜像的下载时间明显低于第一个镜像。这是因为我们拉取的第一个镜像(docker:latest)与第二个镜像(httpd:2.2.29)有许多层是相同的。因此,系统无需再次下载所有的层。这在大型环境中非常有用,可以节省大量时间。

操作镜像

现在,我们想检查本地服务器上可用的镜像。为此,我们可以使用docker images命令:

$ docker images

上述命令的输出将如下所示:

如果我们下载了一个错误的镜像,可以使用docker rmi命令将其从本地服务器中删除:删除镜像RMI)。在我们的案例中,我们有两个版本的同一镜像,因此可以指定要删除的镜像标签:

$ docker rmi httpd:2.2.29

上述命令的输出将如下所示:

目前我们只剩下一个镜像,它是httpd:latest

$ docker images

上述命令的输出将如下所示:

保存与加载镜像

Docker CLI 允许我们使用 export/import 或 save/load 命令导出和导入 Docker 镜像和容器层。save/load 与 export/import 的区别在于,前者处理包含元数据的镜像,而后者仅使用容器层,不包含任何镜像元数据,如名称、标签等。在大多数情况下,save/load 的组合更加相关,并且适用于没有特殊需求的镜像。docker save命令将打包所有用于构建镜像的层和元数据。然后,你可以将这个保存的镜像链加载到另一个 Docker 实例中,并基于这些镜像创建容器。

docker export将提取整个容器,类似于普通虚拟机的快照。它不仅保存操作系统,还包括你在容器生命周期中所做的任何更改和写入的任何数据文件。这更像是传统的备份:

$ docker save httpd -o httpd.tar $ ls -l httpd.tar 

要从文件中加载镜像,我们可以使用docker load命令。不过,在此之前,我们先从本地仓库中删除 httpd 镜像:

$ docker rmi httpd:latest

上述命令的输出将如下所示:

我们验证本地仓库中没有任何镜像:

 $ docker images
 REPOSITORY TAG IMAGE ID CREATED SIZE

使用docker save命令保存的镜像文件,可以通过docker load命令将其加载回来。像docker exportdocker import一样,这个命令与 Docker 的 save 功能成对使用,因此用于将保存的容器归档(包含所有中间层和元数据)加载到 Docker 缓存中:

$ docker load -i httpd.tar

上述命令的输出将如下所示:

使用docker image命令检查本地的 docker 镜像:

$ docker images

上述命令的输出如下所示:

将镜像上传到 Docker 仓库

现在我们知道如何搜索、拉取、删除、保存、加载和列出可用的镜像。最后我们缺少的部分是如何将镜像推送回 Docker Hub 或私有仓库。

要将镜像上传到 Docker Hub,我们需要做一些处理并按照以下步骤操作:

  1. 登录 Docker Hub:
$ docker login
Username: #Enter your username here
Password: #Enter your password here
Login Succeeded
  1. 将您想要推送的 Docker 镜像复制到服务器上 Docker 仓库中的不同路径:
$ docker tag httpd:latest flashdumper/httpd:latest

请注意,flashdumper 是您的 Docker Hub 用户名。

  1. 最后,将复制的镜像推送回 Docker Hub:
$ docker push flashdumper/httpd:latest

上述命令的输出如下所示:

现在,镜像已推送到您的 Docker Hub 并可供任何人下载。

$ docker search flashdumper/*

上述命令的输出如下所示:

您可以使用网页浏览器查看相同的结果。如果您访问 hub.docker.com/,您应该能够在您的帐户下看到该 httpd 镜像:

Docker Hub 账户镜像

使用 Docker CLI 管理容器

下一步是实际运行一个容器,该容器来自于我们在上一章从 Docker Hub 或私有仓库拉取的镜像。我们将使用 docker run 命令来运行一个容器。在此之前,让我们先用 docker ps 命令检查是否有任何容器正在运行:

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAME

使用 docker run 命令运行一个容器:

$ docker run httpd

上述命令的输出如下所示:

容器正在运行,但我们无法离开终端并继续在前台工作。而我们唯一能逃离的方法是发送一个 TERM 信号(Ctrl + C)并终止它。

Docker ps 和日志

运行 docker ps 命令来显示没有正在运行的容器:

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

运行 docker ps -a 命令来显示运行中的和已停止的容器:

$ docker ps -a

上述命令的输出如下所示:

这里有几点需要注意。STATUS 字段表示容器 5e3820a43ffc 大约在一分钟前退出。为了获取容器日志信息,我们可以使用 docker logs 命令:

$ docker logs 5e3820a43ffc

上述命令的输出如下所示:

最后一条消息显示 caught SIGTERM, shutting down。这发生在我们按下 Ctrl + C 之后。为了在后台模式下运行容器,我们可以使用 docker run 命令的 -d 选项:

$ docker run -d httpd
5d549d4684c8e412baa5e30b20697b72593d87130d383c2273f83b5ceebc4af3

它会生成一个随机的 ID,其中前 12 个字符用于容器 ID。同时还会生成一个随机的容器名称。

运行 docker ps 来验证容器的 ID、名称和状态:

$ docker ps

上述命令的输出如下所示:

在容器内执行命令

从输出中,我们可以看到容器状态为UP。现在我们可以使用不同选项的docker exec命令在容器内执行一些命令:

$ docker exec -i 00f343906df3 ls -l /
total 12
drwxr-xr-x. 2 root root 4096 Feb 15 04:18 bin
drwxr-xr-x. 2 root root 6 Nov 19 15:32 boot
drwxr-xr-x. 5 root root 360 Mar 6 21:17 dev
drwxr-xr-x. 42 root root 4096 Mar 6 21:17 etc
drwxr-xr-x. 2 root root 6 Nov 19 15:32 home
...
Output truncated for brevity
...

选项-i--interactive)允许你运行 Docker 而不进入容器。但我们可以通过使用-i-t--tty)选项(或者直接使用-it)轻松覆盖这种行为,进入容器:

$ docker exec -it 00f343906df3 /bin/bash
root@00f343906df3:/usr/local/apache2#

我们应该进入容器的 bash CLI。在这里,我们可以执行其他常规的 Linux 命令。这种技巧在故障排除时非常有用。要退出容器控制台,只需输入exit或按Ctrl + D

启动和停止容器

我们还可以通过运行docker stopdocker start命令来停止和启动正在运行的容器:

输入以下命令以停止容器:

$ docker stop 00f343906df3
00f343906df3 

输入以下命令以启动容器:

$ docker start 00f343906df3
00f343906df3

Docker 端口映射

为了实际利用容器,我们需要使它从外部可访问。为此,我们在运行docker run命令时需要使用-p选项并提供一些参数:

$ docker run -d -p 8080:80 httpd
3b1150b5034329cd9e70f90ee21531b8b1ab1d4a85141fd3a362cd40db80e193

选项-p将容器的端口80映射到服务器端口8080。验证是否有httpd容器暴露并且 web 服务器正在运行:

$ curl localhost:8080
<html><body><h1>It works!</h1></body></html>

检查 Docker 容器

当容器正在运行时,我们可以使用docker inspect命令检查其参数。输出以 JSON 格式提供,给我们一个非常全面的结果:

$ docker inspect 00f343906df3
[
   {
       "Id": "00f343906df3f26c24e02cd61d6a37bbc36106b3b0372073673c2983cb6f",
       ...
       output truncated for brevity
       ...
   }
]

删除容器

为了删除一个容器,你可以使用docker rm命令。如果你要删除的容器正在运行,你可以先停止并删除它,或者使用-f选项,它会完成任务:

$ docker rm 3b1150b50343
Error response from daemon: You cannot remove a running container 3b1150b5034329cd9e70f90ee21531b8b1ab1d4a85141fd3a362cd40db80e193\. Stop the container before attempting removal or force remove

让我们尝试使用-f选项。

$ docker rm  -f 3b1150b50343

你可以使用以下命令来删除所有容器,包括已停止和正在运行的容器:

$ docker rm -f $(docker ps -qa)
830a42f2e727
00f343906df3
5e3820a43ffc
419e7ce2567e

验证所有容器是否已删除:

$ docker ps  -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

使用环境变量

由于容器的动态和无状态特性,应用程序在与中间件和其他应用服务通信时无法依赖固定的 IP 地址或 DNS 主机名。Docker 允许你将数据,如配置设置、加密密钥和外部资源地址,存储在环境变量中。

向容器传递环境变量

在运行时,环境变量会暴露给容器内的应用程序。你可以像使用docker run -e VARIABLE=VALUE一样,使用environment键在服务的容器中设置环境变量。你还可以通过不提供值的方式,直接将环境变量从你的 shell 传递给服务的容器,就像使用docker run -e VARIABLE一样。

环境变量用于设置特定的应用程序参数,如 IP 地址,以便服务器连接到数据库服务器地址和登录凭证。

一些容器启动脚本使用环境变量来执行应用程序的初始配置。

例如,mariadb镜像是为了使用多个环境变量来启动容器,并在启动时创建用户/数据库。该镜像使用以下重要参数(其中包括其他参数):

参数 描述
MYSQL_ROOT_PASSWORD 这个变量是必需的,它指定将为 MariaDB root超级用户账户设置的密码。
MYSQL_DATABASE 这个变量是可选的,允许你指定在镜像启动时要创建的数据库名称。如果提供了用户/密码(见下行参数),则该用户将被授予此数据库的超级用户访问权限(对应于GRANT ALL)。
MYSQL_USERMYSQL_PASSWORD 这些变量是可选的,用于一起创建新用户并设置该用户的密码。这个用户将被授予指定的MYSQL_DATABASE变量所指定数据库的超级用户权限。创建用户时,这两个变量都是必需的。

首先,我们可以尝试拉取并启动一个mariadb容器,而不指定与密码/用户/数据库相关的信息。它将失败,因为镜像期望这些参数。在这个例子中,我们在前台启动容器,以便看到所有错误信息:

$ docker pull mariadb
latest: Pulling from docker.io/library/mariadb
       ...
       output truncated for brevity
       ...
Digest: sha256:d5f0bc88ba397233677ff75b7b1de693d5e84527ecf2b4f59adebf8d0bcac3c4

现在尝试运行没有任何选项和参数的mariadb容器。

$ docker run mariadb
error: database is uninitialized and password option is not specified
You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD

docker run命令失败,因为 MariaDB 镜像的初始启动脚本无法找到所需的变量。该脚本要求我们至少提供 MariaDB 的 root 密码,以便启动数据库服务器。让我们尝试再次启动数据库容器,提供所有必需的变量:

$ docker run -d --name mariadb -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=example -e MYSQL_USER=example_user -e MYSQL_PASSWORD=password mariadb
721dc752ed0929dbac4d8666741b15e1f371aefa664e497477b417fcafee06ce

运行docker ps命令验证容器是否已成功启动:

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
721dc752ed09 mariadb "docker-entrypoint.sh" 10 seconds ago Up 9 seconds 3306/tcp mariadb

容器已成功创建。运行验证命令检查example_user是否有权限访问example数据库:

$ docker exec -it mariadb mysql -uexample_user -ppassword example -e "show databases;"
+--------------------+
| Database           |
+--------------------+
| example            |
| information_schema |
+--------------------+

启动脚本创建了一个名为example_user的用户,并将密码设置为password,这是我们在环境变量中指定的。它还为 root 用户配置了一个密码。你可以在hub.docker.com/_/mariadb/查看你可以指定的 MariaDB 镜像变量的完整列表。

链接容器

环境变量调整单个容器的设置。相同的方法也可以用于启动多层应用程序,其中一个容器或应用程序与另一个协同工作:

多层应用程序示例

在多层应用程序中,应用服务器容器和数据库服务器容器可能都需要共享数据库登录凭据等变量。当然,我们可以使用环境变量将所有数据库连接设置传递给应用程序容器。在将多个 -e 选项传递给 docker run 命令时,很容易出错,而且这种方法非常耗时,更不用说效率低下了。另一种选择是使用容器 IP 地址建立连接。我们可以使用 docker inspect 收集 IP 地址信息,但在多容器环境中追踪这些信息将非常困难。

这意味着,仅使用环境变量不足以构建多层应用程序,其中容器相互依赖。

Docker 有一个名为 链接容器 的功能来解决这个问题。它会自动将一个容器的所有环境变量复制到另一个容器。此外,通过链接容器,我们可以根据另一个容器的 IP 地址和暴露的端口定义环境变量。

使用链接容器非常简单,只需将 --link container:alias 选项添加到 docker run 命令中。例如,以下命令使用 DB 别名链接到一个名为 MariaDB 的容器:

$ docker run --link mariadb:db --name my_application  httpd

新的 my_application 容器将获得从链接容器 mariadb 定义的所有变量。这些变量名以 DB_ENV_ 为前缀,以避免与新容器的环境变量冲突。

请注意,别名都是大写的。

提供有关容器 IP 地址和端口信息的变量按照以下规则命名:

  • {ALIAS}_PORT_{exposed-port}_TCP_ADDR

  • {ALIAS}_PORT_{exposed-port}_TCP_PORT

继续以 MariaDB 镜像为例,应用容器将获得以下变量:

  • DB_PORT_3306_TCP_ADDR

  • DB_PORT_3306_TCP_PORT

如果链接的容器暴露多个端口,每个端口都会生成一组环境变量。

让我们来看一个例子。我们将创建一个需要访问数据库服务器的 WordPress 容器。这个集成将需要共享的数据库访问凭据。创建此应用程序的第一步是创建一个数据库服务器:

$ docker rm -f $(docker ps -qa)
$ docker run -d --name mariadb -e MYSQL_ROOT_PASSWORD=wordpress -e MYSQL_DATABASE=wordpress -e MYSQL_USER=wordpress -e MYSQL_PASSWORD=password mariadb
221462288bc578511154fe79411de002e05f08642b63a72bc7a8f16f7102e52b

下一步是运行一个 WordPress 容器。在该命令中,我们将把 wordpress 容器与 mariadb 容器链接起来:

$ docker run -d --name wordpress --link mariadb:mysql -p 8080:80 wordpress
Unable to find image 'wordpress:latest' locally
Trying to pull repository docker.io/library/wordpress ...
latest: Pulling from docker.io/library/wordpress
...
output truncated for brevity
...
Digest: sha256:670e4156377063df1a02f036354c52722de0348d46222ba30ef6a925c24cd46a
1f69aec1cb88d273de499ca7ab1f52131a87103d865e4d64a7cf5ab7b430983a

使用 docker exec 命令检查容器环境:

$ docker exec -it wordpress env|grep -i mysql
MYSQL_PORT=tcp://172.17.0.2:3306
MYSQL_PORT_3306_TCP=tcp://172.17.0.2:3306
MYSQL_PORT_3306_TCP_ADDR=172.17.0.2
MYSQL_PORT_3306_TCP_PORT=3306
MYSQL_PORT_3306_TCP_PROTO=tcp
...
output truncated for brevity
...

你可以看到所有这些变量,因为 WordPress 容器启动脚本处理了 mysql 链接。我们可以看到,这里链接设置了一些 MYSQL_ENVMYSQL_PORT 变量,WordPress 启动脚本将使用这些变量。

使用持久化存储

在前面的章节中,我们看到容器可以轻松创建和删除。但是,当容器被删除时,所有与该容器相关的数据也会消失。这就是为什么很多人将容器称为无状态架构的原因。但我们可以通过使用持久化卷来改变这一行为,保持所有数据。为了为 Docker 容器启用持久化存储,我们需要使用-v选项,它将容器文件系统与运行该容器的主机文件系统绑定。

在下一个示例中,我们将创建一个在主机/mnt/data文件夹中具有持久化存储的 MariaDB 容器。然后,我们删除 MariaDB 容器,并使用相同的持久化存储重新创建它。

首先,删除所有之前创建的容器:

$ docker rm -f $(docker ps -aq)

我们在开始之前必须为节点准备持久化存储。请注意,我们需要为持久化存储目录授予读写权限。MariaDB 应用程序与容器内的 MySQL 用户 UID=999 配合工作。此外,重要的是要提到,特殊的 SE Linux 安全上下文 svirt_sandbox_file_t 是必需的。可以通过以下命令实现:

# mkdir /mnt/data
# chown 999:999 /mnt/data
# chcon -Rt svirt_sandbox_file_t /mnt/data

下一步是创建运行 MariaDB 服务的容器:

$ docker run -d -v /mnt/data:/var/lib/mysql --name mariadb -e MYSQL_ROOT_PASSWORD=password mariadb
41139532924ef461420fbcaaa473d3030d10f853e1c98b6731840b0932973309

运行docker ps命令:

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
41139532924e mariadb "docker-entrypoint.sh" 4 seconds ago Up 3 seconds 3306/tcp mariadb

创建一个新数据库并验证该新数据库的存在:

$ docker exec -it mariadb mysql -uroot -ppassword -e "create database persistent;"

$ docker exec -it mariadb mysql -uroot -ppassword -e "show databases;"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| persistent         |
+--------------------+

验证/mnt/data目录中是否有新的数据,这些数据是由mariadb容器创建的。这就是我们使数据持久化的方式:

$ ls -l /mnt/data/
drwx------. 2 polkitd ssh_keys 4096 Mar 6 16:18 mysql
drwx------. 2 polkitd ssh_keys 20 Mar 6 16:18 performance_schema
drwx------. 2 polkitd ssh_keys 20 Mar 6 16:23 persistent
...
output truncated for brevity
...

删除mariadb容器并验证所有文件将被保留:

$ docker rm -f mariadb
mariadb

$ ls -l /mnt/data/
drwx------. 2 polkitd ssh_keys 4096 Mar 6 16:18 mysql
drwx------. 2 polkitd ssh_keys 20 Mar 6 16:18 performance_schema
drwx------. 2 polkitd ssh_keys 20 Mar 6 16:23 persistent
...
output truncated for brevity
...

我们将重新运行容器并验证先前创建的数据库 persistent 是否在容器删除和创建后仍然存在:

$ docker run -d -v /mnt/data:/var/lib/mysql --name mariadb mariadb
c12292f089ccbe294cf3b9a80b9eb44e33c1493570415109effa7f397579b235

正如你所看到的,名为persistent的数据库仍然存在。

在继续下一部分之前,删除所有容器:

$ docker rm -f $(docker ps -aq)

创建自定义 Docker 镜像

Docker 社区为大多数流行的软件应用程序提供了 Docker 镜像。例如,包含 Web 服务器(Apache、Nginx 等)、企业应用平台(JBoss EAP、Tomcat)、编程语言镜像(Perl、PHP、Python)等。

在大多数情况下,你无需构建自己的 Docker 镜像来运行标准软件。但是,如果你有需要自定义应用程序的业务需求,你可能需要创建自己的 Docker 镜像。

创建新 Docker 镜像有多种方式:

  • 提交:从运行中的容器创建 Docker 镜像。Docker 允许你使用docker commit命令将一个正在运行的容器转换为 Docker 镜像。这意味着镜像层将作为独立的 Docker 镜像存储。这种方法是创建新镜像的最简单方式。

  • 导入/导出:这与第一个方法类似,但使用了另一个 Docker 命令。运行的容器层会通过 docker export 命令保存到文件系统中,然后通过 docker import 命令重新创建镜像。我们不推荐使用这种方法来创建新镜像,因为第一个方法更简单。

  • Dockerfile:使用 Dockerfile 构建 Docker 镜像。Dockerfile 是一个包含多个步骤的纯文本文件,这些步骤有时被称为指令。这些指令可以在容器内运行特定的命令或将文件复制到容器中。用户可以通过 Dockerfile 启动构建过程,Docker 守护进程会在临时容器中执行 Dockerfile 中的所有指令。然后,这个容器会被转换成 Docker 镜像。这是创建新 Docker 镜像最常见的方法。如何从 Dockerfile 构建自定义 Docker 镜像将在后续章节中详细介绍。

  • 从零开始:构建一个基础的 Docker 镜像。在前两种方法中,Docker 镜像是通过 Docker 镜像创建的,而这些 Docker 镜像又是从基础 Docker 镜像创建的。除非自己创建一个,否则无法修改这个基础镜像。如果你想了解镜像内部的内容,可能需要创建一个基础镜像。可以通过两种方式来实现:

    • 使用 tar 命令创建一个基础镜像层。

    • 使用特殊的 Dockerfile 指令(从零开始)。两种方法将在后续章节中描述。

使用 docker commit 自定义镜像

通常建议所有 Docker 镜像都应该通过 Dockerfile 构建,以创建干净且正确的镜像层,避免包含不需要的临时文件和日志文件,尽管一些供应商提供的 Docker 镜像没有可用的 Dockerfile。如果需要修改现有镜像,可以使用标准的 docker commit 功能将现有容器转换为新镜像。

作为示例,我们将尝试修改现有的 httpd 容器并从中创建一个镜像。

首先,我们需要获取 httpd 镜像:

$ docker pull httpd
Using default tag: latest
Trying to pull repository docker.io/library/httpd ...
latest: Pulling from docker.io/library/httpd
...
output truncated for brevity
...
Digest: sha256:6e61d60e4142ea44e8e69b22f1e739d89e1dc8a2764182d7eecc83a5bb31181e

接下来,我们需要一个正在运行的容器。这个容器将作为未来镜像的模板。

$ docker run -d --name httpd httpd
c725209cf0f89612dba981c9bed1f42ac3281f59e5489d41487938aed1e47641

现在我们可以连接到容器并修改其层。作为示例,我们将更新 index.html 文件:

$ docker exec -it httpd /bin/sh
# echo "This is a custom image" > htdocs/index.html
# exit

让我们来看一下使用 docker diff 命令所做的更改。该命令会显示从原始镜像中修改的所有文件。输出结果如下:

$ docker diff httpd
C /usr
C /usr/local
C /usr/local/apache2
C /usr/local/apache2/htdocs
C /usr/local/apache2/htdocs/index.html
...
output truncated for brevity
...

下表展示了 docker diff 命令的文件状态:

符号 描述
A 添加了文件或目录
D 删除了文件或目录
C 文件或目录已更改

在我们的例子中,docker diff httpd 命令显示 index.html 文件已被更改。

从运行中的容器创建新镜像:

$ docker commit httpd custom_image
sha256:ffd3a523f9848776d65de8302253de9dc78e4948a792569ee46fad5c099312f6

验证新镜像是否已经创建:

$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
custom_image latest ffd3a523f984 3 seconds ago 177.4 MB
docker.io/httpd latest 01154c38b473 2 weeks ago 177.4 MB

最后一步是验证镜像是否正常工作:

$ docker run -d --name custom_httpd -p 80:8080 custom_image
78fc5731d62e5a6377a7de152c0ba25d350603e6d97fa26967e06a82c8257e71
$ curl localhost:8080
This is a custom image

使用 Dockerfile 构建

通常,使用 Docker 容器的人期望实现高度自动化,而 docker commit 命令很难自动化。幸运的是,Docker 可以通过读取一个通常称为 Dockerfile 的特殊文件中的指令来自动构建镜像。Dockerfile 是一个文本文件,包含了用户可以在命令行上调用的所有命令,用于组装镜像。通过使用 docker build,用户可以创建一个自动化构建,依次执行多个命令行指令。在 CentOS 7 上,你可以通过 Dockerfile 内置的文档页面 man Dockerfile 学到更多。

Dockerfile 有许多指令,帮助 Docker 根据你的需求构建镜像。下面是一个 Dockerfile 示例,它允许我们实现与上一节相同的结果:

$ cat Dockerfile
FROM httpd
RUN echo "This is a custom image" > /usr/local/apache2/htdocs/index.html

一旦创建了这个 Dockerfile,我们可以使用 docker build 命令构建自定义镜像:

$ docker build -t custom_image2 . 
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM httpd
 ---> 01154c38b473
Step 2 : RUN echo "This is a custom image" > /usr/local/apache2/htdocs/index.html
 ---> Using cache
 ---> 6b9be8efcb3a
Successfully built 6b9be8efcb3a

请注意,第一行末尾的 . 非常重要,它指定了工作目录。或者,你可以使用 ./ 或甚至 $(pwd)。所以完整的命令将是:

docker build -t custom_image2 .

docker build -t custom_image2 ./

docker build -t custom_image2 $(pwd)

$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
custom_image2 latest 6b9be8efcb3a 2 minutes ago 177.4 MB
custom_image latest ffd3a523f984 19 minutes ago 177.4 MB
docker.io/httpd latest 01154c38b473 2 weeks ago 177.4 MB

使用 Docker 历史记录

我们可以使用 docker history 来查看镜像修改的历史:

$ docker history custom_image2
IMAGE CREATED CREATED BY SIZE COMMENT
6b9be8efcb3a 21 hours ago /bin/sh -c echo "This is a custom image" > /u 23 B
01154c38b473 2 weeks ago /bin/sh -c #(nop) CMD ["httpd-foreground"] 0 B
...
output truncated for brevity
...

请注意,添加了一个新层,6b9be8efcb3a。这就是我们与原始 httpd 镜像相比更改 index.html 文件内容的地方。

Dockerfile 指令

一些 Dockerfile 指令如下表所示:

指令 描述及示例
FROM image[:tag] 设置在构建过程中使用的基础镜像。示例:FROM httpd FROM httpd:2.2
RUN <command> <parameters> RUN 指令在当前镜像上方的新层中执行任何命令并提交结果。示例:RUN yum install -y httpd &&\ echo "custom answer" >/var/www/html/index.html
RUN ["command", "param1", "param2"] 这与最后一个相同,但采用 Docker 格式。
COPY <src> <dst> COPY 指令将新的文件从 <src> 复制并添加到容器的 <dst> 路径中。<src> 必须是相对于正在构建的源目录(构建上下文)的文件或目录路径,或者是远程文件的 URL。示例:COPY index.html /var/www/html/index.html
ENTRYPOINT ["executable", "param1", "param2"] ENTRYPOINT 帮助你配置一个可以作为可执行文件运行的容器。当你指定一个 ENTRYPOINT 时,整个容器就像只运行那个可执行文件一样。示例:ENTRYPOINT ["/usr/sbin/httpd","-D","FOREGROUND"] 在大多数情况下,ENTRYPOINT 的默认值是 /bin/sh -c,这意味着 CMD 将被解释为要运行的命令
EXPOSE <port> 此指令通知 Docker 守护进程,某个应用程序将在运行时监听这个端口。当与独立的 Docker 容器一起工作时,这个指令并不特别有用,因为端口发布是通过 CLI 的-p参数来完成的,但它在 OpenShift 中创建新应用程序服务时会使用,并且 Docker 本身在容器内导出默认环境变量时也会用到。
CMD ["executable", "param1", "param2"] 提供参数给ENTRYPOINT命令,并且可以在运行时通过docker run命令进行覆盖。例如:CMD [" /usr/sbin/httpd", "-D", "FOREGROUND"]

当运行docker build命令时,Docker 从上到下读取提供的 Dockerfile,为每个指令创建一个单独的层并将其放入内部缓存中。如果 Dockerfile 中的某个指令被更新,它将使相应的缓存层以及所有后续层失效,这将迫使 Docker 在再次运行docker build命令时重新构建这些层。因此,将最易变的指令放在 Dockerfile 的末尾更为有效,这样可以最小化失效的层数,并最大化缓存的使用。例如,假设我们有一个包含以下内容的 Dockerfile:

$ cat Dockerfile
FROM centos:latest
RUN yum -y update
RUN yum -y install nginx, mariadb, php5, php5-mysql
RUN yum -y install httpd
CMD ["nginx", "-g", "daemon off;"]

在这个例子中,如果你选择使用 MySQL 而不是 MariaDB,那么第二个 RUN 命令创建的层,以及第三个命令创建的层,将会被失效,这对于复杂的镜像意味着构建过程明显变长。

参考以下示例。Docker 包含了最小操作系统的镜像。这些基础镜像可以用来在其上构建自定义镜像。在这个例子中,我们将使用一个 CentOS 7 基础镜像从零开始创建一个 Web 服务器容器:

  1. 首先,我们需要创建一个project目录:
$ mkdir custom_project; cd custom_project

然后,我们创建一个包含以下内容的 Dockerfile:

$ cat Dockerfile
FROM centos:7
RUN yum install httpd -y
COPY index.html /var/www/html/index.html
ENTRYPOINT ["/usr/sbin/httpd","-D","FOREGROUND"]
  1. 创建index.html文件:
$ echo "A new cool image" > index.html
  1. 使用docker build命令构建镜像:
$ docker build -t new_httpd_image .
Sending build context to Docker daemon 3.072 kB
...
output truncated for brevity
...
Successfully built 4f2f77cd3026
  1. 最后,我们可以检查新镜像是否存在,并且包含所有需要的镜像层:
$ docker history new_httpd_image
IMAGE CREATED CREATED BY SIZE COMMENT
4f2f77cd3026 20 hours ago /bin/sh -c #(nop) ENTRYPOINT "/usr/sbin/htt 0 B
8f6eaacaae3c 20 hours ago /bin/sh -c #(nop) COPY file:318d7f73d4297ec33 17 B
e19d80cc688a 20 hours ago /bin/sh -c yum install httpd -y 129 MB
...
output truncated for brevity
...

前三层是我们在 Dockerfile 中添加的指令。

总结

本章中,我们讨论了容器架构,使用了 Docker 镜像和容器,检查了不同的 Docker 注册表,学习了如何管理容器的持久存储,最后了解了如何使用 Dockerfile 构建 Docker 镜像。所有这些技能都将在[第三章中用到,CRI-O 概述,我们将在那里开始使用 Kubernetes。Kubernetes 是 OpenShift 的一个重要组成部分,至关重要。它们就像滚雪球一样:Kubernetes 需要 Docker 技能,OpenShift 又需要 Kubernetes 技能。

在下一章中,我们将使用 Kubernetes。Kubernetes 是 Docker 容器的行业标准编排层。在这里,你将安装并运行一些基本的 Docker 容器,使用 Kubernetes 进行管理。

问题

  1. Docker 的三个主要组件是什么?请选择一个:

    1. Docker 容器、Docker 镜像、Docker 注册中心

    2. Docker Hub、Docker 镜像、Docker 注册中心

    3. Docker 运行时、Docker 镜像、Docker Hub

    4. Docker 容器、Docker 镜像、Docker Hub

  2. 选择两个有效的注册中心类型:

    1. 个人注册中心

    2. 私有注册中心

    3. 公共注册中心

    4. 安全注册中心

  3. Docker 持久存储的主要目的是确保在容器死亡时,应用数据能够被保存:

    1. 正确

    2. 错误

  4. 什么 Linux 特性控制 Docker 容器的资源限制?选择一个:

    1. Cgroups

    2. 命名空间

    3. SELinux

    4. chroot

  5. 什么命令可以用来从 Dockerfile 构建自定义镜像?选择两个:

    1. docker build -t new_httpd_image .

    2. docker build -t new_httpd_image .\

    3. docker build -t new_httpd_image ($pwd)

    4. docker build -t new_httpd_image ./

  6. docker commit 命令将 Docker 镜像保存到上游仓库:

    1. 正确

    2. 错误

进一步阅读

由于我们正在讲解 Docker 容器的基础知识,你可能有兴趣深入了解一些特定的主题。这里有一份链接列表,供你参考,帮助你更深入地了解 Docker 和容器的相关内容:

第二章:Kubernetes 概述

在上一章中,我们讨论了容器架构,使用了 Docker 镜像和容器,查看了不同的 Docker 注册表,学习了如何管理容器的持久存储,最后,学习了如何使用 Dockerfile 构建自己的 Docker 镜像。所有这些技能将在 第三章,“CRI-O 概述”中使用到,届时我们将开始使用 Kubernetes。Kubernetes 是 OpenShift 中的一个关键组件。一切就像滚雪球一样:Kubernetes 需要 Docker 技能,OpenShift 需要 Kubernetes 技能。

在分布式环境中进行容器管理是困难的,但 Kubernetes 使其变得容易。这篇简短的 Kubernetes 介绍将让你了解 Kubernetes 是什么,以及它是如何工作的。在本章中,你将学习如何使用简化的方法安装和配置 Kubernetes 集群。我们还将解释容器管理的基础知识,包括一些关于 pods、services 和 routes 的理论。我们将向你展示如何在 Kubernetes 集群中部署应用程序。

由于在我们的实验环境中只有一个 Kubernetes 节点,因此我们将在本章中交替使用 Kubernetes 集群和 Minikube 虚拟机的术语。

在本章中,我们将关注以下内容:

  • 容器管理系统概述

  • Kubernetes 与 Docker Swarm 的区别

  • Kubernetes 关键概念

  • Kubernetes 安装与配置

  • 使用 kubectl

  • 清理虚拟环境

  • Kubernetes 限制

技术要求

在本章中,我们将使用以下技术和软件:

  • Minikube

  • Bash Shell

  • GitHub

  • Kubernetes

  • Docker

  • Firefox

你需要在笔记本电脑或任何其他虚拟环境中安装 Minikube。所有关于安装 Minikube 的指令可以在 kubernetes.io/docs/tasks/tools/install-minikube/ 找到。

本章的所有代码都可以在 GitHub 上找到:github.com/PacktPublishing/Learn-OpenShift

Bash Shell 将作为虚拟环境的一部分使用。

可以使用 Firefox 或任何其他浏览器来浏览 Docker Hub。

容器管理系统概述

与虚拟化相比,容器在密度、部署速度和可扩展性方面提供了无与伦比的优势。但仅凭容器本身并不足以满足现代业务的所有需求,今天的业务期望基础设施能够适应动态挑战。启动和管理几十个容器相对简单,但当容器数量增加到数百个时,问题就变得复杂起来,这在大型工作负载中非常常见。这就是容器编排引擎COE)发挥作用的地方。它们为容器带来了真正的力量,提供了多种机制来快速部署、销毁和扩展多个容器。

目前有多种容器管理解决方案,其中最流行的是 Kubernetes 和 Docker Swarm:

  • Kubernetes:Kubernetes 于 2015 年 7 月首次发布,源自 Google 开发的集群管理和作业调度系统 Borg。Kubernetes 也是由 Google 工程师开发的;实际上,许多曾在 Borg 项目中工作的开发人员后来转而参与 Kubernetes 的开发。与 Docker 一样,Kubernetes 是用 Go 语言编写的,而 Go 语言也是 Google 于 2007 年设计和实现的。它围绕资源的概念构建——复杂的 API 实体,作为底层机制的接口,并以 YAML 或 JSON 格式序列化。所有软件组件都运行在两种类型的机器上:主节点和工作节点。主节点负责管理、调度和同步功能,而工作节点为容器的运行提供运行环境。

  • Docker Swarm:Docker Swarm 是由 Docker 项目提供的原生容器编排解决方案。它具备 Kubernetes 提供的许多功能,但使用不同的机制,并可用于快速部署单个服务或甚至一组服务到工作节点上。Swarm 集群由两种类型的节点组成:管理节点和工作节点。管理节点控制容器的位置,在 Swarm 术语中称为任务,工作节点负责容器的实际运行。

Kubernetes 与 Docker Swarm

Kubernetes 和 Docker Swarm 是最常用的编排框架。它们提供了一套相似的功能,基本上解决了相同的问题——在不安全且高度动态的环境中管理容器。虽然它们的一些功能有重叠,但也存在显著差异,系统的选择取决于许多因素,例如容器数量、可用性要求和团队专业知识等。

表格展示了最重要的差异:

Kubernetes Docker Swarm
一个具有独立依赖关系的单独模块化设计项目。 开箱即用的原生容器编排解决方案。
学习曲线较陡峭,因为涉及新的概念和复杂的架构。 容易上手;使用熟悉的术语;更轻量。
Pod 是一个最小的部署单元,表示一组容器。与其他应用程序的集成通过服务完成,在此情况下,服务表示一个一致的IP:port对。 应用程序以容器作为服务的形式,在整个集群或一部分工作节点中使用标签进行部署。
通过部署/复制控制器支持自动扩展,方法是指定所需的 Pod 数量。HorizontalPodAutoscaler资源提供考虑 CPU 利用率的动态自动扩展。 系统本身不支持自动扩展,但仍然可以进行手动扩展。
持久存储层分为两个组件,PVs 和 PVCs,它们根据请求动态绑定在一起,可以用于实现共享存储。 存储卷直接挂载到容器中。
新的主节点可以加入现有的集群,但不支持节点的提升/降级。 工作节点可以轻松地提升为管理节点,反之亦然。
服务根据它们所创建的项目和名称被分配唯一的 DNS 名称,因此每个服务可以通过其名称而无需域名来访问同一命名空间中的其他服务。 每个服务都会在内部 DNS 中注册,名称仅基于服务本身的名称。

Kubernetes 核心概念

像任何复杂的系统一样,Kubernetes 集群可以从多个角度进行观察。从基础设施的角度来看,它由两组节点组成;这些节点可以是裸金属服务器,也可以是虚拟机:

  • 主节点

这种类型的节点负责集群管理、网络分配、配额执行、同步和通信。主节点作为客户的主要联系点——无论是实际的用户还是外部系统。在最简单的设置中,集群中只有一个主节点,但高可用集群需要至少两个主节点,以防止常见的故障情况。主节点运行的最重要的服务是 API。

  • 节点

节点实际执行托管 Docker 容器的工作。更具体地说,节点为运行 Pod 提供运行时环境,Pod 将在本书后面进行描述。这些服务器运行 kubelet 服务以管理 Pod:

Kubernetes 架构

从逻辑上讲,Kubernetes API 提供了许多资源,允许你使用 Kubernetes 提供的各种机制,同时抽象一些底层实现细节。这些资源可以使用 YAML 或 JSON 格式定义。以下是其中的一些:

  • 命名空间:这些资源的目的是在多租户环境中分隔用户和其项目的组织单元。此外,它们还用于更细粒度的访问控制和配额执行。几乎所有 Kubernetes 资源,除了卷和命名空间本身,都是基于命名空间的,这意味着它们的名称在任何给定的命名空间中必须是唯一的。

  • Pod:Pod 代表一组容器,每个 Pod 是 Kubernetes 中的基本管理单元。Pod 中的所有容器共享相同的存储卷和网络。

  • 服务:它们代表客户端与运行在 Pod 中的实际应用之间的接口。一个服务是一个IP:端口对,它将流量以轮询的方式转发到后端 Pod。拥有一个一致的地址和端口可以避免客户端需要跟随集群中任何瞬时的变化。

  • 复制控制器RC):简而言之,这些资源定义了必须复制的 Pod 数量。它们的定义包括描述要启动的 Pod 的 Pod 模板,每个 RC 包含的一个参数是要维护的副本数。如果由于某种原因一个或多个 Pod 宕机,Kubernetes 将启动新的 Pod 以满足这个数量。

  • 持久卷PV):这些资源抽象了实际的物理存储系统,无论是 NFS、iSCSI 还是其他什么。通常,它们由集群管理员创建,并可以通过 PVC 绑定机制挂载到 Pod 内,后者将在后文中提到。

  • 持久卷声明PVC):PVC 代表对存储资源的请求。Pod 定义不会直接使用 PV,而是通过 Kubernetes 将 PV 与 PVC 绑定来实现。

  • Secrets(机密):用于在 Pod 内部传递敏感数据,如密钥、令牌和密码。

  • 标签:标签提供了一种通过选择器作用于一组资源的机制。例如,服务使用选择器来指定将流量转发到哪些 Pod。当使用相同标签启动新的 Pod 时,它们会动态地与在其定义中指定该标签作为选择器的服务关联。

这是一个示例场景,其中有两个团队分别位于丹佛和凤凰城,并且拥有独立的命名空间。选择器、标签和副本数使用与实际 YAML 定义中的服务、Pod 和复制控制器相同的表示法来指定:

Kubernetes 资源

从服务的角度来看,Kubernetes 可以表示为一组相互作用的服务:

  • 这些服务通常运行在主节点上:

    • etcd:这是一个分布式的键值配置存储,用于存储所有元数据和集群资源。由于其仲裁模型,建议您运行一个奇数数量的 etcd 节点,最少从三个节点开始,以确保高可用性。

    • kube-apiserver:暴露 Kubernetes API 给客户端的服务。由于其无状态特性,它可以通过水平扩展的方式部署为高可用配置。

    • kube-scheduler:控制新创建 Pod 在节点上的分配的组件。此过程考虑了硬件/策略限制、数据本地性和亲和性规则等因素。值得注意的是,从集群的角度来看,主节点与其他节点没有区别,因此可以有资格运行 Pod,尽管最佳实践建议不要给主节点增加额外的负担,而应将它们仅用于管理功能。

    • kube-controller-manager:运行各种控制器的组件——其中一些是副本控制器,用于保持所需数量的运行中 Pod,节点控制器用于发现已下线的节点,卷控制器用于将 PV 与 PVC 绑定,以及一个端点控制器,用于将服务和 Pod 绑定在一起。

    • cloud-controller-manager:提供与底层云提供商(如 DigitalOcean 和 Oracle Cloud Infrastructure)集成的服务。

  • 这些服务通常在节点上运行:

    • kubelet:该服务使用 Pod 规范来管理其 Pod,并进行定期的健康检查。

    • kubeproxy:该组件通过提供 TCP 和 UDP 转发能力,在一组后端 Pod 之间实现服务抽象。

    • 容器运行时环境:该组件在 Kubernetes 中由底层容器技术表示。写本文时,Kubernetes 支持 docker 和 rkt 作为运行时:

Kubernetes 服务

Kubernetes 安装与配置

在本章中,你将安装 Minikube——一个简单的单节点 Kubernetes 集群。虽然它不适用于生产环境工作负载,但它是一个快速学习集群管理基础的有用工具。虽然它支持多种虚拟机提供商的驱动程序,但在本教程中我们将使用 KVM2 驱动程序,因为 KVM 虚拟化甚至可以在基础的 Linux 环境中使用。

最简单的方法是访问kubernetes.io/docs/getting-started-guides/minikube/,并在你喜欢的操作系统上安装 Minikube。然后访问kubernetes.io/docs/tasks/tools/install-kubectl/,安装kubectlkubectl是管理 Kubernetes 的 CLI 命令。完成后,到了启动 Minikube 的时候:

$ minikube start
Starting local Kubernetes v1.9.0 cluster...
Starting VM...
Downloading Minikube ISO
...
<output omitted>
...
Kubectl is now configured to use the cluster.
Loading cached images from config file.

下载和设置阶段完成后,使用minikube命令检查你的 Kubernetes 集群状态:

$ minikube status
minikube: Running
cluster: Running
kubectl: Correctly Configured: pointing to minikube-vm at 192.168.99.101

使用kubectl命令检查 Kubernetes 集群状态:

$ kubectl cluster-info
Kubernetes master is running at https://192.168.99.101:8443

我们还可以打开浏览器或使用 curl 命令来验证 Kubernetes API:

$ curl https://192.168.99.101:8443
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "Unauthorized",
"reason": "Unauthorized",
"code": 401
}

Kubernetes 附带了一个漂亮的仪表盘,提供美观的图形用户界面,可以通过 HTTP 在30000端口访问(例如,http://192.168.99.100:30000/)。你可以使用我们用于集群验证的相同 IP 打开浏览器:

Kubernetes 图形用户界面

目前没有太多可以查看的内容,因为我们简单的集群只有一个节点、一个服务和三个默认的命名空间。这是管理 Kubernetes 的一种方式,但为了有效使用其所有功能并排除问题,你需要熟悉使用 CLI,这也是下一节的内容。

使用 kubectl

Kubectl 是一个用于管理 Kubernetes 集群及其资源的命令行接口。在本节中,你将学习到最常用的命令及其使用场景。

所有命令的语法遵循以下约定:

$ kubectl <COMMAND> <RESOURCE_TYPE> <RESOURCE_NAME> <OPTIONS>

括号中的命令<>表示以下内容:

  • COMMAND:要对一个或多个资源执行的操作。

  • RESOURCE_TYPE:要操作的资源类型,例如 Pod 或服务。

  • RESOURCE_NAME:要管理的资源名称。

  • OPTIONS:用于修改 kubectl 命令行为的各种标志。它们的优先级高于默认值和环境变量,因此会覆盖它们。

获取帮助

kubectl 有数百个不同的子命令、选项和参数。幸运的是,kubectl 有非常好的帮助选项。第一个是手册页。如果你使用的是 macOS 或 Linux,可以运行man-f kubectl命令查看与 kubectl 相关的手册页:

$ man -f kubectl
kubectl(1) - kubectl controls the Kubernetes cluster manager
kubectl-alpha(1), kubectl alpha(1) - Commands for features in alpha
kubectl-alpha-diff(1), kubectl alpha diff(1) - Diff different versions of configurations
...
<output omitted>
...

如果由于某些原因你无法在系统上使用手册页,可以直接运行kubectl命令而不带任何选项或参数。它会显示可用子命令的列表:

$ kubectl
kubectl controls the Kubernetes cluster manager.
Find more information at https://github.com/kubernetes/kubernetes.
Basic Commands (Beginner):
  create Create a resource from a file or from stdin.
  expose Take a replication controller, service, deployment or pod and expose it as a new Kubernetes Service
...
<output omitted>
...
Basic Commands (Intermediate):
 get Display one or many resources
 explain Documentation of resources
...
<output omitted>
...
Use "kubectl <command> --help" for more information about a given command.
Use "kubectl options" for a list of global command-line options (applies to all commands)

下一步是通过运行kubectl <command>kubectl <command> --help命令来检查可用资源的列表,例如,kubectl get

$ kubectl get
  * all
  * certificatesigningrequests (aka 'csr')
  * clusterrolebindings
...
<output omitted>
...
Use "kubectl explain <resource>" for a detailed description of that resource (e.g. kubectl explain pods).
See 'kubectl get -h' for help and examples.

如你所见,你还可以通过运行kubectl get -h来获取kubectl get命令的示例,并通过运行kubectl explain pods来获取详细的资源解释。kubectl命令非常易于操作和使用。

使用 kubectl get 命令

第一个必不可少的命令是kubectl get nodes。它会告诉我们可用的 Kubernetes 节点数量:

$ kubectl get nodes
NAME      STATUS  ROLES   AGE   VERSION
minikube   Ready   <none> 2h    v1.9.0

在我们的案例中,节点数将等于 1,因为我们使用的是一个虚拟机进行实践。正如我们之前提到的,为了让不同的项目能够在同一或不同的节点上共存,使用了命名空间。你可能会猜到,我们应该使用的命令是kubectl get namespaces

$ kubectl get namespaces
NAME          STATUS   AGE
default       Active   15h
kube-public   Active   15h
kube-system   Active   15h

它会告诉你,在使用 Minikube 安装 Kubernetes 时,默认有三个命名空间可用:

**名称       ** 描述
default 所有没有指定命名空间的资源所在的命名空间。当没有指定命名空间时,使用此命名空间。
kube-public 用于必须对未认证用户公开的资源。
kube-system 顾名思义,它是 Kubernetes 内部使用的,处理所有系统资源。

这里最后一个主要缺失的组件是 pods;如前所述,pods 代表一组容器,pod 是 Kubernetes 中的基本管理单元。在我们的案例中,pods 是 Docker 容器。目前我们还没有任何正在运行的 pods,可以通过运行 kubectl get pods 来轻松验证:

$ kubectl get pods
No resources found.

它显示 No resources found,这是因为 pod 是 Kubernetes 资源,类似于我们在本书中将要介绍的其他资源。

kubectl get pods 类似,您可以获取任何其他 Kubernetes 资源的状态。我们将在本章稍后讨论其他 Kubernetes 资源。

运行 Kubernetes pods

与 Docker 一样,我们可以使用 kubectl run 命令来运行一个 Kubernetes pod。让我们从一个简单的 Web 服务器示例开始:

$ kubectl run httpd --image=httpd

我们可以通过运行 kubectl get pods 命令来验证结果,获取 Kubernetes pods 的列表:

$ kubectl get pods
NAME                      READY    STATUS    RESTARTS    AGE
httpd-8576c89d7-qjd62      1/1     Running    0          6m

当您第一次运行此命令时,您可能会看到 Kubernetes pod 状态显示为 ContainerCreating。发生的情况是 Docker httpd 镜像正在下载到 Minikube VM 上。请耐心等待,给它一些时间下载镜像。几分钟后,您应该能看到容器状态为 Running

kubectl run 命令不仅仅是下载一个镜像并从中运行一个容器。我们将在本章后面讲解这个。

8576c89d7-qjd62 部分是自动生成的。我们将在本章后面讨论这个。

实际上,这个 pod 是 Minikube VM 中的一个 Docker 容器,我们可以轻松验证这一点。首先,我们需要使用 minikube ssh 登录到 Minikube VM,然后运行 docker ps 命令:

$ minikube ssh
$
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c52c95f4d241 httpd "httpd -g 'daemon ..." 12 minutes ago Up 12 minutes k8s_httpd-container_httpd_default_39531635-23f8-11e8-ab32-080027dcd199_0
...
<output omitted>
...

我们可以尝试杀掉这个 httpd Docker 容器,但 Kubernetes 会自动启动一个新的容器:

$ docker rm -f c52c95f4d241   

再次检查容器状态:

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5e5460e360b6 httpd "httpd -g 'daemon ..." 5 seconds ago Up 5 seconds k8s_httpd-container_httpd_default_4f5e05df-2416-11e8-ab32-080027dcd199_0
$ exit

请注意,httpd 容器仍然在运行,但具有另一个 ID。初始的 ID 是 c52c95f4d241,现在变成了 5e5460e360b6(您将看到其他 ID)。这就是 Kubernetes 的一个优点:如果一个容器挂掉,Kubernetes 会自动启动一个新的。我们将在本章稍后详细讨论这个问题。

描述 Kubernetes 资源

我们可以通过运行 kubectl describe 命令快速查看该 pod 的内部信息:

$ kubectl describe pod httpd-8576c89d7-qjd62
Name: httpd
Namespace: default
Node: minikube/192.168.99.101
Start Time: Sat, 10 Mar 2018 00:01:33 -0700
Annotations: <none>
Status: Running
IP: 172.17.0.4
...
<output omitted>
...

它为我们提供了足够的信息,以便在必要时高效地定位 pod 并进行适当的故障排除。在我们的案例中,我们可以 ssh 登录到 Minikube VM 并运行 curl 命令,检查 pod 是否正确运行 Web 服务器。

您可能需要为 curl 命令使用另一个 IP 地址;在我们的案例中,它是 172.17.0.4,这是从 kubectl describe 命令的输出中获取的。

$ minikube ssh
$
$ curl 172.17.0.4
<html><body><h1>It works!</h1></body></html>
$ exit

请注意,这个 pod 仅在 Kubernetes 集群内可访问。这就是为什么我们需要登录到 Minikube VM 的原因。如果我们尝试从本地 PC 访问这个地址,它将无法工作。我们将在接下来的部分中讨论这个问题。

编辑 Kubernetes 资源

我们还可以使用 kubectl edit pod httpd-8576c89d7-qjd62 编辑正在运行的容器的属性。此时我们不会更改任何内容,但你可以尝试在我们删除容器之前更改某些内容。我们将在后续章节中与 OpenShift 一起使用编辑命令。

kubectl edit 命令默认使用 vi 编辑器。如果你不熟悉这个文本编辑器,建议先学习如何使用 vi,否则可能会遇到麻烦。

另一个小技巧是,通过运行 export EDITOR=nano 来更改编辑器,其中 nano 是你喜欢的文本编辑器。

同样,你可以编辑其他任何 Kubernetes 资源。我们将在本章后面讨论其他 Kubernetes 资源。

暴露 Kubernetes 服务

当我们使用 kubectl run 命令运行一个 Pod 时,这个 Pod 只对 Kubernetes 内部可访问。在大多数情况下,我们希望这个 Pod 也能从外部访问。这时 kubectl expose 命令就派上用场了。让我们再创建一次 httpd Pod,然后将其暴露到外部:

$ kubectl run httpd --image=httpd

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
httpd-66c6df655-8h5f4 1/1 Running 0 27m

现在,让我们使用 kubectl expose 命令,将 httpd Web 服务器暴露到 Kubernetes 外部:

$ kubectl expose pod httpd-66c6df655-8h5f4 --port=80 --name=httpd-exposed --type=NodePort

使用 kubectl expose 命令时,我们指定了几个选项:

  • port:我们将要暴露到 Kubernetes 集群外部的 Pod(Docker 容器)端口。

  • name:Kubernetes 服务名称。

  • type:Kubernetes 服务类型。NodePort 使用 Kubernetes 节点 IP。

获取暴露的 Kubernetes 服务列表的命令是 kubectl get services

$ kubectl get services
NAME         TYPE     CLUSTER-IP   EXTERNAL-IP   PORT(S)      AGE
kubernetes   ClusterIP 10.96.0.1    <none>     443/TCP         1d
httpd-exposed NodePort 10.110.40.149<none>     80:31395/TCP    3s

请注意,端口 80 映射到了 Minikube 虚拟机上的动态端口 31395。该端口在 30000–32767 范围内动态选择。

此外,还有一个 ClusterIP 字段,IP 地址为 10.110.40.149,这是为 httpd-expose 服务分配的。暂时不用关注这个内容,我们将在本书后面讨论这个问题。

最后,使用 curl 检查 httpd 服务器是否可以从 Kubernetes 集群外部访问:

$ curl 192.168.99.101:31395
<html><body><h1>It works!</h1></body></html>

如果你在网页浏览器中打开这个链接,你应该能在网页上看到It works!

使用 Kubernetes 标签

当你的应用程序由一个 Pod 和一个服务组成时,操作这些资源没有问题。但当应用程序规模扩大,或者你有数十个甚至数百个项目、Pods、服务和其他 Kubernetes 资源时,操作和有效地排查问题就会变得更加困难。此时,我们可以使用本章前面提到的 Kubernetes 标签。我们将使用标签再运行几个 Kubernetes Pods:

$ kubectl run httpd1 --image=httpd --labels="app=httpd-demo1"
$ kubectl run httpd2 --image=httpd --labels="app=httpd-demo2"

检查我们目前的 Kubernetes Pods:

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
httpd-8576c89d7-qjd62 1/1 Running 0 17m
httpd1-c9f7d7fd9-rn2nz 1/1 Running 0 21s
httpd2-5b4ff5cf57-9llkn 1/1 Running 0 15s

现在,假设你有至少 10 个或更多 Pods。为了高效地筛选输出结果,我们可以使用 -l 选项:

$ kubectl get pods -l="app=httpd-demo2"
NAME                   READY       STATUS      RESTARTS       AGE
httpd2-5b4ff5cf57-9llkn 1/1         Running    0              2m

使用 Kubernetes 标签筛选输出并非唯一的应用场景。标签还与选择器一起使用。您可以通过 Kubernetes 官方文档了解这两个话题,文档地址:kubernetes.io/docs/concepts/overview/working-with-objects/labels/

删除 Kubernetes 资源

如果我们对 pod 做错了什么,或者它可能因为某种原因崩溃了,可以通过kubectl delete pod命令轻松删除该 pod:

$ kubectl delete pod httpd-8576c89d7-qjd62
pod "httpd-8576c89d7-qjd62" deleted

我们可以使用--all选项删除所有 pod:

$ kubectl delete pod --all
pod "httpd-8576c89d7-qjd62" deleted
pod "httpd1-c9f7d7fd9-rn2nz" deleted
pod "httpd2-5b4ff5cf57-vlhb4" deleted

请注意,如果你运行kubectl get pods,你会看到所有的容器再次运行。原因在于,当我们运行kubectl run命令时,它会创建多个不同的 Kubernetes 资源,这些资源将在接下来的部分中讨论。

我们可以通过运行kubectl delete all命令并加上-l选项来删除 Kubernetes 资源:

$ kubectl delete all -l app=httpd-demo1
deployment "httpd1" deleted
pod "httpd1-c9f7d7fd9-d9w94" deleted

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
httpd-8576c89d7-qjd62 1/1 Running 0 17m
httpd2-5b4ff5cf57-9llkn 1/1 Running 0 15s

此命令将只删除带有httpd-demo1标签的所有 Kubernetes 资源。其他两个 pod 仍然可用。

或者,我们可以通过运行kubectl delete all --all命令删除所有已创建的 Kubernetes 资源:

$ kubectl delete all --all
deployment "httpd" deleted
deployment "httpd2" deleted
pod "httpd-8576c89d7-ktnwh" deleted
pod "httpd2-5b4ff5cf57-t58nd" deleted
service "kubernetes" deleted
service "nginx-exposed" deleted

Kubernetes 高级资源

当我们使用kubectl run命令创建应用程序时,它会处理多个任务。让我们再运行一次这个命令来创建一个 httpd pod,并深入了解背后实际发生了什么:

$ kubectl run httpd1 --image=httpd

我们可以通过运行kubectl get events命令来查看在此过程中发生的一系列事件。它会展示 Kubernetes 在幕后启动应用程序时所做的所有操作。你会看到一个相当长的列表,乍一看可能会让人困惑,但我们可以通过以下命令缩小范围:

$ kubectl get events --sort-by=.metadata.creationTimestamp | tail -n 8
4s 4s ... kubelet, minikube pulling image "httpd"
4s 4s ... replicaset-controller Created pod: httpd1-6d8bb9cdf9-thlkg
4s 4s ... default-scheduler Successfully assigned httpd1-6d8bb9cdf9-thlkg to minikube
4s 4s ... deployment-controller Scaled up replica set httpd1-6d8bb9cdf9 to 1
4s 4s ... kubelet, minikube MountVolume.SetUp succeeded for volume "default-token-dpzmw"
2s 2s ... kubelet, minikube Created container
2s 2s ... kubelet, minikube Successfully pulled image "httpd"
2s 2s ... kubelet, minikube Started container

我们使用kubectl get events命令并加上--sort-by=.metadata.creationTimestamp选项来按时间戳排序事件。如果没有使用此选项,事件将会乱序。

我们主要关注每一行的最后两个字段,它们分别是SOURCEMESSAGE。如果我们从上到下阅读事件序列,会看到一个 Kubernetes 组件告诉另一个组件在 Minikube VM 上创建名为httpd1-6d8bb9cdf9-thlkg的 pod,并且这一过程最终发生了。让我们来描述一下这些组件:

  • replicaset-controller:有时候我们需要多个httpd pod 来处理应用程序的所有负载。ReplicaSet 确保一定数量的 pod 正在运行并可用。ReplicaSet 由 Deployment 控制器管理。

  • default-scheduler:决定将特定的 pod 运行在哪个节点上。在我们的例子中,它是 Minikube VM。

  • deployment-controller:定义 Kubernetes 资源的期望状态。在我们的例子中,它是httpd pod 的状态。Deployment 控制器还会指示 ReplicaSet 确保某些 pod 正在运行。

如前所述,kubectl run 命令会创建其他 Kubernetes 资源,包括 ReplicaSet 和 Deployment。我们可以通过分别运行 kubectl get replicasetkubectl get deployment 来验证这一点:

$ kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
httpd1 1 1 1 1 38m

$ kubectl get rs
NAME DESIRED CURRENT READY AGE
httpd1-6d8bb9cdf9 1 1 1 38m

我们提到过,Deployment 控制器定义了多少个 httpd Pods 运行。默认情况下,这个数量是 1。我们可以轻松改变这个行为,并使用 kubectl edit deploy httpd1 命令编辑 Deployment 配置:

$ kubectl edit deploy httpd1 ...
<output omitted>
...
spec:
 replicas: 1 # change this value to 3
...
<output omitted>
...

一旦你将副本值更改为 3,保存更改并退出编辑模式。Deployment 控制器将检测到配置中的变化,并指示 ReplicaSet 启动两个新的 httpd Pods。让我们验证一下:

$ kubectl get pods
NAME                    READY STATUS   RESTARTS      AGE
httpd1-6d8bb9cdf9-hqks6  1/1   Running   0           5s
httpd1-6d8bb9cdf9-thlkg  1/1   Running   0           48m
httpd1-6d8bb9cdf9-xwmmz  1/1   Running   0           5s

如果我们尝试删除所有的 Pods,ReplicaSet 会自动启动一组新的 Pods。让我们再看一遍它是如何工作的:

$ kubectl delete pods --all
pod "httpd1-6d8bb9cdf9-hqks6" deleted
pod "httpd1-6d8bb9cdf9-thlkg" deleted
pod "httpd1-6d8bb9cdf9-xwmmz" deleted

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
httpd1-6d8bb9cdf9-7nx7k 1/1 Running 0 16s
httpd1-6d8bb9cdf9-gsxzp 1/1 Running 0 16s
httpd1-6d8bb9cdf9-skdn9 1/1 Running 0 16s

在我们进入下一章节之前,删除所有 Kubernetes 资源:

$ kubectl delete all --all 

使用 YAML 和 JSON 文件创建 Kubernetes 服务

你也可以使用 YAML 和 JSON 文件手动创建 Kubernetes 资源。我们来创建一个简单的运行 httpd Web 服务器的 Pod,使用 kubectl create 命令。我们需要创建一个 YAML 格式的文件:

$ cat httpd-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: httpd
  namespace: default
spec:
  containers:
  - name: httpd-container
    image: httpd
    ports:
    - containerPort: 80

阅读 YAML 和 JSON 格式的文件对 Kubernetes 和后续的 OpenShift 章节至关重要。如果你不熟悉 YAML 或 JSON 文件,建议学习相关内容。请查阅 进一步阅读 部分获取更多信息。

这看起来可能有点复杂,难以理解,但随着我们在本书中继续前进,你会发现这些 YAML 和 JSON 文件之间有很多相似之处。

YAML 配置文件结构非常明确,每个资源都是一组键值对。你可以使用 Kubernetes API 文档来了解每个参数的作用:kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/

使用 httpd-pod.yaml 文件创建一个 Pod:

$ kubectl create -f httpd-pod.yaml

$ kubectl get all
NAME        READY       STATUS     RESTARTS     AGE
po/httpd    1/1         Running       0         25s

NAME            TYPE     CLUSTER-IP    EXTERNAL-IP PORT(S) AGE
svc/kubernetes ClusterIP 10.96.0.1     <none>     443/TCP  56s

这个命令创建了一个名为 httpd 的 Pod;它不会创建其他任何内容。在涉及大量自动化的更大部署中,这是部署 Kubernetes 资源的方式,但这当然需要更好的 Kubernetes 技能。

同样,我们也可以创建其他 Kubernetes 资源,包括 ReplicaSet、Deployment 等。

清理虚拟环境

一旦你完成了 Kubernetes 的工作,你可以通过运行 minikube stop 命令轻松停止 Minikube 集群:

$ minikube stop
Stopping local Kubernetes cluster...
Machine stopped.

之后,如果你想删除 Minikube 虚拟机,可以运行 minikube delete 命令:

$ minikube delete
Deleting local Kubernetes cluster...
Machine deleted.

验证 Minikube 集群是否已不再存在:

$ minikube status
minikube:
cluster:
kubectl:

Kubernetes 的限制

尽管 Kubernetes 是一个强大的编排引擎,但它没有像 OpenShift 等 PaaS 解决方案所常见的那些功能:

  • 安全性

Kubernetes 命名空间主要用于不同用户组的资源配额强制执行,但它们不提供任何安全约束或身份验证。例如,每个命名空间中的每个用户都可以看到所有其他命名空间及其资源。

  • 部署

Kubernetes 提供通过单个命令从镜像创建部署的方式,但不会为外部客户端创建服务。

  • SCM 集成

Kubernetes 不支持通过 webhook 与 SCM 集成来促进部署。

  • 构建

Kubernetes 不提供高级构建模式,如源到镜像S2I)和自定义构建器。

  • 身份验证

对于 LDAP、Google、GitHub、Keystone 或 Kerberos 等高级身份验证方案的支持,只有通过 webhook 或身份验证代理才能实现。

  • CI/CD

Kubernetes 没有集成的应用生命周期支持,这使得它难以集成到企业软件交付框架中。

所有这些限制将在后续章节的 OpenShift 部分中得到解决。

总结

在本章中,我们简要讨论了 Kubernetes 的概念、Kubernetes 架构以及 Kubernetes 和 Docker Swarm 之间的主要区别。我们使用 Minikube 安装了 Kubernetes,这是一个非常易于使用的 CLI 工具,可以用来搭建 Kubernetes 实验环境。然后,我们使用kubectl命令执行了各种任务,如运行、编辑、描述和删除 Kubernetes 的 Pod 以及其他 Kubernetes 资源。最后,我们列出了 Kubernetes 的主要限制,后续章节中我们将对此进行讨论。

在下一章,我们将学习如何使用 CRI-O,它是一个通用容器运行时接口,允许 Kubernetes 为不同的容器平台提供支持。

问题

  1. Kubernetes 使用了哪两种节点类型?:

    1. 节点

    2. Minikube

    3. Vagrant

    4. 主节点

  2. Kubernetes 支持哪些容器平台?选择两个:

    1. Docker

    2. OpenShift

    3. Rkt

    4. Minishift

  3. 在 Kubernetes 中,Pod 是最小的部署单位,表示一组容器:

    1. 正确

    2. 错误

  4. Kubernetes 节点上运行的主要两个服务是什么?选择两个:

    1. etcd

    2. kubelet

    3. kube-proxy

    4. kube-node

    5. kube-apiserver

  5. 使用kubectl create -f命令创建 Kubernetes 资源时,接受哪些文件格式?选择两个:

    1. JSON

    2. Jinja2

    3. CSV

    4. YANG

    5. YAML

  6. Kubernetes 拥有内置的 CI/CD 工具集,以改善企业软件交付框架:

    1. 正确

    2. 错误

深入阅读

由于我们只涉及 Docker 容器的基础知识,您可能会有兴趣深入研究特定主题。以下是一些链接,您可以通过它们进一步了解 Docker 和容器的一般知识:

第三章:CRI-O 概述

在上一章中,我们简要讨论了 Kubernetes 的概念、架构以及 Kubernetes 和 Docker Swarm 之间的主要区别。

本章的目标是让你对 Kubernetes 的替代容器运行时技术——CRI-O——有一个基本的了解。本章提供了关于容器运行时接口、开放容器倡议和 CRI-O 的基础知识,并描述了如何使用该技术管理容器。

阅读完本章后,你将对以下主题有一个扎实的理解:

  • 容器运行时接口和开放容器倡议

  • CRI-O 如何与 Kubernetes 协作

  • 安装并使用 CRI-O

技术要求

在本章中,我们将使用以下技术和软件:

  • Minikube

  • Bash Shell

  • GitHub

  • Kubernetes

  • Docker

  • Firefox

你需要在笔记本电脑或任何其他虚拟环境中安装 Minikube。关于如何安装 Minikube 的所有说明可以在kubernetes.io/docs/tasks/tools/install-minikube/找到。

Bash Shell 将作为你的虚拟环境的一部分使用。

可以使用 Firefox 或任何其他浏览器浏览 Docker Hub。

容器运行时和容器运行时接口

在开始使用 CRI-O 之前,我们需要先谈谈基础知识。最好的起点就是容器运行时。我们已经知道什么是容器、Docker 和 Kubernetes 了。但这些是如何在底层工作的呢?以下图示展示了 Kubernetes 和容器之间通信的高层次概述:

Kubernetes 与容器的通信

如你所见,Kubernetes 并不是直接与容器通信的。图中有两个额外的层次。让我们来讨论一下为什么我们实际上需要它们:

  • 容器运行时:每种容器技术,如 Docker、Rkt 或 Windows 容器,都有自己的运行时。简而言之,容器运行时是一组脚本和软件工具,用来运行和维护容器的工作。

  • 容器运行时接口(CRI):顾名思义,CRI 是一个接口,或者说是 Kubernetes 和容器运行时之间的一个适配层,换句话说。

你可能会问自己,为什么我需要 CRI?为什么 Kubernetes 不能直接与容器运行时通信?想象一下,Kubernetes 和容器运行时分别是一个经理和一个讲英语的员工,没问题。但假设有一个新员工,他是该领域的专家并被某个组织聘用,因此他们必须开始一起工作。然而,这个新员工只会讲俄语,现在就有了问题。现在,我们需要逐步增加更多讲不同语言的员工。你能明白我说的意思吧。在我们的情况下,我们有一些解决这个问题的方法:

  1. 每次有新员工讲新语言时,经理必须学习一门新语言

  2. 员工必须学习英语。

  3. 为每一种新员工所讲的语言雇佣一个翻译员。

  4. 雇佣一位能讲通用语言的翻译员。

尝试回答这个问题——在列出的四个选项中,最有效的选项是哪一个?我希望你能理解,选项 4 是最有效的。基于同样的原因,CRI 和 CRI-O 应运而生。沿用前面的类比,CRI 是选项 3,而 CRI-O 是选项 4。

CRI-O 与 开放容器倡议(Open Container Initiative,OCI)

Kubernetes 最初只与 Docker 配合使用,但后来 CoreOS 推出的 Rkt 也进入了市场,他们希望 Kubernetes 能够支持它。因此,每增加一个容器运行时,你必须为其开发一个 CRI。如今,不同的容器技术经常被引入。这种解决方案并不具备良好的扩展性,还会给整个解决方案带来很多复杂性和不稳定性。

这就是 CRI-O 被引入的背景。CRI-O 代表 OCI 兼容的容器运行时接口,而 OCI 代表 开放容器倡议。开放容器倡议是一个开源社区项目,旨在为 Linux 容器设计开放标准。这使得 Kubernetes 可以使用任何其他符合 OCI 标准的容器运行时来运行 Pods。

CRI-O 与 Kubernetes 的工作原理

当你想要使用 Kubernetes 启动或停止一个容器时,Kubernetes 会与 CRI-O 通信,CRI-O 再与符合 OCI 标准的容器运行时(如 Docker 使用的 runc)通信,启动容器。CRI-O 还可以拉取符合 OCI 标准的容器镜像并在磁盘上进行管理。对于容器开发人员来说,这个好消息是——他们无需直接与 CRI-O 打交道,因为 Kubernetes 会自动处理。但理解这个概念和整体架构仍然很重要:

CRI-O 架构

总结一下,在我们动手安装 CRI-O 之前,有几点需要注意:

  • Kubernetes 被配置为与 CRI-O 通信,以便在容器环境中启动一个新的 Pod。

  • CRI-O 从注册表中拉取符合 OCI 标准的容器镜像(如果需要),并在本地进行管理。

  • CRI-O 与符合 OCI 标准的容器运行时(默认为 runc)进行通信,并在 Kubernetes 节点上运行容器。

  • 容器运行时通过与 Linux 内核进行交互,从容器镜像启动容器。

  • Linux 内核启动容器进程,例如不当的命名空间、组、上下文等。

  • 每个容器都由一个独立的进程进行监控和记录,该进程由 Linux 内核控制。

  • 容器的网络部分由 容器网络接口CNI)控制,CRI-O 也可以使用该接口。

安装和使用 CRI-O

现在是时候亲自操作 CRI-O 了。我们不会深入探讨 CRI-O,而是展示如何通过配置 CRI-O 来搭建一个具备一些基本功能的开发环境。

在撰写本文时,CRI-O 仍在开发中。因此,在您的情况下,设置说明可能会有所不同,您需要参考官方的 Minikube 文档。

为了使用 CRI-O 作为容器运行时接口启动 Kubernetes,我们将使用 Minikube,并额外添加 --container-runtime crio 选项:

$ minikube start --container-runtime crio
Starting local Kubernetes v1.9.0 cluster...
...
<output omitted>
...
Loading cached images from config file.

检查 Minikube 的状态,确保它正在运行:

$ minikube status
minikube: Running
cluster:  Running
kubectl:  Correctly Configured: pointing to minikube-vm at 192.168.99.106

看起来相当标准,但如果我们查看 Minikube 日志,我们会看到 Minikube 正在初始化 CRI-O 运行时:

$ minikube logs | grep cri-o
Jul 08 21:11:36 minikube localkube[3098]: I0708 21:11:36.333484 3098 kuberuntime_manager.go:186] Container runtime cri-o initialized, version: 1.8.4, apiVersion: v1alpha1

让我们使用 kubectl run 命令创建一个使用 Docker 镜像的 Pod:

$ kubectl run httpd --image=docker.io/httpd
deployment "httpd" created

我们需要指定 Docker 镜像的完整路径,因为 CRI-O 是一个通用的运行时接口,它不知道我们是否要使用 Docker 或其他任何容器技术的注册表。

等待一分钟左右,直到 Kubernetes Note 下载 httpd 镜像,然后验证我们已经成功启动了一个 httpd Pod:

$ kubectl get pods
NAME                   READY STATUS RESTARTS  AGE
httpd-7dcb9bd6c4-x5dhm 1/1   Running  0       4m

再次从这个角度来看,它看起来相当标准,但如果我们运行 kubectl describe 命令,我们会看到容器 ID 以 cri-o:// 开头:

$ kubectl describe pods/httpd-7dcb9bd6c4-x5dhm
Name: httpd-7dcb9bd6c4-x5dhm...
<output omitted>
...
IP: 10.1.0.4
Container ID: crio://3f2c2826318f1526bdb9710050a29b5d4a3de78d61e0...
Image: docker.io/httpd
...
<output omitted>
...

到此为止,这向我们展示了 Kubernetes 正在使用 CRI-O 运行时接口。这意味着 Kubernetes 正在与 CRI-O 通信。CRI-O(具体来说是 crio 守护进程)负责处理镜像拉取和容器创建过程。让我们通过在 Minikube 虚拟机中运行 docker imagesdocker ps 命令来验证这一点:

$ minikube ssh docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gcr.io/k8s-minikube/storage-provisioner v1.8.0 4689081edb10 4 months ago 80.8MB

$ minikube ssh docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

如您所见,没有名为 httpd 的镜像或容器。我们之前提到,CRI-O 背后使用的是 runc 容器运行时。为了帮助我们进一步验证,我们将在 Minikube 虚拟机中使用 runc 命令。runc 是一个用于运行根据 OCI 格式打包的容器的 CLI 命令。runc 命令的语法与我们在第一章中使用的 docker 命令非常相似,容器和 Docker 概述

$ minikube ssh "sudo runc ps \
3f2c2826318f1526bdb9710050a29b5d4a3de78d61e07ac9d83cedb9827c62e4"
UID PID PPID C STIME TTY TIME CMD
root 5746 5695 0 02:39 ? 00:00:00 httpd -DFOREGROUND
daemon 5788 5746 0 02:39 ? 00:00:00 httpd -DFOREGROUND
daemon 5792 5746 0 02:39 ? 00:00:00 httpd -DFOREGROUND
daemon 5793 5746 0 02:39 ? 00:00:00 httpd -DFOREGROUND

请注意,3f2c2826318f1526bdb9710050a29b5d4a3de78d61e07ac9d83cedb9827c62e4 是我们之前运行的 kubectl describe pods/httpd-7dcb9bd6c4-x5dhm 命令中的容器 ID。

停止虚拟环境

在进入下一章之前,先关闭您的虚拟环境:

$ minikube stop
Stopping local Kubernetes cluster...
Machine stopped.

删除 Minikube 虚拟机:

$ minikube delete
Deleting local Kubernetes cluster...
Machine deleted.

总结

本章简要介绍了容器运行时和容器运行时接口的基础知识,接着介绍了 CRI-O 及其工作原理。最后,我们安装了 Kubernetes 集群,并将 CRI-O 作为容器运行时接口,并运行了一些简单的命令来验证 Kubernetes 是否通过 CRI-O 正常工作。

在下一章中,我们将讨论 OpenShift 架构、不同的 OpenShift 版本以及 OpenShift 的基本情况。我们还将比较 OpenShift 和 Kubernetes,以便您理解为什么我们实际上需要 OpenShift 以及它的独特之处。

问题

  1. 以下哪个 Kubernetes kubelet 服务直接与之通信以运行 pod?请选择 1:

    1. OCI

    2. CRI

    3. Docker

    4. Linux 内核

  2. CRI-O 支持哪些容器技术?请选择 2:

    1. Docker

    2. Minishift

    3. Rkt

    4. Minikube

  3. 最初,CRI-O 设计用于与 Kubernetes 容器管理平台配合使用:

    1. 正确

    2. 错误

  4. 验证 CRI-O 容器 ID 的命令是什么?请选择 1:

    1. kubectl describe pods/httpd

    2. kubectl show pods/httpd

    3. docker ps

    4. docker images

  5. 以下哪些关于 CRI-O 的陈述是正确的?请选择 2:

    1. CRI-O 直接与 Linux 内核通信

    2. CRI-O 直接与容器运行时通信

    3. CRI-O 兼容 OCI

    4. CRI-O 是唯一可用于 Kubernetes 的容器运行时接口

  6. CRI-O 默认使用 runc 容器运行时:

    1. 正确

    2. 错误

深入阅读

相较于其他技术,网上关于 CRI-O 的信息较少。如果你有兴趣了解更多关于 CRI-O 的内容,以下是一些链接:

第四章:OpenShift 概述

在上一章中,我们简要介绍了容器运行时和容器运行时接口的基础知识,接着讲解了 CRI-O 是什么以及它是如何工作的。最后,我们安装了一个以 CRI-O 作为容器运行时接口的 Kubernetes 集群,并运行了一些简单的命令,以验证 Kubernetes 是否通过 CRI-O 正常工作。

本章重点简要描述了 OpenShift 作为平台即服务PaaS)解决方案的概况。我们将介绍基础设施即服务IaaS)和 PaaS 云。作为 PaaS 云描述的一部分,我们将提供 OpenShift 业务价值的基本解释。在本章中,您还将了解 OpenShift 的技术组件和可用的 OpenShift 变体。

阅读本章后,您将了解以下内容:

  • 云技术格局与 PaaS 的角色

  • OpenShift 作为 Kubernetes 的扩展

  • OpenShift 的业务价值

  • OpenShift 口味

  • OpenShift 架构

云技术格局与 PaaS 的角色

今天,任何企业都期望自动化成为成功的基石。传统方法在应对商业生态系统变化的速度时过于缓慢,这是许多企业向自动化和 DevOps 转型的主要原因之一。

如果我们看看软件交付技术的演变,可以很容易发现,旧一代应用程序最初是直接安装在裸机上,然后是虚拟机。随着时间的推移,这些公司开始使用不同的 IaaS 云平台。原因显而易见——IaaS 平台通过使用云编排工具和公开的 API 将自动化带到了一个新层次。IaaS 自动化在基础设施层面上实现这一目标的方式是简化虚拟网络、存储和虚拟服务器。使用 IaaS 平台时,我们仍然需要安装和配置应用程序,这通常是一个耗时的过程。想象一下,每次开发新功能并进行测试时,都需要从头开始安装和配置一个基于 PHP 的应用程序。即使使用自动化工具,这也需要大量时间。

PaaS 是下一代平台,用于以快速和自动化的方式在生产环境中交付应用程序。通过 PaaS,应用程序交付过程看起来非常简单——不需要为 Web 服务器、数据库等安装和配置应用平台。这些都由平台本身提供,在我们的案例中就是 OpenShift。这意味着您只需要上传应用程序代码和数据库结构;其余的将由 PaaS 处理:

下图显示了将应用程序托管在裸机、IaaS 和 PaaS 平台上的差异:

IaaS、PaaS 和 SaaS 云比较

OpenShift 作为 Kubernetes 的扩展

在前几章中,我们讨论了 Kubernetes 如何简化 Docker 容器的管理,并处理容器的部署。Kubernetes 在跨多个节点的容器管理中带来了额外的价值,这在现代高度可用和分布式的基础设施中至关重要。尽管 Kubernetes 具有许多优势,但它也有缺点,大部分缺点都被 OpenShift 修正。

OpenShift 是来自 Red Hat 的 PaaS 服务,原生集成 Docker 和 Kubernetes,创建了一个强大的容器集群管理和编排系统。OpenShift 包括了架构、流程、平台和服务,旨在赋能开发人员和运维团队。OpenShift 提高了所有环境中的可靠性,满足了所有客户需求,同时降低了基础设施成本。

OpenShift 利用 Kubernetes 作为容器管理平台,并添加了多个重要的新组件和功能,其中包括:

  • 自服务门户和服务目录

  • 构建和应用部署自动化

  • 内置注册表服务

  • 扩展应用路由

OpenShift 服务

理解 OpenShift 的商业价值

N 层设计是一种标准的应用设计,例如具有 Web、应用和数据库层的三层 Web 应用。这个设计逐步演变成了微服务架构。微服务架构影响了整个行业,迫使许多公司接受这一新趋势,并将其作为新标准。与单体架构和 N 层架构相比,微服务架构更便宜、更具可扩展性,并且更安全。这就是为什么容器化应用将把你带到全新高度的原因:

应用、基础设施和软件开发的演变

OpenShift 进一步推动了这些边界,允许软件开发人员和运维团队利用强大的微服务架构,以及 OpenShift GUI 的简便性和它带来的附加功能。OpenShift 是你真正能够从自动化和 DevOps 中受益的地方。OpenShift 内置了许多工具,使得新的微服务架构与上一代软件兼容。

正如我们所提到的,软件开发方法也发生了变化;最初,软件开发者遵循瀑布模型,精益求精地打磨应用,逐一添加功能,确保应用没有漏洞。但这种方式既不高效,也不节省时间和成本。因此,敏捷方法应运而生。企业主需要更短的软件开发周期。然而,这并不完美,因为存在许多漏洞和其他不足之处。接下来的进化步骤是 DevOps。OpenShift 利用现代 DevOps 流程,使其变得更加简单和高效。

OpenShift 版本

OpenShift 的生产版有几个不同的变种:

  • OpenShift Origin

  • OpenShift 容器平台

  • OpenShift 在线版

  • OpenShift 专用版

与其他 Red Hat 旗下产品类似,OpenShift 也有一个上游项目,叫做OpenShift Origin。OpenShift Origin 的主页可以访问openshift.org。它提供多种安装方法,包括在本地开发者机器上安装开发环境。

Red Hat OpenShift Container Platform是 OpenShift Origin 的生产级版本,拥有所有 Red Hat 生产优势,包括官方支持、认证等。OpenShift Container Platform 与 Red Hat Enterprise Linux 集成,并通过 Red Hat 的质量保证过程进行测试,旨在为希望拥有自己的私有云或本地云的客户提供一个稳定且可支持的产品。企业可能会每六个月获得一次更新,从而在小版本更新之间保持稳定。OpenShift Container Platform 允许在您的基础设施上构建私有或公共 PaaS 云。

Red Hat OpenShift Online是一个由 Red Hat 管理的多租户公共云平台。对于希望使用 OpenShift 但不想拥有自己本地基础设施的公司,OpenShift Online 是一个不错的选择。这个平台允许免费托管一个小型应用程序。

Red Hat OpenShift Dedicated是一个单租户的容器应用平台,托管在Amazon Web ServicesAWS)或 Google Cloud Platform 上,由 Red Hat 管理。它允许应用开发团队快速构建、部署和扩展传统和云原生应用。OpenShift Dedicated 基于 Red Hat Enterprise Linux、Docker 容器技术和 Google Kubernetes 进行编排和管理。它安全地连接到您的数据中心,让您能够通过最小的基础设施和运营费用实施灵活的混合云 IT 策略。如果不希望与其他公司共享平台,这对于公司来说是一个不错的选择。

OpenShift 架构

就 OpenShift 组件和整体架构而言,OpenShift 容器平台既简单又复杂。它的复杂性在于涉及许多相互连接的组件,但 OpenShift 的简单性在于所有组件相互独立运行,即使某个部分发生故障,其他部分仍能无缝协作。

OpenShift 组件

OpenShift 和 Kubernetes 有许多架构组件是相同的,例如:

  • OpenShift 主节点

  • OpenShift 节点

  • Etcd 作为一个键值存储

OpenShift 特有的组件包括:

  • 路由器作为入口流量控制

  • OpenShift 内部注册表

以下图表描述了从架构角度看 OpenShift 容器平台:

OpenShift 架构

OpenShift 建立在 Docker 和 Kubernetes 之上,有时被称为 Kubernetes++。除了 Docker 和 Kubernetes 解决方案外,OpenShift 还带来了额外的功能和特性,以提供稳定和生产就绪的 PaaS 平台。这些新功能和特性包括:

  • 认证:OpenShift 具有几种内置的认证方法,允许对 OpenShift 项目进行精细的访问控制。

    • 本地认证

    • LDAP 认证

    • 请求头认证

    • Keystone 认证

    • GitHub 认证

  • 多租户:OpenShift 强大的功能允许精细的多用户和多项目访问控制,这是中型和大型组织必备的功能,允许不同组织间的协作和访问控制。

  • 内部镜像注册表:OpenShift 使用内部注册表存储准备部署到 OpenShift 节点上的镜像。它还用于 S2I 构建。

  • GUI 和 Web 控制台:OpenShift 提供易于使用的 Web 界面,功能强大到足以创建、构建、部署、更新和排查 OpenShift 项目及其内部运行的微服务应用程序。

  • SCM 集成:OpenShift 与 Git 有内置集成。该解决方案与镜像构建器紧密耦合。

  • 镜像构建器:用于将图像参数或源代码转换为可运行图像的过程。

  • CI/CD 集成:OpenShift 与 Jenkins 提供非常灵活的持续集成和持续交付管道集成。它通过在容器中进行 Jenkins provisioning 来扩展管道执行。这允许 Jenkins 并行运行所需数量的作业,并且能够按需扩展解决方案。

  • 附加的 Kubernetes 资源扩展:OpenShift 向 Kubernetes 添加了一组资源:

    • Deployment Configurations (DC):这是从相同容器镜像创建的一组 pod。

    • Build Configurations (BC):主要由 S2I 用于从 Git 中的源代码构建镜像。

    • Routes:OpenShift 路由器使用的 DNS 主机名服务,用作 OpenShift 应用程序的入口点。

  • REST API:除了 Kubernetes API 外,Openshift 还提供自己的 API 接口,以利用所有自动化功能,并与外部平台、其他自动化框架和 DevOps 解决方案无缝集成。

摘要

在本章中,我们简要讨论了 IaaS 和 PaaS 解决方案之间的区别。我们还讨论了 OpenShift 作为 PaaS 解决方案的一部分。我们提供了 OpenShift 业务价值的基本解释,主要 OpenShift 组件,并总体上谈到了 OpenShift。

在下一章中,我们将使用 OpenShift 并学习使用最流行的方法和工具构建 OpenShift 开发环境的不同方式。

问题

  1. OpenShift 是以下云平台之一:

    1. IaaS

    2. PaaS

    3. MaaS

    4. SaaS

  2. 哪两个 OpenShift 版本可以在你的数据中心内部署?选择 2 个:

    1. OpenShift Origin

    2. OpenShift 专用版

    3. OpenShift 企业版

    4. OpenShift 离线版

  3. OpenShift 使用自己的容器管理平台来编排和控制容器部署:

    1. 正确

    2. 错误

  4. 以下哪项是 OpenShift 相对于 Kubernetes 增加的新特性?选择一个:

    1. SCM 集成

    2. GUI 和 Web 控制台

    3. 多租户

    4. 持久存储

  5. 相比 Kubernetes,OpenShift 有哪些独特的组件?选择两个:

    1. 路由器作为入口流量控制

    2. OpenShift 内部注册中心

    3. OpenShift 主节点

    4. OpenShift 节点

  6. OpenShift 提供了一个额外的 REST API,除了 Kubernetes API:

    1. 正确

    2. 错误

进一步阅读

OpenShift 的开发生命周期为每 3 个月一次,这意味着每 3 个月都会增加新特性。这也意味着,到本书发布时,OpenShift 可能会有所不同。因此,跟踪这些变化非常重要。以下是一些链接,包括进一步阅读:

第五章:构建 OpenShift 实验环境

在上一章中,我们简要讨论了 IaaS 和 PaaS 解决方案之间的区别。我们还讨论了 OpenShift 作为 PaaS 解决方案的一部分。我们提供了 OpenShift 商业价值和主要组件的基本解释。

一些 OpenShift 用户不想花时间部署和配置 OpenShift 开发或评估环境。然而,他们可能需要该环境来执行日常操作。本章将展示如何在大多数流行桌面操作系统上,使用最流行的方法快速设置一个现成的开发环境。

本章将涵盖以下主题:

  • 为什么使用开发环境?

  • 部署变体

  • 使用 oc cluster up

  • 使用 Minishift

  • 使用 Vagrant

技术要求

为了顺利完成本章的实验,你将需要以下内容:

  • 以下任一桌面操作系统:

    • macOS

    • Linux (CentOS 7)

    • Windows

  • 用于 Linux 或 macOS 的 Bash

  • macOS 的 Brew 包管理器

  • VirtualBox

  • 虚拟化支持

  • 用于 macOS/Linux/Windows 的 Docker

  • OpenShift 客户端工具

你还需要下载并安装一些依赖项和二进制文件。

为什么使用开发环境?

正如我们在上一章提到的,OpenShift 是一个应用交付平台,可以简化基础设施工程师和开发团队的日常工作。如果公司的软件交付策略与容器对接,OpenShift 可以显著减少生产时间。

每个平台都有一个社区。它是围绕平台构建的生态系统,旨在帮助提升平台功能,使平台与最终用户需求对接,等等。OpenShift 拥有一个社区,社区成员包括一些使用该平台的重要人物:软件开发人员、QA 专家、系统管理员、IT 架构师等。对于 OpenShift 来说,扩展社区并吸引新的团队成员和客户是非常重要的。为了实现这一目标,所有团队成员都应该能够访问 OpenShift 环境;根据他们的角色,专家们将进行软件开发、测试和软件交付。并非每个软件开发人员或 QA 团队成员都能从零开始搭建 OpenShift,这是他们寻找简便方法开始使用 OpenShift 的主要原因。

OpenShift 开发环境可以轻松创建,不需要大量的努力。通常,这个环境位于本地计算机上,并且不需要大容量。

拥有易于使用的开发环境有很多好处:

  • OpenShift 评估:有时,用户希望了解 OpenShift 如何与他们的需求对接。他们可能没有 OpenShift 经验,可能想在真实系统上试用它。对于大多数技术人员而言,营销演示并没有实质意义。为开发环境提供一个快速简便的部署过程可以帮助解决这个问题。

  • 为开发者/QA 提供快速环境:开发者通常不想过多参与环境的部署。快速部署 OpenShift 的方法可以节省一些精力,让他们能够专注于主要工作(如软件开发和测试)。

部署变体

有多种方法可以设置和启动开发环境。通常,开发环境运行在工作站上,覆盖最流行的工作站操作系统非常重要。在下一节中,我们将描述几种在不同平台的工作站上快速部署 OpenShift 的方法。最流行的桌面操作系统包括 Windows、macOS 和 Linux。

请注意,开发环境不等同于生产环境,原因是容量、可扩展性和安全性方面的限制。

使用 oc cluster up

名为 oc 的 OpenShift 客户端工具可以启动本地 OpenShift 集群,其中包括所有必要的服务,如内部注册表、路由器、模板等。这是启动开发环境最简单的方法之一。oc cluster up 会创建一个默认用户和项目,完成后,你可以使用任何命令来操作 OpenShift 环境,如 oc new-app

该方法提供了一个容器化的 OpenShift 环境,可以轻松在多个平台上运行。

系统要求和前提条件

oc cluster up 方法支持基于 Linux、macOS 和 Windows 的工作站。默认情况下,该方法需要安装了 Docker 的环境。然而,命令本身也可以创建 Docker 机器。以下表格展示了可用的部署场景:

操作系统 Docker 实现
Linux 操作系统默认 Docker 守护进程
macOS Docker for macOS
macOS Docker Toolbox
Windows Docker for Windows
Windows Docker Toolbox

CentOS 7

此方法也可以用于 Fedora 或基于 RHEL 的主机。

部署过程涉及多个步骤:

  1. 安装 Docker

  2. 配置不安全的注册表

  3. 允许防火墙上的端口

  4. 下载 OpenShift 客户端工具

  5. 启动集群

让我们详细研究这些步骤:

  1. Docker 安装:这并不涉及特别的操作,之前的章节中已有描述。以下命令必须以 root 用户身份运行:
$ sudo -i
# sudo yum -y install docker
# systemctl enable docker
  1. 配置不安全的注册表:这对于能够使用与 OpenShift 安装一起提供的内部 Docker 注册表是必需的。如果没有配置这个,oc cluster up 将会失败。

为了允许不安全的 OpenShift 注册表,请在 root 用户下运行以下命令:

# cat << EOF >/etc/docker/daemon.json
{
   "insecure-registries": [
     "172.30.0.0/16"
   ]
}
EOF

# systemctl start docker

这需要重新启动 Docker 守护进程以应用新的配置。

  1. 配置防火墙:默认的防火墙配置没有启用 OpenShift 集群所需的所有端口。你需要使用firewall-cmd调整设置:
端口 描述
8443/tcp API 端口
53/udp DND
8053/udp 内部 DNS

这可以通过以下代码片段实现:

# firewall-cmd --permanent --new-zone dockerc
# firewall-cmd --permanent --zone dockerc --add-source 172.17.0.0/16
# firewall-cmd --permanent --zone dockerc --add-port 8443/tcp
# firewall-cmd --permanent --zone dockerc --add-port 53/udp
# firewall-cmd --permanent --zone dockerc --add-port 8053/udp
# firewall-cmd --reload

在大多数情况下,防火墙在开发环境中不是问题,可以通过systemctl stop firewalldsystemctl disable firewalld停止防火墙。

你可以通过运行以下命令来确定 Docker 的网络地址:

docker network inspect -f "{{range .IPAM.Config }}{{ .Subnet }}{{end}}" bridge

  1. 下载 oc 工具:名为oc的 OpenShift 客户端工具可以在标准仓库中找到;但是,也可以从github.com/openshift/origin/releases下载该工具。我们建议使用标准的 CentOS 仓库:
# yum -y install centos-release-openshift-origin39
# yum -y install origin-clients

我们省略了命令的输出。预计这些命令将安装多个依赖项。

  1. 启动 OpenShift 集群:一旦满足所有前提条件,你就可以通过运行oc cluster up来启动集群。该命令将从公共仓库下载所有必需的 Docker 镜像,然后运行所有必需的容器:
# oc cluster up --version=v3.9.0
Starting OpenShift using openshift/origin:v3.9.0 ...
...
output truncated for brevity
...
OpenShift server started.

The server is accessible via web console at:
    https://127.0.0.1:8443

You are logged in as:
    User: developer
    Password: <any value>

To login as administrator:
    oc login -u system:admin

在前面的示例中,我们将 OpenShift 集群的版本静态绑定到v3.9.0。在大多数情况下,你无需指定版本。因此,你只需要运行oc cluster up,而不带任何参数。

如你所见,oc cluster up已部署了一个可供使用的单节点 OpenShift 环境。

默认情况下,这个 OpenShift 环境被配置为在回环接口(127.0.0.1)上监听。这意味着你可以通过https://127.0.0.1:8443连接到集群。可以通过添加特定的参数(如--public-hostname=)来更改这种行为。可以使用以下命令显示所有可用选项:

# oc cluster up --help
  1. 验证:集群部署完成后,你可以验证它是否准备好使用。默认的 OpenShift 配置指向一个无特权的用户,名为developer。你可以使用以下命令提升权限:
# oc login -u system:admin
Logged into "https://127.0.0.1:8443" as "system:admin" using existing credentials.

You have access to the following projects and can switch between them with 'oc project <projectname>':

    default
    kube-public
    kube-system
  * myproject
    openshift
    openshift-infra
    openshift-node

Using project "myproject".

一旦你获得管理员访问权限,就可以使用oc get node来验证节点配置:

# oc get node
NAME      STATUS  AGE     VERSION
localhost Ready   9m       v1.7.6+a08f5eeb62
  1. 关闭:一旦oc cluster up环境部署完成,可以通过oc cluster down将其关闭。

macOS

macOS 的安装和配置过程与 Linux 非常相似。假设正在使用 Docker for macOS。部署过程包括以下步骤:

  • Docker for macOS 的安装和配置

  • 安装openshift-cli和所需的包

  • 启动集群

oc cluster up 命令要求系统上安装 Docker,因为它本质上是创建一个 Docker 容器并在该容器内运行 OpenShift。这是一个非常优雅且清晰的解决方案。

Docker for macOS 的安装过程可以参考官方门户:docs.docker.com/docker-for-mac

一旦 Docker 服务运行起来,你需要配置不安全的注册表(172.30.0.0/16)。在工具栏的 Docker 菜单中,选择首选项菜单并点击 Daemon 图标。在配置对话框的基本选项卡中,点击不安全注册表下的 + 图标,并添加以下新条目:172.30.0.0/16

完成后,点击应用并重启。

一旦配置好 Docker 服务,我们需要安装所有必需的软件,并按照以下步骤启动集群:

  1. OpenShift 客户端安装:按照以下步骤在系统上安装 socatopenshift-cli 包:
$ brew install openshift-cli socat --force

如果你没有安装brew,可以在brew.sh/找到安装过程。

  1. 启动和停止 OpenShift 集群:集群可以像在 Linux 中一样启动:
$ oc cluster up
Starting OpenShift using registry.access.redhat.com/openshift3/ose:v3.7.23 ...
OpenShift server started.

The server is accessible via web console at:
https://127.0.0.1:8443

You are logged in as:
User: developer
Password: <any value>

To login as administrator:
oc login -u system:admin

在编写本书时,oc cluster up在 macOS 上的版本 3.9 不可用。我建议你暂时使用版本 3.7 的客户端工具。

OpenShift 管理员用户可以执行安装验证,方法如下:

$ oc login -u system:admin
Logged into "https://127.0.0.1:8443" as "system:admin" using existing credentials.

You have access to the following projects and can switch between them with 'oc project <projectname>':

default
kube-public
kube-system
* myproject
openshift
openshift-infra
openshift-node

Using project "myproject".

OpenShift 集群已经启动并准备好工作。我们可以使用以下命令检查集群的状态:

$ oc get nodes
NAME      STATUS AGE VERSION
localhost Ready 20h  v1.7.6+a08f5eeb62

集群可以通过以下方式停止:

$ oc cluster down

Windows

OpenShift 环境可以部署在支持 Docker for Windows 的 Windows 机器上。

  • Docker for Windows 安装与配置

Docker for Windows 的安装过程可以参考docs.docker.com/docker-for-windows

一旦 Docker 运行,你需要配置不安全注册表设置,如下所示:

  1. 右键点击通知区域的 Docker 图标,选择设置。

  2. 在设置对话框中点击 Docker Daemon。

  3. 通过添加 172.30.0.0/16"insecure-registries": 设置中,编辑 Docker 守护进程配置:

{
"registry-mirrors": [],
"insecure-registries": [ "172.30.0.0/16" ]
}
  1. 点击应用,Docker 将重启。

一旦配置好 Docker 服务,就可以按照以下方式安装 OpenShift 客户端oc。示例还展示了如何启动集群:

您还可以从 github.com/openshift/origin/releases 下载最新代码,在 Assets 下。

  • 启动/停止集群:OpenShift 客户端的 Windows 版本也可以启动和停止集群,具体操作如下:
C:\> oc cluster up

OpenShift 集群已启动。您可能希望使用以下命令检查集群的状态:

C:\> oc get node
NAME STATUS AGE
origin Ready 1d

通过 Web 浏览器访问 OpenShift。

无论您是使用 oc cluster up 还是其他解决方案,当 OpenShift 正在运行时,您都可以通过 Web 浏览器访问它。OpenShift 默认在端口 8443 上可用。在 oc cluster up 的情况下,您可以通过 https://localhost:8443/ 访问 OpenShift 登录页面:

使用开发者登录并输入任意密码即可登录 OpenShift。稍后我们将通过本书中的 Web 界面开发和运行微服务应用程序。

登录后,您将看到服务目录,您可以从中选择可用的语言运行时:

OpenShift 中的 Project 扩展了 Kubernetes 的命名空间概念,作为将同一 OpenShift 集群中的团队和个人用户分隔开来的手段。项目的另一个常用术语是 tenant(例如,在 OpenStack 中)。您可以通过点击“创建项目”按钮并指定项目名称来从 Web 控制台创建项目:

创建项目后,您可以点击屏幕右侧的项目名称,系统将引导您进入项目概览页面,在该页面您可以创建应用程序和其他资源:

为了让您基本了解如何在 OpenShift Web 控制台中导航,请参阅下面的简要指南:

  • 应用程序菜单用于访问与您的应用程序直接相关的资源,如部署、Pods、服务、Stateful Sets 和路由。

  • Builds 菜单让您管理构建配置和构建策略,如 Pipelines,以及用于从源代码构建应用程序的镜像。

  • Resources 菜单让您访问可以在高级用例中由应用程序使用的其他次要资源,如配额、配置映射、秘密和其他资源。您还可以使用此菜单查看和管理项目的成员资格。

  • 存储菜单用于通过创建持久存储声明来请求持久存储。

  • 监控菜单为您提供访问 OpenShift 收集的各种指标,包括 CPU、RAM 和网络带宽利用率(如果启用了指标收集),以及实时发生的事件。

  • 最后,Catalog 菜单是您可以直接访问服务目录的快捷方式,您无需返回首页即可从当前项目访问。这一功能在 OpenShift Origin 3.9 中引入。

使用 Minishift。

Minishift 是另一种在本地部署 OpenShift 的方式,它通过在虚拟机中运行一个单节点的 OpenShift 集群来实现。最近,我们展示了如何使用 Minikube 来部署 Kubernetes 的开发环境。Minishift 采用了相同的方法,因为它是 Kubernetes 社区开发的,并作为 Minikube 的延续(详见 第二章,Kubernetes 概述),因此使用了相同的命令行语法。Minishift 支持在 Windows、macOS 和 Linux 操作系统上进行部署。Minishift 使用 libmachine 来提供虚拟机,使用 boot2docker 作为操作系统,并使用 OpenShift Origin 来运行集群。Minishift 需要一个虚拟化管理程序来运行包含 OpenShift 的虚拟机。

根据你的主机操作系统,你可以选择以下虚拟化管理程序:

操作系统 虚拟化管理程序 平台
macOS xhyve VirtualBox
Linux KVM VirtualBox
Windows Hyper-V VirtualBox

boot2docker 是一个基于 Tiny Core Linux 的轻量级 Linux 发行版,专门为运行容器而构建。该项目现已弃用。

在开始之前,你需要配置虚拟化支持(docs.openshift.org/latest/minishift/getting-started/installing.html#install-prerequisites)并安装 VirtualBox。

然后,你需要从 Minishift 发布页面下载适用于你操作系统的压缩包(github.com/minishift/minishift/releases)并解压。压缩包中的 minishift 二进制文件需要复制到操作系统的首选位置,并将该位置添加到你的 PATH 环境变量中。Minishift 将使用系统 PATH 环境中找到的 SSH 二进制文件。

在 macOS 上,你还可以使用 Homebrew Cask 安装 Minishift 的稳定版本:

$ brew cask install minishift

要更新二进制文件,请运行以下命令:

$ brew cask install --force minishif

如果所有先决条件都满足,你可以使用 minishift start 启动一个集群。

$ minishift start --vm-driver=virtualbox
-- Starting profile 'minishift'
-- Checking if requested OpenShift version 'v3.7.1' is valid ... OK
...
output truncated for brevity
...
OpenShift server started.

The server is accessible via web console at:
    https://192.168.64.2:8443

You are logged in as:
    User: developer
    Password: <any value>

To login as administrator:
    oc login -u system:admin

-- Exporting of OpenShift images is occuring in background process with pid 7123.

部署过程完成后,你将能够使用 OpenShift 客户端连接:

$ oc login -u system:admin
Logged into "https://192.168.64.2:8443" as "system:admin" using existing credentials.

You have access to the following projects and can switch between them with 'oc project <projectname>':

* default
kube-public
kube-system
myproject
openshift
openshift-infra
openshift-node

Using project "default".

你还需要安装 oc client。你可以参考本章前面的章节来获取 oc client 安装的步骤。

OpenShift 集群已启动。你可能需要使用 oc get nodes 来检查其状态,如下所示:

$ oc get nodes
NAME      STATUS   AGE  VERSION
localhost Ready   20h   v1.7.6+a08f5eeb62

你可以通过 minishift ssh 连接到 Minishift 虚拟机。可以使用 minishift stop 停止 Minishift 集群。

根据 oc client 和 OpenShift 的版本,输出结果可能有所不同。

要停止一个 Minishift 环境并删除它,你可以使用 minishift stopminishift delete,如下所示:

$ minishift stop
Stopping local OpenShift cluster...
Cluster stopped.

$ minishift delete
You are deleting the Minishift VM: 'minishift'. Do you want to continue [y/N]?: y
Deleting the Minishift VM...
Minishift VM deleted.

与 Vagrant 一起使用

该方法允许使用已安装所有 OpenShift 软件的可用 Vagrant box,在单个虚拟机上运行。

Vagrant 软件通过使用不同的底层虚拟化技术,简化了虚拟机的部署和初始配置。在大多数情况下,一个名为 Vagrantfile 的纯文本文件描述了虚拟机的参数(如名称、主机名、内存、vCPUs、存储等)。一旦 Vagrantfile 准备好,Vagrant 软件就可以使用它来创建、停止和销毁虚拟机。

使用 Vagrant 的优点是我们可以根据需要重新部署虚拟机多次,并且每次都会得到相同的结果:

Vagrant 架构

Vagrant 包含以下主要组件:

  • Vagrant 软件:自动化虚拟机的构建和配置。具有 vagrant CLI 工具,适用于不同的操作系统。

  • Box:包含虚拟机镜像和元数据的 TAR 文件。Box 文件及其包含的镜像特定于每个提供商。

  • Provider:允许 Vagrant 与不同虚拟化平台通信的接口。默认情况下,使用 VirtualBox。也可以使用 Libvirt、KVM、OpenStack 和其他提供商。

  • Vagrantfile:一个纯文本文件,包含创建虚拟机的指令。这些指令是使用基于 Ruby 的 领域特定语言DSL)编写的。对于高级用例,也可以使用纯 Ruby。

Vagrant 安装

Vagrant 的安装指令和下载软件可在 www.vagrantup.com/docs/installation/ 找到。

只需下载适用于你的操作系统的安装包,然后进行安装。Vagrant 还需要一个虚拟化平台,如 Vmware、KVM、VirtualBox、AWS、Hyper-V 或 Docker。根据操作系统的不同,你需要安装适当的虚拟化平台。

对于 macOS,安装以下组件

  1. 从上述链接下载最新的 Vagrant 软件并安装。

  2. www.virtualbox.org/wiki/Downloads 下载并安装 VirtualBox。

对于 CentOS,安装以下组件

  1. 从上述链接下载最新的 Vagrant 软件并安装。

  2. 安装 libvirt 驱动程序和 kvm

# yum install epel-release -y
# yum localinstall https://releases.hashicorp.com/vagrant/1.9.5/vagrant_1.9.5_x86_64.rpm
# yum install qemu libvirt libvirt-devel ruby-devel gcc qemu-kvm -y
# vagrant plugin install vagrant-libvirt

对于 Windows,安装以下组件

  1. 从上述链接下载最新的 Vagrant 软件并安装。

  2. www.virtualbox.org/wiki/Downloads 下载并安装 VirtualBox。

安装完成后,通过检查 Vagrant 软件版本来验证 Vagrant 是否已安装:

$ vagrant --version
Vagrant 1.9.5

使用全功能 Vagrant box 安装 OpenShift

在 2017 年 5 月引入 Minishift 之前,用户主要依赖 openshift/origin-all-in-one Vagrant 全功能盒子。这种方法不太流行,因为该盒子已近两年没有更新,并且代表的是 OpenShift Origin 1.3,功能和稳定性较差,因此现在主要具有历史意义。尽管它仍然可以用于测试目的,我们建议你使用 oc cluster up 和 Minishift。

可以按照以下方式部署基于 Vagrant 的环境:

$ vagrant init openshift/origin-all-in-one
$ vagrant up

上述命令将创建以下 Vagrantfile

$ cat Vagrantfile
Vagrant.configure("2") do |config|
   config.vm.box = "openshift/origin-all-in-one"
end

一旦盒子部署完成,你可以使用 admin 和密码登录,如下所示:

$ oc login
Server [https://localhost:8443]:
The server uses a certificate signed by an unknown authority.
You can bypass the certificate check, but any data you send to the server could be intercepted by others.
Use insecure connections? (y/n): y

Authentication required for https://localhost:8443 (openshift)
Username: admin
Password: admin
Login successful.

You have access to the following projects and can switch between them with 'oc project <projectname>':

cockpit
* default
kube-system
openshift
openshift-infra

Using project "default".
Welcome! See 'oc help' to get started.

OpenShift 集群已启动。你可能想要检查集群状态,使用以下命令查看:

$ oc get node
NAME STATUS AGE
origin Ready 1y

请记住,这种方法的 Vagrant 盒子落后于最新版本的 OpenShift 两年,因此,如果你想看到最新的功能,建议使用其他选项。

总结

在这一章中,我们讨论了如何使用最流行和最简单的方法:oc cluster up、Minishift 和 Vagrant,在 CentOS 7、macOS 和 Windows 操作系统上快速轻松地创建 OpenShift 实验环境。

在下一章中,你将了解 OpenShift Origin 的硬件和软件要求。你将对 OpenShift 的部署场景和安装方法有一个基本的理解。此外,你还将使用 Ansible 执行 OpenShift 的高级安装,并了解如何通过 Ansible 清单自定义你的设置。

问题

  1. oc cluster up 解决方案的主要前提是什么?(请选择一个):

    1. Docker

    2. Minishift

    3. Virtualbox

    4. Hyper-V

  2. 默认情况下,访问 OpenShift 管理网页所使用的端口是哪个?请选择一个:

    1. 8443

    2. 443

    3. 8080

    4. 80

  3. oc cluster up 命令使用虚拟机启动 OpenShift 集群:

    1. True

    2. False

  4. 用于通过 CLI 登录 OpenShift 的命令是什么?请选择一个:

    1. oc login system:admin

    2. oc login -u system:admin

    3. oc login -u admin

    4. oc login admin

  5. 使用 oc 命令启动和停止 OpenShift 演示集群的命令是什么?请选择一个:

    1. oc cluster up

    2. oc cluster start

    3. oc cluster down

    4. oc cluster stop

  6. minishift start 命令使用虚拟机启动 OpenShift 集群:

    1. True

    2. False

深入阅读

如果你有兴趣了解更多内容或在实验环境中遇到问题,请查看以下链接:

第六章:OpenShift 安装

在上一章中,我们讨论了如何使用oc cluster up、MiniShift 和 Vagrant 在 CentOS7、macOS 和 Windows 操作系统上快速轻松地设置 OpenShift 实验环境。

在本章中,您将了解 OpenShift Origin 的硬件和软件要求,并对 OpenShift 部署场景和安装方法有一个基本的了解。您还将能够使用 Ansible 执行 OpenShift 的高级安装,并了解通过 Ansible 库存自定义设置的各种选项。

在本章中,我们将讨论以下主题:

  • 前提条件

  • OpenShift 安装方法概览

  • 环境准备

  • 高级安装

技术要求

本章的学习环境将通过 Vagrant 在 VirtualBox 上部署单个虚拟机表示。为了支持该环境,您将需要以下最低配置:

CPU RAM, GiB 操作系统
启用 HT 的 2 核 6 Fedora 26/CentOS 7/RHEL 7

虚拟机的大小约为 3 GB,因此请确保你的/home分区有足够的空闲空间,或者通过文件 | 偏好设置 | 常规 | 默认机器文件夹更改 VirtualBox 存储虚拟机文件的位置。

可用于部署我们虚拟机的Vagrantfile可能类似于以下内容:

$ cat Vagrantfile 
Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"
  config.vm.hostname = "openshift.example.com"
  config.vm.provider "virtualbox" do |v|
    v.memory = 4096
    v.cpus = 4
  end
  config.vm.network "private_network", ip: "172.24.0.11"
end

由于本章中的所有 OpenShift 服务都将在单个虚拟机上部署,因此我们将为其提供 4 个 vCPU 和 4 GiB 的 RAM,以便所有进程能够得到足够的资源启动。

为了能够拉取centos/7镜像,您需要 Vagrant 版本 2.x。

前提条件

在本章中,我们将重点介绍 OpenShift 的 Origin 版本,Origin 版本可以公开访问,无需任何订阅。与其他 OpenShift 变种一样,Origin 支持所有由 RedHat 提供的商业版 OpenShift 的功能。OpenShift 是一个复杂的平台,包含多个相互交互和协同工作的组件,每个组件都有自己的要求,因此在开始之前,我们需要满足这些要求。

硬件要求

官方 OpenShift Origin 文档提供了不同主机类型的足够硬件要求。它们总结在以下表格中:

Masters Nodes External etcd
vCPU 2 1 2
RAM, GiB 16 8 8
磁盘存储/分区, GB /var: 40``/usr/local/bin: 1``/tmp: 1 /var: 15``/usr/local/bin: 1``/tmp: 1``Docker 存储后端: 15 20, 推荐使用 SSD
网络, GB/s 1 1 1

上述信息并非一成不变,仅供参考。特定安装的硬件要求会受到许多因素的影响,例如预期的应用程序数量和工作负载。实际上,要求可能会更宽松。

OpenShift 安装方法概览

OpenShift Origin 可以根据操作系统以及关于可用性、可靠性和可扩展性的要求采用不同的安装方式。

RPM 安装

这是默认方法。它从 RPM 包中安装所有必需的服务作为 systemd 单元,并且可以在 RHEL/CentOS/Fedora 上使用。我们将在接下来的章节中使用 CentOS 7 Linux 发行版重点介绍这种方法,以便你更好地理解涉及的机制。

容器化安装

这是 Atomic Host OS 唯一可用的方法。它在 Docker 容器中安装 OpenShift 服务,从而提供了额外的控制层级。该方法在企业环境中更为优选,因为 Atomic Host 使得软件包之间的依赖问题成为过去,并且简化了主机准备过程,因为 Atomic Host 提供了一个开箱即用的容器化环境。

部署场景

OpenShift Origin 支持多种部署模型,列举如下:

  • 一体化:单个 master、单个 etcd 和单个节点安装在同一系统上。

  • 单个 master、单个 etcd 和多个节点:这是我们将要重点关注的场景,因为它相对简单易设且能提供相关经验。master 和 etcd 将安装在同一主机上。

  • 单个 master、多个外部 etcd 和多个节点:该场景通过集群提供 etcd 节点的高可用性(HA)。多个 etcd 节点形成法定多数,因此建议使用奇数个节点。

  • 多个 master、多个外部 etcd 和多个节点:该场景还提供了 API 的原生高可用性(HA),这一点将在本书后续部分中探讨。Master 本身是无状态的,这意味着它们不需要任何同步机制。

以下图表展示了一个一体化安装:

一体化

以下图表展示了一个包含单个 master 和 etcd 实例的多节点 OpenShift 安装:

单个 master、单个 etcd 和多个节点

以下图表展示了使用多节点 etcd 集群进行安装的场景:

单个 master、多个 etcd 和多个节点

在生产环境中,冗余性和数据持久性非常重要。以下图表给出了一个生产环境中 OpenShift 安装的示例。该安装包含三个 master 和三个 etcd 服务,所有入口流量都经过负载均衡:

多个 master、多个 etcd 和多个节点

虽然将 etcd 部署在与 master 相同的主机上并不罕见,但它们是独立的实体,尽管前者需要在高可用性设置中实现法定多数,master 不需要,并且可以在实际中以任何数量单独部署。

环境准备

对于 RPM 安装方法,在安装开始之前需要进行一些配置。以下小节中的所有命令都需要以 root 身份执行。

首先,启动 vagrant 虚拟机并以 root 用户登录:

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
...
<output omitted>
...
$ vagrant ssh
$ sudo -i
#

在做任何其他操作之前,确保所有主机都已更新,以便内核和用户空间库能够支持 OpenShift 所需的所有功能:

# yum -y update
…
<output omitted>
…
Complete!
# reboot

你还需要以下软件包:

# yum -y install git docker epel-release
…
<output omitted>
…
Complete!
# yum -y install ansible
…
<output omitted>
…
Complete!

我们还安装了 epel-release 包,以启用 EPEL 仓库,从而可以安装 OpenShift 3.9 所需的最新版本的 Ansible。

通常,你可以在任何系统上安装 Ansible,甚至在你的笔记本上安装并从那里安装 OpenShift。但为了简化操作,我们将包括 Ansible 控制功能在内的所有内容集中在一个虚拟机上。

Docker

与 Kubernetes 一样,OpenShift 依赖 Docker 提供容器运行时环境。因此,我们需要在 OpenShift 虚拟机上激活 Docker:

# systemctl start docker
# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.

SELinux

在运行安装程序之前,必须在所有 OpenShift 节点(包括主节点)上启用 SELinux。这是为了使用特殊的 MLS 标签隔离容器进程所必需的。

打开虚拟机上的 /etc/selinux/config 文件,确保 SELINUXSELINUXTYPE 参数设置如下:

$ cat /etc/selinux/config
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=enforcing
# SELINUXTYPE= can take one of three two values:
# targeted - Targeted processes are protected,
# minimum - Modification of targeted policy. Only selected processes are protected.
# mls - Multi Level Security protection.
SELINUXTYPE=targeted

SELinux 默认应该已经配置好。如果你做了任何更改,需要重启虚拟机以激活这些更改。

检查 SELINUX 是否启用:

# getenforce
Enforcing

Ansible 安装

Ansible 是一个用 Python 编写的配置管理和编排工具。OpenShift Origin 安装程序实际上是一个 Ansible playbook 集合,采用 YAML 格式,定义了必须安装的服务及其配置方式。

Ansible playbook 从一个单一的控制机器运行,在 Ansible 术语中,控制机器可以是任何机器,在我们的高级安装演示中,我们将使用主节点作为控制机器。

这些软件包由 EPEL 仓库提供,我们的实验室已经启用了该仓库。如果没有启用,你需要安装 epel-release 包。

接下来,从 Git 仓库克隆安装 playbook:

# git clone https://github.com/openshift/openshift-ansible
Cloning into 'openshift-ansible'...
remote: Counting objects: 95983, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 95983 (delta 1), reused 6 (delta 0), pack-reused 95969
Receiving objects: 100% (95983/95983), 24.45 MiB | 972.00 KiB/s, done.
Resolving deltas: 100% (59281/59281), done.
# cd openshift-ansible
# git branch -r
…
<output omitted>
…
origin/release-3.9
…
<output omitted>
…
# git checkout release-3.9
Branch release-3.9 set up to track remote branch release-3.9 from origin.
Switched to a new branch 'release-3.9'

我们特别切换到了 3.9 版本,因为主分支实际上跟踪的是开发版本。

SSH 访问

Ansible 通过 SSH 协议连接到其他系统,因此它需要 SSH 密钥对。在本节中,我们将确保从虚拟机的 root 账户到同一虚拟机的 root 账户的 SSH 访问。这可以通过以下命令完成:

# ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
…
<output omitted>
…
# cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys

你可以通过运行以下命令来检查访问是否已启用:

# ssh 172.24.0.11
The authenticity of host '172.24.0.11 (172.24.0.11)' can't be established.
ECDSA key fingerprint is SHA256:JX1N6Zt7136jH2cXzd0cwByvFTahuOj3NHYvcIjpG2A.
ECDSA key fingerprint is MD5:9b:04:4a:89:5d:65:7a:b0:4b:02:62:fa:25:91:d3:05.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '172.24.0.11' (ECDSA) to the list of known hosts.
[root@openshift ~]# logout
Connection to 172.24.0.11 closed.

高级安装

能够在几分钟内启动一个小型集群,并且只需最小的努力,当然很棒。但是,如果你需要对各种功能进行更多控制,以便在安装后即可获得一个完全功能的集群呢?答案就是高级安装。与快速安装不同,它包括以下内容:

  1. 创建 Ansible 库存文件,其中包含所有按照选择的部署模型分组的主机,以及自定义安装环境的变量

  2. 运行 Ansible playbook 来安装、配置和启动所有 OpenShift 组件,包括内部注册表、注册表控制台和路由器

OpenShift Ansible 库存

一个典型的 Ansible 库存是一个文本文件,其中包含主机组及其变量。在 OpenShift 的情况下,可能会根据部署拓扑和其他要求,包含特定的节。它们在下表中总结:

Section Description Required
masters 安装主服务的主机,尤其是 API。可以是单个节点用于独立部署,或者是奇数个主机用于高可用设置。
new_masters 在扩展现有集群时,安装主节点组件的额外主机。
nodes 安装节点组件的主机,尤其是 kubelet。通常会有多个节点,因为最佳实践建议将至少一个节点指定为基础设施节点,用于托管如注册表和路由器等系统服务。本节必须包含所有主节点。
new_nodes 在扩展现有集群时,安装节点组件的额外主机。
etcd 仅在进行高可用配置且有多个外部 etcd 的情况下,才会指定此节。它可能包含与主机相同的主机,但由于主机和 etcd 的系统要求不同,最好将它们部署在不同的机器上。建议选择奇数个以确保投票机制。
nfs 配置 NFS 作为 Ansible 中介、内部注册表、Hawkular 指标和 Elasticsearch 日志的持久存储后端的主机。
lb 如果有多个主节点,你需要提供一个外部客户端的接入点,该接入点将在主机之间分配流量。通过将主机放入此节,你将指示 Ansible 在该主机上安装 HAProxy 并配置负载均衡和 TLS/SSL 穿透。
glusterfs 配置 GlusterFS 作为持久存储后端的主机。
glusterfs_registry 配置 GlusterFS 作为内部注册表的持久存储后端的主机。
OSEv3:vars 本节包含配置 OpenShift 各个方面的全局变量,如身份验证、注册表位置等。
OSEv3:children 本节列出了文件中其余部分所指定的所有组。

Ansible 寻找特定的变量来决定如何配置各个子系统。我们将使用的变量列在下面的表格中。完整的变量列表可以在官方文档中找到:docs.openshift.org/latest/install_config/install/advanced_install.html

变量 描述
openshift_node_labels 分配给特定节点或所有节点的标签,具体取决于它是为节点还是全局在 OSEv3:vars 中设置的。您应该至少有一个节点标记为 {'region': 'infra'}
openshift_schedulable 控制一个节点是否可以用来运行 Pod。默认情况下,主节点被配置为不可调度,但如果没有标记为 infra 的节点,则必须显式将其设置为 true,至少为一个主节点,否则,注册表和路由器的 Pod 将无法启动。
ansible_ssh_user Ansible 用于通过 SSH 连接到主机的用户账户。
openshift_master_identity_providers 身份验证后端。默认情况下,OpenShift 使用 AllowAllPasswordIdentityProvider,实际上接受所有凭据,这在企业环境中是不安全且不可接受的。
deployment_type 要安装的 OpenShift 发行版。可接受的值为 enterprise,表示 Red Hat OpenShift 容器平台,和 origin,表示 OpenShift Origin。
openshit_master_default_subdomain 曝露服务的子域名。默认情况下,它是<namespace>.svc.cluster.local
openshift_disable_check 在实际安装之前,Ansible 会执行一系列检查,以确保环境满足某些要求,例如可用内存、磁盘空间等。这是为了防止资源规划不当,但在像我们这样的概念验证安装中,这些检查可能显得过于严格。由于我们不需要 8 GB 的内存、40 GB 的主节点磁盘空间以及 15 GB 的 /var 节点磁盘空间用于测试环境,因此可以安全跳过这些检查。
openshift_clock_enabled OpenShift 主机依赖时间戳来正确传播更新通过 etcd、故障转移和法定人数,这是通过 NTP 时间同步实现的。此设置控制是否必须启用 chronyd 守护进程。

总结前面表格中的信息,库存文件的结构应类似于以下内容:

# cat /etc/ansible/hosts
...
<output omitted>
...
[masters]
172.24.0.11

[nodes]
172.24.0.11 openshift_node_labels="{'region': 'infra', 'zone': 'default'}" openshift_schedulable=true

[etcd]
172.24.0.11

[OSEv3:vars]
openshift_deployment_type=origin
openshift_disable_check=memory_availability,disk_availability
openshift_ip=172.24.0.11
ansible_service_broker_install=false
openshift_master_cluster_hostname=172.24.0.11
openshift_master_cluster_public_hostname=172.24.0.11
openshift_hostname=172.24.0.11
openshift_public_hostname=172.24.0.11

[OSEv3:children]
masters
nodes
etcd

由于我们只有一个节点,我们必须将其标记为可调度并属于infra区域,以便可以部署注册表、注册表控制台和路由器。在生产环境中,通常会将专用的主节点用于管理和同步任务,而将所有运行容器的工作留给节点,并且将多个节点分布在不同区域以保证可用性。

OpenShift Ansible 剧本

OpenShift 的 Ansible 仓库包含了各种不同任务的操作手册。下表展示了一些操作手册:

操作手册 描述
playbooks/prerequisites.yml 为集群部署设置先决条件,例如将主机订阅到 Red Hat(如果是 Red Hat Enterprise Linux),启用软件仓库并配置防火墙。
playbooks/deploy_cluster.yml 完成 OpenShift Origin 的完整安装,安装所有必需的包,配置并启动服务。我们将在本章中使用这个操作手册。
playbooks/openshift-master/scaleup.yml 在清单中查找主机组new_masters,并将这些主机配置为集群的新成员。扩展完成后,必须将这些主机移至masters组,以避免 Ansible 在下一次运行时将它们视为新的主机。
playbooks/openshift-node/scaleup.yml 在清单中查找主机组new_nodes,并将这些主机配置为集群的新成员。扩展完成后,必须将这些主机移至nodes组,以避免 Ansible 在下一次运行时将它们视为新的主机。
playbooks/openshift-etcd/scaleup.yml 在清单中查找主机组new_etcd,并将这些主机配置为集群的新成员。扩展完成后,必须将这些主机移至etcd组,以避免 Ansible 在下一次运行时将它们视为新的主机。

对 Ansible 的熟悉有帮助,但不是必需的。

安装

此时,我们应该已经具备了开始安装所需的一切,接下来就直接运行先决条件操作手册开始安装吧:

# ansible-playbook playbooks/prerequisites.yml
…
<output omitted>
…
PLAY RECAP **************************************************************************************
172.24.0.11 : ok=65 changed=17 unreachable=0 failed=0 
localhost   : ok=12 changed=0  unreachable=0 failed=0 

INSTALLER STATUS ********************************************************************************
Initialization : Complete (0:00:13)
…
<output omitted>
…

这个操作手册通常运行几分钟。

最后,我们开始实际的部署:

# ansible-playbook playbooks/deploy_cluster.yml
…
<output omitted>
…
PLAY RECAP **************************************************************************************
172.24.0.11 : ok=555 changed=234 unreachable=0 failed=0 
localhost   : ok=13  changed=0   unreachable=0 failed=0 

INSTALLER STATUS ********************************************************************************
Initialization            : Complete (0:00:18)
Health Check              : Complete (0:01:12)
etcd Install              : Complete (0:00:50)
NFS Install               : Complete (0:00:17)
Master Install            : Complete (0:04:27)
Master Additional Install : Complete (0:00:29)
Node Install              : Complete (0:02:37)
Hosted Install            : Complete (0:02:21)
Web Console Install       : Complete (0:01:13)
Service Catalog Install   : Complete (0:01:23)

这个过程通常需要 15 到 20 分钟,所以你可以利用这段时间快速浏览一下本书的基本操作,帮助你更快地上手。

验证

安装完成且没有任何故障后,你可以通过查询主节点 API 检查已安装的节点,以进行基本的健康检查:

# oc get node
NAME          STATUS   ROLES          AGE      VERSION
172.24.0.11   Ready  compute,master   32m      v1.9.1+a0ce1bc657

正如清单所示,我们的单节点既作为主节点又作为工作节点。

你还可以进行另一个检查,看看基础设施组件,如注册表、注册表控制台和路由器是否成功部署:

# oc get po -n default
NAME                     READY STATUS  RESTARTS AGE
docker-registry-1-8g89z  1/1   Running 0        42m
registry-console-1-2srg8 1/1   Running 0        42m
router-1-c6h95           1/1   Running 0        42m

OpenShift 的基础设施组件位于default项目/命名空间中。

你也可以登录到https://172.24.0.11:8443的网页控制台,在这里你会被提示接受自签名证书,你需要同意接受。你可以使用任何凭据,因为 OpenShift 默认接受任何人,你将看到以下页面:

图 1. OpenShift 服务目录

用户登录 OpenShift 后看到的第一个内容是服务目录,它展示了他们可以用来部署应用程序的各种语言和运行时。技术上来说,这些是模板,你将在第九章,高级 OpenShift 概念和第十三章,使用模板部署多层应用程序中深入了解这些模板。

总结

在本章中,你了解了 OpenShift Origin 的各种部署场景,以及安装方法。你编写了一个 Ansible 清单文件,并使用它部署了一个即用型的 OpenShift Origin 平台,平台内置了内部注册表、注册表控制台,并且即开即用地安装了路由器。

在接下来的章节中,我们将介绍 OpenShift 的核心概念,例如如何使用 OpenShift pod、服务、路由、项目和用户创建新应用程序。这将为你提供基础技能,足以让你能够在 OpenShift 中运行和管理你的应用容器基础设施。

问题

  1. 在 Ansible 清单文件中,哪些部分是 OpenShift 安装必需的?请选择两个:

    1. masters

    2. nfs

    3. new_masters

    4. etcd

  2. 必须为至少一个节点分配什么标签,才能成功部署路由器和内部注册表?

    1. 基础设施

    2. dedicated

    3. infra

    4. special

  3. 用于部署 OpenShift 集群的 Ansible playbook 是什么?

    1. playbooks/deploy_cluster.yml

    2. playbooks/byo/config.yml

    3. playbooks/prerequisites.yml

    4. playbooks/common/openshift-cluster/config.yml

进一步阅读

请参阅以下内容以进一步阅读本章相关内容:

第七章:管理持久存储

在上一章中,我们描述了如何使用高级安装方法安装 OpenShift 集群。安装过程的下一步是为 OpenShift 用户提供持久存储。在 第一章 容器和 Docker 概述 中,我们已经介绍了如何使用 Docker 持久卷。通常,开发或测试时不需要持久存储,但在生产环境中,我们有时需要存储持久数据。本章将描述有关 OpenShift 基础设施的持久存储概念。我们还将解释在生产环境中使用持久存储的必要性。本章的重点是如何配置基础设施以支持持久存储。这包括以下存储类型:NFS、GlusterFS、iSCSI 等。除了基础设施准备之外,本章还介绍了如何在 OpenShift 中使用 持久卷PVs)和 持久卷声明PVCs)来利用持久存储。最后,我们将展示如何在部署在 OpenShift 上的 Pods/应用程序中使用持久存储。

本章将涵盖以下主题:

  • 持久存储与临时存储

  • OpenShift 持久化存储概念

  • 存储后端比较

  • 存储基础设施设置

  • 配置 PVs

  • 在 Pods 中使用持久存储

技术要求

本章的学习环境由两台虚拟机组成,具有以下特点:

主机名 内存 vCPU 操作系统
openshift.example.com 4GB 2 CentOS 7
storage.example.com 2GB 1 CentOS 7

这些机器可以部署在任何地方(裸机、VMware、OpenStack、AWS 等)。然而,出于教育目的,我们建议使用 Vagrant + VirtualBox/libvirt 配置,以简化虚拟环境的部署和重新部署过程。

我们还假设所有服务器都可以通过 FQDN 和短名称访问。这需要配置 /etc/hosts 记录,具体配置如下:

172.24.0.11 openshift.example.com openshift
172.24.0.12 storage.example.com storage

这些 IP 地址必须与以下 Vagrantfile 中指定的 IP 地址相同。如果你只想在此实验中使用一台机器,可以将 /etc/hosts 文件配置为将两个记录指向同一台机器。

可以通过使用以下 Vagrantfile 来简化实验环境的部署:

$ cat Vagrantfile 
$lab_script = <<SCRIPT
cat <<EOF >> /etc/hosts
172.24.0.11 openshift.example.com openshift
172.24.0.12 storage.example.com storage
EOF
SCRIPT

$lab_openshift = <<SCRIPT
systemctl disable firewalld
systemctl stop firewalld
yum install -y epel-release git
yum install -y docker
cat << EOF >/etc/docker/daemon.json
{
 "insecure-registries": [
 "172.30.0.0/16"
 ]
}
EOF
systemctl start docker
systemctl enable docker
yum -y install centos-release-openshift-origin39
yum -y install origin-clients
oc cluster up
SCRIPT

Vagrant.configure(2) do |config|
 config.vm.define "openshift" do |conf|
 conf.vm.box = "centos/7"
 conf.vm.hostname = 'openshift.example.com'
 conf.vm.network "private_network", ip: "172.24.0.11"
 conf.vm.provider "virtualbox" do |v|
 v.memory = 4096
 v.cpus = 2
 end
 conf.vm.provision "shell", inline: $lab_script
 conf.vm.provision "shell", inline: $lab_openshift
 end

 config.vm.define "storage" do |conf|
 conf.vm.box = "centos/7"
 conf.vm.hostname = 'storage.example.com'
 conf.vm.network "private_network", ip: "172.24.0.12"
 conf.vm.provider "virtualbox" do |v|
 v.memory = 2048
 v.cpus = 1
 end
 conf.vm.provision "shell", inline: $lab_script
 end
end

不必使用前面代码中使用的相同 IP 地址。重要的是将你的 /etc/hosts 记录指向这些 IP。

持久存储与临时存储

默认情况下,OpenShift/Kubernetes 容器不会持久化存储数据。我们可以启动一个应用程序,OpenShift 会从一个不可变的 Docker 镜像启动一个新的容器。它使用的是短暂存储,这意味着数据在容器被删除或重建之前是可用的。如果我们的应用程序(及所有相关容器)被重建,所有数据都会丢失。然而,这种方法对于任何无状态应用程序来说是可行的。例如,它适用于一个简单的网站,这个网站不充当门户,只提供嵌入在 HTML/CSS 中的信息。另一个例子是用于开发的数据库——通常没有人在乎数据是否丢失。

让我们考虑另一个例子。假设我们需要为 WordPress 容器提供数据库。如果我们将数据库文件存储在短暂存储中,当数据库容器被重建或删除时,我们可能会丢失所有数据。我们不能允许数据库文件被删除或丢失。OpenShift 可以无问题地重建我们的数据库容器。它将给我们一个工作实例的数据库,但没有所需的数据库/表结构和表中的数据。从应用程序的角度来看,这意味着所有必需的信息都丢失了。对于这些类型的应用程序(有状态的),我们需要一个持久存储,即使容器崩溃、被删除或重建,它依然可用。

存储需求(短暂存储与持久存储)取决于您的具体使用案例。

OpenShift 持久存储概念

OpenShift 使用持久卷PV)概念,允许管理员为集群提供持久存储,然后让开发人员通过持久卷声明PVC)请求存储资源。因此,最终用户可以在无需深入了解底层存储基础设施的情况下请求存储。同时,管理员可以配置底层存储基础设施,并通过 PV 概念将其提供给最终用户。

PV 资源在 OpenShift 集群中是共享的,因为其中任何一个都可以(如果允许的话)被任何用户/项目使用。另一方面,PVC 资源是特定于某个项目(命名空间)的,通常由最终用户(如开发人员)创建和使用。一旦 PVC 资源被创建,OpenShift 会尝试找到一个合适的 PV 资源,满足特定的标准,比如大小要求、访问模式(RWO、ROX、RWX)等。如果找到了满足 PVC 请求的 PV,OpenShift 会将该 PV 绑定到我们的 PVC。一旦绑定完成,PV 将不能再绑定到其他 PVC。

这个概念在以下截图中有所展示:

OpenShift Pod、PV、PVC 和存储关系

持久卷

PV 由一个 PersistentVolume OpenShift API 对象表示,该对象描述了现有的存储基础设施,如 NFS 共享、GlusterFS 卷、iSCSI 目标、Ceph RBD 设备等。假定底层存储组件已经存在并准备好被 OpenShift 集群使用。

PV(持久卷)有其自己的生命周期,这与任何使用 PV 的 pod 是独立的。

基础设施中的存储高可用性由底层存储提供商负责。

持久卷声明(PVC)

正如我之前提到的,OpenShift 用户可以通过 PVC(由 PersistentVolumeClaim OpenShift API 对象定义)请求应用程序所需的存储资源。PVC 代表终端用户(通常是开发人员)提出的请求。PVC 消耗 PV 资源。

PVC 包含一些关于应用程序/用户请求的资源的重要信息:

  • 所需大小

  • 访问模式

在 OpenShift 基础架构中,可以使用几种访问模式:

模式 描述 示例
ReadOnlyMany 卷可以被多个节点以只读模式挂载。 NFS 只读模式
ReadWriteOnce 卷可以被单个节点以读写模式挂载。 基于 iSCSI 的 xfs 等
ReadWriteMany 卷可以被多个节点以读写模式挂载。 GlusterFS,NFS

一旦 PVC 资源被创建,OpenShift 必须找到合适的 PV 资源并将其绑定到 PVC。如果绑定成功,PVC 资源可以被应用程序使用。

OpenShift 中的存储生命周期

PV 和 PVC(持久卷声明)资源之间的交互包括多个步骤,如下图所示:

OpenShift: 存储生命周期

OpenShift 集群管理员可以配置动态 PV 配置,或提前配置 PV 资源。一旦用户使用 PVC 请求具有特定大小和访问模式要求的存储资源,OpenShift 将查找可用的 PV 资源。至少,用户总是能获得他们要求的资源。为了将存储使用保持在最低限度,OpenShift 会绑定最小的符合所有标准的 PV。PVC 会一直处于未绑定状态,直到找到合适的 PV。如果有符合所有标准的卷,OpenShift 软件会将它们绑定在一起。从这一步骤开始,存储可以被 pod 使用。一个 pod 消耗 PVC 资源作为卷。

OpenShift 检查声明,找到绑定的卷并将该卷挂载到 pod。对于那些支持多种访问模式的卷,用户在将其作为 pod 中的卷使用时,指定希望使用的模式。

用户可以删除 PVC 对象,这样就可以回收存储资源。如果 PVC 被删除,该卷被视为 已释放,但尚未立即可以绑定到其他声明。这要求根据回收策略处理存储在卷上的数据。

回收策略定义了 OpenShift 在释放卷后如何处理卷的方式。支持以下回收策略:

策略 描述
保留 允许手动回收支持此功能的卷插件的资源。在这种情况下,存储管理员应手动删除数据。
删除 从 OpenShift 容器平台中删除 PV 对象以及在外部基础设施(如 AWS EBS、GCE PD 或 Cinder 卷)中关联的存储资产。

存储后端比较

OpenShift 支持多种不同工作方式的持久存储后端。其中一些支持多客户端的读写(如 NFS),而其他的则仅支持单个挂载。

下表包含了支持的存储后端/插件的比较:

卷后端 ReadWriteOnce ReadWriteMany ReadOnlyMany
AWS EBS
Azure Disk
Ceph RBD
光纤通道
GCE Persistent Disk
GlusterFS
HostPath
iSCSI
NFS(网络文件系统)
OpenStack Cinder
VMware vSphere
本地

HostPath 允许您直接从运行您的 Pod 的节点挂载持久存储,因此不适合生产使用。请仅在测试或开发目的下使用它。

在 OpenShift 集群中支持两种类型的存储:

  • 基于文件系统的存储(如 NFS、Gluster 和 HostPath)

  • 基于块的存储(如 iSCSI、OpenStack Cinder 等)

Docker 容器需要基于文件系统的存储作为持久卷使用。这意味着 OpenShift 可以直接使用基于文件系统的存储。在使用作为持久卷之前,OpenShift 需要在块存储上创建文件系统。例如,如果提供了一个 iSCSI 块设备,集群管理员在 PV 创建过程中必须定义将在块设备上创建的文件系统。

存储基础设施设置

配置底层存储基础设施通常是存储管理员的任务。这需要一些设置和设计决策,以便达到预期的耐用性、可用性和性能水平。这需要对底层资源、物理基础设施、网络等有重要的了解。一旦存储子系统由存储管理员正确配置,OpenShift 集群管理员可以利用它来创建 PV。

本书讲述的是 OpenShift 管理,因此底层存储技术的配置超出了其范围。然而,我们想演示如何在 Linux 系统上为 NFS、GlusterFS 和 iSCSI 执行存储基础设施的基本设置。

如果你仍然需要设置其他类型的存储,请参考相关文档。例如,你可以在 ceph.com 查找到 Ceph 存储文档;OpenStack Cinder 文档可以在项目主页 openstack.org 查找到。

我们选择了 NFS 和 GlusterFS 基于存储的方式,原因有很多:

  • 两者都是基于文件系统的存储解决方案

  • 两者都支持 ReadWriteMany OpenShift 访问类型

  • 两者都可以轻松地在任何 OpenShift 集群节点上配置

  • NFS 是任何 Linux 系统管理员都熟知的

我们还想展示如何在 OpenShift 集群中使用基于块的存储。我们选择了基于 iSCSI 的存储作为示例存储,因为它是 Linux 上使用块存储的最简单方式之一。

设置 NFS

网络文件系统 (NFS) 是一种客户端/服务器文件系统协议,最初由 Sun Microsystems 于 1984 年开发。NFS 允许客户端计算机(NFS 客户端)通过网络甚至互联网访问存储在 NFS 服务器上的文件。NFS 服务器与多个允许的 NFS 客户端共享一个或多个 NFS 共享。NFS 客户端将 NFS 共享挂载为常规文件系统。由于 NFS 是符合 POSIX 的文件系统协议,因此不需要特定的应用程序设置。这是 NFS 作为网络存储解决方案如此受欢迎的主要原因。NFS 默认由 Linux 内核支持,可以在任何基于 Linux 的服务器上进行配置。

在本教程中,我们将使用一个独立的 NFS 服务器为我们将在 OpenShift 集群上部署的应用程序提供持久存储。

所描述的安装过程适用于 CentOS 7。

NFS 安装和配置过程涉及多个步骤:

  1. 在服务器和客户端上安装 NFS 软件包

  2. 配置服务器上的 NFS 导出

  3. 启动并启用 NFS 服务

  4. 在客户端上验证或挂载 NFS 共享

在我们开始之前,需要部署两台机器,具体描述请见 技术要求 部分。在本实验中,我们假设这些机器是通过 Vagrant 部署为虚拟机的。

启动你的 Vagrant 环境并登录到 storage 虚拟机:

$ vagtrant up
$ vagrant ssh storage

在服务器和客户端上安装 NFS 软件包

需要在 NFS 服务器以及所有 OpenShift 节点上安装 NFS 软件包,因为它们将作为 NFS 客户端。NFS 库和二进制文件由 nfs-utils 包提供:

# yum install -y nfs-utils
…
<output omitted>
…
Updated:
  nfs-utils.x86_64 1:1.3.0-0.54.el7
Complete!

我们将在 storage.example.com 上配置 NFS 服务。所有配置均在 root 账户下完成。你可以使用 sudo -i 命令切换到 root 用户。

配置服务器上的 NFS 导出

这只需要在服务器端完成。我们将会在 /exports 目录下导出多个文件系统。这些导出的文件系统将仅对 OpenShift 节点可访问。

OpenShift 集群使用随机 用户 ID (UIDs) 运行 Docker 容器。由于难以预测 UID 以授予正确的 NFS 权限,因此我们必须配置以下 NFS 设置,允许 OpenShift 正常使用 NFS 共享:

  • 共享应由 nfsnobody 用户和组拥有。

  • 共享应具有 0700 访问权限。

  • 共享应使用 all_squash 选项进行导出。稍后将在本主题中描述这一点。

  1. 创建所需的目录并赋予正确的权限:
# mkdir -p /exports/{nfsvol1,nfsvol2,nfsvol3}
# chmod 0700 /exports/{nfsvol1,nfsvol2,nfsvol3}
# chown nfsnobody:nfsnobody /exports/{nfsvol1,nfsvol2,nfsvol3}
  1. 配置防火墙:
# firewall-cmd --perm --add-service={nfs,mountd,rpc-bind}
success

# firewall-cmd --reload
success

在 Vagrant box centos/7 上不需要此步骤,因为默认情况下 firewalld 被禁用。

  1. 通过将以下行添加到 /etc/exports 文件中创建 NFS 导出:
# cat <<EOF > /etc/exports
/exports/nfsvol1 openshift.example.com(rw,sync,all_squash)
/exports/nfsvol2 openshift.example.com(rw,sync,all_squash)
/exports/nfsvol3 openshift.example.com(rw,sync,all_squash)
EOF

你也可以指定节点的 IP 地址,而不是提供 FQDN。如下所示,代码中将显示:

# cat <<EOF > /etc/exports
/exports/nfsvol1 172.24.0.11(rw,sync,all_squash)
/exports/nfsvol2 172.24.0.11(rw,sync,all_squash)
/exports/nfsvol3 172.24.0.11(rw,sync,all_squash)
EOF

all_squash NFS 导出选项将 NFS 配置为将所有 UID 映射到 nfsnobody 用户 ID。

启动并启用 NFS 服务

我们还需要使用 systemctl 命令启用并启动 nfs-server。以下代码片段展示了如何启用和启动 NFS 服务所需的所有服务:

# systemctl enable rpcbind nfs-server
Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.
# systemctl start rpcbind nfs-server

验证

你可能需要检查 NFS 共享是否正确导出。以下命令将显示所有可用的导出:

# exportfs -v
/exports/nfsvol1 openshift.example.com(rw,sync,wdelay,hide,no_subtree_check,sec=sys,secure,root_squash,all_squash)
/exports/nfsvol2 openshift.example.com(rw,sync,wdelay,hide,no_subtree_check,sec=sys,secure,root_squash,all_squash)
/exports/nfsvol3 openshift.example.com(rw,sync,wdelay,hide,no_subtree_check,sec=sys,secure,root_squash,all_squash)

配置 GlusterFS 共享

GlusterFS 是一个免费的可扩展网络文件系统,适用于数据密集型任务,如云存储和媒体流。GlusterFS 在一个或多个存储节点上创建一个卷,使用 砖块。砖块代表存储节点上的文件系统。根据数据在砖块上的存放方式,定义了几种类型的 GlusterFS 卷。更多信息可以通过本章末尾提供的链接找到。在本章中,我们只需要了解以下几种卷类型的基本知识:

类型 描述
分布式 所有文件在砖块/存储节点之间分布。此卷类型不提供冗余。
复制 所有文件在两个或多个砖块之间复制。因此,每个文件至少存储在两个砖块上,从而提供冗余。
条带化 每个文件被分布在多个砖块上。

在本演示中,我们将在单个存储节点上设置一个基本的 GlusterFS 卷。

安装软件包

首先,我们需要安装 GlusterFS 软件包,这些软件包位于一个特殊的 GlusterFS 仓库中。centos-release-gluster312 包配置了 GlusterFS 3.12 仓库。我们需要在服务器端(storage.example.com)安装 glusterfs-server,并在客户端(openshift.example.com)安装 glusterfs 包:

# yum install -y centos-release-gluster312 ...
<output omitted>
...
# yum install -y glusterfs-server
...
<output omitted>
...
Dependency Installed:
 attr.x86_64 0:2.4.46-12.el7
 glusterfs.x86_64 0:3.12.6-1.el7
 glusterfs-api.x86_64 0:3.12.6-1.el7
 glusterfs-cli.x86_64 0:3.12.6-1.el7
 glusterfs-client-xlators.x86_64 0:3.12.6-1.el7
 glusterfs-fuse.x86_64 0:3.12.6-1.el7
 glusterfs-libs.x86_64 0:3.12.6-1.el7
 psmisc.x86_64 0:22.20-15.el7
 userspace-rcu.x86_64 0:0.10.0-3.el7

Complete!

一旦安装了 GlusterFS 软件包,我们需要启动并启用 Gluster 管理服务 glusterd

# systemctl enable glusterd
Created symlink from /etc/systemd/system/multi-user.target.wants/glusterd.service to /usr/lib/systemd/system/glusterd.service.
# systemctl start glusterd

配置砖块和卷

以下 GlusterFS 卷配置步骤要求我们创建一个砖块文件系统和卷本身:

对于本实验,我们将使用根文件系统来创建 GlusterFS 砖块。此设置仅适用于测试和开发目的,不适合生产使用。所有 GlusterFS 生产安装应使用独立的文件系统来存储 GlusterFS 砖块,最好位于独立的物理块设备上。

# mkdir /exports/gluster
# gluster volume create gvol1 storage.example.com:/exports/gluster force
volume create: gvol1: success: please start the volume to access data

由于我们使用/文件系统来创建 GlusterFS 卷,因此需要使用force选项。如果未提供此选项,您可能会看到以下输出:

# gluster volume create gvol1 storage.example.com:/exports/gluster
volume create: gvol1: failed: The brick storage.example.com:/exports/gluster is being created in the root partition. It is recommended that you don't use the system's root partition for storage backend. Or use 'force' at the end of the command if you want to override this behavior.

现在我们已经创建了一个卷,正是启动它、使其对客户端可用的好时机:

# gluster volume start gvol1
volume start: gvol1: success

配置 iSCSI

互联网小型计算机系统接口iSCSI)是一种客户端/服务器协议,通过在 TCP/IP 网络上传输 SCSI 命令,为存储设备提供块级访问。由于 iSCSI 使用 TCP/IP 网络,它可以在局域网LAN)、广域网WAN)和互联网中传输数据,实现位置无关的数据存储和检索。该协议允许客户端(发起者)向远程服务器上的存储设备(目标)发送 SCSI 命令。它是存储区域网络SAN)协议。iSCSI 允许客户端与远程块设备一起工作,并将其视为本地连接的磁盘。存在多种 iSCSI 目标实现(如 stgtd、LIO 目标等)。在本章中,我们将配置基于 LIO 目标的 iSCSI 存储。

配置storage.example.com上的 iSCSI 目标所需的步骤如下:

  1. 安装 CLI 工具:
# yum install -y targetcli
  1. 启用target服务:
# systemctl enable target; systemctl start target
  1. 配置防火墙:
# firewall-cmd --permanent --add-port=3260/tcp
# firewall-cmd --reload
  1. 使用targetcli配置 iSCSI 导出:
# targetcli
targetcli shell version 2.1.fb46
Copyright 2011-2013 by Datera, Inc and others.
For help on commands, type 'help'.

/> /backstores/fileio create iscsivol1 /exports/iscsivol1.raw 1g
Created fileio iscsivol1 with size 1073741824
/> /iscsi create iqn.2018-04.com.example.storage:disk1
Created target iqn.2018-04.com.example.storage:disk1.
Created TPG 1.
Global pref auto_add_default_portal=true
Created default portal listening on all IPs (0.0.0.0), port 3260.
/> cd iscsi/iqn.2018-04.com.example.storage:disk1/tpg1/
/iscsi/iqn.20...ge:disk1/tpg1> luns/ create /backstores/fileio/iscsivol1
Created LUN 0.
/iscsi/iqn.20...ge:disk1/tpg1> set attribute authentication=0 demo_mode_write_protect=0 generate_node_acls=1 cache_dynamic_acls=1
Parameter generate_node_acls is now '1'.
/iscsi/iqn.20...ge:disk1/tpg1> exit
Global pref auto_save_on_exit=true
Last 10 configs saved in /etc/target/backup.
Configuration saved to /etc/target/saveconfig.json

所有基本配置选项都可以在 man targetcliQUICKSTART部分找到。出于教育目的,前面的示例将 iSCSI 卷导出到任何主机。请注意,这不是一个生产就绪的配置。在生产环境中,您可能只希望将目标的访问权限授予某些主机。

客户端验证

接下来的主题将描述如何在 OpenShift 集群内使用 NFS、Gluster 和 iSCSI 存储资源。不过,您也可以手动使用之前配置的资源。在进入下一个主题之前,我们强烈建议通过在客户端进行挂载来验证所有资源是否正确配置。在我们的例子中,客户端位于openshift.example.com节点。我们在开始之前,先登录到 OpenShift 节点并切换到 root 账户:

$ vagrant ssh openshift
Last login: Sun Jul 8 22:24:44 2018 from 10.0.2.2
[vagrant@openshift ~]$ sudo -i

NFS 验证

为了验证 NFS 导出是否正常工作,我们需要在openshift.example.com节点上挂载它们,下面的代码展示了这一点。如果所有共享都能顺利挂载,您可以认为该共享已正确导出:

# yum install -y nfs-utils
... <output omitted>
...
# showmount -e storage.example.com
Export list for storage.example.com:
/exports/nfsvol3 openshift.example.com
/exports/nfsvol2 openshift.example.com
/exports/nfsvol1 openshift.example.com
# mkdir /mnt/{nfsvol1,nfsvol2,nfsvol3}
# mount storage.example.com:/exports/nfsvol1 /mnt/nfsvol1
# mount storage.example.com:/exports/nfsvol2 /mnt/nfsvol2
# mount storage.example.com:/exports/nfsvol3 /mnt/nfsvol3
# df -h|grep nfsvol
storage.example.com:/exports/nfsvol1 38G 697M 37G 2% /mnt/nfsvol1
storage.example.com:/exports/nfsvol2 38G 697M 37G 2% /mnt/nfsvol2
storage.example.com:/exports/nfsvol3 38G 697M 37G 2% /mnt/nfsvol3
# umount /mnt/nfsvol1 /mnt/nfsvol2 /mnt/nfsvol3

假设通过运行yum install -y nfs-utils,所有必需的包已安装。

GlusterFS 验证

可以通过使用 FUSE 客户端手动挂载 GlusterFS 卷。验证过程如下:

# yum install centos-release-gluster312 -y
# yum install glusterfs-fuse -y
# mkdir /mnt/gvol1
# mount -t glusterfs storage.example.com:/gvol1 /mnt/gvol1 

创建一个示例持久数据以供以后使用:

# echo "Persistent data on GlusterFS" > /mnt/gvol1/index.html

此存储将作为 Web 服务器根数据存储使用。

验证挂载点是否可用,然后卸载存储:

# df -h /mnt/gvol1/
Filesystem Size Used Avail Use% Mounted on
storage.example.com:/gvol1 38G 713M 37G 2% /mnt/gvol1
# umount /mnt/gvol1

iSCSI 验证

iSCSI 验证假设 OpenShift 节点可以访问块存储设备。如果一切顺利,你应该会在 /proc/partitions 看到一个额外的磁盘。iSCSI 客户端工具由 iscsi-initiator-utils 包提供。安装该包后,可以使用 iscsiadm 工具扫描目标以查找 iSCSI 导出:

# yum install -y iscsi-initiator-utils
... <output omitted>
...
# iscsiadm --mode discoverydb --type sendtargets --portal storage.example.com --discover 172.24.0.12:3260,1 iqn.2018-04.com.example.storage:disk1 # iscsiadm --mode node --login Logging in to [iface: default, target: iqn.2018-04.com.example.storage:disk1, portal: 172.24.0.12,3260] (multiple)
Login to [iface: default, target: iqn.2018-04.com.example.storage:disk1, portal: 172.24.0.12,3260] successful.
# cat /proc/partitions major minor #blocks name

   8 0 41943040 sda
   8 1 1024 sda1
   8 2 1048576 sda2
   8 3 40892416 sda3
 253 0 39288832 dm-0
 253 1 1572864 dm-1
   8 16 1048576 sdb

# iscsiadm --mode node --logout Logging out of session [sid: 2, target: iqn.2018-04.com.example.storage:disk1, portal: 172.24.0.12,3260]
Logout of [sid: 2, target: iqn.2018-04.com.example.storage:disk1, portal: 172.24.0.12,3260] successful.

# iscsiadm --mode node -T iqn.2018-04.com.example.storage:disk1 --op delete

你还可以使用 lsblk 工具发现系统中可用的块设备。

配置物理卷(PV)

如前所述,OpenShift 集群管理员可以为 OpenShift 用户创建 PV 资源以供将来使用。

本主题假设 OpenShift 环境已在 openshift.example.com 节点上运行。你可以使用 oc cluster up 或通过 Ansible 进行高级 OpenShift 安装。

如前所述,只有集群管理员才能配置 PV。因此,在开始以下实验之前,你必须切换到管理员账户:

# oc login -u system:admin 

我们建议创建一个新项目来进行这个与 持久存储 相关的实验:

# oc new-project persistent-storage

客户端将自动将当前项目切换到新创建的项目。

在接下来的示例中,我们将创建以下 PV:

PV 存储后端 大小
pv-nfsvol1 NFS 2 GiB
pv-gluster GlusterFS 3 GiB
pv-iscsi iSCSI 1 GiB

为 NFS 共享创建 PV

由 OpenShift API 创建的与 NFS 相关的 PersistentVolume 资源可以使用 YAML 或 JSON 表示法定义,并可以通过 oc create 命令提交给 API。之前,我们在 storage.example.com 上设置了多个 NFS 导出。现在,我们需要为它们创建适当的 PV 资源。

以下示例提供了一个文件,可以使 NFS 资源在 OpenShift 集群中可用:

# cat pv-nfsvol1.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfsvol1
spec:
  capacity:
    storage: 2Gi 
  accessModes:
    - ReadWriteMany 
  persistentVolumeReclaimPolicy: Retain 
  nfs: 
    path: /exports/nfsvol1
    server: storage.example.com
    readOnly: false

该文件包含以下信息:

  • 持久卷名称(pv_nfsvol1)位于 metadata 部分

  • 可用容量(2 GiB)

  • 支持的访问模式(ReadWriteMany)

  • 存储回收策略(保留)

  • NFS 导出信息(服务器地址和路径)

文件创建后,我们可以通过以下命令使资源可用于集群:

# oc create -f pv-nfsvol1.yaml persistentvolume "pv-nfsvol1" created

上述命令会创建相应的 OpenShift API 资源。请注意,资源尚未挂载到 Pod,但已准备好与 PVC 绑定。

你可能希望创建另外两个定义来抽象之前创建的其他 NFS 共享。这些共享位于 storage.example.com:/exports/nfsvol2storage.example.com:/exports/nfsvol3。共享 /exports/nfsvol2/exports/nfsvol3 将不会被使用。

与任何其他 OpenShift API 资源一样,我们可以通过运行describe命令查看其配置:

# oc describe pv pv-nfsvol1
Name: pv-nfsvol1
Labels: <none>
Annotations: <none>
StorageClass:
Status: Available
Claim:
Reclaim Policy: Retain
Access Modes: RWX
Capacity: 2Gi
Message:
Source:
    Type: NFS (an NFS mount that lasts the lifetime of a pod)
    Server: storage.example.com
    Path: /exports/nfsvol1
    ReadOnly: false
Events: <none>

你可以通过以下命令使用oc get pv查看我们的 PV:

# oc get pv | egrep "^NAME|^pv-"
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-nfsvol1 2Gi RWX Retain Available 37s

oc cluster up创建了一些预定义的 PV,命名为pv0001pv0100。它们未显示在前面的输出中。

为 GlusterFS 卷创建 PV

GlusterFS 天生是分布式的,且与之前基于 NFS 的存储有很大不同。OpenShift 集群需要了解底层的 Gluster 存储基础设施,以便任何可调度的 OpenShift 节点都能挂载 GlusterFS 卷。配置 GlusterFS 持久卷涉及以下内容:

  • 在每个可调度的 OpenShift 节点上安装glusterfs-fuse

  • 你底层基础设施中的现有 GlusterFS 存储

  • 在 GlusterFS 集群中定义一组不同的服务器(IP 地址)作为端点

  • 一个用于持久化端点的服务(可选)

  • 一个现有的 Gluster 卷将作为持久卷对象中的引用。

首先,我们需要在 OpenShift 节点上安装glusterfs-fuse包:

# yum install -y centos-release-gluster312
# yum install -y glusterfs-fuse

端点定义旨在表示 GlusterFS 集群的服务器作为端点,因此包括你的 Gluster 服务器的 IP 地址。端口值可以是端口范围内的任何数值(0 - 65535)。可选地,你可以创建一个持久化端点的服务。

GlusterFS 服务由Service OpenShift API 对象表示,显示如下:

# cat gluster-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: glusterfs-cluster
spec:
  ports:
    - port: 1

一旦该文件创建完成,可以像常规 API 对象一样创建glusterfs端点:

# oc create -f gluster-service.yaml
service "glusterfs-cluster" created
# oc get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
glusterfs-cluster 172.30.193.29 <none> 1/TCP 3s

GlusterFS 端点的定义应包含所有用于数据交换的 Gluster 存储节点的信息。我们的示例只包含一个节点,IP 地址为172.24.0.12。因此,为了创建 Gluster 端点定义文件并创建 Gluster 端点,请运行以下命令:

# cat gluster-endpoint.yaml
apiVersion: v1
kind: Endpoints
metadata:
  name: glusterfs-cluster 
subsets:
  - addresses:
      - ip: 172.24.0.12
    ports:
      - port: 1

# oc create -f gluster-endpoint.yaml
endpoints "glusterfs-cluster" created

# oc get endpoints
NAME ENDPOINTS AGE
glusterfs-cluster 172.24.0.12:1 17s

现在,我们准备创建一个指向之前创建的 Gluster 卷的 PV:

# cat pv-gluster.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-gluster 
spec:
  capacity:
    storage: 3Gi 
  accessModes: 
    - ReadWriteMany
  glusterfs: 
    endpoints: glusterfs-cluster 
    path: gvol1 
    readOnly: false
  persistentVolumeReclaimPolicy: Retain 

# oc create -f pv-gluster.yaml
persistentvolume "pv-gluster" created

我们正在使用Retain策略来演示系统管理员需要手动处理数据回收。

如我们所见,GlusterFS 的 PV 定义文件包含端点信息和卷的名称。

现在,以下卷应该可以使用:

# oc get pv | egrep "^NAME|^pv-"
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-gluster 3Gi RWX Retain Available 2s
pv-nfsvol1 2Gi RWX Retain Available 2m

iSCSI 的 PV

与 NFS 或 GlusterFS 持久卷不同,iSCSI 卷一次只能从一个客户端/Pod 访问。这是一个基于块的持久存储,我们应该提供我们将要使用的文件系统类型。在以下示例中,将使用ext4文件系统:

# cat pv-iscsi.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-iscsi
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  iscsi:
     targetPortal: storage.example.com
     iqn: iqn.2018-04.com.example.storage:disk1
     lun: 0
     fsType: 'ext4'
     readOnly: false

让我们创建卷:

# oc create -f pv-iscsi.yaml
persistentvolume "pv-iscsi" created

在实验结束时,你应该至少有三个 PV,如以下代码所示:

# oc get pv | egrep "^NAME|^pv-"
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-gluster 3Gi RWX Retain Available 1m
pv-iscsi   1Gi RWO Retain Available 6s
pv-nfsvol1 2Gi RWX Retain Available 3m

在 Pod 中使用持久存储

之前,我们创建了所有必需的 PV OpenShift API 对象,这些对象由 OpenStack 集群管理员提供。现在,我们将向你展示如何在应用程序中使用持久化存储。任何 OpenShift 用户都可以通过 PVC 概念请求持久化卷。

请求持久化卷

一旦 PV 资源可用,任何 OpenShift 用户都可以创建 PVC 来请求存储,并稍后使用该 PVC 将其附加为 Pod 中容器的卷。

接下来的示例不需要在system:admin账户下运行。任何非特权的 OpenShift 用户都可以使用 PVC 请求持久化卷。

用户应使用 YAML 或 JSON 语法创建 PVC 定义。以下示例展示了一个请求 1 GiB 持久化存储,并具有ReadWriteOnce权限的声明:

# cat pvc-db.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-db 
spec:
  accessModes:
  - ReadWriteOnce 
  resources:
     requests:
       storage: 1Gi 

现在,我们可以创建相应的 API 实体——PVC:

# oc create -f pvc-db.yaml
persistentvolumeclaim "pvc-db" created

我们可以使用oc get pvoc get pvc命令来验证 PVC 的状态。两者应显示 PV/PVC 的状态:

# oc get pv | egrep "^NAME|^pv-"
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-gluster 3Gi RWX Retain Available 2m
pv-iscsi   1Gi RWO Retain Bound persistent-storage/pvc-db 1m
pv-nfsvol1 2Gi RWX Retain Available 4m

# oc get pvc
NAME STATUS VOLUME CAPACITY ACCESSMODES STORAGECLASS AGE
pvc-db Bound pv-iscsi 1Gi RWO 28s

在你的特定案例中,PVC 将绑定到基于 iSCSI 的物理卷,因为它满足所有要求(ReadWriteOncecapacity)。Bound状态意味着 OpenShift 能够找到合适的物理卷来执行绑定过程。

将 PVC 绑定到特定的 PV

通常,用户无需担心底层存储基础设施。用户只需使用 PVC 指定所需的存储大小和访问模式即可订购所需的存储。在某些情况下,可能需要将 PVC 绑定到特定的 PV。假设以下场景:你的存储基础设施复杂,并且你需要为数据库服务器提供尽可能快速的存储。将其放在 SSD 存储上会是一个不错的选择。在这种情况下,存储管理员可以为你提供由 SSD 硬盘支持的 FC 或 iSCSI 基础卷。OpenShift 管理员可能会为未来使用创建一个特定的 PV。在用户端,我们需要将新创建的 PVC 绑定到该特定的 PV。通过在spec部分下指定volumeName参数(spec.volumeName),可以实现 PVC 到 PV 的静态绑定。

在我们的特定示例中,我们剩下两个未绑定的卷,具有ReadWriteMany访问类型:pv-glusterpv-nfsvol1。在以下示例中,我们将执行它们的静态绑定。

让我们为 Web 服务器数据创建一个 PVS 定义:

# cat pvc-web.yaml
apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
  name: "pvc-web"
spec:
  accessModes:
    - "ReadWriteMany"
  resources:
    requests:
      storage: "1Gi"
  volumeName: "pv-gluster"

从之前的定义创建 PVC,并查看 OpenShift 是否为其找到匹配的 PV:

# oc create -f pvc-web.yaml
persistentvolumeclaim "pvc-web" created

# oc get pv | egrep "^NAME|^pv-"
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-gluster 3Gi RWX Retain Bound persistent-storage/pvc-web 3m
pv-iscsi   1Gi RWO Retain Bound persistent-storage/pvc-db 2m
pv-nfsvol1 2Gi RWX Retain Available 5m

最后,我们将使用以下 PVC 请求 100 MiB 的数据:

# cat pvc-data.yaml
apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
  name: "pvc-data"
spec:
  accessModes:
    - "ReadWriteMany"
  resources:
    requests:
      storage: "100Mi"

# oc create -f pvc-data.yaml
persistentvolumeclaim "pvc-data" created

# oc get pv | egrep "^NAME|^pv-"
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-gluster 3Gi RWX Retain Bound persistent-storage/pvc-web 4m
pv-iscsi 1Gi RWO Retain Bound persistent-storage/pvc-db 2m
pv-nfsvol1 2Gi RWX Recycle Bound persistent-storage/pvc-data 6m

请注意,所有 PVS 现在都处于Bound状态。

在 Pod 定义中使用声明作为卷

之前,我们通过创建 PVC 请求了持久化存储,现在我们将创建一个应用程序,使用相应的 PVC,因为它们现在已绑定到由真实存储支持的 PVs。OpenShift 允许开发人员创建Pod并将 PVC 用作卷。以下示例展示了如何在基于 Apache 的容器中使用它:

# cat pod-webserver.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mywebserverpod
  labels:
    name: webeserver
spec:
  containers:
    - name: webserver
      image: docker.io/centos/httpd
      ports:
        - name: web
          containerPort: 80
      volumeMounts:
        - name: volume-webroot
          mountPath: /var/www/html
 volumes:
 - name: volume-webroot
 persistentVolumeClaim:
 claimName: pvc-web

在前面的代码中,我们定义了一个 Apache pod,并配置它附加上之前申请的 pvc-web 提供的持久化卷到容器。OpenShift 会自动找到绑定的 PV,并将其挂载到容器上。

名为 pvc-web 的 PVC 已绑定到基于 GlusterFS 的 PV。这个持久化存储实现要求在 OpenShift 的每个命名空间/项目中定义 Gluster 端点和服务。所以,在进入实验的下一部分之前,我们需要通过运行以下命令再次创建这些服务和端点:

oc create -f gluster-service.yaml

oc create -f gluster-endpoint.yaml

# oc create -f pod-webserver.yaml
pod "mywebserverpod" created

我们可以使用以下命令显示 pod 和卷相关信息:

# oc describe pod mywebserverpod | grep -A 4 Volumes:
Volumes:
  nfsvol:
    Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName: pvc-web
    ReadOnly: false

如果我们连接到容器并尝试创建 /var/www/index.html 文件,它将存储在 GlusterFS 中。我们可以验证 GlusterFS 卷是否已经挂载到节点上:

# df -h | grep gvol1
172.24.0.12:gvol1 38G 720M 37G 2% /var/lib/origin/openshift.local.volumes/pods/e2ca34d3-4823-11e8-9445-5254005f9478/volumes/kubernetes.io~glusterfs/pv-gluster

所以,现在容器可以访问挂载在/var/www/html的持久化数据。

之前,我们创建了一个存储在 GlusterFS 上的 index.html 文件。这意味着我们的 Web 服务器将自动访问 GlusterFS 卷 gvol1 上的所有数据。

现在,我们可以验证之前写入的持久化数据是否可访问。首先,我们需要获取 Web 服务器的集群 IP 地址:

# oc describe pod mywebserverpod | grep IP:
IP: 172.17.0.2

其次,尝试通过 curl 访问:

# curl http://172.17.0.2
Persistent data on GlusterFS

如我们所见,当前,Web 服务器显示的是 GlusterFS 上可用的数据。

现在,我们可以通过两种不同的方式验证数据是持久存储的:

  • 在后台存储上

  • 通过重新创建容器

让我们验证该文件确实存在于我们的 storage.example.com 服务器上:

[root@storage ~]# cat /exports/gluster/index.html Persistent data on GlusterFS

最后,让我们尝试再次删除并创建容器:

# oc delete pod mywebserverpod
pod "mywebserverpod" deleted
# oc create -f pod-webserver.yaml
pod "mywebserverpod" created
# oc describe pod mywebserverpod | grep IP:
IP: 172.17.0.2
# curl http://172.17.0.2:80 Persistent data on GlusterFS

如我们所见,数据得以持久存储并可访问,即使容器已被删除。

通过 oc volume 管理卷

OpenShift 用户可以通过 oc volume 将卷附加到任何运行中的应用程序。在这个例子中,我们将创建一个包含基本应用程序的 pod,并将持久化卷附加到该 pod 上。

首先,使用 oc new-app 部署一个基本的 Apache Web 服务器:

# oc new-app httpd ...
<output omitted>
...

一会儿后,所有 httpd 服务资源将会可用:

# oc get pod | egrep "^NAME|httpd"
NAME           READY STATUS   RESTARTS  AGE
httpd-1-qnh5k   1/1  Running    0       49s

oc new-app 创建了一个部署配置,控制应用程序部署过程。

在这个例子中,我们将把名为 pvc-data 的 PVC 作为卷附加到正在运行的容器中:

# oc volume dc/httpd --add --name=demovolume -t pvc --claim-name=pvc-data --mount-path=/var/www/html
deploymentconfig "httpd" updated

# oc get pod | egrep "^NAME|httpd" NAME READY STATUS RESTARTS AGE
httpd-2-bfbft 1/1 Running 0 40s

# oc describe pod httpd-2-bfbft | grep -A 4 Volumes:
Volumes:
 demovolume:
 Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
 ClaimName: pvc-data
 ReadOnly: false

我们可以验证是否将 NFS 共享挂载到容器:

# df -h | grep nfsvol1
storage.example.com:/exports/nfsvol1 38G 720M 37G 2% /var/lib/origin/openshift.local.volumes/pods/12cfe985-482b-11e8-9445-5254005f9478/volumes/kubernetes.io~nfs/pv-nfsvol1

现在,我们可以直接在导出目录中,在我们的存储服务器上创建一个 index.html 文件:

[root@storage ~]# echo "New NFS data" >/exports/nfsvol1/index.html

前面的命令是在存储服务器上运行的,而不是在 OpenShift 上!

一旦持久化数据可用,我们可以尝试访问 Web 服务:

# oc describe pod httpd-2-bfbft | grep IP:
IP: 172.17.0.3

# curl http://172.17.0.3:8080
New NFS data

如我们所见,将卷附加到 pod 上是成功的。现在,我们可以将其分离:

# oc volume dc/httpd --remove --name=demovolume
deploymentconfig "httpd" updated

请注意,每次更新相应的部署配置时,OpenShift 会滚动部署新的 pod。这意味着容器的 IP 地址会发生变化。为了避免这种情况,我们建议使用服务的 IP 地址来测试配置。

一旦容器被重新创建,注意到持久数据不可用。httpd 守护进程会显示默认页面:

# oc get pod
NAME READY STATUS RESTARTS AGE
httpd-3-fbq74 1/1 Running 0 1m

# oc describe pod httpd-3-fbq74 | grep IP:
IP: 172.17.0.4

# curl http://172.17.0.4:8080
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html  xml:lang="en">
 <head>
 <title>Test Page for the Apache HTTP Server on Red Hat Enterprise Linux</title>
...
<output omitted>
...

数据库容器的持久数据

让我们为 MariaDB 实例附加一个基于 iSCSI 的持久卷。首先,我们需要启动一个 mariadb 应用程序,如下所示:

# oc new-app \
-e MYSQL_USER=openshift \
-e MYSQL_PASSWORD=openshift \
-e MYSQL_DATABASE=openshift \
mariadb
Found image a339b72 (10 days old) in image stream "openshift/mariadb" under tag "10.1" for "mariadb"

MariaDB 10.1
------------
MariaDB is a multi-user, multi-threaded SQL database server. The container image provides a containerized packaging of the MariaDB mysqld daemon and client application. The mysqld server daemon accepts connections from clients and provides access to content from MariaDB databases on behalf of the clients.

Tags: database, mysql, mariadb, mariadb101, rh-mariadb101, galera

* This image will be deployed in deployment config "mariadb"
* Port 3306/tcp will be load balanced by service "mariadb"
* Other containers can access this service through the hostname "mariadb"
* This image declares volumes and will default to use non-persistent, host-local storage.
You can add persistent volumes later by running 'volume dc/mariadb --add ...'

--> Creating resources ...

deploymentconfig "mariadb" created
service "mariadb" created
--> Success
Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
'oc expose svc/mariadb'
Run 'oc status' to view your app.

等待几分钟,然后检查 mariadb 实例的状态:

# oc get pod | egrep "^NAME|mariadb"
NAME            READY STATUS   RESTARTS AGE
mariadb-1-lfmrn 1/1   Running   0       1m

我们需要知道数据库文件的默认位置。可以使用oc describe dc命令来获取此信息,示例如下所示:

# oc describe dc mariadb
Name: mariadb
...
<output omitted>
...
 Mounts:
 /var/lib/mysql/data from mariadb-volume-1 (rw)
 Volumes:
 mariadb-volume-1:
 Type: EmptyDir (a temporary directory that shares a pod's lifetime)
 Medium:
...
<output omitted>
...

如我们所见,默认情况下,该容器将所有数据存储在 /var/lib/mysql/data 目录下的 mariadb-volume-1 卷中。这允许我们使用 oc volume 子命令替换其中的数据。

现在,我们将为 mariadb 容器附加一个卷。请注意,之前创建的数据库结构将丢失,因为它们未被持久化存储:

# oc volume dc/mariadb --add --name=mariadb-volume-1 -t pvc --claim-name=pvc-db --mount-path=/var/lib/mysql --overwrite deploymentconfig "mariadb" updated

这将自动重新部署 mariadb 并将数据库文件放置在持久存储上。

ext4 文件系统应该提前在 iSCSI 目标上创建。

现在你可以看到,OpenShift 可以轻松与最流行的存储协议集成,并允许你使容器化应用程序更具弹性。

总结

在生产环境中,持久存储的使用是 OpenShift 集群管理员和 OpenShift 用户的日常活动。在本章中,我们简要讨论了 OpenShift API 对象,如 PV 和 PVC,它们允许你定义并使用持久存储。我们展示了如何配置基础的存储服务,如 NFS、GlusterFS 和 iSCSI,并通过 PV 对象将它们添加到 OpenShift 的基础设施中。此外,我们还介绍了如何通过 PVC 对象请求持久存储。最后,我们从应用程序的角度展示了持久存储的基本使用示例。

问题

  1. 哪种情况下使用持久存储比较合适?

    1. 开发环境中的 PostgreSQL 数据库

    2. 生产环境中的 MariaDB 数据库

    3. Memcached

    4. JDBC-连接器

  2. 以下哪些 OpenShift 存储插件支持 ReadWriteMany 访问模式?选择两个:

    1. NFS

    2. iSCSI

    3. Cinder 卷

    4. GlusterFS

  3. PV 必须属于哪个项目?

    1. 默认

    2. openshift

    3. 任何项目

    4. openshift-infra

  4. 假设我们创建了一个请求 2 Gi 存储的 PVC。哪个 PV 会与其绑定?

    1. 1950 Mi

    2. 1950 M

    3. 2 Gi

    4. 3 Gi

  5. 在使用 GlusterFS 卷之前,必须创建哪些 OpenShift API 对象?选择两个:

    1. Pod

    2. 服务

    3. 端点

    4. 路由

深入阅读

如果你对本章讨论的主题感兴趣,以下是一些链接供你参考:

第八章:OpenShift 核心概念

在前几章中,我们介绍了 Docker 和 Kubernetes 的基础知识,并讲解了 OpenShift 的架构。我们已经知道如何构建 OpenShift 实验环境,现在是时候动手操作,看看如何处理 OpenShift 的主要资源了。

本章将介绍 OpenShift 的核心概念,例如如何使用 OpenShift Pods、服务、路由、项目和用户创建新应用程序。这将为您提供基础技能,足以让您在 OpenShift 中运行和管理应用容器基础设施。

阅读完本章后,您将学习以下内容:

  • 在 OpenShift 中管理项目和命名空间

  • 在 OpenShift 中管理普通用户

  • 在 OpenShift 中创建新应用程序

  • 在 OpenShift 中管理 Pods

  • 在 OpenShift 中管理服务

  • 在 OpenShift 中管理路由

在 OpenShift 中管理项目

在我们开始之前,请确保您的 OpenShift 已经启动并运行。我们将使用 MiniShift 来完成本章内容:

$ minishift start --openshift-version=v3.9.0 --vm-driver=virtualbox
-- Starting profile 'minishift'
...
<output omitted>
...

一旦虚拟机启动,作为系统管理员登录:

$ oc login -u system:admin ...
<output omitted>
...
Using project "myproject".

如前所述,OpenShift CLI 与 Kubernetes CLI 有许多相似之处。oc 是 OpenShift 客户端的缩写,其工作方式类似于 Kubernetes 的 kubectl。随着我们深入了解,您会发现这两个命令有很多相似之处。

在我们深入了解如何创建第一个 OpenShift 应用程序并使用基础的 OpenShift 资源(如 Pods、服务和路由)之前,我们需要先熟悉 OpenShift 的管理和其他基本功能,如 OpenShift 项目和用户账户。首先,我们将重点关注 OpenShift 项目。

OpenShift 项目是一个带有额外功能(称为注释)的 Kubernetes 命名空间,注释为 OpenShift 提供了用户多租户和基于角色的访问控制。每个项目都有自己的一套策略、约束和服务账户。您可以看到,在 OpenShift 中,命名空间和项目的数量是相同的。我们需要使用的命令是 oc get namespacesoc get projects

$ oc get projects
NAME DISPLAY NAME STATUS
default Active
kube-public Active
kube-system Active
myproject My Project Active
openshift Active
openshift-infra Active
openshift-node Active
$ oc get namespaces
NAME              STATUS    AGE
default           Active     3d
kube-public       Active     3d
kube-system       Active     3d
myproject         Active     3d
openshift         Active     3d
openshift-infra   Active     3d
openshift-node    Active     3d

正如我们之前提到的,每个命名空间,或者说项目,是通过一系列规则与其他项目隔离的。这使得不同的团队可以独立工作。为了识别我们当前正在使用的项目,您可以使用 oc projects 命令。此命令会列出您可以使用的 OpenShift 项目,并告诉您当前正在使用的项目:

$ oc projects
You have access to the following projects and can switch between them with 'oc project <projectname>':
    default
    kube-public
    kube-system
    myproject - My Project
  * new-project1
    openshift
    openshift-infra
    openshift-node
Using project "new-project1" on server "https://127.0.0.1:8443".

星号 * 也表示当前项目。

我们可以看到有许多不同的项目可用,尽管我们还没有创建任何项目,因为系统管理员用户可以访问所有内容。

要创建一个新的 OpenShift 项目,必须使用 oc new-project 命令:

$ oc new-project new-project1
Now using project "new-project1" on server "https://127.0.0.1:8443".
...
<output omitted>
...

此命令创建一个新项目并自动切换到该项目。在我们的例子中,它会切换到 new-project1。我们也可以通过运行 oc project 命令手动切换到另一个可用的项目。现在让我们切换到 default 项目:

$ oc project default
Now using project "default" on server "https://127.0.0.1:8443".

请注意,oc project default 的输出不仅告诉你项目已切换到 default,还指定了 OpenShift 集群的 URL:https://127.0.0.1:8443。当我们处理多个独立工作的 OpenShift 集群时,这非常有用。

要删除一个 OpenShift 项目,可以使用 oc delete 命令:

$ oc delete project new-project1
project "new-project1" deleted

在接下来的章节中,我们将紧密地与项目一起工作。

在 OpenShift 中管理用户

当我们使用 oc 命令时,它会使用用户凭据向 OpenShift 集群发出 API 调用。

OpenShift 中有三种主要的用户类型。让我们快速了解这三种类型:

  • 普通用户:一个普通的 OpenShift 用户。普通用户通常是具有 OpenShift 项目访问权限的开发人员。普通 OpenShift 用户的示例包括 user1 和 user2。

  • 系统用户:系统 OpenShift 用户是特殊的,其中大多数用户在安装 OpenShift 时创建。系统用户的示例包括:

    • system:admin:OpenShift 集群管理员用户

    • system:node:node1.example.comnode1.example.com 节点用户

    • system:openshift-registry:OpenShift 注册表用户

  • 服务账户:与项目相关联的特殊系统用户。其中一些用户在创建新的 OpenShift 项目时会被创建。

在下一章中,我们将处理系统用户和服务账户。本章中,我们将处理普通用户。

我们可以通过使用 oc whoami 命令获取当前登录的 OpenShift 用户信息:

$ oc whoami
system:admin

要创建一个普通用户,可以使用 oc create user 命令:

$ oc create user user1
user "user1" created

在这个实验中,我们不需要设置用户密码,因为我们的实验环境已经设置为接受任何用户的任何密码。

默认情况下,用户会为我们当前正在工作的项目创建。要获取用户列表,可以使用 oc get users 命令:

$ oc get users
NAME      UID FULL NAME IDENTITIES
developer 46714e6b-2981-11e8-bae6-025000000001 anypassword:developer
user1 473664ec-299d-11e8-bae6-025000000001

我们应该能够看到两个用户:developeruser1

开发者用户是通过 oc cluster up 命令以及项目 myproject 创建的。

IDENTITIES 字段定义了身份验证方法。在我们的实验环境设置中,开发者用户可以使用任何密码。这就是 anypassword:developer 的含义。

最后,我们需要学习的一个基本操作是如何在不同用户之间切换。我们可以使用 oc login 命令来实现:

$ oc login -u developer
Logged into "https://127.0.0.1:8443" as "developer" using existing credentials.
You have one project on this server: "myproject"
Using project "myproject".

user1 没有定义任何身份验证方法。这就是为什么你如果尝试登录 user1 会失败的原因。

在接下来的章节中,我们将把所有这些内容整合在一起,并为一个或多个项目分配特定用户,并为用户授予不同的权限。

在 OpenShift 中创建新应用程序

在 OpenShift 中运行的第一个最基本命令是 oc new-app。这个命令类似于 Kubernetes 的 kubectl run,但是 oc new-app 更加强大,并且工作方式略有不同。我们将在本章后面详细展示 oc new-app 是如何工作的。

测试 oc new-app 命令的最佳方式是创建一个新项目,然后在那里运行它:

$ oc new-project project1
Now using project "project1" on server "https://127.0.0.1:8443".
You can add applications to this project with the 'new-app' command. For example, try:

    oc new-app centos/ruby-22-centos7~https://github.com/openshift/ruby-ex.git
to build a new example application in Ruby.

命令输出告诉我们运行 oc new-app centos/ruby-22-centos7~https://github.com/openshift/ruby-ex.git 命令。运行该命令看看它是如何工作的:

$ oc new-app centos/ruby-22-centos7~https://github.com/openshift/ruby-ex.git
--> Found Docker image 1f02469 (8 days old) from Docker Hub for "centos/ruby-22-centos7"
...
<output omitted>
...
 * An image stream will be created as "ruby-22-centos7:latest" that will track the source image
 * A source build using source code from https://github.com/openshift/ruby-ex.git will be created
 * The resulting image will be pushed to image stream "ruby-ex:latest"
 * Every time "ruby-22-centos7:latest" changes a new build will be triggered
 * This image will be deployed in deployment config "ruby-ex"
 * Port 8080/tcp will be load balanced by service "ruby-ex"
 * Other containers can access this service through the hostname "ruby-ex"
...
<output omitted>
...
 Run 'oc status' to view your app.

我们已经看到,运行 oc new-app 有时会花费很长时间,或者只是卡住好几个小时。

如果你遇到这种情况,可以尝试重新安装集群。

同时,请确保主机上的防火墙已关闭。

如果这些选项对你都无效,你可以始终运行一个独立的虚拟机,并从头安装 OpenShift。

相较于我们之前看到的输出,将会有更多的信息,但如果仔细阅读,你应该能够发现:

  • OpenShift 拉取 Docker 镜像 centos/ruby-22-centos7

  • OpenShift 从 GitHub 获取源代码并下载

  • OpenShift 将 GitHub 的源代码应用到镜像中,并将其存储在内部注册表中,命名为 ruby-ex

  • ruby-ex 用于创建构建和部署配置

  • ruby-ex 服务用于负载均衡那些名称中包含 ruby-ex 的 pod 的流量。

以下图表表示这一流程:

oc new-app workflow

我们将在下一章深入了解所有这些资源。

管理 OpenShift 中的 pod

OpenShift pod 是 Kubernetes pod,代表一组容器,每个 pod 作为基本的管理单元。pod 中的所有容器共享相同的存储卷和网络。为了获取 OpenShift 中 pod 的列表,我们可以使用 oc get pods 命令:

$ oc get pods
NAME            READY  STATUS    RESTARTS      AGE
ruby-ex-1-build  0/1   Completed   0           1h
ruby-ex-1-zzhrc  1/1   Running     0           56m

它与 Kubernetes pod 没有区别,这意味着它背后运行的是一个 Docker 容器。唯一的区别是现在有两个容器。其中一个容器是用于构建应用了源代码的最终镜像的容器(ruby-ex-1-build)。我们可以通过在 Minishift 虚拟机中运行 docker ps 命令轻松验证这一点:

$ minishift ssh docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d07dd7cf63e4 172.30.1.1:5000/myproject/ruby-ex@sha256:aa86aab41fbf81ce0b09a1209618b67f353b18e5ac2ed00a030e7577bad1ed44 "container-entrypoint"...
<output omitted>
...

我们可以通过查看镜像 ID 中的 myproject/ruby-ex 部分,轻松找到正确的运行容器。我们可以在 Kubernetes 中执行类似的操作,例如获取日志、编辑或描述、删除 pod 等。

在开始下一部分之前,尝试运行 oc delete

管理 OpenShift 中的服务

与 Kubernetes 类似,OpenShift 服务代表客户端与运行在 pod 中的实际应用程序之间的接口。一个服务是一个 IP:端口 对,将流量以轮询方式转发到后端 pod。

通过运行 oc new-app 命令,OpenShift 会自动创建一个服务。我们可以通过运行 oc get services 命令来验证这一点:

$ oc get services
NAME CLUSTER-IP         EXTERNAL-IP    PORT(S)           AGE
ruby-ex 172.30.173.195  <none>         8080/TCP          1h

输出结果与我们在 Kubernetes 中使用 kubectl get services 命令时得到的类似。我们可以通过运行 oc deleteoc expose 命令来删除并重新创建这个服务。在此之前,先在 Minishift 虚拟机内运行 curl 命令,验证服务是否正常运行:

$ minishift ssh "curl -I -m3 172.30.173.195:8080" ...
<output omitted>
...
0 0 0 0 0 0 0 0 --:--:-- --:--:-- 0 HTTP/1.1 200 OK ...
<output omitted>
...

状态码为200,这意味着网页可用且服务正常运行:

$ oc delete svc/ruby-ex
service "ruby-ex" deleted

确保服务已删除并且不再可用:

$ oc get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE

$ minishift ssh "curl -I -m3 172.30.173.195:8080" ...
<output omitted>
...
Command failed: exit status 28

现在,使用oc expose命令创建一个新的服务:

$ oc expose pods/ruby-ex-1-zzhrc
service "ruby-ex-1-zzhrc" exposed

在您的情况下,容器名称将会不同。重新运行oc get pods以获取运行的 Pod 名称。还有其他方法来创建服务,我们稍后将在本书中介绍:

$ oc get svc
NAME           CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ruby-ex-1-zzhrc 172.30.79.183 <none> 8080/TCP 1m

最后,通过在 Minishift VM 上运行curl命令来检查服务是否恢复正常:

$ minishift ssh "curl -I -m3 172.30.79.183:8080" ...
<output omitted>
...
0 0 0 0 0 0 0 0 --:--:-- --:--:-- 0 HTTP/1.1 200 OK ...
<output omitted>
...

在 OpenShift 中管理路由

OpenShift 有一种优雅的方式来暴露服务,以便可以从 OpenShift 集群外部访问。这个资源在 OpenShift 中称为router。OpenShift 提供了将外部主机名映射到负载均衡器的功能,以将流量分发到 OpenShift 服务:

OpenShift 路由器工作流程

为了使 OpenShift 路由器能够负载均衡外部流量,必须使用 HTTP 或 HTTPS 协议,并且可以通过 DNS 使用 DNS 通配符解析。

通过使用额外的参数运行oc expose命令,让我们将我们的服务暴露到 OpenShift 集群外部:

$ oc expose service/ruby-ex-1-zzhrc --hostname="openshiftdemo.local"

检查路由列表:

$ oc get routes
NAME    HOST/PORT PATH SERVICES    PORT  TERMINATION       WILDCARD
ruby-ex-1-zzhrc openshiftdemo.local ruby-ex-1-zzhrc 8080   None

如果尝试运行curl openshiftdemo.local命令,它将无法工作,因为openshiftdemo.local没有 DNS 记录。最简单的方法之一是在 hosts 文件中添加静态 DNS 记录。由于我们使用的是 Minishift VM,因此需要创建一个指向 Minishift VM 的静态 DNS 记录。第一步是使用oc status命令检查 Minishift VM:

$ oc status
In project My Project (myproject) on server https://192.168.99.109:8443

现在,我们需要通过使用 Minishift VM 的 IP 地址和我们的项目主机名在 hosts 文件中创建静态记录:

$ cat /etc/hosts | grep openshift
192.168.99.109 openshiftdemo.local

一切准备就绪,现在我们可以进行最终测试。打开 Web 浏览器并访问openshiftdemo.local/webpage

Ruby 演示应用程序网页

如果您没有看到欢迎网页,请检查是否可以 ping 通openshiftdemo.local,并尝试运行curl openshiftdemo.local命令。

如果尝试使用 Minishift VM 的 IP 地址而不是openshiftdemo.local打开网页,将会收到Application is not available。这是因为 OpenShift 路由器是基于 HAProxy 的容器,根据 URL 进行负载均衡。我们可以通过运行带有 OpenShift 路由器 Pod 的oc describe命令轻松验证这一点。首先,以system:admin身份登录,然后检查default命名空间中的路由器 Pod 名称:

$ oc login -u system:admin ...
<output omitted>
...
$ oc get pods -n default
NAME READY STATUS RESTARTS AGE
docker-registry-1-qctp4 1/1 Running 0 22h
persistent-volume-setup-mdmw6 0/1 Completed 0 22h
router-1-s9b7f 1/1 Running 0 22h

最后,运行oc describe命令,指定router-1-s9b7f pod,并查找Image行:

$ oc describe pods router-1-s9b7f -n default | grep Image:
    Image: openshift/origin-haproxy-router:v3.9.0

您可以将基于 HAProxy 的路由器替换为其他路由器,如 Nginx 或其他内容,但这超出了本书的范围。

摘要

在本章中,我们介绍了 OpenShift 核心概念,如 pods、services、routes、projects 和 users。在接下来的章节中,我们将详细探讨这些主题,并与这些资源密切合作,展示它们如何与其他 OpenShift 资源集成。

在下一章,我们将探讨高级 OpenShift 资源,包括源到镜像(source to image)和镜像流(image streams)、构建和构建配置(builds and build configs)、部署和部署配置(deployments and deployment configs),以及配置映射(config maps)和模板(templates)。我们还将使用 YAML 和 JSON 文件通过手动方式创建这些资源。

问题

  1. OpenShift 使用的两种节点类型是什么?选择两个:

    1. 节点

    2. MiniShift

    3. Vagrant

    4. Master

  2. 以下哪项不是 OpenShift 用户类型?选择两个:

    1. 普通用户

    2. 管理员用户

    3. 系统用户

    4. 服务账户

    5. 服务用户

  3. 在 OpenShift 中,pod 是最小的部署单元,表示一组容器:

    1. 正确

    2. 错误

  4. 在 OpenShift 中,哪个资源负责将 OpenShift 应用程序暴露给外部?选择一个:

    1. 服务

    2. 路由

    3. Pod

    4. 负载均衡器

  5. 列出可用 pods 和 routes 的两个命令是什么?选择两个:

    1. oc 获取 po

    2. oc 列出 pods

    3. oc 获取 rt

    4. oc 获取 routes

  6. 在 OpenShift 中,系统管理员用户账户仅能访问与系统相关的资源:

    1. 正确

    2. 错误

深入阅读

在本章中,我们简要介绍了 OpenShift 容器平台的核心概念。以下是一些链接,供您浏览,以便深入学习 OpenShift 基础知识:

第九章:高级 OpenShift 概念

在上一章中,我们简要描述了基本的 OpenShift 对象,如 pod、服务和路由。我们还让您了解了如何使用命名空间进行资源隔离,以及如何管理 OpenShift 中的用户。

本章讲解了高级 OpenShift 资源,如 ImageStreamsConfigMaps,逻辑上延续了上一章关于 OpenShift 核心概念的内容。OpenShift API 提供了数十种不同的资源来控制应用部署、安全性等各个方面。现在,我们将重点介绍一些最重要的资源。

完成本章后,您将学到以下内容:

  • 使用 ImageStreams 跟踪镜像版本历史

  • 使用 ConfigMaps 将配置与应用程序代码分离

  • 使用 LimitRanges 和 ResourceQuotas 控制资源消耗

  • 根据 CPU 和 RAM 使用情况自动扩展应用程序

技术要求

本章中,我们将在 Vagrant 管理的虚拟机上实践部署的 OpenShift。最后一部分关于自动扩展需要启用 Hawkular 度量,因此您需要通过 openshift_metrics_install_metrics Ansible 变量来安装 OpenShift。度量收集器和仪表板被部署在它们自己的 pod 中,因此我们还需要为虚拟机提供更多的 RAM。使用以下 Vagrantfile 来部署实验:

$ cat Vagrantfile $lab_openshift = <<SCRIPT
yum -y update
yum install -y epel-release git docker httpd-tools java-1.8.0-openjdk-headless
yum install -y ansible python-passlib
systemctl start docker
systemctl enable docker
git clone -b release-3.9 https://github.com/openshift/openshift-ansible /root/openshift-ansible
ssh-keygen -f /root/.ssh/id_rsa -N ''
cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
ssh-keyscan 172.24.0.11 >> .ssh/known_hosts
cp .ssh/known_hosts /root/.ssh/known_hosts
ssh-copy-id -i /root/.ssh/id_rsa root@172.24.0.11
reboot
SCRIPT

Vagrant.configure(2) do |config|
 config.vm.define "openshift" do |conf|
 conf.vm.box = "centos/7"
 conf.vm.hostname = 'openshift.example.com'
 conf.vm.network "private_network", ip: "172.24.0.11"
 conf.vm.provider "virtualbox" do |v|
 v.memory = 6144
 v.cpus = 2
 end
 conf.vm.provision "shell", inline: $lab_openshift
 end
end

为了能够从您的主机系统访问 VM 内的集群,请确保您笔记本电脑上的文件 /etc/hosts 看起来像这样:

$ cat /etc/hosts
127.0.0.1 localhost openshift localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
172.24.0.11 openshift.example.com hawkular-metrics.openshift.example.com

运行vagrant up并等待它完成所有工作。这可能需要最多 30 分钟,具体取决于您的互联网连接和计算资源:

$ vagrant up
Bringing machine 'openshift' up with 'virtualbox' provider...
...
<output omitted>
...

完成后,打开 SSH 会话连接到 VM 并切换为 root 用户:

$ vagrant ssh
[vagrant@openshift ~]$ sudo -i
[root@openshift ~]#

您可以使用以下清单来部署 OpenShift:

# cat /etc/ansible/hosts ...
<output omitted>
...
[masters]
172.24.0.11

[nodes]
172.24.0.11 openshift_node_labels="{'region': 'infra', 'zone': 'default'}" openshift_schedulable=true

[etcd]
172.24.0.11

[OSEv3:vars]
openshift_deployment_type=origin
openshift_disable_check=memory_availability,disk_availability
openshift_ip=172.24.0.11
ansible_service_broker_install=false
openshift_master_cluster_hostname=172.24.0.11
openshift_master_cluster_public_hostname=openshift.example.com
openshift_hostname=172.24.0.11
openshift_public_hostname=openshift.example.com
openshift_metrics_install_metrics=true
openshift_metrics_image_version=v3.9
openshift_master_default_subdomain=openshift.example.com

[OSEv3:children]
masters
nodes
etcd

请注意,我们已经指定了 openshift_metrics_install_metrics 变量来配置与自动扩展相关的度量。

截至写作时,度量镜像尚未标记正确版本,因此我们不得不提供 openshift_metrics_image_version 变量,以防止度量 pod 进入 ImagePullBackOff 状态。更多细节请参见 github.com/openshift/origin/issues/19440

现在,到了安装 OpenShift 的时候:

# cd openshift-ansible
# ansible-playbook playbooks/prerequisites.yml
...
<output omitted>
...
# ansible-playbook playbooks/deploy_cluster.yml
...
<output omitted>
..

以普通用户身份登录:

# oc login -u alice Username: alice
Password: anypassword
Login successful.

请记住,由于这次我们没有显式配置身份提供者,OpenShift 默认为 AllowAll,因此我们可以使用任何密码。

接下来,创建一个专门用于我们实验的项目:

# oc new-project advanced
...
<output omitted>
...

system:admin 用户身份重新登录:

# oc login -u system:admin ...
<output omitted>
...

接下来,我们需要运行以下命令:

# oc adm policy add-scc-to-user anyuid -z default
scc "anyuid" added to: ["system:serviceaccount:advanced:default"]

我们还没有讨论上面命令背后的概念,但在此时,只需理解它放宽了 OpenShift 对 pod 强加的权限即可。这个概念被称为 安全上下文约束SCC),在第十章 OpenShift 安全性 中的 安全上下文约束 部分会更详细地讨论。

最后,以 alice 用户重新登录:

# oc login -u alice

使用镜像流跟踪镜像的版本历史

某些 OpenShift 资源,如 pods、deployments、DeploymentConfigs、ReplicationControllers 和 ReplicaSets,引用 Docker 镜像来部署容器。通常的做法是通过镜像流引用这些镜像,而不是直接引用它们,镜像流充当了内部/外部仓库与客户端资源之间的间接层,创建了一个虚拟的镜像视图。

在官方文档和一些博客中,你可能会看到将镜像流与仓库进行比较。虽然从某种意义上说,资源通过镜像流引用镜像的方式类似于仓库,但这个类比不够清晰;镜像流本身不存储任何东西,它们仅仅是镜像管理的抽象。因此,在本章中,我们将它们视为虚拟视图,以便更准确地理解它们的实际作用。

使用镜像流有以下优点:

  • 如果上游镜像的更新引入了错误,你的应用程序不会意外崩溃,因为镜像流会标记你的 pod,使其仍然映射到镜像的工作版本,有效保护你免受故障影响

  • 可以在镜像流级别配置镜像更改触发器和周期性重新导入镜像

你很可能不需要从头创建镜像流,但理解它们的结构对于理解它们的功能非常重要。

使用 Ansible 安装的 Minishift 和 OpenShift 包含一些最流行镜像的默认镜像流,如 PostgreSQL、HTTPD 和 Python。它们位于 openshift 项目中:

# oc get is -n openshift
NAME        DOCKER REPO                                            ...
...
<output omitted>
...
mongodb     docker-registry.default.svc:5000/openshift/mongodb     ...
...
<output omitted>
...

为了更清楚地理解本节开头提到的“间接层”是什么意思,我们来仔细看看 mongodb 镜像流:

# oc describe is/mongodb -n openshift
...
<output omitted>
...
Unique Images:   3
Tags:            4

3.2 (latest)
  tagged from centos/mongodb-32-centos7:latest

  Provides a MongoDB 3.2 database on CentOS 7\. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/mongodb-container/tree/master/3.2/README.md.
  Tags: mongodb

  * centos/mongodb-32-centos7@sha256:d4dc006a25db1423caed1dcf0f253f352dbbe0914c20949a6302ccda55af72b1
      22 hours ago
...
<output omitted>
...

镜像流使用特定的符号来引用仓库中的镜像。我们以之前的例子为参考,进行分析:

centos/mongodb-32-centos7@sha256:d4dc006a25db1423caed1dcf0f253f352dbbe0914c20949a6302ccda55af72b1

上述镜像引用具有以下结构:

  • centos/mongodb-32-centos7:Docker 仓库中镜像的路径

  • sha256:表示镜像标识符是通过 SHA256 哈希算法生成的

  • d4dc006a25db1423caed1dcf0f253f352dbbe0914c20949a6302ccda55af72b1:镜像的哈希/ID 本身

镜像流本身没有实际用途,只有在支持应用程序生命周期时才存在。它们通常在以下场景中被后台创建:

  • 从 S2I 构建创建应用程序

  • 导入镜像

  • 直接从 Docker 镜像创建应用程序

  • 手动将镜像推送到内部注册表

由于本书中会进一步讨论 S2I 构建,我们将考虑另外三种方法。

导入镜像

可以通过从外部注册表导入镜像到内部注册表来创建镜像流:

# oc import-image nginx --confirm
The import completed successfully.

Name: nginx
Namespace: advanced
Created: Less than a second ago
Labels: <none>
Annotations: openshift.io/image.dockerRepositoryCheck=2018-07-18T20:02:07Z
Docker Pull Spec: docker-registry.default.svc:5000/advanced/nginx
Image Lookup: local=false
Unique Images: 1
Tags: 1

latest
  tagged from nginx

  * nginx@sha256:42e8199b5eb4a9e4896308cabc547740a0c9fc1e1a1719abf31cd444d426fbc8
      Less than a second ago

Image Name: nginx:latest
Docker Image: nginx@sha256:42e8199b5eb4a9e4896308cabc547740a0c9fc1e1a1719abf31cd444d426fbc8
Name: sha256:42e8199b5eb4a9e4896308cabc547740a0c9fc1e1a1719abf31cd444d426fbc8
...
<output omitted>
...

你可以从前面的输出中看到,Nginx 镜像已经上传到内部注册表

docker-registry.default.svc:5000/advanced/nginx。如你所见,它的名称与我们之前提供的镜像引用结构相对应。

让我们删除镜像流,为下一个练习提供一个干净的起点:

# oc delete is/nginx
imagestream "nginx" deleted

直接从 Docker 镜像创建应用程序

创建镜像流的另一种方法是使用 new-app 命令从现成的 Docker 镜像创建应用程序:

# oc new-app gists/lighttpd
--> Found Docker image cd7b707 (11 days old) from Docker Hub for "gists/lighttpd"

    * An image stream will be created as "lighttpd:latest" that will track this image
...
<output omitted>
...

Lighttpd 是另一个 Web 服务器,类似于 Nginx 或 Apache。我们在这个示例中使用它,因为 Nginx 和 Apache 镜像流都已开箱即用地提供给 OpenShift。

这将创建多个资源,其中之一就是镜像流。

如果你 describe 新创建的部署配置,你会看到它实际上引用的是镜像流,而不是镜像本身:

# oc describe dc/lighttpd
...
<output omitted>
...
  Containers:
   lighttpd:
    Image: gists/lighttpd@sha256:23c7c16d3c294e6595832dccc95c49ed56a5b34e03c8905b6db6fb8d66b8d950
...
<output omitted>
...

在之前的示例中,DeploymentConfig 根据以下方案引用了 Lighttpd 服务器镜像的镜像流:

  • gists/lighttpd:镜像流名称

  • sha256:表示镜像标识符是使用 SHA256 哈希算法生成的

  • 23c7c16d3c294e6595832dccc95c49ed56a5b34e03c8905b6db6fb8d66b8d950:镜像哈希值/ID

这就是部署配置和复制控制器通常在 OpenShift 中引用镜像的方式。

再次,让我们清理一下环境:

# oc delete all --all
deploymentconfig "lighttpd" deleted
imagestream "lighttpd" deleted
pod "lighttpd-1-hqjfg" deleted
service "lighttpd" deleted

手动将镜像推送到内部注册表

我们将讨论的最后一种创建镜像流的方法是直接将镜像推送到 OpenShift 内部注册表。

如果你还没有登录,请以 alice 的非特权账户登录:

# oc login -u alice

然后,运行以下命令以登录到内部注册表:

# docker login -u $(oc whoami) -p $(oc whoami -t) docker-registry.default.svc:5000 Login Succeeded

在前面的命令中,我们使用了一个称为 命令扩展 的 Bash 特性,它允许我们从左到右提供 login 命令所需的用户名、密码/令牌和注册表 IP:port。你可以单独运行所有这些命令(oc whoamioc whoami -t)来查看它们的输出。

现在我们已经在内部注册表中完成身份验证,可以像操作一般的 Docker 注册表一样直接推送镜像。让我们看看 OpenShift 内部注册表中有什么:

# docker images
REPOSITORY     TAG     IMAGE ID        CREATED                 SIZE
docker.io/cockpit/kubernetes latest 110aeeca4b8c 7 days ago     425 MB
docker.io/centos/nginx-112-centos7 <none> b6923820bf5b 7 days ago     313 MB
docker.io/gists/lighttpd <none>  cd7b7073c0fc  11 days ago    12.1 MB
docker.io/openshift/origin-web-console v3.9.0  aa12a2fc57f7      3 weeks ago    495 MB
docker.io/openshift/origin-docker-registry v3.9.0  8e6f7a854d66  3 weeks ago    465 MB
docker.io/openshift/origin-haproxy-router v3.9.0  448cc9658480   3 weeks ago    1.28 GB
docker.io/openshift/origin-deployer v3.9.0 39ee47797d2e 3 weeks ago 1.26 GB
docker.io/openshift/origin-service-catalog v3.9.0  96cf7dd047cb  3 weeks ago    296 MB
docker.io/openshift/origin-template-service-broker v3.9.0  be41388b9fcb  3 weeks ago    308 MB
docker.io/openshift/origin-pod v3.9.0  6e08365fbba9  3 weeks ago    223 MB
docker.io/sebp/lighttpd <none>  6b681cc70957  20 months ago  8.53 MB

让我们删除上一个练习中遗留下的 Lighttpd 镜像:

# docker rmi cd7b7073c0fc
...
<output omitted>
...

现在使用与上一小节相同的 Lighttpd 镜像:

# docker pull gists/lighttpd ...
<output omitted>
...
Status: Downloaded newer image for docker.io/gists/lighttpd:latest

用包含注册表地址和端口的标签标记它:

# docker tag docker.io/gists/lighttpd docker-registry.default.svc:5000/advanced/lighttpd

我们使用项目的名称来创建镜像流,作为仓库中镜像路径的一部分,因为我们使用的令牌只授予开发者用户在 myproject 项目中创建镜像流的权限。OpenShift 期望我们在特定位置找到镜像,这样它就可以从镜像中创建镜像流。

让我们检查一下是否存在两个标签引用的镜像:

# docker images
REPOSITORY    TAG   IMAGE  ID                                          ...
docker-registry.default.svc:5000/advanced/lighttpd latest cd7b7073c0fc ...
docker.io/gists/lighttpd                           latest cd7b7073c0fc ...
...
<output omitted>
...

最后,我们需要将镜像推送到仓库中:

# docker push docker-registry.default.svc:5000/advanced/lighttpd
The push refers to a repository [docker-registry.default.svc:5000/advanced/lighttpd]
...
<output omitted>
...

现在验证一下 lighttpd 镜像流是否已在 OpenShift 中创建:

# oc get is
NAME     DOCKER REPO                                        TAGS   UPDATED
lighttpd docker-registry.default.svc:5000/advanced/lighttpd latest 15 minutes ago

正如预期的那样,镜像流已创建。

和之前一样,我们需要删除所有内容,然后才能继续下一部分:

$ oc delete is/lighttpd
imagestream "lighttpd" deleted

使用 ConfigMap 将配置与应用程序代码分离

ConfigMap 资源用于将数据与运行应用程序的 pod 分离。这类资源包含任意数据,可以作为配置注入到 pod 中。在这个上下文中,“注入”意味着 pod 可以通过以下方式使用它:

  • 将其键/值对导出为环境变量

  • 将其值作为命令行参数传递给应用程序

  • 将其作为卷挂载到 pod 内部,挂载到应用程序期望找到配置文件的位置

在开始之前,确保你以非特权用户登录,以获得最具代表性的体验:

# oc login -u alice

让我们看看将 ConfigMap 导出为容器中的环境变量的过程。首先,我们需要从一系列环境变量创建 ConfigMap 本身:

# cat example.env 
VAR_1=Hello
VAR_2=World

# oc create cm example-config-map --from-env-file=example.env
configmap "example-config-map" created

使用以下命令查看实际资源的样子:

# oc describe configmap/example-config-map
Name: example-config-map
Namespace: advanced
Labels: <none>
Annotations: <none>

Data
====
VAR_1:
----
Hello
VAR_2:
----
World
Events: <none>

现在,我们已经准备好将其注入到 pod 中。创建一个简单的 Pod 定义,引用新创建的 ConfigMap:

# cat example-pod-1.yml 
apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
    - name: example
      image: cirros
      command: ["/bin/sh", "-c", "env"]
      envFrom:
        - configMapRef:
            name: example-config-map

然后,使用前面的定义创建 pod:

# oc create -f example-pod-1.yml
pod "example" created

正如你在第二章《Kubernetes 概览》中学到的,OpenShift 支持 YAML 和 JSON 两种格式用于资源定义;在本书中,我们主要依赖前者。为了提醒你 YAML 语法,可以参考这个链接:www.yaml.org/start.html

无论你使用 YAML 还是 JSON,OpenShift REST API 都支持非常具体的字段,这些字段在不同的资源类型之间有所不同,且已在 docs.openshift.org/latest/rest_api/api/ 中进行了文档化。

由于该命令是一个简单的 Linux 命令 env,而不是某种进程或监听服务器,pod 在命令完成后立即退出,但你仍然可以看到其日志:

# oc logs po/example
...
<output omitted>
...
VAR_1=Hello
VAR_2=World

正如你所看到的,我们在 ConfigMap 中定义的两个环境变量已经成功注入到容器中。如果我们在容器中运行应用程序,它将能够读取这些变量。

相同的方法可以用来将这些变量作为命令行参数传递给容器命令。首先,让我们删除旧的 pod:

# oc delete po/example
pod "example" deleted

然后,创建一个新的 pod 定义,以便你可以使用这些变量作为命令行参数来回显命令:

# cat example-pod-2.yml
apiVersion: v1
kind: Pod
metadata:
  name: example2
spec:
  containers:
    - name: example2
      image: cirros
      command: ["/bin/sh", "-c", "echo ${VAR_1} ${VAR_2}"]
      envFrom:
        - configMapRef:
            name: example-config-map

现在,使用更新后的定义创建一个容器:

# oc create -f example-pod-2.yml
pod "example2" created

如前所述,容器将在命令返回后立即退出,但其日志将包含由我们 ConfigMap 中的两个变量构成的命令输出:

# oc logs po/example2
Hello World

最后,我们将演示如何将 ConfigMap 作为配置文件挂载到 pod 中。再次,先删除之前练习中的 pod:

# oc delete po/example2
pod "example2" deleted

在本例中,我们将为 Nginx Web 服务器提供自定义配置文件,这将使其默认虚拟主机监听 8888 端口,而不是 80 端口。以下是实现这一目标的简单配置:

# cat nginx_custom_default.conf 
server {
    listen       8888;
    server_name  localhost;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}

现在,让我们继续从这个配置中创建一个 ConfigMap:

# oc create cm nginx --from-file nginx_custom_default.conf 
configmap "nginx" created

如果我们查看这个 ConfigMap 的原始资源定义,将会看到以下内容:

# oc export configmap/nginx
apiVersion: v1
data:
  nginx_custom_default.conf: |
    server {
        listen 8888;
        server_name localhost;
        location / {
            root /usr/share/nginx/html;
            index index.html index.htm;
        }
    }
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: nginx

正如你所看到的,配置文件的全部内容作为值插入到 config map 定义中的nginx_custom_default.conf键下,这样可以在 Pod 中引用该配置。

现在是时候创建一个将使用这个 ConfigMap 的 Pod 了。使用以下结构再创建一个 Pod 定义:

# cat example-pod-3.yml 
apiVersion: v1
kind: Pod
metadata:
  name: example3
  labels:
    role: web
spec:
  containers:
  - name: example3
    image: nginx
    volumeMounts:
    - name: conf
      mountPath: /etc/nginx/conf.d
  volumes:
  - name: conf
    configMap:
      name: nginx
      items:
      - key: nginx_custom_default.conf
        path: default.conf

你可以在configMap.items中指定路径参数,以提供配置将被存储的文件名。如果我们在前面的例子中没有这么做,文件名将与configMap-nginx_custom_default.conf中的键相同。我们必须为我们的 Pod 指定标签,以便之后能够为其创建服务。

现在我们来创建 Pod:

# oc create -f example-pod-3.yml 
pod "example3" created

为了检查服务器是否在 ConfigMap 中指定的端口上监听,我们可以在 Pod 内部打开一个 bash 会话,查看配置文件是否到位,但让我们使用一种更好的方法,这样我们可以更多地实践 OpenShift 资源。

我们需要为这个 Pod 创建一个服务并暴露它。首先,创建一个服务:

# oc expose po/example3 --port 8888
service "example3" exposed

我们必须在命令中明确指定端口,因为我们没有在 Pod 的定义中的containerPort参数中提供端口。

然后通过route暴露这个服务:

# oc expose svc/example3
route "example3" exposed
# oc get route
... example3-advanced.openshift.example.com ...

最后,我们可以使用curl命令从服务器的默认虚拟主机请求一个默认网页:

# curl -H 'Host: example3-advanced.openshift.example.com' 127.0.0.1
...
<output omitted>
...
<title>Welcome to nginx!</title>
...
<output omitted>
...

我们本可以在/etc/hosts中为上述路由创建一个单独的记录,指向127.0.0.1,但是为了保持环境尽可能干净,最好使用Host HTTP 头来选择特定的应用程序。

上述输出表明 Nginx 确实在8888/tcp端口上监听,正如 ConfigMap 中所指定的。这标志着我们对 ConfigMap 的练习结束,接下来我们清理实验环境:

$ oc delete all --all
route "example3" deleted
pod "example3" deleted
service "example3" deleted
$ oc delete configmap --all configmap "example-config-map" deleted
configmap "nginx" deleted

ConfigMap 与其他资源(如 Pod 或服务)不同,必须单独删除。

使用 ResourceQuota 控制资源消耗

OpenShift 项目在多租户环境中的主要思想之一是需要在比整个集群更精细的级别上限制资源消耗,从而为操作提供将这些限制应用于组织和部门的能力。

OpenShift 提供了两种机制来设置集群中资源消耗的限制:

  • ResourceQuota

  • LimitRanges

本节仅介绍 ResourceQuota,LimitRanges 将在下一节讨论。

ResourceQuota 可以用来控制可以创建的 API 资源数量,或者控制在同一项目中定义配额的 Pod 所消耗的 CPU、内存和存储量。本质上,它们决定了一个项目的容量。ResourceQuota 允许你控制以下类型的资源:

  • Pods

  • ReplicationControllers

  • 服务

  • Secrets

  • ResourceQuotas

  • ConfigMap

  • ImageStreams

  • PersistentVolumeClaims

  • requests.storage

  • CPU

  • 内存

  • 临时存储

  • limits.ephemeral-storage

  • limits.cpu

  • limits.memory

如果 CPU/内存或 limits.cpu/limits.memory 受配额管理,则同一项目中的所有 Pods 必须为相应的计算资源指定请求/限制。

在配额的背景下,所有 Pods 属于以下范围,配额可以应用于这些范围,并在这些范围内设置一定数量的资源:

范围 描述 受管资源
BestEffort 适用于所有以 BestEffort 服务质量运行的 Pods,这意味着对于 CPU、内存或两者都具有相等请求和限制的 Pods。这些 Pods 可以申请它们需要的任何资源,但在运行它们的节点内存不足时,它们最有可能被终止。
  • Pods

|

NotBestEffort 适用于所有运行时没有 BestEffort 服务质量的 Pods。
  • Pods

  • CPU

  • limits.cpu

  • 内存

  • 临时存储

  • limits.ephemeral-storage

  • limits.memory

|

Terminating 适用于所有通过 spec.activeDeadlineSeconds >= 0 部署的作业 Pods,例如在 S2I 构建期间部署的构建 Pods。
NotTerminating 适用于所有通过 spec.activeDeadlineSeconds 为 nil 的作业部署的 Pods,这意味着通常用于应用的 Pods。

现在,让我们看看如何为项目创建配额。与任何其他资源一样,它们可以通过 API 创建,但您也可以使用 CLI,这正是我们要做的。让我们切换回 system:admin 用户,因为管理配额需要管理员权限:

# oc login -u system:admin 

然后,我们将能够创建我们的第一个配额:

# oc create quota my-quota \
--hard=cpu=500m,memory=256Mi,pods=1,resourcequotas=1
resourcequota "my-quota" created

正如您所见,配额已成功创建:

# oc describe quota/my-quota
Name:            my-quota
Namespace:       advanced
Resource         Used    Hard
--------         ----    ----
cpu              0       500m
memory           0       256Mi
pods             0       1
resourcequotas   1       1

有趣的是,每个项目本身的配额数量可以由 ResourceQuota 控制。即使您为配额设置限制为 0,只要没有其他已存在的限制此数量的配额,您仍然可以创建第一个配额。

通过创建这个配额,我们在当前项目上设置了500 CPU 毫核(半核)、256Mi 请求的 RAM、1 个 Pod,以及 1 个 ResourceQuota。让我们看看配额是否生效。

首先,创建一个简单的 Pod 定义:

$ cat nginx-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    role: web
spec:
  containers:
  - name: nginx
    image: nginx

让我们尝试从中创建一个 Pod:

# oc create -f nginx-pod.yml 
Error from server (Forbidden): error when creating "nginx-pod.yml": pods "nginx" is forbidden: failed quota: my-quota: must specify cpu,memory

正如您所见,我们的定义没有通过配额的检查,因为它明确限制了请求的 CPU 和 RAM 量,但我们没有指定它们。让我们修改 nginx-pod.yml 并添加 resources 部分:

# cat nginx-pod.yml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    role: web
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
 requests:
 cpu: 100m
 memory: 128Mi

在创建时,Pod 将请求 1 个 CPU 核心和 128 MiB RAM,这完全在配额设置的限制范围内。让我们再试一次:

# oc create -f nginx-pod.yml 
pod "nginx" created

如预期,Pod 已成功创建。此时,我们可以查看我们的配额消耗了多少:

# oc describe quota/my-quota
Name:            my-quota
Namespace:       advanced
Resource         Used    Hard
--------         ----    ----
cpu              100m    500m
memory           128Mi   256Mi
pods             1       1
resourcequotas   1       1

现在,让我们看看如果我们尝试创建更多的 Pod 会发生什么。通过将用于创建第一个 Pod 的定义替换 nginxhttpd 来准备一个新的 Pod 定义:

# cat httpd-pod.yml 
apiVersion: v1
kind: Pod
metadata:
  name: httpd
  labels:
    role: web
spec:
  containers:
  - name: httpd
    image: httpd
    resources:
      requests:
        cpu: 400m
        memory: 128Mi

如果我们尝试创建第二个 Pod,我们将看到以下内容:

$ oc create -f httpd-pod.yml 
Error from server (Forbidden): error when creating "httpd-pod.yml": pods "httpd" is forbidden: exceeded quota: my-quota, requested: pods=1, used: pods=1, limited: pods=1

即使请求的内存量不会违反配额,pod 创建仍然被拒绝,因为配额限制了当前项目中的 pod 总数为 1

编辑配额以允许 2 个 pod 和 2 个 CPU 核心:

$ oc edit quota/my-quota
spec:
  hard:
    cpu: 500m
    memory: 256Mi
 pods: "2"
    resourcequotas: "1"

尝试再次创建第二个 pod:

$ oc create -f httpd-pod.yml
pod "httpd" created

它之所以有效,是因为配额设置为允许当前项目中最多 2 个 pod。

让我们再次查看配额允许的总资源使用情况:

$ oc describe quota/my-quota
Name:            my-quota
Namespace:       myproject
Resource         Used    Hard
--------         ----    ----
cpu              500m    500m
memory           256Mi   256Mi
pods             2       2
resourcequotas   1       1

如你所见,我们已用尽整个配额,无法创建新的 pod。

现在这项练习结束了,是时候通过清理实验室来准备下一项任务:

$ oc delete all --all
pod "httpd" deleted
pod "nginx" deleted

$ oc delete quota/my-quota
resourcequota "my-quota" delete

ConfigMaps 和 ResourceQuotas 被视为不同种类的资源,必须分别删除。

使用 LimitRanges 控制资源消耗

这是一种在 OpenShift 中按项目级别控制资源分配的方式,但与 ResourceQuotas 不同,它们在某些方面有所不同:

  • 它们应用于单个 pod、容器、镜像或镜像流

  • 它们无法控制某些资源,如机密、ConfigMaps、ResourceQuotas、服务和 ReplicationControllers。

  • 它们只能从原始定义中创建

根据它们应用的资源类型,LimitRanges 控制不同的计算资源和对象:

资源类型 控制的计算资源/属性
Pod
  • CPU

  • RAM

|

Container
  • CPU

  • RAM

|

镜像 推送到内部注册表的镜像大小
ImageStream
  • 根据镜像流的规格,唯一镜像标签的数量

  • 根据镜像流的状态,唯一镜像引用的数量

|

PersistentVolumeClaim 请求的存储量

Pod 和容器可以明确声明它们所需的 CPU 和/或 RAM 以及它们的限制,LimitRanges 会确保它们不会超出某些边界。此外,如果未指定,LimitRanges 还可以为请求的资源数量及其限制提供默认值。

根据 pod 声明的计算资源请求和限制的存在及差异,它们以不同的 服务质量QoS)等级运行,以便在资源争用时优先运行某些 pod。下表总结了可用的等级以及何时应用它们:

QoS 等级 描述
BestEffort 此等级分配给未明确指定请求和限制的 pod。这些 pod 可以根据需要消耗任何 CPU 和 RAM,但如果 pod 所在的节点缺少这些资源,它们将是首先被终止的 pod。
Burstable 请求的限制高于请求的 pod 被分配到 Burstable QoS 等级。它们的优先级低于 BestEffort pod,意味着只有在没有 BestEffort pod 可终止时,它们才会被终止。
Guaranteed 这个层级适用于计算资源请求和限制相等的 pod。每个使用此 QoS 的 pod 有权获得请求的资源量,但不能更多。它们具有最高优先级,这意味着只有在没有 BestEffort 或 Burstable pod 时,才会被终止。

正如上一节所述,设置 LimitRanges 需要管理员权限,因此请确保以 system:admin 用户登录:

# oc login -u system:admin

让我们考虑一个从头创建 LimitRange 的示例:

# cat my-limits.yaml 
apiVersion: v1
kind: LimitRange
metadata:
  name: my-limits
spec:
  limits:
    - type: Pod
      min:
        cpu: 200m
        memory: 256Mi
      max:
        cpu: 400m
        memory: 512Mi
    - type: Container
      min:
        cpu: 100m
        memory: 128Mi
      max:
        cpu: 300m
        memory: 256Mi

从前面的定义创建限制:

# oc create -f my-limits.yaml 
limitrange "my-limits" created

现在,让我们描述一下我们新创建的限制:

# oc describe limits/my-limits
Name:        my-limits
Namespace:   advanced
Type    Resource    Min    Max    Default Request    Default Limit    ...
----    --------    ---    ---    ---------------    -------------    ...
Pod         cpu     200m   400m   -                  -                ...
Pod         memory  256Mi  512Mi  -                  -                ...
Container   cpu     100m   300m   300m               300m             ...
Container   memory  128Mi  256Mi  256Mi              256Mi            ...

还有 spec.limits[].defaultspec.limits[].defaultRequest 参数,它们分别确定容器使用的 CPU/RAM 限制量以及默认请求的量。由于我们没有明确指定,它们默认与最大值相同。

下一步是创建一个请求特定计算资源并为自身设置资源使用限制的 pod。准备以下 pod 定义:

# cat limits-example-pod.yml 
apiVersion: v1
kind: Pod
metadata:
  name: limits-example
  labels:
    role: web
spec:
  containers:
  - name: httpd
    image: httpd
    resources:
      requests:
        cpu: 100m
        memory: 256Mi
      limits:
        cpu: 350m
        memory: 256Mi

接下来,从定义中创建一个 pod:

# oc create -f limits-example-pod.yml 
Error from server (Forbidden): error when creating "limits-example-pod.yml": pods "limits-example" is forbidden: [minimum cpu usage per Pod is 200m, but request is 100m., maximum cpu usage per Container is 300m, but limit is 350m.]

正如您通过查看 pod 定义可能预料到的那样,操作被拒绝,因为 pod 的请求和限制范围违反了之前定义的策略。

最小边界也被强制执行。

让我们编辑 pod 的定义,使其符合定义的 LimitRange:

# cat limits-example-pod.yml 
...
<output omitted>
...
    resources:
      requests:
        cpu: 200m
        memory: 256Mi
      limits:
        cpu: 250m
        memory: 256Mi

再次尝试创建并观察它是否正常工作:

# oc create -f limits-example-pod.yml 
pod "limits-example" created
# oc get po
NAME          READY   STATUS    RESTARTS   AGE
limits-example 1/1    Running    0         4s

让我们清理实验环境,为下一节做准备:

# oc delete po/limits-example
pod "limits-example" deleted

# oc delete limits/my-limits
limitrange "my-limits" delete

LimitRanges 也被视为一种单独的资源,就像模板、ConfigMaps 和 ResourceQuotas 一样,因此它们必须通过单独的命令删除。

使用模板创建复杂的应用程序堆栈

另一个有用的 OpenShift 资源是模板。与其逐个创建资源——例如 pod、服务和路由——模板允许您通过单个 CLI 命令一次性创建多个对象。更重要的是,它们可能包含可以是可选的参数,或者根据特定规则生成的静态或默认值。从某种意义上说,它们类似于 Docker Compose 或 OpenStack Heat——这些都提供了从零开始创建完整应用程序堆栈的功能。通过模板,集群管理员可以为开发人员提供部署多层应用程序及其所有依赖服务的能力。

默认情况下,OpenShift 安装了许多默认模板,称为 Instant AppQuick Start 模板。它们可用于部署基于各种语言和框架的运行时环境,例如 Ruby on Rails(Ruby)、Django(Python)和 CakePHP(PHP)。它们还包括带有持久存储的 SQL 和 NoSQL 数据库引擎模板,其中包括 PersistentVolumeClaims 作为提供数据持久性的对象之一。

在本次练习中,您不需要管理员权限,因此可以作为普通用户登录:

# oc login -u alice

默认模板在安装期间会在 openshift 项目中创建。您可以通过运行以下命令查看它们:

# oc get template -n openshift | cut -d' ' -f1
NAME
3scale-gateway
amp-apicast-wildcard-router
amp-pvc
cakephp-mysql-example
cakephp-mysql-persistent
dancer-mysql-example
dancer-mysql-persistent
django-psql-example
django-psql-persistent
dotnet-example
dotnet-pgsql-persistent
dotnet-runtime-example
httpd-example
...
<output omitted>
...

我们使用了 cut 命令来排除描述和其他信息,目的是简化输出,但您也可以在不使用 cut 的情况下运行该命令,以查看完整输出。

当 MiniShift 和 OpenShift 通过 Ansible 安装器安装时,默认模板会直接安装。但在容器化快速安装的情况下,您可能需要从位于 roles/openshift_examples/files/examples/ 目录中的 YAML 定义手动创建它们。

要获取特定模板支持的参数列表,请使用 process 命令:

# oc process --parameters mariadb-persistent -n openshift 
NAME                   DESCRIPTION       GENERATOR       VALUE
MEMORY_LIMIT           ...                               512Mi
NAMESPACE              ...                               openshift
DATABASE_SERVICE_NAME  ...                               mariadb
MYSQL_USER             ...               expression      user[A-Z0-9]{3}
MYSQL_PASSWORD         ...               expression      [a-zA-Z0-9]{16}
MYSQL_ROOT_PASSWORD    ...               expression      [a-zA-Z0-9]{16}
MYSQL_DATABASE         ...                               sampledb
MARIADB_VERSION        ...                               10.2
VOLUME_CAPACITY        ...                               1Gi

我们省略了参数的描述,以便让输出更加易读。

正如您可能已经注意到的,某些参数具有动态默认值,这些值是由表达式生成的,这些表达式 loosely 基于 Perl 兼容正则表达式 (PCREs)。

process 命令会从所有动态表达式中生成默认值,使得模板定义准备好用于创建资源,可以通过将其输出传递给 create 命令,或运行 new-app 命令来实现——稍后我们将详细讲解。现在,让我们使用该命令查看要创建的对象的 List

# oc process openshift//mariadb-persistent
{
    "kind": "List",
    "apiVersion": "v1",
    "metadata": {},
    "items": [
        {
            "apiVersion": "v1",
            "kind": "Secret",
            ...
            <output omitted>
            ...
            "stringData": {
                "database-name": "sampledb",
                "database-password": "tYuwInpmocV1Q1uy",
                "database-root-password": "icq5jd8bfFPWXbaK",
                "database-user": "userC7A"
            }
        },
        ...
        <output omitted>
        ...
    ]
}

process 命令允许使用另一种语法,<NAMESPACE>//<TEMPLATE>。我们在此处使用它是为了演示,但您可以自由地使用更熟悉的 -n <NAMESPACE> 语法。

列表很长,所以我们只提供了一个摘录,显示包含所有生成的敏感值的 Secret 资源,这些值将用于模板实例化。

为了更清楚地理解,让我们看一下生成这些值的原始模板定义中的表达式:

# oc export template mariadb-persistent -n openshift
apiVersion: v1
kind: Template
...
<output omitted>
...
objects:
- apiVersion: v1
  kind: Secret
  ...
  <output omitted>
  ... 
  stringData:
    database-name: ${MYSQL_DATABASE}
    database-password: ${MYSQL_PASSWORD}
    database-root-password: ${MYSQL_ROOT_PASSWORD}
    database-user: ${MYSQL_USER}
...
<output omitted>
...
parameters:
...
<output omitted>
...
- description: Username for MariaDB user that will be used for accessing the database.
  displayName: MariaDB Connection Username
  from: user[A-Z0-9]{3}
  generate: expression
  name: MYSQL_USER
  required: true
...
<output omitted>
...
- description: Name of the MariaDB database accessed.
  displayName: MariaDB Database Name
  name: MYSQL_DATABASE
  required: true
  value: sampledb
...
<output omitted>
...

您可能已经注意到,例如,MYSQL_DATABASEsampledb,而 MYSQL_USER 以字符串 user 开头,后面跟着三个字母数字字符,正如我们在之前的列表中看到的那样。

要了解如何为动态参数构造正则表达式,请参考 perldoc.perl.org/perlre.html

现在,我们将创建我们自己的简单模板。创建一个新的模板定义,内容如下:

# cat example-template.yml 
kind: Template
apiVersion: v1
metadata:
  name: example-template
labels:
  role: web
message: You chose to deploy ${WEB_SERVER}
objects:
  - kind: Pod
    apiVersion: v1
    metadata:
      name: example-pod
    spec:
      containers:
        - name: ${WEB_SERVER}
          image: ${WEB_SERVER}
  - kind: Service
    apiVersion: v1
    metadata:
      name: example-svc
    spec:
      ports:
        - port: 80
      selector:
        role: web
  - kind: Route
    apiVersion: v1
    metadata:
      name: example-route
    spec:
      to:
        kind: Service
        name: example-svc
parameters:
  - name: WEB_SERVER
    displayName: Web Server
    description: Web server image to use
    value: nginx

尽管在我们的案例中,message 参数的使用方式相当基础,但在更复杂的模板中,它的目的是告诉用户如何使用模板——例如生成了哪些用户名、密码、URL 等。

这个模板可以用来创建三个资源:

  • 一个运行 Web 服务器的 pod,您可以通过提供 WEB_SERVER 参数来选择服务器类型。默认情况下,它是 nginx

  • 一个代理服务,用于转发传入流量到 pod。

  • 用于外部访问的路由。

我们可以立即处理该定义,并将生成的资源列表传递给 create 命令,但一种常见的策略是先从定义中创建模板:

# oc create -f example-template.yml 
template "example-template" created

让我们试着处理一下:

# oc process --parameters example-template
NAME       DESCRIPTION             GENERATOR         VALUE
WEB_SERVER Web server image to use                   nginx

你可以看到唯一的参数,以及你之前定义的默认值和描述。

现在,是时候从我们的模板创建一组资源了。可以通过将 process 命令的输出传递给之前提到的 create 命令,或者使用 new-app 命令来完成。让我们从前者开始:

# oc process example-template | oc create -f -
pod "example-pod" created
service "example-svc" created
route "example-route" created

如你所见,create 命令只是将资源列表传递给 API,并一一提交请求以创建它们,因此输出类似于你手动创建三个单独的资源定义并从中创建资源时看到的输出。

但另一种实例化模板的方法会为你提供更多关于发生了什么的信息。我们先删除已创建的资源:

# oc delete all --all
route "example-route" deleted
pod "example-pod" deleted
service "example-svc" deleted

我们不必删除模板,因为它不会发生变化。现在,我们可以使用 new-app 命令:

# oc new-app --template=example-template
--> Deploying template "myproject/example-template" to project myproject

     example-template
     ---------
     You chose to deploy nginx

     * With parameters:
        * Web Server=nginx

--> Creating resources ...
    pod "example-pod" created
    service "example-svc" created
    route "example-route" created
--> Success
    Access your application via route 'example-route-advanced.openshift.example.com' 
    Run 'oc status' to view your app.
# oc status
In project advanced on server https://172.24.0.11:8443

http://example-route-advanced.openshift.example.com (svc/example-svc)
  pod/example-pod runs nginx

1 info identified, use 'oc status -v' to see details.

如你所见,我们创建了 pod,前端服务并通过路由暴露出来,仅用一个命令就完成了。请注意,你无需运行 oc get route 命令来查找你的应用程序可通过哪个 URL 访问——这一切都会在输出中显示出来。

让我们检查一下我们的 Web 服务器是否可以通过 curl 访问:

# curl -IH 'Host: example-route-advanced.openshift.example.com' 127.0.0.1
HTTP/1.1 200 OK
Server: nginx/1.15.1
...
<output omitted>
...

我们使用了 curl 命令的 -I 参数来只查看响应头,这足以检查服务器的响应性并确保它不会将原始 HTML 输出到控制台。同时,和之前一样,我们使用了 -H 选项来从 OpenShift 的路由器请求特定的应用程序。

你可以轻松删除所有资源并再次实例化模板,但这次使用另一种 Web 服务器镜像,比如 Apache:

# oc delete all --all
route "example-route" deleted
pod "example-pod" deleted
service "example-svc" deleted

# oc new-app --template=example-template -p WEB_SERVER=httpd
--> Deploying template "myproject/example-template" to project myproject

     example-template
     ---------
     You chose to deploy httpd
...
<output omitted>
...
    Access your application via route 'example-route-advanced.openshift.example.com' 
    Run 'oc status' to view your app.

# curl -H 'Host: example-route-advanced.openshift.example.com' 127.0.0.1
<html><body><h1>It works!</h1></body></html>

# curl -IH 'Host: example-route-advanced.openshift.example.com' 127.0.0.1
HTTP/1.1 200 OK
Date: Thu, 19 Jul 2018 00:59:47 GMT
Server: Apache/2.4.34 (Unix)
...
<output omitted>
...

就是这样——一个参数,你就可以在几秒钟内部署一个不同的 Web 服务器。

你也可以执行逆向操作——从现有资源创建模板。为此,使用 export 命令:

# oc export all --as-template=exported-template > exported-template.yml

让我们删除我们的资源,以防发生冲突:

# oc delete all --all
route "example-route" deleted
pod "example-pod" deleted
service "example-svc" deleted

并从导出的模板中重新创建它们:

# oc new-app -f exported-template.yml 
--> Deploying template "advanced/exported-template" for "exported-template.yml" to project advanced

--> Creating resources ...
    route "example-route" created
    pod "example-pod" created
    service "example-svc" created
--> Success
    Access your application via route 'example-route-advanced.openshift.example.com' 
    Run 'oc status' to view your app.

你可能注意到 Web 服务器是通过与之前相同的 URL 暴露的。这是因为导出的模板是从已经实例化的资源创建的,所有参数都解析为具体值,因此 OpenShift 无法知道哪些字段是参数化的。你也可以从 process 命令的输出中推断出这一点,它会显示所有字段已经初始化。因此,严格来说,这并不是一个完全的逆向操作,但它可以用于备份。

现在我们完成了,让我们进行清理:

# oc delete all --all
route "example-route" deleted
pod "example-pod" deleted
service "example-svc" deleted

# oc delete template/example-template
template "example-template" deleted

根据 CPU 和 RAM 使用情况自动扩展应用程序

你可以使用 oc scale 命令来扩展应用程序中的 pod,但它有两个缺点:

  • 每次需要扩展 pod 时都必须手动运行此命令

  • 你需要自己考虑 CPU 和 RAM 使用情况

这种方式无法使企业迅速适应不断变化的客户需求。还有一种更好的方式——HorizontalPodAutoscaler

在撰写本文时,自动扩缩只能跟踪 CPU 和 RAM 的使用情况。例如,基于流量的自动扩缩并不被支持。

让我们以system:admin身份登录,看看 Hawkular、Cassandra 和 Heapster 的 Pod 是否已启动并运行:

# oc login -u system:admin
...
<output omitted>
...
# oc get po -n openshift-infra
NAME                       READY STATUS  RESTARTS AGE
hawkular-cassandra-1-ffszl 1/1   Running 0        10m
hawkular-metrics-bl6jh     1/1   Running 0        10m
heapster-brvfd             1/1   Running 0        10m

当你到达本节时,所有的指标 Pod 都会准备就绪,但通常需要 8 到 10 分钟才能在安装完成后启动。

基于 CPU 的自动扩缩

基于 CPU 的自动扩缩还需要为被扩缩的 Pod 设置 CPU 请求的限制范围,因此我们可以使用之前章节中的LimitRange定义。

# cat my-limits.yaml apiVersion: v1
kind: LimitRange
metadata:
  name: my-limits
spec:
  limits:
    - type: Pod
      min:
        cpu: 50m
        memory: 64Mi
      max:
        cpu: 150m
        memory: 128Mi
    - type: Container
      min:
        cpu: 50m
        memory: 64Mi
      max:
        cpu: 150m
        memory: 128Mi
 # oc create -f my-limits.yaml 
limitrange "my-limits" created

根据你的主机 CPU,可能需要调整上述文件中的值,以使自动扩缩功能生效。这也是为什么在上面的列表中,它们与章节开头的设置有所不同。

自动扩缩功能可以应用于部署配置,因此创建一个最简单的方法是使用已经熟悉的new-app命令:

# oc new-app httpd
...
<output omitted>
...
--> Creating resources ...
    deploymentconfig "httpd" created
    service "httpd" created
--> Success
    Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
     'oc expose svc/httpd' 
    Run 'oc status' to view your app.

出于演示目的,我们使用了 Apache Web 服务器镜像来创建镜像流,镜像流又用于创建应用程序。现在deploymentconfig已经准备好管理 Pod,我们可以创建一个HorizontalPodAutoscaler来管理deploymentconfig本身:

# oc autoscale dc/httpd --min=2 --max=4 --cpu-percent=10
deploymentconfig "httpd" autoscaled

我们指定了2作为始终必须保持的最小 Pod 数量,这样你就可以快速观察到自动扩缩的效果,而不必在 Pod 上生成 CPU 负载来触发它。我们稍后也会这样做。

让我们确认它已经创建:

# oc get hpa
NAME   REFERENCE              TARGETS   MINPODS MAXPODS REPLICAS  AGE
httpd  DeploymentConfig/httpd 0% / 20%  2       4       2         3m

如果你在创建后立即运行此命令,你很可能会在前面的输出中看到"unknown"而不是0%。这是预期的,因为HorizontalPodAutoscaler通常需要几分钟时间来收集足够的指标。

几分钟后,你可以列出正在运行的 Pod,并注意到现在有两个 Pod:

# oc get po
NAME            READY     STATUS    RESTARTS   AGE
httpd-1-5845b   1/1       Running   0          7s
httpd-1-scq85   1/1       Running   0          2m

现在,我们需要模拟大量用户请求到我们的 Pod,以增加 CPU 负载,使得自动扩缩能够生效。但在此之前,我们需要先创建一个路由:

# oc expose svc/httpd
route "httpd" exposed

# oc get route
... httpd-advanced.openshift.example.com ...

到此为止,我们已经准备好了所有所需内容,接下来让我们使用ab Apache 基准测试工具来模拟 CPU 负载:

# ab -c 100 -n 10000000 -H 'Host: httpd-advanced.openshift.example.com' \ http://127.0.0.1/

This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
...
<output omitted>
...
^C
Percentage of the requests served within a certain time (ms)
  50% 46
  66% 56
  75% 66
  80% 73
  90% 95
  95% 124
  98% 171
  99% 200
 100% 528 (longest request)

httpd的 DeploymentConfig 被扩展时,你可以按Ctrl+C停止生成流量,如上面输出中的^C所示。

在另一个终端中以system:admin身份登录,之后你应该能看到你有 4 个 Pod 在运行:

# oc get po
NAME            READY     STATUS    RESTARTS   AGE
httpd-1-5wsb5   1/1       Running   0          6m
httpd-1-gvqg2   1/1       Running   0          4m
httpd-1-n92jp   1/1       Running   0          1m
httpd-1-smqhb   1/1       Running   0          1m

一旦你按下Ctrl + C并停止基准测试,过一会儿,Pod 的数量会恢复正常:

# oc get po
NAME            READY     STATUS    RESTARTS   AGE
httpd-1-5wsb5   1/1       Running   0          35m
httpd-1-gvqg2   1/1       Running   0          34m

如果你感兴趣,你可以在 Web 控制台中查看收集的指标和自动扩缩的情况。在浏览器中打开openshift.example.com:8443/,确认自签名证书的安全异常,并以用户名alice和任意密码登录。

由于我们的 OpenShift 集群使用自签名的 TLS 证书来加密 HTTP 流量,初始时,你无法从 web 控制台的 Overview 选项卡访问 Hawkular 指标——你会在 pod 列表上方看到一个错误信息。为了解决这个问题,点击提供的链接,在浏览器中打开 Hawkular URL 并确认证书的安全例外。之后,刷新 Overview 选项卡,你就可以看到每个 pod 的计算指标,这些指标会用不同的颜色标出:

你也可以使用 Monitoring 选项卡来获得更详细的视图:

你可以清楚地看到与ab运行对应的 CPU 负载和网络流量的峰值。

我们需要在下一个练习之前删除基于 CPU 的自动扩展器:

# oc delete hpa/httpd
horizontalpodautoscaler "httpd" deleted

基于内存的自动扩展

与基于 CPU 使用率的自动扩展不同,基于内存的自动扩展只能通过从原始 YAML/JSON 定义创建 HorizontalPodAutoscaler 来启用:

# cat hpa-memory.yml 
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v1
metadata:
  name: hpa-httpd-memory
spec:
  scaleTargetRef:
    apiVersion: v1
    kind: DeploymentConfig
    name: httpd
  minReplicas: 2
  maxReplicas: 4
  metrics:
  - type: Resource
    resource:
      name: memory
      targetAverageUtilization: 10

现在让我们启用自动扩展:

# oc create -f hpa-memory.yml 
horizontalpodautoscaler "hpa-httpd-memory" created

等待一两分钟,以便从 Heapster 收集度量数据,你将能够看到当前的内存使用情况与目标之间的差异:

# oc get hpa
NAME      REFERENCE  TARGETS   MINPODS MAXPODS          ... 
hpa-httpd-memory DeploymentConfig/httpd 7% / 10%  2  4  ...

如果在创建之后立即运行此命令,你很可能会看到输出中显示“unknown”而不是7%。这是预期中的情况,因为HorizontalPodAutoscaler通常需要几分钟时间来收集足够的度量数据。

现在让我们继续为应用生成流量,就像上一节那样,不过这次我们将建立 1000 个并发连接,而不是 100

# ab -c 1000 -n 10000000 -H 'Host: httpd-advanced.openshift.example.com' http://127.0.0.1/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
...
<output omitted>
...
^C
Percentage of the requests served within a certain time (ms)
  50% 382
  66% 410
  75% 429
  80% 441
 90% 502
  95% 737
  98% 1439
  99% 3181
 100% 38031 (longest request)

保持基准测试打开 5-10 分钟,同时在浏览器中打开 hawkular-metrics.openshift.example.com/hawkular/metrics,以确保 Hawkular 指标正在运行,然后再打开 openshift.example.com:8443/console/project/advanced/overview

你可以从 web 控制台观察到自动扩展的发生。首先,它将我们的 web 服务器扩展到 3 个副本:

随后转到 4:

ab完成生成流量后,pod 数量会慢慢减少:

如果对服务施加过大负载,你可能会观察到副本数出现短暂的激增。这是正常的,你可能会从事件中看到 deploymentconfig 从 3 扩展到 6 而没有过渡状态,然后迅速发现异常并通过缩减副本数恢复到最大值。

由于 pods 的内存使用特点,通常deploymentconfig/replicationcontroller 不会完全缩放回最小副本数。

练习结束了,现在是清理的时候了:

# oc delete all --all
horizontalpodautoscaler "hpa-httpd-memory" deleted
deploymentconfig "httpd" deleted
imagestream "httpd" deleted
route "httpd" deleted
pod "httpd-1-97dnr" deleted
pod "httpd-1-qgl9c" deleted
service "httpd" deleted

# oc delete limits/my-limits
limitrange "my-limits" deleted

不建议同时启用基于 CPU 和内存的自动扩展器,因为它们可能会相互冲突。确定应用程序通常依赖于哪些资源,并使用适当的自动扩展。

总结

在本章中,我们向您介绍了镜像流的概念以及创建镜像流的方法,如何使用配置映射来管理应用程序的配置,如何通过资源配额和限制范围机制来限制每个项目的资源消耗,如何使用模板创建多个相关的资源,以及如何根据 CPU 或内存使用情况配置应用程序的自动扩展。

在下一章中,我们将讨论 OpenShift 中的安全性。我们将帮助您理解 OpenShift 安全性实施,这对于任何生产环境都是必需的。

问题

  1. ImageStreams 用于什么?选择 2 个:
    1. 保护应用程序不因 ImageStream 指向的镜像发生变化而导致意外崩溃。

    2. 实现应用程序的滚动更新。

    3. 用于存储构建镜像。

    4. 实现镜像更改时的自动构建和部署。

  1. 创建 ConfigMap 时可以使用哪些命令?选择 2 个:
    1. oc create configmap my-configmap --from-file=nginx.conf

    2. oc create cm --from-env-file=environment.env

    3. oc create -f configmap_definition.yaml

    4. oc edit configmap/my-configmap

  1. 以下哪些命令是有效的创建配额命令?选择 2 个:
    1. oc create resourcequota example-quota --hard=cpus=2,memory=512Mi

    2. oc create quota example-quota --hard=cpu=4,ram=1Gi

    3. oc create resourcequota my-quota --hard=cpu=4,services=5

    4. oc create quota another-quota --hard=pods=8,secrets=4

  1. LimitRange 不能控制哪些资源?选择 2 个:
    1. Pod

    2. ConfigMap

    3. ImageStream

    4. 服务

  1. 在模板中引用 VARIABLE 参数的正确语法是什么?
    1. $

    2. %VARIABLE%

  1. CPU 自动扩展时,Pods 必须指定什么?
    1. 标签

    2. 限制

    3. 请求

    4. 选择器

  1. 配置内存自动扩展时必须使用什么 API 版本?
    1. v1

    2. v2

    3. v2alpha1

    4. v1beta1

深入阅读

在本章中,我们介绍了 OpenShift 容器平台的高级概念。以下是一些可能有助于您深入学习的链接列表:

第十章:OpenShift 中的安全性

之前,我们使用了高级 OpenShift 资源,如 ImageStreams、ConfigMaps 和模板。这些资源可以简化 OpenShift 资源管理和应用交付过程。

在本章中,我们将介绍 OpenShift 中的安全领域。任何企业的成功取决于许多因素,其中之一就是公司能够为不同的用户、部门和应用程序实施不同的安全策略。OpenShift 是一个企业级的应用平台,支持多种安全特性,使其能够融入任何企业的安全架构。

本章将帮助你理解以下概念:

  • 身份验证——用户和身份、服务账户以及身份提供者

  • 授权和基于角色的访问控制

  • 访问控制器

  • 安全上下文约束

  • 在 OpenShift 中存储敏感数据

技术要求

在本节中,我们将使用 Vagrant 来演示这些方法之间的区别,因为我们需要两个虚拟机:一个用于单节点 OpenShift 集群,另一个用于 FreeIPA 服务器。使用以下 Vagrantfile 来启动环境:

$ cat Vagrantfile 
$lab_idm = <<SCRIPT
cat <<EOF >> /etc/hosts
172.24.0.11 openshift.example.com openshift
172.24.0.12 idm.example.com idm
EOF
sed -i '/¹²⁷.0.0.1.*idm.*$/d' /etc/hosts
yum -y update
yum -y install ipa-server
systemctl restart dbus
ipa-server-install -r IDM.EXAMPLE.COM -n idm.example.com -p idmsecret -a idmsecret --unattended
echo idmsecret | kinit admin
echo supersecret | ipa user-add alice --first Alice --last Springs --password
SCRIPT

$lab_openshift = <<SCRIPT
cat <<EOF >> /etc/hosts
172.24.0.12 idm.example.com idm
EOF
yum -y update
yum install -y epel-release git docker
yum install -y ansible
systemctl start docker
systemctl enable docker
git clone -b release-3.9 https://github.com/openshift/openshift-ansible /root/openshift-ansible
ssh-keygen -f /root/.ssh/id_rsa -N ''
cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
ssh-keyscan 172.24.0.11 >> .ssh/known_hosts
cp .ssh/known_hosts /root/.ssh/known_hosts
ssh-copy-id -i /root/.ssh/id_rsa root@172.24.0.11
reboot
SCRIPT

Vagrant.configure(2) do |config|
  config.vm.define "openshift" do |conf|
    conf.vm.box = "centos/7"
    conf.vm.hostname = 'openshift.example.com'
    conf.vm.network "private_network", ip: "172.24.0.11"
    conf.vm.provider "virtualbox" do |v|
       v.memory = 4096
       v.cpus = 2
    end
    conf.vm.provision "shell", inline: $lab_openshift
  end

  config.vm.define "idm" do |conf|
    conf.vm.box = "centos/7"
    conf.vm.hostname = 'idm.example.com'
    conf.vm.network "private_network", ip: "172.24.0.12"
    conf.vm.provider "virtualbox" do |v|
 v.memory = 2048
       v.cpus = 1
    end
    conf.vm.provision "shell", inline: $lab_idm
  end
end

与 第六章 OpenShift 安装 中的文件相比,上面的文件可能看起来很复杂,但它所做的只是自动化了该章节中手动执行的步骤,因为本章节的目的是在建立到此为止获得的知识的基础上,讨论安全性。此外,它在另一个虚拟机上设置 FreeIPA 服务器,并创建一个将在本章稍后使用的用户。

命令 systemctl restart dbus 是必要的,以防止在重新启动认证管理器时 FreeIPA 安装失败。

为了简单起见,我们为目录管理器和 IPA 管理员使用了相同的简单密码,但在生产环境中,请确保使用复杂且独特的密码!

运行 vagrant up,并等待直到它完成所有工作。根据你的网络连接和计算资源,可能需要最多 30 分钟:

$ vagrant up
Bringing machine 'openshift' up with 'virtualbox' provider...
Bringing machine 'idm' up with 'virtualbox' provider...
...
<output omitted>
...

完成后,打开 openshift 虚拟机的 SSH 会话并成为 root 用户:

$ vagrant ssh openshift
[vagrant@openshift ~]$ sudo -i
[root@openshift ~]#

不要被上述命令输出中的一些红色内容吓到。许多 CentOS 命令(如 yum)会将警告、错误甚至其他信息发送到标准错误,这些信息会被 Vagrant 解释为错误。

然后使用以下 Ansible 清单文件在 openshift 虚拟机上安装 OpenShift。如果你已经完成了 第六章 OpenShift 安装,你会注意到这是相同的文件,只是新增了 openshift_master_identity_providers 变量:

# cat /etc/ansible/hosts ...
<output omitted>
...
[masters]
172.24.0.11

[nodes]
172.24.0.11 openshift_node_labels="{'region': 'infra', 'zone': 'default'}" openshift_schedulable=true

[etcd]
172.24.0.11

[OSEv3:vars]
openshift_deployment_type=origin
openshift_disable_check=memory_availability,disk_availability
openshift_ip=172.24.0.11
ansible_service_broker_install=false
openshift_master_cluster_hostname=172.24.0.11
openshift_master_cluster_public_hostname=172.24.0.11
openshift_hostname=172.24.0.11
openshift_public_hostname=172.24.0.11
openshift_master_identity_providers=[{'name': 'LDAP', 'challenge': 'true', 'login': 'true', 'kind': 'LDAPPasswordIdentityProvider', 'mappingMethod': 'claim', 'attributes': {'id': ['dn'], 'email': ['mail'], 'name': ['cn'], 'preferredUsername': ['uid']}, 'insecure': 'true', 'bindDN': 'uid=admin,cn=users,cn=accounts,dc=idm,dc=example,dc=com', 'bindPassword': 'idmsecret', 'url': 'ldap://idm.example.com/cn=users,cn=accounts,dc=idm,dc=example,dc=com?uid'}, {'name': 'PASSWORD_FILE', 'challenge': 'true', 'login': 'true', 'kind': 'HTPasswdPasswordIdentityProvider', 'mappingMethod': 'claim', 'filename': '/etc/origin/master/.users'}]

[OSEv3:children]
masters
nodes
etcd

即使 openshift_schedulable 变量看起来位于单独的一行,它实际上是在上一行。如果你直接复制本书提供的文件,它会照常工作。

运行以下剧本来分别执行前提检查和实际安装:


# cd openshift-ansible
# ansible-playbook playbooks/prerequisites.yml
...
<output omitted>
...
# ansible-playbook playbooks/deploy_cluster.yml
...
<output omitted>
...

在我们的练习中,我们将使用两个身份提供者:LDAPHTPasswd。我们将在后续的子章节中更详细地讨论它们。注意,我们为它们都指定了 claim 映射方法,以展示如何在多个提供者中使用此方法。

在 OpenShift 安装完成后,使用 httpd-tools 包提供的以下命令创建一个包含用户 alice 和哈希密码 supersecrethtpasswd 文件:

# htpasswd -c /etc/origin/master/.users alice
New password: `redhat123`
Re-type new password: `redhat123`
Adding password for user alice

现在我们已经准备好进一步进行操作。

身份验证

身份验证一词指的是验证一个人身份的过程。通常,用户不会在 OpenShift 中创建,而是由外部实体提供,如 LDAP 服务器或 GitHub。OpenShift 唯一涉及的部分是授权—确定用户的角色及其权限。OpenShift 支持与多种在企业环境中使用的身份管理解决方案集成,如 FreeIPA/Identity Management、Active Directory、GitHub、Gitlab、OpenStack Keystone 和 OpenID。为了简洁起见,我们将仅讨论最常用的几种,但你可以参考 docs.openshift.org/latest/install_config/configuring_authentication.html 获取完整文档。

用户和身份

用户是任何能够向 OpenShift API 发出请求、访问资源并执行操作的人。用户通常是在外部身份提供者中创建的,通常是企业身份管理解决方案,如 轻量级目录访问协议 (LDAP) 或 Active Directory。

为了支持多个身份提供者,OpenShift 依赖于身份的概念,作为用户和身份提供者之间的桥梁。默认情况下,在第一次登录时会创建新的用户和身份。将用户映射到身份有四种方式:

方法 描述
声明 如果已存在具有相同名称的用户并且该用户映射到另一个身份,则无法创建另一个身份并进行登录。这在你希望在用户名相同的情况下,保持由多个提供者提供的身份之间的清晰区分时非常有用。此方法的一个潜在使用场景是从一种认证方案迁移到另一种认证方案。
添加 如果已存在具有相同名称的用户并且该用户映射到另一个身份,则会为同一用户创建另一个映射的身份。如果你需要为来自不同组织实体的用户提供身份管理解决方案,并允许他们使用方便的认证机制进行认证,这种方式会很有用。
lookup OpenShift 查找现有的用户、身份和映射,但不创建其中任何一个,因此这些实体必须在用户能够登录之前存在。
generate 如果已存在具有相同名称的用户并映射到另一个身份,则为该身份生成一个单独的用户。

在你的浏览器中访问https://172.24.0.11:8443,你将看到登录页面,您可以从可用的身份提供者中进行选择:

使用LDAP身份提供者通过浏览器登录,用户名为alice,密码为supersecret,并观察到用户是通过 CLI 创建的:

# oc get user
NAME    UID      FULL NAME     IDENTITIES
alice bf11471e-47a8-11e8-8dee-525400daa710  Alice Springs   LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com

请注意,身份的名称由其类型和用户定位符组成,用冒号分隔。定位符是特定于提供者的,并指定如何从特定的提供者请求特定的用户。

你还可以看到创建了一个身份,并将其映射到该用户:

# oc get identity
NAME  IDP  NAME  IDP  USER NAME                                             USER   NAME  USER UID
LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com   LDAP            uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com   alice       bf11471e-47a8-11e8-8dee-525400daa710

让我们尝试使用相同的凭据通过PASSWORD_FILE提供者登录:

凭据是正确的,但 OpenShift 无法创建新的身份并将其映射到现有用户,因为该用户已被LDAP提供者认领。这正是消息“无法创建用户”所表示的含义。

让我们删除用户及其身份,以为即将进行的演示提供干净的环境:

# oc delete user/alice
user "alice" deleted

# oc delete \
identity/LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com
identity "LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com" deleted

PASSWORD_FILE提供者的映射方法更改为add

# cat /etc/origin/master/master-config.yaml
...
<output omitted>
...
 - challenge: true
 login: true
 mappingMethod: add
 name: PASSWORD_FILE
...
<output omitted>
...

并重启主 API 服务:

# systemctl restart origin-master-api

再次使用alice:supersecret通过LDAP登录,像第一次一样,然后使用alice:redhat123通过PASSWORD_FILE登录。请注意,第二个身份已被添加到现有身份中,并映射到同一个用户:

# oc get user
NAME      UID     FULL NAME     IDENTITIES
alice     bf11471e-47a8-11e8-8dee-525400daa710   Alice Springs   LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com, PASSWORD_FILE:alice

使用身份提供者登录的顺序很重要!由于你只为PASSWORD_FILE提供者指定了add方法,如果你首先尝试使用它进行登录,你将无法通过LDAP登录,因为它仍然设置为认领不存在的用户,并在用户已存在时返回错误。

为了更清楚地看到我们现在有两个身份映射到同一个用户,请运行以下命令:

# oc get identity
NAME IDP     NAME IDP USER NAME                                             USER  NAME   USER UID
LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com   LDAP            uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com   alice       bf11471e-47a8-11e8-8dee-525400daa710
PASSWORD_FILE:alice                                            PASSWORD_FILE   alice                                                     alice       bf11471e-47a8-11e8-8dee-525400daa7

如果用户名不同,第二个身份将映射到该单独的用户。

在继续下一次演示之前,删除用户和身份:

# oc delete user/alice
user "alice" deleted
 # oc delete \
identity/LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com
identity 
"LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com" deleted
 # oc delete identity/PASSWORD_FILE:alice
identity "PASSWORD_FILE:alice" deleted

现在,将同一提供者的映射方法更改为lookup

# cat /etc/origin/master/master-config.yaml
...
<output omitted>
...
 - challenge: true
 login: true
 mappingMethod: lookup
 name: PASSWORD_FILE
...
<output omitted>
...

重启主 API 以应用更改:

# systemctl restart origin-master-api

现在,首先使用alice:supersecret通过LDAP登录。它应该允许你登录。然后尝试使用alice:redhat123通过PASSWORD_FILE登录。你应该看到如下提供的新错误:

无法找到用户意味着我们没有为此提供者在 OpenShift 中创建身份,也没有将其映射到任何用户,因为lookup映射方法要求如此。让我们通过首先创建身份来纠正这一点:

# oc create identity PASSWORD_FILE:alice
identity "PASSWORD_FILE:alice" created

然后,将其映射到预先存在的用户:

# oc create useridentitymapping PASSWORD_FILE:alice alice
useridentitymapping "PASSWORD_FILE:alice" created

之后,登录尝试将会成功。

一旦你验证了可以使用alice:redhat123登录后,你可以删除该用户和两个身份以继续操作。

# oc delete user/alice
user "alice" deleted
# oc delete \
identity/LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com
identity "LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com" deleted
# oc delete identity/PASSWORD_FILE:alice
identity "PASSWORD_FILE:alice" deleted

我们不需要再次运行oc get identity命令来获取所有身份的列表,因为你可以使用其名称删除一个身份,这个名称取决于提供者的名称和用户的定位符;由于它们没有变化,我们可以使用之前的命令。

最后,将映射方法更改为generate

# cat /etc/origin/master/master-config.yaml
...
<output omitted>
...
  - challenge: true
    login: true
    mappingMethod: generate
    name: PASSWORD_FILE
...
<output omitted>
...

重启主 API:

# systemctl restart origin-master-api

就像之前一样,使用LDAP登录,凭证为alice:supersecret,然后使用PASSWORD_FILE登录,凭证为alice:redhat123。让我们来看一下创建了哪些用户:

# oc get user
NAME      UID       FULL NAME       IDENTITIES
alice     97bd5ede-47b5-11e8-9f47-525400daa710   Alice Springs   LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com
alice2    a145c96e-47b5-11e8-9f47-525400daa710                   PASSWORD_FILE:alice

如你所见,如果已有相同名称的用户存在,这种方法会创建一个使用单独身份映射的generate名称的独立用户。

你可以看到现在两个身份已映射到不同的用户:

# oc get identity
NAME   IDP NAME IDP USER NAME  USER NAME   USER UID
LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com   LDAP            uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com   alice       97bd5ede-47b5-11e8-9f47-525400daa710
PASSWORD_FILE:alice                                            PASSWORD_FILE   alice                                                     alice2      a145c96e-47b5-11e8-9f47-525400daa710

最后,让我们删除该用户和身份:

# oc delete user/alice
user "alice" deleted # oc delete user/alice2 user "alice2" deleted
# oc delete \
identity/LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com
identity "LDAP:uid=alice,cn=users,cn=accounts,dc=idm,dc=example,dc=com" deleted
# oc delete identity/PASSWORD_FILE:alice
identity "PASSWORD_FILE:alice" deleted # exit
$ exit

现在这个练习已经结束,我们可以停止我们的虚拟机释放 MiniShift 的系统资源,接下来的章节不再使用它们:

$ vagrant halt
==> idm: Attempting graceful shutdown of VM...
==> openshift: Attempting graceful shutdown of VM...

我们建议你只需halt此实验室,因为它将在Admission controllers部分中使用。

服务账户

服务账户使我们能够灵活控制 API 访问,而无需共享用户的凭证。为了向你展示它是如何工作的,我们需要启动 MiniShift 虚拟机:

$ minishift start --openshift-version=v3.9.0 --vm-driver=virtualbox
...
<output omitted>
...

接下来,export PATHoc客户端:

$ export PATH="/home/$USER/.minishift/cache/oc/v3.9.0/linux:$PATH"

现在,将你的 Docker 客户端指向 MiniShift 虚拟机中的 docker 守护进程:

$ eval $(minishift docker-env)

最后,以特权用户system:admin身份登录,以便能够执行特权操作,例如添加 SCC 和角色:

$ oc login -u system:admin

现在,我们准备开始了。

另一种我们将使用的用户类型是服务账户。它们由 Pods 和其他非人类实体使用来执行各种操作,是其访问资源的主要管理方式。默认情况下,每个项目中都会创建三个服务账户:

名称 描述
builder 由构建 Pods 使用,并分配了 system:image-builder 角色,该角色授予将镜像推送到项目中任何镜像流的权限。
deployer 由部署 Pods 使用,并分配了 system:deployer 角色,该角色允许修改项目中的复制控制器。
default 默认由所有其他 Pods 使用。

你可以通过运行以下命令看到它们:

$ oc get serviceaccounts
NAME               SECRETS   AGE
builder            2         58s
default            2         58s
deployer           2         58s

每个服务账户由ServiceAccount资源表示,并与两个额外的密钥相关联——用于访问 OpenShift API 和内部注册表:

$ oc describe serviceaccounts/default
Name: default
Namespace: myproject
Labels: <none>
Annotations: <none>
Image pull secrets:    default-dockercfg-wggrl
Mountable secrets:     default-token-mg64x
                       default-dockercfg-wggrl
Tokens:                default-token-7cljg
                       default-token-mg64x
Events:                <none>

服务账户可以通过简单的命令创建:

$ oc create sa myserviceaccount
serviceaccount "myserviceaccount" created

现在我们先删除它,因为我们将在本章后面重新创建它:

$ oc delete sa/myserviceaccount
serviceaccount "myserviceaccount" deleted

每个服务账户也是两个组的成员:

  • system:serviceaccounts,包含集群中的所有服务账户。

  • system:serviceaccounts:<project>,它包括project中所有的服务账户。

您可以向服务账户组授予权限,这将有效地将这些权限授予组中的所有账户。例如,要授予集群中myproject项目下所有服务账户查看权限,请输入以下命令:

$ oc adm policy add-role-to-group view system:serviceaccounts -n myproject
role "view" added: "system:serviceaccounts"

我们仅以演示为目的指定了-n;由于您的当前项目是myproject,您本可以省略它。

让我们恢复之前的更改,并了解如何从用户中移除特定角色:

$ oc adm policy remove-role-from-group view system:serviceaccounts -n myproject
role "view" removed: "system:serviceaccounts"

身份提供者

通常,用户不会直接在 OpenShift 中创建,而是通过外部身份管理解决方案提供,身份管理解决方案可以像 LDAP 和 Active Directory 那样复杂,并且它们之间建立了信任关系,或者可以像一个带有哈希密码的文本密码文件那样简单。选择特定的身份提供者受到以下因素的影响:

  • 您公司现有的身份管理解决方案

  • 用户添加和删除的频率

  • 是否需要集中管理

OpenShift 实现了以下身份提供者:

  • AllowAll

  • DenyAll

  • HTPasswd

  • LDAP

  • Keystone

  • BasicAuth

  • RequestHeader

  • GitLab

  • GitHub

  • OpenID

  • Google

为了简洁起见,我们将仅限于演示前面列表中的前四个提供者,您可以通过参考docs.openshift.org/latest/install_config/configuring_authentication.html来填补其他部分。这些配置需要应用于位于/etc/origin/master/master-config.yml的主配置文件。

如果您想测试下面的配置,您需要启动您的 vagrant 环境,因为它不能与 minishift 一起使用。

AllowAll

这是 MiniShift 和 OpenShift Origin 的默认身份提供者,包括在没有配置文件的情况下启动 master 时。它允许所有用户使用所有密码,因此不会执行身份验证。仅在将集群与企业身份解决方案集成时,用于调试目的。

该提供者的主配置文件的配置片段如下:

...
<output omitted>
... 
 identityProviders:
  - challenge: true
    login: true
    mappingMethod: claim
    name: anypassword
    provider:
      apiVersion: v1
      kind: AllowAllPasswordIdentityProvider
...
<output omitted>
... 

DenyAll

这个身份提供者与 AllowAll 相反,即它拒绝所有人的访问。当您想临时锁定集群以进行维护或其他目的时,它非常有用。这是 Red Hat OpenShift 容器平台的默认提供者。

该提供者的主配置文件的配置片段如下:

...
<output omitted>
... 
 identityProviders:        
  - challenge: true   
    login: true                        
    mappingMethod: claim       
    name: anypassword        
    provider:            
      apiVersion: v1
      kind: DenyAllPasswordIdentityProvider
...
<output omitted>
... 

如您所见,启用它所需的唯一操作是将Allow改为Deny

HTPasswd

该提供者允许您创建一个包含带有哈希密码的用户文件,从而实现更细粒度的访问控制。虽然这比让您的安装只是接受或拒绝所有人要好,但这种方法仍然有两个缺点:

  • 对于具有多个主节点的高可用安装,你需要在所有主节点之间同步密码文件的更改,这容易出错且效率低下。当然,使用配置管理和 版本控制系统 (VCS) 可以自动化这一过程,但几乎没有使用场景是不希望将集群与企业身份管理服务器集成的。

  • 它不与企业身份管理解决方案集成,这要求你要么直接在密码文件中创建所有用户,要么在身份管理服务器和 OpenShift 主节点/配置管理服务器之间设置某种同步机制。

要启用此身份提供者,我们首先需要创建密码文件,这反过来需要安装 httpd-tools 包:

# sudo yum -y install httpd-tools
...
<output omitted>
...
Complete!

接下来,创建文件并将用户添加到其中。创建第一个用户时,请指定 -c 标志来创建文件:

# htpasswd -c /etc/origin/master/.htpasswd bob
New password: 
Re-type new password: 
Adding password for user bob
# htpasswd /etc/origin/master/.htpasswd alice
New password: 
Re-type new password: 
Adding password for user alice

现在,我们可以通过将默认配置中的 AllowAll 更改为 htpasswd 并指定密码文件的位置来启用此身份提供者:

...
<output omitted>
... 
 identityProviders:        
  - challenge: true   
    login: true                        
    mappingMethod: claim       
    name: anypassword        
    provider:            
      apiVersion: v1
      kind: HTPasswdPasswordIdentityProvider
      file: /etc/origin/master/.htpasswd
...
<output omitted>
...

最后,重启主节点 API 服务以使其更新变更:

# systemctl restart origin-master-api

LDAP

这是你在组织中最可能使用的身份提供者,因为 LDAP(轻量目录访问协议)非常流行。如果你从头开始构建 OpenShift 实验环境,可以使用 FreeIPA 或 IdM——它们非常容易设置。像其他集中式身份管理解决方案一样,LDAP 使你不需要在多个主节点设置中同步密码文件的更改。

该身份提供者的主节点配置文件中的配置片段可能如下所示:

...
<output omitted>
...
  identityProviders:        
  - challenge: true   
    login: true                        
    mappingMethod: claim       
    name: ldap               
    provider:            
      apiVersion: v1
      kind: LDAPPasswordIdentityProvider   
      attributes:        
        id:                                  
          - dn                               
        email:                               
          - mail                             
        name:                                
          - cn                               
        preferredUsername:                   
          - uid                              
     insecure: true                          
     bindDN: 'uid=openshift_admin,cn=users,cn=accounts,dc=example,dc=com'        
     bindPassword: 'secretpassword'          
     url: 'ldap://idm.example.com/cn=users,cn=accounts,dc=example,dc=com?uid'
...
<output omitted>
... 

如果你的 LDAP 服务器支持 LDAPS 安全协议,你可以将 insecure 参数设置为 false

授权和基于角色的访问控制

OpenShift 中的授权围绕以下概念构建:

  • 规则: 允许对特定资源执行的操作集。

  • 角色: 一组规则,允许根据特定的使用配置文件将其应用于用户。角色可以在集群级别或项目级别应用。

  • 角色绑定: 用户/组与角色之间的关联。一个给定的用户或组可以与多个角色关联。

让我们回到我们的 MiniShift 环境。要查看所有可用集群角色的列表,请运行以下命令:

$ oc get clusterrole
NAME
admin
basic-user
cluster-admin

...
<output omitted>
...
view

使用 describe 命令了解某个角色中的规则:

$ oc describe clusterrole/edit
...
<output omitted>
...

从之前的输出中你可以看到,例如,具有此角色的用户可以创建和删除如 pods、configmaps、deploymentconfigs、imagestreams、routes 和 services 等资源,但不能对项目执行任何操作,除了查看它们。

另一方面,如果你描述视图角色,你会注意到对资源的唯一允许操作是获取、列出和观察,这使得它成为一个完美的选择。例如,如果你想授予开发团队查看生产环境中应用程序资源的权限,但不允许修改任何资源或创建新资源:

$ oc describe clusterrole/view
...
<output omitted>
...

使用内置角色

让我们看看如何使用edit预定义角色将用户授予另一个用户的项目访问权限。首先,作为alice在 MiniShift 中登录:

$ oc login -u aliceAuthentication required for https://192.168.99.100:8443 (openshift)
Username: alice
Password: <anypassword>
Login successful.

就像通过 Ansible 部署的 OpenShift 集群一样,默认的身份提供者是 AllowAll,允许你使用任何凭证。

接下来,创建一个名为alice-project的新项目:

$ oc new-project alice-project
Now using project "alice-project" on server "https://192.168.99.100:8443".
...
<output omitted>
...

bob身份登录并观察,默认情况下他没有被添加为任何项目的成员:

$ oc login -u bob
Username: bob
Password: <anypassword>
Login successful.

$ oc project alice-project
error: You are not a member of project "alice-project".
You are not a member of any projects. You can request a project to be created with the 'new-project' command.
To see projects on another server, pass '--server=<server>'.

让我们通过授予bobalice-project项目中edit大多数资源的权限来纠正这个问题:

$ oc login -u alice
...
<output omitted>
...
Using project "alice-project".

$ oc adm policy add-role-to-user edit bob
role "edit" added: "bob"

你可以使用以下命令查看当前alice-project项目中现有的rolebinding

$ oc get rolebinding
NAME                    ROLE   USERS GROUPS  SERVICE ACCOUNTS   SUBJECTS
admin                   /admin                  alice
edit                    /edit                   bob
system:deployers        /system:deployer                                                         deployer
system:image-builders   /system:image-builder                                                    builder
system:image-pullers    /system:image-puller              system:serviceaccounts:alice-project

请注意,deployerbuilder服务帐户始终有两个本地绑定,以及一个绑定授予alice-project项目中的所有服务帐户从内部注册表中拉取镜像的权限。另有两个绑定使alice成为她项目的管理员,并授予bob在该项目中编辑大多数资源的权限。

让我们查看edit角色绑定的详细信息:

$ oc describe rolebinding edit
Name:                          edit
Namespace:                     alice-project
Created:                       17 hours ago
Labels:                        <none>
Annotations:                   <none>
Role:                          /edit
Users:                         bob
...
<output omitted>
...

上面的输出告诉我们用户bob被绑定到alice-project项目中的edit角色。省略的输出与edit角色的详细信息相同。

bob身份重新登录,并查看你现在是否可以访问 Alice 的项目:

$ oc login -u bob
Logged into "https://192.168.99.100:8443" as "bob" using existing credentials.

You have one project on this server: "alice-project"

Using project "alice-project".

创建自定义角色

如果预定义的角色不足以满足你的需求,你始终可以创建自定义角色,只需包含你需要的特定规则。让我们创建一个自定义角色,可以代替edit角色来创建和获取 pod:

$ oc login -u system:admin
...
$ oc create clusterrole alice-project-edit --verb=get,list,watch --
resource=namespace,project
clusterrole "alice-project-edit" created

请注意,我们必须以集群管理员身份登录才能创建集群角色。集群角色是将其用户加入特定项目所必需的。

OpenShift 的create clusterrole命令仅限于创建一组资源和动词,因此我们无法为 pods 添加不同的动词。我们可以通过直接编辑角色来解决这个限制:

$ oc edit clusterrole/alice-project-edit
...
<output omitted>
...
- apiGroups:
  - ""  # DO NOT MISS THIS LINE OR IT IS NOT GOING TO WORK  
  attributeRestrictions: null
  resources:
  - pods
  verbs:
  - create
  - get
  - list
  - watch

clusterrole "alice-project-edit" edited

接下来,从bob中删除edit角色:

$ oc adm policy remove-role-from-user edit bob
role "edit" removed: "bob"

将新角色分配给bob

$ oc adm policy add-role-to-user alice-project-edit bob
role "alice-project-edit" added: "bob"

bob身份登录:

$ oc login -u bob
...

并启动一个新的 pod,就像我们之前做的一样:

$ cat nginx-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    role: web
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        cpu: 100m
        memory: 128Mi

$ oc create -f nginx-pod.yml
pod "nginx" created

$ oc get po
NAME  READY STATUS RESTARTS AGE
nginx 0/1   Error  0        2h

发生了什么?让我们看看 pod 的日志:

$ oc logs -f nginx 
Error from server (Forbidden): pods "nginx" is forbidden: User "bob" cannot get pods/log in the namespace "alice-project": User "bob" cannot get pods/log in project "alice-project"

这是预期的结果,因为我们没有授予自定义角色alice-project-edit访问 pod 日志的权限。如果你仔细看,错误信息实际上包含了我们需要做的操作——授予对pods/log资源的访问权限。让我们通过以system:admin用户身份登录,向角色定义中添加所需的部分,然后再以bob身份重新登录来修复它:

$ oc login -u system:admin
...
<output omitted>
...
$ oc edit clusterrole/alice-project-edit
...
<output omitted>
...
- apiGroups:
  - ""
  attributeRestrictions: null
  resources:
  - pods
  - pods/log
  verbs:
  - get

$ oc login -u bob
...
<output omitted>
...

再次尝试列出 pod 的日志:

$ oc logs -f nginx 
2018/07/18 02:44:31 [warn] 1#1: the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:2
nginx: [warn] the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:2
2018/07/18 02:44:31 [emerg] 1#1: mkdir() "/var/cache/nginx/client_temp" failed (13: Permission denied)
nginx: [emerg] mkdir() "/var/cache/nginx/client_temp" failed (13: Permission denied)

这次成功了,你能够看到容器在权限上出现问题。目前,运行以下命令来修正这个问题,但我们将在 安全上下文约束 部分更详细地讨论这个问题:

$ oc login -u system:admin
...
<output omitted>
...

$ oc delete po/nginx 
pod "nginx" deleted

$ oc adm policy add-scc-to-user anyuid -z default
scc "anyuid" added to: ["system:serviceaccount:alice-project:default"]

$ oc login -u bob
...
<output omitted>
...

现在它有效了:

$ oc create -f nginx-pod.yml
pod "nginx" created $ oc get po 
NAME  READY STATUS  RESTARTS AGE
nginx 1/1   Running 0        2h

我们刚刚创建了一个自定义角色,可以用来授予其用户只创建 pod 的权限。它本身并不是很有用,因为它不会让你创建一个服务,例如:

$ oc expose po/nginx --port=80
Error from server (Forbidden): User "bob" cannot create services in the namespace "alice-project": User "bob" cannot create services in project "alice-project" (post services)

现在这项操作结束了,让我们清理一下所有内容:

$ oc login -u system:admin
... <output omitted>
...
$ oc delete po/nginx
pod "nginx" deleted
$ oc adm policy remove-role-from-user alice-project-edit bob
role "alice-project-edit" removed: "bob"
$ oc delete clusterrole alice-project-edit
clusterrole "alice-project-edit" deleted

我们不得不以 system:admin 身份登录来删除我们的 pod,因为我们没有授予 alice-project-edit 角色删除 pod 的权限,只授予了创建 pod 的权限。这是 OpenShift 中 RBAC 可细化的又一例证。

入站控制器

由于本节内容的特殊性,我们将不使用 MiniShift,因此现在就停止它:

$ minishift stop
...

然后,改为启动 Vagrant 虚拟机:

$ vagrant up
...
<output omitted>
...

最后,在 openshift 虚拟机中打开一个会话:

$ vagrant ssh openshift
$ sudo -i

入站控制器是在 API 请求经过身份验证和授权后调用的一个子程序,但在将其持久化到 etcd 之前。入站控制器的作用是变更和验证将要持久化的资源,例如添加各种注释和默认值,并确保它们符合特定的限制。所有入站控制器是链式的,因此首先应用变更控制器,然后应用验证控制器。

以下图示展示了整体资源入站过程:

OpenShift 资源入站链

如上图所示,入站链通常由以下类型的入站控制器组成:

  • 可以在主配置文件的 admissionConfig 部分启用的默认入站插件

  • 变更入站 webhook

  • 验证入站 webhook

以下是主配置文件中默认存在的入站控制器,除非在安装过程中通过 openshift_master_admission_plugin_config 变量另行配置:

# cat /etc/origin/master/master-config.yaml
...
<output omitted>
...
admissionConfig:
  pluginConfig:
    BuildDefaults:
      configuration:
        apiVersion: v1
        env: []

        kind: BuildDefaultsConfig
        resources:
          limits: {}
          requests: {}
    BuildOverrides:
      configuration:
        apiVersion: v1
        kind: BuildOverridesConfig
    PodPreset:
      configuration:
        apiVersion: v1
        disable: false
        kind: DefaultAdmissionConfig
    openshift.io/ImagePolicy:
      configuration:
        apiVersion: v1
        executionRules:
        - matchImageAnnotations:
          - key: images.openshift.io/deny-execution
            value: 'true'
          name: execution-denied
          onResources:
          - resource: pods
          - resource: builds
          reject: true
          skipOnResolutionFailure: true
        kind: ImagePolicyConfig
...
<output omitted>
...

即使你通过 SSH 登录到 MiniShift 虚拟机并查看主配置文件,也无法在 MiniShift 主 API 配置中找到这些入站插件。这就是为什么我们使用自己安装的 OpenShift 的原因。

OpenShift 默认支持以下入站控制插件:

Admission Control Plugin 描述
ProjectRequestLimit 限制每个用户自我提供的项目数量。
BuildDefaults 为 BuildConfigs 定义默认的配置参数,例如 git 代理服务器或运行构建器 pod 的节点。
BuildOverrides 可用于覆盖 BuildConfig 中定义的设置。
RunOnceDuration 限制构建器和部署器 pod 可以运行的时间。
PodPreset 启用使用 PodPreset 向 pod 提供资源,如 Secrets、ConfigMaps 和卷。
PodNodeConstraints 将 pod 限制在具有特定标签的节点上。
PodNodeSelector 将 Pod 限制为具有特定标签的项目。
openshift.io/ImagePolicy 控制哪些镜像可以在集群中使用,依据其注册表和注解。
openshift.io/IngressAdmission 禁用路由和入口对象的主机名冲突防止功能,允许没有cluster-admin集群角色的用户在对象创建后更改主机名。

让我们使用ProjectRequestLimit准入插件来演示如何配置准入控制器。

  1. ProjectRequestLimit部分添加到主配置文件的准入链末尾:
admissionConfig:
  pluginConfig:
    ProjectRequestLimit:
      configuration:
        apiVersion: v1
        kind: ProjectRequestLimitConfig
        limits:
        - selector:
            department: ops
          maxProjects: 2
        - maxProjects: 3
...
<output omitted>
...

在这个特定示例中,我们创建了两个不同的限制。一个是针对“ops”部门,限制最大项目数为 2;另一个是针对其他所有人,限制最大项目数为 3。

  1. 重启主 API 以应用更改:
# systemctl restart origin-master-api
  1. alice身份登录,使用密码supersecret,这样用户和其身份会被创建:
$ oc login -u alice Username: alice
Password: supersecret
Login successful
  1. 以集群管理员身份登录,才能创建标签:
$ oc login -u system:admin

现在,我们需要用与准入插件配置中的选择器匹配的键/值对来标记``alice

$ oc label user/alice department=ops
user "alice" labeled
  1. alice身份重新登录:
$ oc login -u alice

然后尝试创建多个项目:

$ oc new-project alice-project-1
...
$ oc new-project alice-project-2
...
$ oc new-project alice-project-3
Error from server (Forbidden): projectrequests.project.openshift.io "alice-project-3" is forbidden: user alice cannot create more than 2 project(s).

如你所见,创建第三个项目的请求被准入插件拦截并由于验证失败被拒绝。

让我们通过创建一个没有任何标签的新用户来查看全局限制如何作用。

  1. 首先,创建一个名为bob的新用户。为了简便起见,你可以使用相同的密码supersecret
$ htpasswd /etc/origin/master/.users bob
New password: supersecret
Re-type new password: supersecret
Adding password for user bob
  1. 接下来,以新创建的用户身份登录。这次,我们不会为其关联任何标签:
$ oc login -u bob Username: bob Password: supersecret Login successful

让我们尝试创建项目,直到达到限制为止:

$ oc new-project bob-project-1
...
$ oc new-project bob-project-2
...
$ oc new-project bob-project-3
...
$ oc new-project bob-project-4
Error from server (Forbidden): projectrequests.project.openshift.io "bob-project-4" is forbidden: user bob cannot create more than 3 project(s).

如你所见,bob不能创建超过三个项目,正如准入插件配置中为没有标签的用户所指定的那样。

现在这个练习完成了,让我们进行清理:

$ oc delete project bob-project-{1..3}
project "bob-project-1" deleted
project "bob-project-2" deleted
project "bob-project-3" deleted
$ oc login -u alice
...
$ oc delete project alice-project-{1,2}
project "alice-project-1" deleted
project "alice-project-2" deleted

同时,从主配置文件中删除准入插件的部分,并重启主 API:

# sed -i '/ProjectRequestLimit/,+8d' /etc/origin/master/master-config.yaml
# systemctl restart origin-master-api
# exit 
$ exit 

上述代码段中的第一个命令删除匹配字符串及其后面的八行内容。

由于我们在本章剩余部分不再需要 Vagrant,因此让我们停止其虚拟机,以便为接下来的练习做准备:

$ vagrant halt
...

如果你完成了 Vagrant 环境的实验,可以运行vagrant destroy来清除它。

安全上下文约束

在我们开始之前,让我们重新启动 MiniShift 环境:

$ minishift start --openshift-version 3.9.0 --vm-driver virtualbox
...
<output omitted>
...

控制 Pod 行为的另一种机制是安全上下文约束SCCs)。这些集群级别的资源定义了 Pod 可以访问哪些资源,并提供了额外的控制层级。默认情况下,OpenShift 支持七种 SCC:

$ oc login -u system:admin ...
<output omitted>
... $ oc get scc
anyuid                ...
hostaccess            ...
hostmount-anyuid      ...
hostnetwork           ...
nonroot               ...
privileged            ...
restricted            ...

你可能注意到我们在“小节‘创建自定义角色’”中使用的anyuid SCC,目的是解决容器权限问题。

默认情况下,除构建和部署用的 pod 外,所有 pod 都使用由 restricted SCC 分配的 default 服务账户,该账户不允许特权容器运行——也就是说,不允许以 root 用户身份运行并监听特权端口(<1024)。

使用 docker inspect 查看 nginx 镜像元数据中指定的用户:

$ docker inspect nginx
[
    {
...
<output omitted>
...
            "User": "",
...
<output omitted>
...
    }
]

恰巧,元数据没有指定用户,这导致镜像以 root 用户身份运行。这正是受限的 SCC(安全上下文约束)设计用来防止的情况。

之前,我们只是将 anyuid SCC 分配给了默认服务账户,这意味着所有容器都可以作为特权用户运行,这存在很大的安全风险。一个好的做法是为我们的 pod/容器创建一个专用服务账户,因此我们要以正确的方式来做:

$ oc create sa myserviceaccount
serviceaccount "myserviceaccount" created

接下来,我们需要将 anyuid SCC 分配给这个账户,但为此我们必须首先以集群管理员身份登录:

$ oc adm policy add-scc-to-user anyuid -z myserviceaccount scc "anyuid" added to: ["system:serviceaccount:myproject:myserviceaccount"]

现在,将我们的服务账户添加到 pod 的定义中:

$ cat nginx-pod2.yml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    role: web
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
  serviceAccountName: myserviceaccount

让我们再次尝试创建 pod:

$ oc create -f nginx-pod2.yml 
pod "nginx" created
$ oc get po
NAME      READY     STATUS    RESTARTS   AGE
nginx     1/1       Running   0          15s

如你所见,既然我们已经将适当的 SCC 添加到 pod 使用的服务账户中,它已经成功启动。

与其允许镜像作为 root 用户运行,一个更好的做法是通过指定一个非特权用户来优化它,使其作为任意用户运行,设置文件和目录的适当权限,并配置应用程序监听非特权端口。

清理我们的操作后,但保留已分配 anyuid SCC 的 myserviceaccount 服务账户——我们稍后需要它来运行特权 pod:

$ oc delete po/nginx
pod "nginx" deleted

在 OpenShift 中存储敏感数据

在现代世界中,应用程序如此复杂,以至于它们由许多服务组成,通过 REST/SOAP API、二进制协议、消息中间件、集成总线等相互交互。举个例子,一个电子商店的后端应用程序;管理客户订单意味着你需要访问包含产品详细信息的数据库。另一个例子是支付处理应用程序,它必须能够访问国际支付网络,如 SWIFT,以便验证卡片详细信息并处理支付。这些例子在范围和使用的技术上非常不同,但它们有一个共同点——所有服务都需要某种认证数据来相互识别,并且这些数据必须存储在某个地方。

一个明显但最不安全的策略是通过配置映射将这些数据作为纯文本传递给 pods,就像其他任何数据一样。采用这种方法,任何具有项目资源查看权限的人都可以查看敏感数据,因此无法实现细粒度的访问控制。

作为一个企业级的 PaaS 解决方案,OpenShift 采用了专门的安全机制来保护这些数据免受未经授权的泄露——Secret(机密)。

虽然 OpenShift 为敏感信息提供了足够的保护,但它并不是专门的安全解决方案的替代品。例如,如果你需要将加密/解密密钥存储在安全的位置,你可能需要考虑使用硬件安全模块HSM)。

什么数据被认为是敏感的?

一般来说,任何需要防止未经授权访问的数据都被视为敏感数据。这包括登录凭据、令牌、加密密钥、智能卡、机密文件等。各种类型的敏感数据的保护由许多政府批准的安全信息标准和公司政策规范,因此成为信息安全的一个独立领域。在应用程序和微服务的上下文中,我们主要关注敏感数据的一个子集,例如:

  • 登录凭据(用户名/密码)

  • API 令牌

  • 加密密钥

  • X.509 证书/密钥对

秘密

秘密与我们在前一章中讨论的配置映射类似,它们也用于将数据传递给 Pod,但它们的相似之处仅止于此。秘密的唯一用途是存储应用程序使用的敏感数据——如 API 令牌、凭据、X.509 证书/密钥等。它们由 tmpfs(内存中的临时文件系统)支持,防止它们被存储在持久化存储中。秘密与配置映射的不同之处在于:

  • 很多秘密在 OpenShift 启动时自动创建(例如,供 Pod 访问 OpenShift API 和内部注册表等)

  • 默认情况下,秘密值是经过 base64 编码的,这使得它们在被注入到容器之前无法查看

OpenShift 支持三种类型的秘密:

类型 描述
generic 任意数据,如文件、目录或字符串的内容。
docker-registry 存储在.dockercfg文件中的 Docker 注册表凭据。
tls X.509 证书及其对应的密钥。我们将在本节稍后使用此类型的秘密作为示例。

如果你还没有登录,请首先以system:admin用户身份登录:

$ oc login -u system:admin
..
<output omitted>
...

使用熟悉的get命令查看当前项目中的所有秘密:

$ oc get secret
NAME                               TYPE                                  DATA      AGE
builder-dockercfg-2bpc7            kubernetes.io/dockercfg               1         4d
builder-token-2cdj5                kubernetes.io/service-account-token   4         4d
builder-token-8lhrx                kubernetes.io/service-account-token   4         4d
default-dockercfg-wggrl            kubernetes.io/dockercfg               1         4d
default-token-7cljg                kubernetes.io/service-account-token   4         4d
default-token-mg64x                kubernetes.io/service-account-token   4         4d
deployer-dockercfg-kd88d           kubernetes.io/dockercfg               1         4d
deployer-token-5rf6f               kubernetes.io/service-account-token   4         4d
deployer-token-k8lwh               kubernetes.io/service-account-token   4         4d
myserviceaccount-dockercfg-n6lg7   kubernetes.io/dockercfg               1         4d
myserviceaccount-token-fxwpn       kubernetes.io/service-account-token   4         4d
myserviceaccount-token-k4d5g       kubernetes.io/service-account-token   4         4d

请注意,每个服务帐户在项目中都有三个秘密,这一点在服务帐户的子节中已有说明。让我们来看一下默认服务帐户的dockercfg令牌实际包含哪些数据:

$ oc edit secret/default-dockercfg-wggrl 
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
 .dockercfg: ... <output omitted> ...
kind: Secret
...
<output omitted>
...

你会看到data属性包含一个长的 base64 编码字符串,表示用于访问内部注册表的 Docker 客户端凭据。使用以下 Python 单行命令解码.dockercfg的内容:

$ python -c "import base64, json; secret_json = base64.b64decode('...<base64-encoded value of .dockercfg from the secret>...'); parsed = json.loads(secret_json); print json.dumps(parsed, indent=4, sort_keys=True)"
$ {
    "172.30.1.1:5000": {
        "auth": "c2Vydmlj...<output omitted>...ZkFRTEE=", 
        "email": "serviceaccount@example.org", 
        "password": "eyJhbGci...<output omitted>...wVBfAQLA", 
        "username": "serviceaccount"
    }, 
    "docker-registry.default.svc:5000": {
        "auth": "c2Vydmlj...<output omitted>...ZkFRTEE=", 
        "email": "serviceaccount@example.org", 
        "password": "eyJhbGci...<output omitted>...wVBfAQLA", 
        "username": "serviceaccount"
    }
}

请注意,authpassword值在文档的两个部分中是相同的,因为它们指定了相同注册表的凭据。让我们进一步深入并解码auth字段的值:

$ python -c "import base64; print base64.b64decode('c2Vydmlj...<output omitted>...ZkFRTEE=')"
$ serviceaccount:eyJhbGci...<output omitted>...wVBfAQLA

你可能已经注意到,冒号后面的字符串实际上是我们之前解码的 JSON 文档中的密码。你可以使用相同的一行命令来解码它,不过我们没有在这里提供输出,因为它包含了非 Unicode 字符。

现在,我们开始动手创建自己的秘密并在 pod 中使用它。秘密的一个典型用例是配置了 SSL/TLS 支持的 Web 服务器,其中秘密用于存储证书/密钥对。

首先,我们需要创建一个 X.509 证书及其密钥:

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl.key -out ssl.crt
Generating a 2048 bit RSA private key
.............+++
..............+++
writing new private key to 'ssl.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:US
State or Province Name (full name) []:AZ
Locality Name (eg, city) [Default City]:Phoenix
Organization Name (eg, company) [Default Company Ltd]:ACME
Organizational Unit Name (eg, section) []:IT
Common Name (eg, your name or your server's hostname) []:localhost
Email Address []:

在证书字段中指定的内容并不重要,因为它只会用于展示配置了 SSL 的工作 Nginx 服务器。

接下来,创建一个自定义的 Nginx 虚拟主机,监听 TCP 端口443并配置证书和密钥的位置:

$ cat nginx_custom_default.conf 
server {
    listen       80;
    listen       443 ssl;

    server_name  localhost;
    ssl_certificate     ssl/..data/tls.crt;
    ssl_certificate_key ssl/..data/tls.key;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}

TLS 秘密被挂载在容器的 mountPath 下的子目录..data中——这就是证书和密钥路径包含..data的原因。

使用前面的配置文件创建一个配置映射,稍后 pod 将使用它:

$ oc create cm nginx --from-file nginx_custom_default.conf 
configmap "nginx" created

下一步是使用证书和密钥创建秘密:

$ oc create secret tls nginx --cert ssl.crt --key ssl.key 
secret "nginx" created

让我们来看一下新创建的秘密定义:

$ oc export secret/nginx
apiVersion: v1
data:
  tls.crt: ...<base64-encoded certificate>...
  tls.key: ...<base64-encoded key>...
kind: Secret
metadata:
  creationTimestamp: null
  name: nginx
type: kubernetes.io/tls

注意,秘密的密钥tls.crttls.key分别存储证书和密钥。当 TLS 秘密被挂载到 pod 时,证书和密钥会被解密并存储到这些密钥名称的文件中——这就是为什么在虚拟主机配置中我们必须指定密钥名称而不是文件名的原因,因为我们是通过openssl命令创建它们的。

我们需要创建的最后一个构建块是 pod 本身。使用适当的指令将配置映射和秘密挂载为 pod 中的卷:

$ cat nginx-pod3.yml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    role: web
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
    volumeMounts:
      - name: conf
        mountPath: /etc/nginx/conf.d
      - name: tls
        mountPath: /etc/nginx/ssl
  volumes:
    - name: conf
      configMap:
        name: nginx
        items:
          - key: nginx_custom_default.conf
            path: default.conf
    - name: tls
      secret:
        secretName: nginx
  serviceAccountName: myserviceaccount

现在,轮到我们创建 pod 了:

$ oc create -f nginx-pod3.yml 
pod "nginx" created

观察它的创建过程:

$ oc get po
NAME    READY   STATUS            RESTARTS   AGE

nginx    0/1    ContainerCreating   0         3s
nginx    1/1    Running   0         7s

出于好奇,我们来看看容器内部,看看证书和密钥是如何访问的:

$ oc rsh nginx ls -l /etc/nginx/ssl
total 0
lrwxrwxrwx 1 root root 14 Apr 25 11:21 tls.crt -> ..data/tls.crt
lrwxrwxrwx 1 root root 14 Apr 25 11:21 tls.key -> ..data/tls.key

使用以下命令查看秘密未存储在文件系统上(即使它是短暂的,在我们这个案例中),而是通过tmpfs挂载在内存中:

$ oc rsh nginx df -h
Filesystem Size Used Avail Use% Mounted on
overlay 19G 2.0G 17G 11% /
tmpfs 1000M 0 1000M 0% /dev
tmpfs 1000M 0 1000M 0% /sys/fs/cgroup
/dev/sda1 19G 2.0G 17G 11% /etc/hosts
shm 64M 0 64M 0% /dev/shm
tmpfs 1000M 8.0K 1000M 1% /etc/nginx/ssl
tmpfs 1000M 16K 1000M 1% /run/secrets/kubernetes.io/serviceaccount
tmpfs 1000M 0 1000M 0% /proc/scsi
tmpfs 1000M 0 1000M 0% /sys/firmware

为 pod 创建一个服务:

$ oc expose po/nginx --port 443
service "nginx" exposed

然后,expose服务以创建一个可供外部访问的路由:

$ oc expose svc/nginx
route "nginx" exposed

路由已创建:

$ oc get route
NAME   HOST/PORT  PATH   SERVICES  PORT   TERMINATION  WILDCARD
nginx     nginx-myproject.192.168.42.43.nip.io             nginx      443                     None

最后一步,我们需要为路由添加 TLS 终止,并将其类型设置为passthrough,这样 OpenShift 路由器就能接受加密的流量并且不对其进行任何修改。无法通过expose CLI 命令创建安全路由,所以我们必须直接patch路由定义:

$ oc patch route/nginx -p '{"spec" : {"tls": {"termination": "passthrough"}}}'
route "nginx" patched

为了验证我们的 Nginx 服务器是否正确配置了 TLS 支持,请打开你喜欢的网页浏览器,访问代表该路由的 URL,并确认安全例外,因为我们使用的是自签名证书:

注意 URL 左侧的小锁图标,表示已建立安全连接。黄色三角形警告(如在 Mozilla Firefox 中所示)表示证书无效,但在我们的实验环境中这是完全可以接受的。

在 Google Chrome 和 Chromium 浏览器中,你会看到 URL 字段左侧显示“不安全”的警告,这两者是相同的。

既然这个练习已经结束,让我们清理当前的项目:

$ oc delete all --all
route "nginx" deleted
pod "nginx" deleted
service "nginx" deleted
$ oc delete cm/nginx secret/nginx
configmap "nginx" deleted
secret "nginx" deleted

你可以在stringData部分中指定未编码的纯文本数据,而不是data。这对于模板非常有用,这样你可以为生成的各种秘密(例如 Webhook 密钥)参数化。

总结

在本章中,你了解了在 OpenShift 中实现认证的各种身份提供者、服务账户,并理解了用户与身份之间的关系。我们还讨论了授权过程以及如何授予用户权限、准入控制器和安全上下文约束。最后,我们讨论了 Secrets,并学习了它们如何被应用程序使用。

在下一章,我们将处理更高层次的网络—OpenShift SDN。

问题

  1. 可以使用哪些映射方法来防止将多个身份映射到同一用户?选择两个:

    1. lookup

    2. generate

    3. add

    4. claim

  2. 哪个服务账户用于运行应用程序 Pod?

    1. all

    2. builder

    3. deployer

    4. default

  3. 可以为用户分配哪些角色,以授予其在特定项目中创建和删除资源的权限?选择两个:

    1. create

    2. admin

    3. view

    4. edit

  4. 哪个准入控制插件可以限制每个用户自我创建项目的数量?

    1. PodPreset

    2. ProjectRequestNumber

    3. ProjectRequestLimit

    4. SelfProvisionedProjects

  5. 可以使用哪些 SCCs 来以 root 身份运行容器?选择 2 个:

    1. anyuid

    2. restricted

    3. hostmount-anyuid

    4. privileged

  6. 哪个 secret 的属性存储了 base64 编码的数据?

    1. stringData

    2. data

    3. base64

    4. spec

进一步阅读

第十一章:管理 OpenShift 网络

在上一章中,我们介绍了 OpenShift 中的安全领域。OpenShift 是一个企业级的应用管理平台,支持多种安全功能,能够融入任何企业的安全环境。

和任何云平台一样,OpenShift 在两个不同的层次上严重依赖网络堆栈:

  • 基础网络拓扑,直接由物理网络设备或在虚拟环境中部署的 OpenShift 自身的虚拟网络设备确定。此层提供 OpenShift 主节点和节点之间的连接,超出了 OpenShift 本身的控制范围。

  • 由所使用的 OpenShift SDN 插件确定的虚拟网络拓扑。此层关注于管理应用程序之间的连接性并提供外部访问。

在本章中,我们将处理更高层次的网络配置——OpenShift SDN,并将涵盖以下主题:

  • OpenShift 中的网络拓扑

  • SDN 插件

  • 出口路由器

  • 外部项目流量的静态 IP

  • 出口网络策略

  • DNS

技术要求

对于本章,我们将使用通过 Vagrant 管理的以下 VM 配置,使用默认的 VirtualBox 提供者:

名称 角色
openshift-master 主节点
openshift-node-1 节点
openshift-node-2 节点

确保你使用的桌面或笔记本电脑有足够的 RAM。上面的配置在 8GB RAM 上进行了测试,但几乎不够,所以我们建议至少在 16GB 的系统上运行。

此配置对应以下 Vagrantfile:

$ cat Vagrantfile 
$common_provision = <<SCRIPT
cat <<EOF >> /etc/hosts
172.24.0.11 openshift-master.example.com openshift-master
172.24.0.12 openshift-node-1.example.com openshift-node-1
172.24.0.13 openshift-node-2.example.com openshift-node-2
EOF
sed -i '/¹²⁷.0.0.1.*openshift.*$/d' /etc/hosts
yum -y update
yum install -y docker
systemctl start docker
systemctl enable docker
SCRIPT

$master_provision = <<SCRIPT
yum -y install git epel-release
yum -y install ansible
git clone -b release-3.9 https://github.com/openshift/openshift-ansible /root/openshift-ansible
ssh-keygen -f /root/.ssh/id_rsa -N ''
cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
for i in 1 3; do ssh-keyscan 172.24.0.1$i; done >> .ssh/known_hosts
cp .ssh/known_hosts /root/.ssh/known_hosts
for i in 1 2; do sudo ssh-copy-id -o IdentityFile=/vagrant_private_keys/machines/openshift-node-$i/virtualbox/private_key -f -i /root/.ssh/id_rsa root@172.24.0.1$((i+1)); done
reboot
SCRIPT

$node_provision = <<SCRIPT
cp -r /home/vagrant/.ssh /root
reboot
SCRIPT

Vagrant.configure(2) do |config|
  config.vm.define "openshift-node-1" do |conf|
    conf.vm.box = "centos/7"
    conf.vm.hostname = 'openshift-node-1.example.com'
    conf.vm.network "private_network", ip: "172.24.0.12"
    conf.vm.provider "virtualbox" do |v|
       v.memory = 2048
       v.cpus = 2
    end
    conf.vm.provision "shell", inline: $common_provision
    conf.vm.provision "shell", inline: $node_provision
  end

  config.vm.define "openshift-node-2" do |conf|
    conf.vm.box = "centos/7"
    conf.vm.hostname = 'openshift-node-2.example.com'
    conf.vm.network "private_network", ip: "172.24.0.13"
    conf.vm.provider "virtualbox" do |v|
       v.memory = 2048
       v.cpus = 2
    end
    conf.vm.provision "shell", inline: $common_provision
    conf.vm.provision "shell", inline: $node_provision
  end

  config.vm.define "openshift-master" do |conf|
    conf.vm.box = "centos/7"
    conf.vm.hostname = 'openshift-master.example.com'
    conf.vm.network "private_network", ip: "172.24.0.11"
    conf.vm.synced_folder '.vagrant/', '/vagrant_private_keys', type: 'rsync'
    conf.vm.provider "virtualbox" do |v|
       v.memory = 4096
       v.cpus = 2
    end
    conf.vm.provision "shell", inline: $common_provision
    conf.vm.provision "shell", inline: $master_provision
  end
end

为了能够从你的主机系统访问 VM 中的集群,确保你的笔记本电脑上的 /etc/hosts 文件如下所示:

$ cat /etc/hosts
127.0.0.1   localhost openshift localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
172.24.0.11 openshift.example.com

运行 vagrant up 并等待直到它完成所有工作。根据你的网络连接和计算资源,这可能需要最多 30 分钟:

$ vagrant up
Bringing machine 'openshift-node-1' up with 'virtualbox' provider...
Bringing machine 'openshift-node-2' up with 'virtualbox' provider...
Bringing machine 'openshift-master' up with 'virtualbox' provider...
...
<output omitted>
...

完成后,打开 SSH 会话进入主 VM,并切换为 root 用户:

$ vagrant ssh openshift-master
[vagrant@openshift-master ~]$ sudo -i
[root@openshift-master ~]#

你可以使用以下清单来部署 OpenShift:

# cat /etc/ansible/hosts ...
<output omitted>
...
[masters]
172.24.0.11

[nodes]
172.24.0.11 openshift_node_labels="{'region': 'us', 'zone': 'default'}" openshift_ip=172.24.0.11 openshift_master_cluster_hostname=172.24.0.11 openshift_hostname=172.24.0.11
172.24.0.12 openshift_node_labels="{'region': 'infra', 'zone': 'east'}" openshift_ip=172.24.0.12 openshift_master_cluster_hostname=172.24.0.12 openshift_hostname=172.24.0.12
172.24.0.13 openshift_node_labels="{'region': 'us', 'zone': 'west'}" openshift_ip=172.24.0.13 openshift_master_cluster_hostname=172.24.0.13 openshift_hostname=172.24.0.13

[etcd]
172.24.0.11

[OSEv3:vars]
openshift_deployment_type=origin
openshift_disable_check=memory_availability,disk_availability
ansible_service_broker_install=false
openshift_master_cluster_public_hostname=openshift.example.com
openshift_public_hostname=openshift.example.com
openshift_master_default_subdomain=openshift.example.com
openshift_schedulable=true

[OSEv3:children]
masters
nodes
etcd

即使 nodes 组中的变量看起来在单独的行上,它们实际上是与其关联的主机上的前一行。如果你直接复制这个文件,并且它与本书其他材料提供的文件一致,它将正常工作。

现在,是时候安装 OpenShift 了:

# cd openshift-ansible
# ansible-playbook playbooks/prerequisites.yml
...
<output omitted>
...
# ansible-playbook playbooks/deploy_cluster.yml
...
<output omitted>
...

OpenShift 中的网络拓扑

为了提供容器之间通信的公共介质,OpenShift 使用了通过 VXLAN 实现的覆盖网络。虚拟扩展局域网VXLAN)协议提供了一种在第 3 层(IP 网络)上传输第 2 层(以太网)帧的机制。根据使用的 SDN 插件,通信的范围可能仅限于同一项目内的 Pods,或者可能完全不受限制。无论使用哪个插件,网络拓扑仍然是相同的。

当新节点在 etcd 中注册时,主节点从集群网络中分配一个私有的/23子网。默认情况下,子网从 10.128.0.0/14 中分配,但可以在主节点配置文件的 networkConfig 部分进行配置。以下是文件中包含相关参数的摘录:

# cat /etc/origin/master/master-config.yaml
...
<output omitted>
...
networkConfig:
  clusterNetworkCIDR: 10.128.0.0/14
  clusterNetworks:
  - cidr: 10.128.0.0/14
    hostSubnetLength: 9
...
<output omitted>
...

hostSubnetLength 设置决定了每个节点分配多少个 IP 地址,以便在该节点上运行的 Pod 之间分配。在我们默认的配置中,每个子网的大小是 2⁹ = 512 个地址,这使得 510 个 IP 可供 Pod 使用。总结一下,每个 Pod 的 IP 地址将有 /23 的子网掩码(14+9)。

请注意,clusterNetworks[].cidr 设置只能更改为一个包含先前设置的较大子网。例如,它可以设置为 10.128.0.0/12,因为它包含 /14,但不能设置为 10.128.0.0/16,因为它们不完全重叠。

此外,hostSubnetLength 一旦集群创建后不能更改。

OpenShift 中的覆盖网络由以下组件构成:

  • br0:一个 OVS 桥接器,所有在特定节点上运行的 Pod 都通过 veth 对连接到它。每个节点都有一个 br0 设备,充当虚拟交换机。

  • tun0:这是 br0 的一个内部端口,编号为 2,分配给每个节点子网的默认网关地址,并用于外部访问。OpenShift 还创建了路由和 netfilter 规则,通过 NAT 将流量引导到外部网络。

  • vxlan_sys_4789br0 的 OVS 端口 1,提供跨不同节点运行的 Pod 之间的连接。在 OVS 规则中,它被称为 vxlan

跟踪连接性

在本小节中,我们将创建一个名为 demo 的项目,托管一个 httpd Pod,以便亲身体验 OpenShift 中覆盖网络的构建过程。

  1. 首先,让我们创建项目:
# oc new-project demo
...
  1. 然后,在该项目中创建一个运行 Apache Web 服务器的 Pod:
# cat httpd-pod.yml 
apiVersion: v1
kind: Pod
metadata:
  name: httpd
  labels:
    role: web
spec:
  containers:
  - name: httpd
    image: manageiq/httpd
    resources:
      requests:
        cpu: 400m
        memory: 128Mi
# oc create -f httpd-pod.yml
pod "httpd" created
  1. 我们将需要分配给 Pod 的 IP 地址,以及它被调度到的节点的地址:
# oc describe po/httpd | grep '\(Node\|IP\):'
Node: 172.24.0.13/172.24.0.13
IP: 10.129.0.20
  1. 另一个步骤是获取 Pod 网络接口的名称,它实际上是用来连接 Pod 和 br0 桥接器的 veth 对的一端:
# oc rsh httpd ip a
...
<output omitted>
...
3: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether 0a:58:0a:81:00:2c brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.129.0.20/23 brd 10.129.1.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::14d1:32ff:fef9:b92c/64 scope link 
       valid_lft forever preferred_lft forever

现在,让我们转到另一个名为 default 的项目。此项目用于托管路由器和内部 Docker 注册表的特殊 Pod。这两个 Pod 部署在标记为 infra 的节点上,在我们这里是 openshift-node-1。让我们确认这一点并找出注册表 Pod 的 IP 地址:

# oc project default
Now using project "default" on server "https://openshift-master.example.com:8443".
# oc get po
NAME READY STATUS RESTARTS AGE
docker-registry-1-cplvg 1/1 Running 1 17h
router-1-52xrr 1/1 Running 1 17h
# oc describe po/docker-registry-1-cplvg | grep '\(Node\|IP\):'
Node: 172.24.0.12/172.24.0.12
IP: 10.128.0.5

我们选择注册表 Pod 的原因是路由器 Pod 以特权模式运行,以便能够直接访问节点的网络栈;因此,它不能代表典型的配置。

现在,运行以下命令获取注册表 Pod 的网络接口卡(NIC)的名称:

# oc rsh docker-registry-1-cplvg ip a
...
<output omitted>
...
3: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
    link/ether 0a:58:0a:80:00:05 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.128.0.5/23 brd 10.128.1.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::948e:9aff:feca:7f61/64 scope link
       valid_lft forever preferred_lft forever

以下步骤将在第一个节点openshift-node-1上执行。

首先,让我们看看 OpenShift 安装后在该节点上创建了哪些网络设备:

# ip a
...
<output omitted>
...
9: br0: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN group default qlen 1000
    link/ether ae:da:68:ed:ac:4c brd ff:ff:ff:ff:ff:ff
10: vxlan_sys_4789: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65520 qdisc noqueue master ovs-system state UNKNOWN group default qlen 1000
    link/ether 0e:fd:50:11:a1:a8 brd ff:ff:ff:ff:ff:ff
11: tun0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether fa:4e:a6:71:84:8e brd ff:ff:ff:ff:ff:ff
    inet 10.128.0.1/23 brd 10.128.1.255 scope global tun0
       valid_lft forever preferred_lft forever
    inet6 fe80::f84e:a6ff:fe71:848e/64 scope link
       valid_lft forever preferred_lft forever
12: veth5d5b06ef@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master ovs-system state UP group default
    link/ether be:3e:2d:24:22:42 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::bc3e:2dff:fe24:2242/64 scope link
       valid_lft forever preferred_lft forever

br0 是在 OpenShift 网络拓扑 部分开始时提到的 OVS 桥接器。为了查看其活动端口,请使用 ovs-vsctl 命令,该命令由 openvswitch 包提供:

# ovs-vsctl show
bb215e68-10dc-483f-863c-5cd67927ed6b
    Bridge "br0"
        fail_mode: secure
        Port "vxlan0"
            Interface "vxlan0"
                type: vxlan
                options: {key=flow, remote_ip=flow}
        Port "tun0"
            Interface "tun0"
                type: internal
        Port "veth5d5b06ef"
            Interface "veth5d5b06ef"
        Port "br0"
            Interface "br0"
                type: internal
    ovs_version: "2.6.1"

现在,了解关于第二个节点 openshift-node-2 的相同信息:

# ip a
...
<output omitted>
...
9: br0: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN group default qlen 1000
    link/ether ea:e2:58:58:04:44 brd ff:ff:ff:ff:ff:ff
10: vxlan_sys_4789: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65520 qdisc noqueue master ovs-system state UNKNOWN group default qlen 1000
    link/ether 76:ef:26:5c:61:08 brd ff:ff:ff:ff:ff:ff
11: tun0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 4a:a7:ab:95:bc:46 brd ff:ff:ff:ff:ff:ff
    inet 10.129.0.1/23 brd 10.129.1.255 scope global tun0
       valid_lft forever preferred_lft forever
    inet6 fe80::48a7:abff:fe95:bc46/64 scope link
       valid_lft forever preferred_lft forever
30: veth7b4d46e7@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master ovs-system state UP group default
    link/ether 7e:82:7a:d2:9b:df brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::7c82:7aff:fed2:9bdf/64 scope link
       valid_lft forever preferred_lft forever
# ovs-vsctl show
3c752e9a-07c9-4b19-9789-a99004f2eaa3
    Bridge "br0"
        fail_mode: secure
        Port "br0"
            Interface "br0"
                type: internal
        Port "vxlan0"
            Interface "vxlan0"
                type: vxlan
                options: {key=flow, remote_ip=flow}
        Port "veth7b4d46e7"
            Interface "veth7b4d46e7"
        Port "tun0"
            Interface "tun0"
                type: internal
    ovs_version: "2.6.1"

为了总结前面的代码,以下图示直观展示了我们集群中结果覆盖网络的样子:

图 1 - 覆盖网络拓扑

最后,让我们在下一节之前进行清理:

# oc delete project demo
project "demo" deleted

SDN 插件

在前面的部分中,我们了解了 OpenShift 中覆盖网络的组成部分。现在,是时候查看如何配置它以满足特定环境的需求了。

OpenShift 默认提供内部 SDN 插件,以及与第三方 SDN 框架集成的插件。以下是 OpenShift 中可用的三个内置插件:

  • `ovs-subnet`

  • ovs-multitenant

  • ovs-networkpolicy

关于使用哪个插件的决策取决于你希望达到的安全性和控制级别。在以下小节中,我们将讨论每个插件的主要功能和使用场景。

随着 SDN 接管网络,第三方供应商也开始开发他们自己的可编程网络解决方案。Red Hat 与这些供应商紧密合作,确保其产品能够顺利集成到 OpenShift 中。以下解决方案已被 Red Hat 测试并验证为生产就绪:

  • Nokia Nuage

  • Cisco Contiv

  • Juniper Contrail

  • Tigera Calico

  • VMWare NSX-T

使这些与 OpenShift 配合工作超出了本书的范围,但你可以通过访问本章结尾提供的链接找到详细的说明。

ovs-subnet 插件

这是 OpenShift 安装后默认启用的插件。它为整个集群中的 Pods 提供了无任何限制的连接,这意味着流量可以在所有 Pods 之间自由流动。在高度重视安全性的多租户环境中,这可能是不可取的。正在使用的 SDN 插件由主配置文件中的 networkConfig.networkPluginName 设置决定:

# cat /etc/origin/master/master-config.yaml
...
<output omitted>
...
networkConfig:
  clusterNetworkCIDR: 10.128.0.0/14
  clusterNetworks:
  - cidr: 10.128.0.0/14
    hostSubnetLength: 9
  externalIPNetworkCIDRs:
  - 0.0.0.0/0
  hostSubnetLength: 9
  networkPluginName: redhat/openshift-ovs-subnet ...
<output omitted>
...

安装时,也可以通过 os_sdn_network_plugin_name Ansible 变量显式指定 SDN 插件。默认情况下,它是 redhat/openshift-ovs-subnet

为了亲自了解 ovs-subnet 插件的作用(或者说不作用),可以创建两个项目,每个项目一个 Pod,并尝试从一个 Pod 访问另一个 Pod。

  1. 首先,创建一个 demo-1 项目:
# oc new-project demo-1
...
  1. 接下来,使用相同的 YAML 定义启动一个 Pod,运行 httpd Web 服务器,就像我们在 跟踪连接性 小节中做的那样:
# cat httpd-pod.yml apiVersion: v1
kind: Pod
metadata:
  name: httpd
  labels:
    role: web
spec:
  containers:
  - name: httpd
    image: manageiq/httpd
    resources:
      requests:
        cpu: 400m
        memory: 128Mi # oc create -f httpd-pod.yml
pod "httpd" created
  1. 让我们找出分配给 Pod 的 IP 地址:
# oc describe po/httpd | grep IP:
IP: 10.129.0.22
  1. 继续创建第二个项目:
# oc new-project demo-2
...

并在该项目中创建相同的 Pod:

# oc create -f httpd-pod.yml
pod "httpd" created
  1. 现在,让我们看一下能否通过 ping 测试从刚创建的 pod 访问第一个 pod:
# oc rsh httpd ping 10.129.0.22
PING 10.129.0.22 (10.129.0.22) 56(84) bytes of data.
64 bytes from 10.129.0.22: icmp_seq=1 ttl=64 time=0.345 ms
...
<output omitted>
...

为了确保准确性,让我们逆向实验,尝试从 demo-1 部署的 pod 访问 demo-2 项目的 pod:

# oc project
Using project "demo-2" on server "https://openshift-master.example.com:8443".
# oc describe po/httpd | grep IP:
IP: 10.129.0.23
# oc project demo-1Now using project "demo-1" on server "https://openshift-master.example.com:8443".
# oc rsh httpd ping 10.129.0.23
PING 10.129.0.23 (10.129.0.23) 56(84) bytes of data.
64 bytes from 10.129.0.23: icmp_seq=1 ttl=64 time=0.255 ms
...
<output omitted>
...

如你所见,pod 之间的通信完全没有限制,这可能并不是我们所希望的。在接下来的两个子节中,我们将演示如何使用其他 OpenShift 插件强制执行项目隔离。

ovs-multitenant 插件

虽然在 PoC 和沙箱环境中通常不是大问题,但在拥有多元团队和项目组合的大型企业中,安全问题变得至关重要,尤其是当某些应用的开发外包给第三方公司时。如果仅仅将项目分开就足够,ovs-multitenant 插件是一个完美的选择。与将所有流量通过所有 pod 的 ovs-subnet 插件不同,ovs-multitenant 插件为每个项目的所有 pod 分配相同的 VNID,确保项目间的唯一性,并在 br0 桥接器上设置流量规则,确保只有拥有相同 VNID 的 pod 之间才能互相通信。

然而,这条规则有一个例外——流量可以在 default 项目与其他项目之间流动。因为 default 项目是特权项目并分配了 VNID,所以集群中的所有 pod 都可以访问路由器和内部注册表。它们是 OpenShift 的核心组件。

为了切换到新插件,我们需要执行一系列步骤。

  1. 首先,修改主节点配置文件中的 networkPluginName
# cat /etc/origin/master/master-config.yaml
...
<output omitted>
...
networkConfig:
  clusterNetworkCIDR: 10.128.0.0/14
  clusterNetworks:
  - cidr: 10.128.0.0/14
    hostSubnetLength: 9
  externalIPNetworkCIDRs:
  - 0.0.0.0/0
  hostSubnetLength: 9
  networkPluginName: redhat/openshift-ovs-multitenant
...
<output omitted>
...

然后,在所有节点上:

# cat /etc/origin/node/node-config.yaml
...
<output omitted>
...
networkPluginName: redhat/openshift-ovs-multitenant
# networkConfig struct introduced in origin 1.0.6 and OSE 3.0.2 which
# deprecates networkPluginName above. The two should match.
networkConfig:
   mtu: 1450
   networkPluginName: redhat/openshift-ovs-multitenant
...
<output omitted>
...
  1. 接下来,重启主 API 和控制器:
# systemctl restart origin-master-{api,controllers}
  1. 停止所有节点上的节点进程:
# systemctl stop origin-node
  1. 重启所有节点上的 OpenVSwitch 服务:
 # systemctl restart openvswitch
  1. 最后,再次启动节点进程:
# systemctl start origin-node

注意,当你重启节点进程时,该节点上的 pod 会获得新的 IP 地址。

现在,让我们检查一下 demo-1demo-2 项目是否能够互相访问。首先,我们从 demo-1 项目获取 httpd pod 的新 IP 地址:

# oc describe po/httpd | grep IP:
IP: 10.129.0.25

现在,对 demo-2 项目做同样的操作:

# oc project demo-2
Now using project "demo-2" on server "https://openshift-master.example.com:8443".

# oc describe po/httpd | grep IP:
IP: 10.129.0.24

让我们尝试 pingdemo-2 访问 demo-1 项目的 pod:

# oc rsh httpd ping 10.129.0.25
PING 10.129.0.25 (10.129.0.25) 56(84) bytes of data.
^C
--- 10.129.0.25 ping statistics ---
10 packets transmitted, 0 received, 100% packet loss, time 9057ms

command terminated with exit code 1

反过来也可以:

# oc project demo-1
Now using project "demo-1" on server "https://openshift-master.example.com:8443".
# oc rsh httpd ping 10.129.0.24

PING 10.129.0.24 (10.129.0.24) 56(84) bytes of data.

^C
--- 10.129.0.24 ping statistics ---
5 packets transmitted, 0 received, 100% packet loss, time 4012ms

command terminated with exit code 1

我们已经确认项目之间确实是隔离的。但如果在实际场景中有例外,且需要某些项目之间能够通信该怎么办呢?例如,一个标准的三层应用,数据库、后台和前端部署在不同的项目中,以便对资源分配进行更细粒度的控制。对于这些用例,OpenShift CLI 提供了一个命令来将项目 join 在一起,从而有效地启用它们之间的通信:

# oc adm pod-network join-projects --to=demo-1 demo-2

此命令没有输出,可以作为快速在安全策略中做出例外的方式。

值得注意的是,通过交换项目也可以实现相同的结果:oc adm pod-network join-projects --to=demo-2 demo-1

现在,让我们看看是否成功。先从demo-1项目尝试ping我们的 pod,然后从demo-2尝试:

# oc rsh httpd ping 10.129.0.24
PING 10.129.0.24 (10.129.0.24) 56(84) bytes of data.
64 bytes from 10.129.0.24: icmp_seq=1 ttl=64 time=0.323 ms
...
<output omitted>
...
# oc project demo-2
Now using project "demo-2" on server "https://openshift-master.example.com:8443".
# oc rsh httpd ping 10.129.0.25
PING 10.129.0.25 (10.129.0.25) 56(84) bytes of data.
64 bytes from 10.129.0.25: icmp_seq=1 ttl=64 time=0.287 ms
...
<output omitted>
...

现在我们已经用普通项目测试过插件的功能,接下来确认default项目的特权状态。切换到default,查找注册表 pod 的 IP 地址:

# oc project default
Now using project "default" on server "https://openshift-master.example.com:8443".
# oc get po
NAME                        READY   STATUS      RESTARTS  AGE
docker-registry-1-cplvg     1/1     Running     2         22h
router-1-52xrr              1/1     Running     1         22h
# oc describe po/docker-registry-1-cplvg | grep IP:
IP: 10.128.0.6

接下来,切换回demo-1并尝试从那里访问注册表 pod:

# oc project demo-1
Now using project "demo-1" on server "https://openshift-master.example.com:8443".
# oc rsh httpd ping 10.128.0.6
PING 10.128.0.6 (10.128.0.6) 56(84) bytes of data.
64 bytes from 10.128.0.6: icmp_seq=1 ttl=64 time=1.94 ms
...
<output omitted>
...

再次对demo-2项目执行相同操作:

# oc project demo-2
Now using project "demo-2" on server "https://openshift-master.example.com:8443".
# oc rsh httpd ping 10.128.0.6
PING 10.128.0.6 (10.128.0.6) 56(84) bytes of data.
64 bytes from 10.128.0.6: icmp_seq=1 ttl=64 time=1.72 ms
...
<output omitted>
...

将项目连接在一起并不是不可逆的操作——你可以轻松地将某个项目从其他环境中隔离出来:

# oc adm pod-network isolate-projects demo-1

上述命令有效地阻止了所有流量进出demo-1项目中的所有 pod。让我们通过尝试从demo-2(我们当前所在的项目)访问该项目中的 pod 来确认这一点:

# oc rsh httpd ping 10.129.0.25
PING 10.129.0.25 (10.129.0.25) 56(84) bytes of data.

^C
--- 10.129.0.25 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2014ms

command terminated with exit code 1

就像我们之前做的那样,继续从demo-1执行相同操作:

# oc project demo-1
Now using project "demo-1" on server "https://openshift-master.example.com:8443".

# oc rsh httpd ping 10.129.0.24
PING 10.129.0.24 (10.129.0.24) 56(84) bytes of data.
^C
--- 10.129.0.24 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3009ms

command terminated with exit code 1

如你所见,该项目已经成功隔离,只用了一个命令。

除了将项目连接和隔离,OpenShift 还提供了另一种管理 pod 网络的功能——将项目设为全局。这允许来自所有项目的所有 pod 与该项目之间的流量互通——与default项目相同。这样的配置的一个潜在用例是项目承载一个消息总线,供集群中的所有其他应用程序使用。

让我们把demo-2项目设为全局:

# oc adm pod-network make-projects-global demo-2

让我们看看是否成功:

# oc rsh httpd ping 10.129.0.24
PING 10.129.0.24 (10.129.0.24) 56(84) bytes of data.
64 bytes from 10.129.0.24: icmp_seq=1 ttl=64 time=0.276 ms
...
<output omitted>
...

与之前将demo-1项目隔离不同,现在允许流量通过。

现在,让我们继续处理最后一个由 OpenShift 提供的 SDN 插件。

ovs-networkpolicy插件

尽管提供了一个简单易用且大致足够的机制来管理项目间的访问,但ovs-multitenant插件缺乏更细粒度的访问控制能力。此时,ovs-networkpolicy插件发挥作用——它允许你创建自定义的NetworkPolicy对象,例如,可以对流入或流出流量进行限制。

为了从ovs-multitenant插件迁移到这个插件,我们需要将普通项目彼此隔离,并允许与全局项目之间的流量。全局项目的 NETID 为0,如下输出所示:

[root@openshift-master book]# oc get netnamespaces
NAME                NETID      EGRESS IPS
default             0          []
demo-1              9793016    []
demo-2              0          []
kube-public         10554334   []
kube-system         8648643    []
logging             11035285   []
management-infra    4781458    []
openshift           13291653   []
openshift-infra     12251614   []
openshift-node      38906      []

在我们的案例中,唯一的全局项目是defaultdemo-2

为了节省你的手动操作,已经编写了一个辅助脚本,用于创建所有必要的NetworkPolicy对象,以允许同一项目内的 pod 之间以及每个项目与全局项目之间的流量。此脚本必须在执行从一个 OpenShift 插件迁移到另一个插件的常规步骤之前运行。

首先,我们需要下载脚本并使其可执行:

# curl -O https://raw.githubusercontent.com/openshift/origin/master/contrib/migration/migrate-network-policy.sh
# chmod +x migrate-network-policy.sh

接下来,运行它并观察采取了哪些步骤,以确保跨项目存在正确的网络策略:

# ./migrate-network-policy.sh

NAMESPACE: default
Namespace is global: adding label legacy-netid=0

NAMESPACE: demo-1
networkpolicy "default-deny" created
networkpolicy "allow-from-self" created
networkpolicy "allow-from-global-namespaces" created

NAMESPACE: demo-2
Namespace is global: adding label legacy-netid=0
...
<output omitted>
...

注意全局项目的特殊处理:它们被赋予了pod.network.openshift.io/legacy-netid=0标签,NetworkPolicy 对象使用该标签作为选择器,允许来自这些项目的访问。要亲自查看这一点,可以导出allow-from-global-namespaces网络策略的定义:

# oc export networkpolicy/allow-from-global-namespaces
apiVersion: extensions/v1beta1
kind: NetworkPolicy
metadata:
  creationTimestamp: null
  generation: 1
  name: allow-from-global-namespaces
spec:
  ingress:
 - from:
 - namespaceSelector:
 matchLabels:
 pod.network.openshift.io/legacy-netid: "0"
  podSelector: {}
  policyTypes:
  - Ingress

一旦完成,接下来的过程与前一小节相同,只需将networkPluginName设置为redhat/openshift-ovs-networkpolicy。有关如何启用 OpenShift 插件的详细说明,请参阅前一部分。

既然这些问题已经解决,接下来我们来提醒一下自己当前所在的项目:

# oc project
Using project "demo-1" on server "https://openshift-master.example.com:8443".

接下来,查找我们 Apache pod 的新 IP 地址以供将来参考:

# oc describe po/httpd | grep IP:
IP: 10.129.0.26

demo-2项目做同样的操作:

# oc project demo-2
...
# oc describe po/httpd | grep IP:
IP: 10.129.0.27

现在,尝试从demo-2中 pingdemo-1中的 pod:

# oc rsh httpd ping 10.129.0.26
PING 10.129.0.26 (10.129.0.26) 56(84) bytes of data.
64 bytes from 10.129.0.26: icmp_seq=1 ttl=64 time=0.586 ms
...
<output omitted>
...

反过来,从demo-1demo-2

# oc project demo-1
Now using project "demo-1" on server "https://openshift-master.example.com:8443".
# oc rsh httpd ping 10.129.0.27
PING 10.129.0.27 (10.129.0.27) 56(84) bytes of data.
64 bytes from 10.129.0.27: icmp_seq=1 ttl=64 time=0.346 ms
...
<output omitted>
...

细心的读者可能会记得,demo-2实际上是一个全局项目,这意味着由于allow-from-global-namespaces网络策略,它与任何其他项目之间的入站和出站流量都是启用的。

让我们再创建一个名为demo-3的项目,来托管相同的httpd pod,并获取该 pod 的 IP 地址:

# oc new-project demo-3
...

# oc create -f httpd-pod.yml
pod "httpd" created

# oc describe po/httpd | grep IP:
IP: 10.129.0.28

尝试访问demo-1项目中的 pod:

# oc rsh httpd ping 10.129.0.26
PING 10.129.0.26 (10.129.0.26) 56(84) bytes of data.
^C
--- 10.129.0.26 ping statistics ---
10 packets transmitted, 0 received, 100% packet loss, time 9063ms

command terminated with exit code 1

这次数据包没有通过,因为demo-3只是一个普通项目,因此它受网络策略限制。我们通过在demo-1项目中创建一个网络策略,允许来自demo-3的流量,但在此之前,我们需要为demo-3项目添加标签,以便策略能够通过选择器引用它:

# oc project demo-1
...

# oc label namespace demo-3 name=demo-3
namespace "demo-3" labeled

# cat networkpolicy-demo-3.yml
kind: NetworkPolicy
apiVersion: extensions/v1beta1
metadata:
  name: networkpolicy-demo-3
spec:
  podSelector:
  ingress:
 - from:
 - namespaceSelector:
 matchLabels:
 name: demo-3

# oc create -f networkpolicy-demo-3.yml
networkpolicy "networkpolicy-demo-3" created

注意ingresspodSelector的缩进在同一层级——这不是笔误,而是省略了 pod 选择器,因为在我们的示例中,我们是根据命名空间来匹配,而不是 pod。

让我们再尝试访问demo-1

# oc project demo-3
Now using project "demo-3" on server "https://openshift-master.example.com:8443".
# oc rsh httpd ping 10.129.0.26
PING 10.129.0.26 (10.129.0.26) 56(84) bytes of data.
64 bytes from 10.129.0.26: icmp_seq=1 ttl=64 time=0.546 ms
...
<output omitted>
...

如你所见,网络策略现在已经生效。

OpenShift 还可以配置为在实例化项目时为每个项目创建一个默认的网络策略。OpenShift CLI 提供了一个用于引导项目模板的命令:

# oc adm create-bootstrap-project-template -o yaml > demo-project-request.yml

修改模板,使其包含一个网络策略,阻止所有的入站流量——这是检查它是否有效的最简单方法:

# cat demo-project-request.yml
apiVersion: v1
kind: Template
metadata:
  creationTimestamp: null
  name: demo-project-request
objects:
...
<output omitted>
...
- apiVersion: extensions/v1beta1
  kind: NetworkPolicy
  metadata:
    name: default-deny
  spec:
    ingress:
...
<output omitted>
...

从其 YAML 定义创建模板:

# oc create -f demo-project-request.yml
template "demo-project-request" created

该模板是在demo-3项目中创建的,因为它在技术上并不重要,但建议将其存储在现有的项目中,如defaultopenshift-infra

为了让 OpenShift 能够使用新的模板,修改主节点的配置文件如下:

# cat /etc/origin/master/master-config.yaml
projectConfig:
  defaultNodeSelector: node-role.kubernetes.io/compute=true
  projectRequestMessage: ''
  projectRequestTemplate: demo-3/demo-project-request

最后,重启主 API 服务以激活更改:

# systemctl restart origin-master-api

让我们创建一个new-project,看看是否创建了网络策略:

# oc new-project demo-4
...

# oc get networkpolicy
NAME POD-SELECTOR AGE
default-deny <none> 7s

现在项目已经成功实例化,并应用了我们配置的安全策略,接下来让我们检查一下该策略是否有效。就像我们之前做的那样,我们将创建一个运行 Apache Web 服务器的 pod,并获取它的 IP 地址:

# cat httpd-pod.yml apiVersion: v1
kind: Pod
metadata:
  name: httpd
  labels:
    role: web
spec:
  containers:
  - name: httpd
    image: manageiq/httpd
    resources:
      requests:
        cpu: 400m
        memory: 128Mi
 # oc create -f httpd-pod.yml
pod "httpd" created

# oc describe po/httpd | grep IP:
IP: 10.129.0.29

接下来,我们将切换项目到demo-3,看看是否能从那里访问我们的 Pod:

# oc project demo-3
Now using project "demo-3" on server "https://openshift-master.example.com:8443".
# oc rsh httpd ping 10.129.0.29
PING 10.129.0.29 (10.129.0.29) 56(84) bytes of data.
^C
--- 10.129.0.29 ping statistics ---
5 packets transmitted, 0 received, 100% packet loss, time 4019ms

command terminated with exit code 1

正如预期的那样,所有进入的流量都被阻止了。

在此,我们结束了关于 OpenShift SDN 插件的部分。

出口路由器

正如您之前所学,OpenShift 中的路由器将来自外部客户端的入口流量引导到服务,而服务又将其转发到 Pod。OpenShift 还提供了一种反向类型的路由器,用于将 Pod 中的出口流量转发到外部网络的特定目标。但与通过 HAProxy 实现的入口路由器不同,出口路由器是基于 Squid 构建的。出口路由器在以下情况下可能非常有用:

  • 使用单一全局资源来屏蔽多个应用程序所使用的不同外部资源。例如,应用程序可能以这样一种方式开发,即它们从不同的镜像拉取依赖,而它们的开发团队之间的协作相对松散。因此,操作团队可以设置一个出口路由器来拦截所有指向这些镜像的流量,并将其重定向到同一个站点,而不是让它们使用相同的镜像。

  • 将所有针对特定站点的可疑请求重定向到审计系统进行进一步分析。

OpenShift 支持以下类型的出口路由器:

  • redirect 用于将流量重定向到特定的目标 IP。

  • http-proxy 用于代理 HTTP、HTTPS 和 DNS 流量。

由于在 VirtualBox 中macvlan接口的限制,我们无法在虚拟实验室或 AWS 中设置出口路由器。最适合使用的环境是裸机。

外部项目流量的静态 IP

OpenShift 调度器负责决定 Pod 在节点上的部署位置,考虑因素包括 Pod 的均匀分布、节点亲和性和可用资源。OpenShift 默认的调度策略的核心目标是尽可能高效地利用可用资源,但它不会考虑其中创建的项目 Pods。这是因为开发人员不应该关心应用程序的 Pods 在集群中的位置,这也是他们完全无法控制 Pods 最终位置的原因。问题在于,当组织规模较大,涉及多个应用程序,并且这些应用程序受到不同的安全性和合规性政策的约束时,这个问题就开始显现出来。

例如,处理银行账户信息的应用程序必须经过严格的审计,而其开发版本不能访问生产数据库。由于调度器无法识别项目的概念,不同应用程序的 pod 可能会部署在同一节点上,从而产生相同源 IP 地址(节点的 IP 地址)的流量,使得在公司防火墙上无法区分它们,也无法应用适当的策略。从技术上讲,可以创建一个自定义调度策略,将具有特定标签的 pod 固定到特定节点或节点集,这将提供一个一致的源地址池,允许通过防火墙。然而,随着时间的推移,这将严重扭曲 pod 在集群中的分布,导致资源使用效率低下,并混淆操作和开发团队的控制范围,最终破坏调度的目的。

OpenShift 为这种问题提供了解决方案——您可以为特定项目分配一个可外部路由的 IP 地址,并在公司防火墙上将其列入白名单,同时使调度对开发人员保持完全透明。

与出口路由器一样,VirtualBox 的虚拟环境对演示此功能的可能性有限制。

出口网络策略

网络策略的理念是控制跨项目的 pod 间访问,而出口网络策略允许您限制来自项目中所有 pod 对某些外部资源的访问。此功能的典型用例是禁止 pod 访问托管提供商的源代码和内容镜像,以防止在这些 pod 中更新应用程序和/或系统库。需要理解的是,与出口路由器不同,出口网络策略不进行任何流量重定向,而是仅基于允许与拒绝来工作。

让我们查看一下demo-1项目的 pod 访问级别:

# oc project demo-1
...
# oc rsh httpd ping github.com
PING github.com (192.30.255.113) 56(84) bytes of data.
64 bytes from lb-192-30-255-113-sea.github.com (192.30.255.113): icmp_seq=1 ttl=61 time=61.8 ms
...
<output omitted>
...
# oc rsh httpd ping google.com
PING google.com (172.217.14.78) 56(84) bytes of data.
64 bytes from lax17s38-in-f14.1e100.net (172.217.14.78): icmp_seq=1 ttl=61 time=132 ms
...
<output omitted>
...

当前,项目中没有强制执行出口网络策略,因此访问外部资源完全不受限制。

现在,从 YAML 定义中创建一个自定义出口网络策略,阻止所有流量到 GitHub 并允许流量访问所有其他外部资源:

# cat demo-egress-policy.yml 
kind: EgressNetworkPolicy
apiVersion: v1
metadata:
  name: demo-egress-policy
spec:
  egress:
  - type: Deny
    to:
      dnsName: github.com

# oc create -f demo-egress-policy.yml 
egressnetworkpolicy "demo-egress-policy" created

让我们尝试访问与本节开始时相同的资源:

# oc rsh httpd ping github.com
PING github.com (192.30.255.113) 56(84) bytes of data.
^C
--- github.com ping statistics ---
20 packets transmitted, 0 received, 100% packet loss, time 19090ms

command terminated with exit code 1
# oc rsh httpd ping google.com
PING google.com (172.217.14.78) 56(84) bytes of data.
64 bytes from lax17s38-in-f14.1e100.net (172.217.14.78): icmp_seq=1 ttl=61 time=35.9 ms
...
<output omitted>
...

如您所见,GitHub 现在无法访问,这正是我们预期的结果。

在这个例子中,我们实现了仅拒绝其他类型的安全策略,但我们也可以实现一个反向类型,允许访问单个资源,阻止所有其他访问。继续我们以 GitHub 和 Google 为例,编辑策略的规范如下:

# oc edit egressnetworkpolicy/demo-egress-policy
...
<output omitted>
...
spec:
  egress:
  - to:
      dnsName: github.com
    type: Allow
  - to:
      cidrSelector: 0.0.0.0/0
    type: Deny

上述配置指示策略阻止所有流量到外部资源,除了 GitHub 和节点上的dnsmasq

让我们来测试一下:

# oc rsh httpd-egress ping github.com
PING github.com (192.30.253.112) 56(84) bytes of data.
64 bytes from lb-192-30-253-112-iad.github.com (192.30.253.112): icmp_seq=1 ttl=61 time=68.4 ms
...
<output omitted>
...
# oc rsh httpd-egress ping google.com
PING google.com (209.85.201.138) 56(84) bytes of data.
^C
--- google.com ping statistics ---
18 packets transmitted, 0 received, 100% packet loss, time 17055ms

command terminated with exit code 1

再次,策略如预期般工作。

注意,出站规则是按照指定的顺序进行评估的,匹配的第一个规则会生效。这意味着,如果我们先放置了拒绝规则,GitHub 的流量也会被阻止,即使在后续规则中明确允许了该流量。

DNS

之前在本书中讨论的将 Pod 连接在一起的一种机制依赖于环境变量——这和使用普通 Docker 实现的方式相同。当你在 OpenShift 上部署一个多容器应用时,提供某些环境变量给消费它们的 Pod 的那些 Pod 必须先启动,以便 OpenShift 能正确配置这些变量。例如,如果你部署一个包含数据库、后端和前端的三层应用,你必须首先部署数据库,以便后端 Pod 能够获取到带有正确地址和端口的数据库环境变量。

Pod 可以通过它们的 IP 直接访问彼此的服务,但在一个高度动态的环境中,服务可能经常被重建,因此需要一种更稳定的解决方案。除了使用环境变量,OpenShift 还提供了其内部 DNS,使用 SkyDNS 和 dnsmasq 实现服务发现。这个方法不会限制部署顺序,且不需要在部署策略中实现额外的逻辑。通过 OpenShift DNS,所有应用可以通过一致的名称在整个集群中发现彼此,这使得开发人员在迁移到 OpenShift 时可以依赖这些名称。唯一需要做的就是与运维团队达成一致,确定服务的名称。

OpenShift 中的 DNS 使 Pod 能够发现以下资源:

名称 域名
服务 <service>.<project>.svc.cluster.local
端点 <service>.<project>.endpoints.cluster.local

在接下来的练习中,我们将看到部署在不同项目中的两个应用如何互相连接。

  1. 首先,让我们创建一个名为 demo-1 的项目:
# oc new-project demo-1
...
  1. 接下来,创建一个运行 Apache Web 服务器的 Pod。我们将使用之前相同的 YAML 配置:
# oc create -f httpd-pod.yml
pod "httpd" created

为了模拟 真实 应用之间的交互,我们需要创建一个服务,作为 Pod 唯一的入口点:

# oc expose po/httpd --port 80
service "httpd" exposed

现在第一个项目已准备好,我们来创建另一个项目:

# oc new-project demo-2
...

和之前一样,使用之前相同的 YAML 定义创建一个 Pod:

# oc create -f httpd-pod.yml
pod "httpd" created

然后通过暴露 Pod 创建一个服务:

# oc expose po/httpd --port 80
service "httpd" exposed

现在,让我们打开一个 bash 会话,进入新创建的 Pod,并尝试从 demo-1 项目访问该 Pod:

# oc exec httpd -it bash
bash-4.2$ dig httpd.demo-1.svc.cluster.local
...
<output omitted>
...
;; ANSWER SECTION:
httpd.demo-1.svc.cluster.local. 30 IN A 172.30.35.41
...
<output omitted>
...

为了完整性,我们将切换项目到 demo-1 并从第一个 Pod 尝试相同的操作:

# oc project demo-1
Now using project "demo-1" on server "https://openshift-master.example.com:8443".
# oc exec httpd -it bash
bash-4.2$ dig httpd.demo-2.svc.cluster.local
...
<output omitted>
...
;; ANSWER SECTION:
httpd.demo-2.svc.cluster.local. 30 IN A 172.30.81.86
...
<output omitted>
...

即便如此,也可以获取某个特定服务的所有端点,尽管建议使用服务作为接入点:

$ dig httpd.demo-2.endpoints.cluster.local
...
<output omitted>
...
;; ANSWER SECTION:
httpd.demo-2.endpoints.cluster.local. 30 IN A 10.129.0.7 ...
<output omitted>
...

OpenShift 将集群级别的子域注入到本地解析器的配置中,路径为 /etc/resolv.conf,所以如果你查看该文件,你会找到一行 search <project>.svc.cluster.local svc.cluster.local cluster.local。因此,FQDN(完全限定域名)不必在项目边界和同一项目内的资源间传递。例如,你可以使用 httpd.demo-1 来调用 demo-1 项目中的名为 httpd 的服务,或者如果在同一项目中,则只需使用 httpd

如你所见,两个 pod 可以通过它们的服务互相访问,这使得不再依赖环境变量成为可能。因此,为了将他们的应用迁移到 OpenShift,开发人员需要配置应用的环境变量,以指向依赖服务的 DNS 名称。

在本章开始时,我们提供了详细的 OpenShift 中 DNS 架构和 DNS 请求流的图示。现在,让我们看看在实际集群中的表现。

在 master 节点上运行以下命令,查看监听 53(DNS)端口的 TCP 和 UDP 进程:

# ss -tulpn | grep '53 '
udp UNCONN 0 0 *:8053 *:* users:(("openshift",pid=1492,fd=10))
tcp LISTEN 0 128 *:8053 *:* users:(("openshift",pid=1492,fd=13))

openshift 二进制文件启动的进程就是 OpenShift Master API,因为 SkyDNS 被嵌入其中,并且使用 etcd 作为权威源来跟踪新服务,并删除已删除服务的记录:

# ps auxf | grep openshift
...
root       1492  7.3 10.3 1040996 402668 ?      Ssl  00:41  17:51 /usr/bin/openshift start master api --config=/etc/origin/master/master-config.yaml --loglevel=2 --listen=https://0.0.0.0:8443 --master=https://openshift-master.example.com:8443
...

现在,让我们查看第一个节点上的监听端口——该设置在集群中的所有节点上完全相同:

# ss -tulpn | grep '53 '
udp UNCONN 0 0 172.24.0.12:53 *:* users:(("dnsmasq",pid=1202,fd=4))
udp UNCONN 0 0 127.0.0.1:53 *:* users:(("openshift",pid=2402,fd=26))
udp UNCONN 0 0 10.128.0.1:53 *:* users:(("dnsmasq",pid=1202,fd=15))
udp UNCONN 0 0 172.17.0.1:53 *:* users:(("dnsmasq",pid=1202,fd=19))
udp UNCONN 0 0 10.0.2.15:53 *:* users:(("dnsmasq",pid=1202,fd=6))
...
tcp LISTEN 0 5 172.24.0.12:53 *:* users:(("dnsmasq",pid=1202,fd=5))
tcp LISTEN 0 128 127.0.0.1:53 *:* users:(("openshift",pid=2402,fd=31))
tcp LISTEN 0 5 10.128.0.1:53 *:* users:(("dnsmasq",pid=1202,fd=16))
tcp LISTEN 0 5 172.17.0.1:53 *:* users:(("dnsmasq",pid=1202,fd=20))
tcp LISTEN 0 5 10.0.2.15:53 *:* users:(("dnsmasq",pid=1202,fd=7))
...

从前面的输出中,我们可以看到 SkyDNS 仍然存在于节点上,但也有 dnsmasq。后者实际上将 DNS 请求转发到 cluster.localin-addr.arpa 区域,同时将其他所有请求重定向到上游 DNS 服务器——在我们的案例中,其 DNS 由 VirtualBox 本身提供。

让我们查看在节点上运行的 OpenShift 进程:

# ps auxf | grep openshift
...
root       2402  9.0  5.4 1067700 102980 ?      Ssl  00:42  22:39 /usr/bin/openshift start node --config=/etc/origin/node/node-config.yaml --loglevel=2

请注意,这是与 ss 命令输出中列出的相同的 OpenShift 进程。与 Master API 一样,SkyDNS 也被嵌入到节点进程中,以为部署在 OpenShift 上的应用服务提供 DNS 请求服务。

我们学到的信息可以通过以下图示表示:

图 2 - OpenShift 中的 DNS 架构

最后,让我们弄清楚 DNS 查询在到达目的地之前实际经过的路径。为此,我们将查看各种解析器和 dnsmasq 配置文件。

我们的第一站是 demo-2 项目中 httpd pod 的本地 DNS 解析器配置:

# oc exec httpd -it bash
bash-4.2$ cat /etc/resolv.conf 
nameserver 10.0.2.15
search demo-1.svc.cluster.local svc.cluster.local cluster.local example.com
options ndots:5

根据前面的配置,search 指令中指定的域名的 DNS 查询将由 10.0.2.15 上的 DNS 服务器解析,这个 IP 地址是 dnsmasq 在节点上监听的网络接口之一。

现在,让我们查看指定内部区域 DNS 转发策略的文件:

# cat /etc/dnsmasq.d/node-dnsmasq.conf
server=/in-addr.arpa/127.0.0.1
server=/cluster.local/127.0.0.1

上述配置指示 dnsmasq 将所有针对 in-addr.arpacluster.local 域的 DNS 查询转发到监听在本地主机上的 DNS 服务器,即 SkyDNS。

接下来,打开以下文件:

# cat /etc/dnsmasq.d/origin-upstream-dns.conf
server=10.0.2.2

与之前的配置不同,这个指令配置 dnsmasq 将所有其他 DNS 查询转发到上游 DNS,实际上是我们使用的 VirtualBox 提供的 DNS。

我们刚刚发现的内容可以通过以下图示表示:

图 3 - OpenShift 中的 DNS 查询流

以上就是我们对 OpenShift DNS 的探索总结。

概述

本章中,你了解了 SDN 的重要性以及它在 OpenShift 中的作用,了解了现有 OpenShift 集群的网络拓扑图,掌握了各种 OpenShift 插件和第三方插件的功能,并亲自体验了它们提供的特性。你还了解了出口路由器和静态 IP 在外部项目流量中的使用场景,并创建了自己的出口网络策略以限制访问外部资源。

在下一章中,我们将部署一个简单的应用程序。

问题

  1. OVS 桥接器的哪个接口用于在特定节点上运行的 pod 与在其他节点上运行的 pod 之间传递流量?

    1. tun0

    2. br0

    3. veth...

    4. vxlan_sys_4789

  2. 假设我们有一个多租户环境,里面有许多由独立团队和外包承包商开发的应用,这些应用需要在少数情况下能够协作,但大部分时间必须完全隔离。要实现这一点,最简单的方式是什么?

    1. 使用 ovs-networkpolicy 插件,并编写自定义网络策略,允许相关方使用的项目之间进行全向流量传输

    2. 使用 ovs-subnet 插件

    3. 使用 ovs-multitenant 插件,根据需要加入或隔离项目

    4. 使用第三方解决方案的插件,例如 VMWare NSX-T 或 Tigera Calico

  3. 哪个特性最适合对白名单中的流量进行过滤,针对来自特定项目中所有 pod 的流量?

    1. 代理模式下的出口路由器

    2. 重新定向模式下的出口路由器

    3. 项目外部流量的静态 IP

    4. 自定义调度策略

    5. OPENSHIFT-ADMIN-OUTPUT-RULES 链中的自定义 iptables 规则(属于 filter 表)

  4. 以下哪项是正确的出口网络策略,允许仅访问 rubygems.orglaunchpad.net,假设这是项目中的唯一出口网络策略?

    1. - type: Deny

      to:

      cidrSelector: 0.0.0.0/0

      - type: Allow

      to:

      dnsName: rubygems.org

      - type: Allow

      to:

      dnsName: launchpad.net

    2. - type: Allow

      to:

      dnsName: rubygems.org

      - type: Allow

      to:

      dnsName: launchpad.net

    3. - type: Allow

      to:

      dnsName: rubygems.org

      - type: Allow

      to:

      dnsName: launchpad.net

      - type: Deny

      to:

      cidrSelector: 0.0.0.0/0

    4. - type: Allow

      to:

      dnsNames:

    5. - launchpad.net

      - rubygems.org

      - type: Deny

      to:

      cidrSelector: 0.0.0.0/0

  5. 服务webdev项目中的正确 DNS 名称是什么?

    1. web.dev.cluster.local

    2. web.cluster.local

    3. web.dev.svc.cluster.local

    4. web.dev.endpoints.cluster.local

进一步阅读

请查看以下链接,进一步了解本章节相关内容:

第十二章:在 OpenShift 中部署简单应用程序

应用程序部署是 OpenShift 最重要且最常用的功能,因为它就是为此而构建的。所有 OpenShift 用户都需要从 Docker 镜像进行应用程序部署。如果有一个知名应用且其镜像已经在 Docker Hub 或其他注册表中可用,OpenShift 用户可以以简单且可重复的方式部署它。在本章中,我们将处理从现有 Docker 镜像部署几个简单应用程序。

完成本章后,你将学到以下内容:

  • 手动应用程序部署镜像,包括从 YAML 文件手动创建 Pod 和 Service 对象

  • 如何利用 oc new-app 工具从现有 Docker 镜像部署应用程序

  • 通过路由暴露应用程序

技术要求

本章没有严格的环境限制;支持任何 OpenShift 安装和开发环境:MinitShift,oc cluster up 或基于 Ansible 的标准生产就绪部署。使用哪种方式由你决定。然而,本章基于在 Vagrant 中运行的 oc cluster up。以下 Vagrantfile 可用于部署实验环境:

$ cat Vagrantfile
Vagrant.configure(2) do |config| 
  config.vm.define "openshift" do |conf| 
    conf.vm.box = "centos/7" 
    conf.vm.network "private_network", ip: "172.24.0.11" 
    conf.vm.hostname = 'openshift.example.com' 
    conf.vm.network "forwarded_port", guest: 80, host: 980
    conf.vm.network "forwarded_port", guest: 443, host: 9443
    conf.vm.network "forwarded_port", guest: 8080, host: 8080
    conf.vm.network "forwarded_port", guest: 8443, host: 8443
    conf.vm.provider "virtualbox" do |v| 
      v.memory = 4096 
      v.cpus = 2 
    end
    conf.vm.provision "shell", inline: $lab_main 
  end 
end
$lab_main = <<SCRIPT
cat <<EOF >> /etc/hosts
172.24.0.11 openshift.example.com openshift
172.24.0.12 storage.example.com storage nfs
EOF
systemctl disable firewalld
systemctl stop firewalld
yum update -y
yum install -y epel-release
yum install -y docker
cat << EOF >/etc/docker/daemon.json
{
   "insecure-registries": [
     "172.30.0.0/16"
   ]
}
EOF
systemctl restart docker
systemctl enable docker
yum -y install centos-release-openshift-origin39
yum -y install origin-clients
oc cluster up
SCRIPT

环境可以按如下方式部署:

$ vagrant up

部署完之前列出的 Vagrant 虚拟机后,你可以按如下方式连接到它:

$ vagrant ssh

最后,作为 developer 用户登录,以便能够运行大多数命令:

$ oc login -u developer
Server [https://localhost:8443]:
The server uses a certificate signed by an unknown authority.
You can bypass the certificate check, but any data you send to the server could be intercepted by others.
Use insecure connections? (y/n): y

Authentication required for https://localhost:8443 (openshift)
Username: developer
Password: <ANY PASSWORD>
Login successful.

You have one project on this server: "myproject"

Using project "myproject".
Welcome! See 'oc help' to get started.

我们可以使用任何密码

手动应用程序部署

在其他方法中,OpenShift 允许直接从现有 Docker 镜像部署应用程序。假设你的开发团队有一个内部流程,用于从他们的应用程序构建 Docker 镜像——通过这种方式,你可以在 OpenShift 环境中使用这些镜像部署应用程序,无需任何修改,这大大简化了迁移到 OpenShift 的过程。创建所有所需的 OpenShift 实体需要几个步骤。

首先,你需要创建一个 Pod,该 Pod 运行从应用程序的 Docker 镜像部署的容器。一旦 Pod 启动并运行,你可能需要创建一个服务,以便为其关联持久的 IP 地址和内部 DNS 记录。该服务允许你的应用程序通过 OpenShift 内部的一个一致的 地址:端口 对外访问。如果是仅供内部使用的应用程序,不需要外部访问(例如数据库或键值存储),这就足够了。

如果你的应用程序需要对外部可用,你需要通过创建 OpenShift 路由来 expose 它,使其可以通过外部网络(如互联网)访问。

简而言之,过程如下:

  1. 创建一个 Pod

  2. 通过暴露 Pod 创建服务

  3. 通过暴露服务创建路由

在本章中,我们将使用一个简单的 httpd Docker 容器来演示应用部署过程。我们选择 httpd 是因为它足够简单,同时也能让我们专注于主要目标——演示与 OpenShift 相关的任务。

创建一个 Pod

httpd Docker 镜像可以在 Docker Hub 上找到。你可能想通过运行以下命令来确认这一点:

$ sudo docker search httpd
INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED
docker.io docker.io/httpd The Apache HTTP Server Project 1719 [OK]
<OMITTED>

根据图片文档(docs.docker.com/samples/library/httpd/),它监听 TCP 端口80。我们不能简单地使用这个容器,因为它绑定了一个特权端口。OpenShift 的默认安全策略不允许应用程序绑定 1024 以下的端口。为了避免问题,OpenShift 提供了一个名为httpd的镜像流,指向一个适用于 OpenShift 的httpd镜像构建。例如,在我们版本的 OpenShift 中,httpd镜像流指向docker.io/centos/httpd-24-centos7 Docker 容器。你可能需要通过运行以下命令来验证这一点:

$ oc get is -n openshift | egrep "^NAME | ^httpd"
NAME DOCKER REPO TAGS UPDATED
httpd 172.30.1.1:5000/openshift/httpd latest,2.4 3 hours ago

$ oc describe is httpd -n openshift | grep "tagged from"
  tagged from centos/httpd-24-centos7:latest

每次我们想要使用httpd镜像部署 Pod 时,我们需要使用centos/httpd-24-centos7镜像。

让我们为实验创建一个单独的项目,如下所示:

$ oc new-project simpleappication
Now using project "simpleappication" on server "https://localhost:8443".
<OMITTED>

简单的httpd Pod 可以从其定义手动部署:

$ cat <<EOF > pod_httpd.yml
apiVersion: v1
kind: Pod
metadata:
  name: httpd
  labels:
    name: httpd
spec:
  containers:
    - name: httpd
      image: centos/httpd-24-centos7
      ports:
        - name: web
          containerPort: 8080
EOF

centos/httpd-24-centos7绑定到端口8080,这允许在不调整默认安全策略的情况下在 OpenShift 内部运行容器。

一旦文件创建完成,我们可以通过运行oc create来创建 Pod:

$ oc create -f pod_httpd.yml
pod "httpd" created

OpenShift 需要一些时间来下载 Docker 镜像并部署 Pod。一旦完成,你应该能够看到httpd Pod 处于Running状态:

$ oc get pod
NAME READY STATUS RESTARTS AGE
httpd 1/1 Running 0 2m

这个 Pod 提供与更复杂应用程序相同的功能(默认的httpd网页)。我们可能想要验证这一点,方法如下。

首先,获取 Pod 的内部 IP 地址:

$ oc describe pod httpd | grep IP:
IP: 172.17.0.2

然后使用 curl 查询上面输出的 IP 地址:

$ curl -s http://172.17.0.2:8080 | head -n 4
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html  xml:lang="en">
 <head>

注意:这是默认 Apache 欢迎页面的开始。你可能需要在生产环境中替换它。可以通过将一个持久卷挂载到/var/www/html来实现这一点。为了演示目的,这个输出表明应用程序本身可以正常工作并且是可访问的。

创建一个服务

服务可以通过两种不同的方式创建:

  • 使用oc expose

  • 从 YAML/JSON 定义

我们将描述这两种方法。你不必同时使用两者。

使用 oc expose 创建服务

你可以通过以下方式使用oc expose创建 Pod:

$ oc get pod
NAME READY STATUS RESTARTS AGE
httpd 1/1 Running 0 13m

$ oc expose pod httpd --name httpd
service "httpd" exposed

$ oc get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd 172.30.128.131 <none> 8080/TCP 3s

上述命令通过暴露 Pod 创建了一个服务,使用name=httpd作为选择器。你可以通过--name选项定义一个自定义服务名称。

相同的httpd应用程序将从服务 IP 地址提供,假设在我们的例子中是172.30.128.131,但你从上一个命令得到的输出很可能会不同:

$ curl -s http://172.30.128.131:8080 | head -n4
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html  xml:lang="en">
  <head>

让我们删除服务,并使用另一种方法重新创建它,如下小节所示:

$ oc delete svc/httpd
service "httpd" deleted

从 YAML 定义创建一个服务

以下 YAML 文件允许你定义一个 Service OpenShift 对象:

$ cat <<EOF > svc-httpd.yml
apiVersion: v1
kind: Service
metadata:
  labels:
    name: httpd
  name: httpd
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    name: httpd
EOF

一旦文件就位,你可以通过运行以下命令来创建服务:

$ oc create -f svc-httpd.yml
service "httpd" created

$ oc get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd 172.30.112.133 <none> 8080/TCP 2s

服务将显示与之前描述相同的输出:

$ curl -s http://172.30.112.133:8080 | head -n 4
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html  xml:lang="en">
  <head>

创建路由

服务使应用程序能够通过一致的地址:端口对在内部访问。为了能够从集群外部访问它,我们需要确保创建了 OpenShift Route。一旦路由创建完成,OpenShift 将通过集群的路由器(默认通过 HAProxy Docker 容器实现)将服务暴露到外部。

和服务一样,路由可以通过两种方式创建:

  • 使用 oc expose

  • 从 YAML/JSON 定义

本节展示了两种方法,但只需使用其中一种即可。

假设你之前创建了一个名为 httpd 的服务。

使用 oc expose 创建路由

你可以通过以下方式使用 oc expose 创建路由:

$ oc expose svc httpd
route "httpd" exposed
$ oc get route
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
httpd httpd-simpleappication.127.0.0.1.nip.io httpd 8080 None
$ curl -s http://httpd-simpleappication.127.0.0.1.nip.io | head -n 4
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html  xml:lang="en">
  <head> $ oc delete route httpd route "httpd" deleted

默认情况下,oc cluster up 方法使用 127.0.0.1.nip.io DNS 区域。

你可能希望通过 --hostname 选项创建一个具有备用 URL 的路由:

$ oc expose svc httpd --name httpd1 --hostname httpd.example.com
route "httpd1" exposed
$ oc get route
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
httpd httpd-simpleappication.127.0.0.1.nip.io httpd 8080 None
httpd1 httpd.example.com httpd 8080 None
 $ sudo bash -c  'echo "127.0.0.1 httpd.example.com" >>/etc/hosts'
 $ curl -s http://httpd.example.com | head -n 4
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html  xml:lang="en">
  <head>

OpenShift 允许为单个应用程序创建多个路由。

如果你使用了备用名称,请确保 DNS 记录指向托管路由器 pod 的 OpenShift 节点的 IP 地址。

一旦路由被创建,你就可以通过这个外部路由访问你的应用程序。

从 YAML 定义创建路由

让我们为名为 httpd2 的应用程序创建一个备用路由。该路由将具有 myhttpd.example.com URL:

$ cat <<EOF > route-httpd2.yml
apiVersion: v1
kind: Route
metadata:
 labels:
 name: httpd2
 name: httpd2
spec:
 host: myhttpd.example.com
 port:
 targetPort: 8080
 to:
 kind: Service
 name: httpd
 weight: 100
EOF

可以通过 oc create 来创建路由:

$ oc create -f route-httpd2.yml route "httpd2" created $ oc get route
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
httpd httpd-simpleappication.127.0.0.1.nip.io httpd 8080 None
httpd1 httpd.example.com httpd 8080 None
httpd2 myhttpd.example.com httpd 8080 None

你可以看到新路由已成功添加。现在,如果存在相应的 DNS 记录,你将能够通过该备用路由访问你的应用程序。

使用 oc new-app

oc 工具允许你以用户友好的方式部署简单的应用程序。通常,你只需传递一个或多个选项给 oc new-app 命令,命令会创建应用程序所需的所有资源,包括 pod(s) 和 service(s)。此外,该命令还会创建 ReplicationControllerDeploymentConfig API 对象,用于控制应用程序的部署方式。

oc new-app 命令

因此,oc new-app 在应用程序部署过程中会从现有的 Docker 镜像创建以下资源:

资源 缩写 描述
Pod pod 表示你容器的 Pod
服务 svc 包含内部应用程序端点的服务
ReplicationController rc 复制控制器是 OpenShift 对象,用于控制应用程序的副本数量
DeploymentConfig dc 部署配置是你部署的定义

oc new-app 是一个非常简单的工具,但它足够强大,可以满足大多数简单的部署需求。

oc new-app 在从 Docker 镜像部署应用程序时不会创建路由!

oc new-app 提供的功能也可以通过 Web 控制台访问,开发人员通常更倾向于使用该方式。

使用默认选项的 oc new-app

让我们删除之前创建的资源:

$ oc delete all --all
route "httpd" deleted
route "httpd1" deleted
route "httpd2" deleted
pod "httpd" deleted
service "httpd" deleted

删除所有内容的另一种方法是删除项目并重新创建它。

最近,我们展示了 OpenShift 自带一个镜像流,包含指向 OpenShift 准备好的 httpd 镜像的路径。默认情况下,oc new-app 工具使用镜像流引用的 Docker 镜像。

这是创建基础 httpd 应用程序的示例:

$ oc new-app httpd
--> Found image cc641a9 (5 days old) in image stream "openshift/httpd" under tag "2.4" for "httpd"

    Apache httpd 2.4
    ----------------
    Apache httpd 2.4 available as container, is a powerful, efficient, and extensible web server. Apache supports a variety of features, many implemented as compiled modules which extend the core functionality. These can range from server-side programming language support to authentication schemes. Virtual hosting allows one Apache installation to serve many different Web sites.

    Tags: builder, httpd, httpd24

    * This image will be deployed in deployment config "httpd"
    * Ports 8080/tcp, 8443/tcp will be load balanced by service "httpd"
      * Other containers can access this service through the hostname "httpd"

--> Creating resources ...
    deploymentconfig "httpd" created
    service "httpd" created
--> Success
    Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
     'oc expose svc/httpd'
    Run 'oc status' to view your app.

部署过程需要一些时间。一旦一切准备就绪,你可以检查是否已创建所有资源:

$ oc get all
NAME REVISION DESIRED CURRENT TRIGGERED BY
deploymentconfigs/httpd 1 1 1 config,image(httpd:2.4)

NAME READY STATUS RESTARTS AGE
po/httpd-1-n7st4 1/1 Running 0 31s

NAME DESIRED CURRENT READY AGE
rc/httpd-1 1 1 1 32s

NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/httpd 172.30.222.179 <none> 8080/TCP,8443/TCP 33s

让我们确认已使用正确的镜像:

$ oc describe pod httpd-1-n7st4 | grep Image:
    Image: centos/httpd-24-centos7@sha256:6da9085c5e7963efaae3929895b9730d7e76e937a7a0109a23015622f3e7156b

剩下的工作是暴露服务,使应用可以在外部访问:

$ oc expose svc httpd
route "httpd" exposed

$ curl -s http://httpd-simpleappication.127.0.0.1.nip.io | head -n 4
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html  xml:lang="en">
  <head>

高级部署

oc new-app 命令接受多个参数,允许你根据需求修改部署过程。例如,你可能需要修改名称、指定环境变量等。

高级选项始终可以通过使用内置的帮助功能显示,帮助功能可以通过 oc new-app --help 显示:

$ oc new-app --help
Create a new application by specifying source code, templates, and/or images

...
<OMITTED>
...

If you provide source code, a new build will be automatically triggered. You can use 'oc status' to
check the progress.

Usage:
  oc new-app (IMAGE | IMAGESTREAM | TEMPLATE | PATH | URL ...) [options]

Examples:
  # List all local templates and image streams that can be used to create an app
  oc new-app --list

  # Create an application based on the source code in the current git repository (with a public
remote)
  # and a Docker image
  oc new-app . --docker-image=repo/langimage
...
<OMITTED>
...

在接下来的章节中,我们将大量使用 oc new-app。你现在不需要了解所有选项。

部署 MariaDB

本节中,我们将部署一个带有额外配置选项的数据库容器。容器需要将一些参数传递给 oc new-app。让我们按照下面的示例创建一个简单的 mariadb 容器。

首先删除之前创建的对象:

$ oc delete all --all ...
<OUTPUT OMITTED>
...

现在我们想创建一个数据库容器,允许名为 openshift 的数据库用户连接到名为 openshift 的数据库。为了简化,我们将使用 openshift 作为数据库密码。以下示例展示了如何启动一个 MariaDB 容器:

$ oc new-app -e MYSQL_USER=openshift -e MYSQL_PASSWORD=openshift \
-e MYSQL_DATABASE=openshift mariadb 
--> Found image 1b0e3a6 (5 days old) in image stream "openshift/mariadb" under tag "10.2" for "mariadb"

...
<OUTPUT OMITTED>
...

    Run 'oc status' to view your app.

验证 mariadb 是否启动并运行:

$ oc get all
NAME REVISION DESIRED CURRENT TRIGGERED BY
deploymentconfigs/mariadb 1 1 1 config,image(mariadb:10.1)

NAME READY STATUS RESTARTS AGE
po/mariadb-1-54h6x 1/1 Running 0 2m

NAME DESIRED CURRENT READY AGE
rc/mariadb-1 1 1 1 2m

NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/mariadb 172.30.233.119 <none> 3306/TCP 2m

现在你可以使用容器名称 "po/mariadb-1-54h6x" 来访问数据库,针对我们的案例是这样。

现在使用 'oc exec' 登录到容器:

$ oc exec -it mariadb-1-54h6x /bin/bash
bash-4.2$

连接到 mariadb 数据库,并验证名为 'openshift' 的数据库已创建,并且你可以通过运行 'show databases' 命令访问它。

$ mysql -uopenshift -popenshift -h127.0.0.1 openshift
...
<OUTPUT OMITTED>
...

MariaDB [openshift]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| openshift |
| test |
+--------------------+
3 rows in set (0.00 sec)

之前的输出显示数据库服务已启动并运行,准备从应用端使用。我们将在接下来的章节中进一步探讨这个话题。

你可以退出数据库,为下一章节做好准备。

MariaDB [openshift]> exit
Bye
bash-4.2$ exit
exit
[vagrant@openshift ~]

清理你的实验环境。

$ oc delete all --all
deploymentconfig "mariadb" deleted
imagestream "mariadb" deleted
pod "mariadb-1-9qcsp" deleted
service "mariadb" deleted

$ oc delete project simpleappication
project "simpleappication" deleted

$ oc project myproject
Now using project "myproject" on server "https://localhost:8443".

如果你打算继续进行下一章节,可以保持 OpenShift 集群运行,否则可以关闭或删除 vagrant 虚拟机。

总结

在本章中,我们向您展示了如何从 Docker 镜像部署多个简单应用程序,如何手动创建 pod,以及如何手动创建服务。我们详细讲解了创建服务的过程,如何手动创建路由,以及如何使用 oc expose 创建路由。最后,我们向您展示了如何使用 oc new-app 命令从 Docker 镜像部署应用程序。

在下一章,我们将使用 OpenShift 模板部署多层应用程序。

问题

  1. 以下哪个 OpenShift 实体不是 oc new-app 自动创建的?

    1. Pod

    2. 路由

    3. 副本控制器

    4. 部署配置

    5. 服务

  2. 以下哪些实体应创建,以便在最小配置下将应用程序公开到外部?(选择三个)

    1. Pod

    2. 服务

    3. 路由

    4. 副本控制器

    5. 部署配置

    6. 镜像流

  3. 哪个命令创建了自定义 URL myservice.example.com 的路由?

    1. oc expose svc httpd

    2. oc expose svc httpd --host myservice.example.com

    3. oc expose svc httpd --hostname myservice.example.com

    4. oc create svc httpd --hostname myservice.example.com

  4. 哪些命令显示所有 OpenShift 路由?(选择两个)

    1. oc get all

    2. oc get pod

    3. oc get application

    4. oc get route

    5. docker ps

    6. ip route

进一步阅读

我们已经向您介绍了 OpenShift 中的应用程序部署基础知识。如果您想进一步了解,以下链接会对您有所帮助:

第十三章:使用模板部署多层应用程序

之前,你已经学会了如何在 OpenShift 中运行一个独立的容器。在现实世界中,几乎所有的应用程序都是由多个相互连接的容器组成。例如,WordPress 容器需要访问一个数据库实例。OpenShift 提供了一个功能,可以将与你的应用程序相关的所有内容打包到一个对象中,这个对象叫做模板,并通过处理该模板一次性部署所有内容。基本的模板概念已在第九章,高级 OpenShift 概念中进行了解释,它与 Docker Compose 非常相似。本章将进行一个实践实验,演示如何使用 OpenShift 模板来部署应用程序。接下来的实验将详细展示如何从零开始创建一个多层应用程序。

本章将涵盖以下主题:

  • OpenShift 模板概述

  • 创建自定义模板

  • 使用模板部署应用程序

技术要求

本章没有严格的环境限制;支持任何 OpenShift 安装和开发环境:MinitShift、oc cluster up 或基于 Ansible 的标准生产就绪部署。你可以选择使用哪种版本。然而,本章基于在 Vagrant 中运行的 oc cluster up。以下 Vagrantfile 可用于部署实验:

$ cat Vagrantfile
Vagrant.configure(2) do |config| 
  config.vm.define "openshift" do |conf| 
    conf.vm.box = "centos/7" 
    conf.vm.network "private_network", ip: "172.24.0.11" 
    conf.vm.hostname = 'openshift.example.com' 
    conf.vm.network "forwarded_port", guest: 80, host: 1080
    conf.vm.network "forwarded_port", guest: 443, host: 9443
    conf.vm.network "forwarded_port", guest: 8080, host: 8080
    conf.vm.network "forwarded_port", guest: 8443, host: 8443
    conf.vm.provider "virtualbox" do |v| 
      v.memory = 4096 
      v.cpus = 2 
    end
    conf.vm.provision "shell", inline: $lab_main 
  end 
end
$lab_main = <<SCRIPT
cat <<EOF >> /etc/hosts
172.24.0.11 openshift.example.com openshift
172.24.0.12 storage.example.com storage nfs
EOF
systemctl disable firewalld
systemctl stop firewalld
yum update -y
yum install -y epel-release
yum install -y docker
cat << EOF >/etc/docker/daemon.json
{
   "insecure-registries": [
     "172.30.0.0/16"
   ]
}
EOF
systemctl restart docker
systemctl enable docker
yum -y install centos-release-openshift-origin39
yum -y install origin-clients
oc cluster up
SCRIPT

环境可以按如下方式部署:

$ vagrant up

一旦先前列出的 Vagrant 虚拟机部署完成,你可以按照以下方式连接到它:

$ vagrant ssh
$ sudo -i
#

最后,以 developer 用户身份登录,以便能够运行大多数命令:

# oc login -u developer
Server [https://localhost:8443]:
The server uses a certificate signed by an unknown authority.
You can bypass the certificate check, but any data you send to the server could be intercepted by others.
Use insecure connections? (y/n): y

Authentication required for https://localhost:8443 (openshift)
Username: developer
Password: <ANY PASSWORD>
Login successful.

You have one project on this server: "myproject"

Using project "myproject".
Welcome! See 'oc help' to get started.

我们可以使用任何密码,因为 OpenShift 默认使用 AllowAll 身份提供程序。

一些实验项目将需要自定义 DNS 记录,可以通过在 /etc/hosts 中设置记录来模拟。两种方法都可以接受。

还假设你有一个 web 浏览器,如 Mozilla Firefox 或 Google Chrome。

OpenShift 模板概述

OpenShift 模板是一组可以参数化并处理的 API 资源,用于生成由 OpenShift 创建的对象列表。模板可以处理并创建任何所需的 OpenShift 对象(例如部署配置、构建配置等)。模板还可以定义一组要应用于模板中每个对象的标签。你可以使用 CLI 或 Web 控制台来应用模板。例如,一个模板可能包含两个 pod(一个应用程序和一个数据库)、一个服务和一个路由。模板开发完成后,你可以重复使用它。

模板语法

模板与任何其他 OpenShift 资源一样,可以通过原始的 YAML 或 JSON 定义来创建。一个示例如下:

# cat mytemplate.yaml
apiVersion: v1
kind: Template 
metadata:
  name: template1
objects: 
- apiVersion: v1
  kind: Pod
  metadata:
    name: app1
  spec:
    containers:
    - env:
      - name: SHOW_DATA
        value: ${SHOW_DATA} 
      image: example/appimage
      name: app1
      ports:
      - containerPort: 8080
        protocol: TCP
parameters: 
- description: Myapp configuration data
  name: SHOW_DATA
  required: true
labels: 
  mylabel: app1

上面的示例仅包含一个资源——名为 app1 的 pod。它还包含一个参数——SHOW_DATA。参数可以用于自定义应用程序部署,以适应各种使用场景。

参数也可以有以下默认值:

parameters: 
- description: Myapp configuration data
  name: SHOW_DATA
  required: true
  value: Example value

在某些情况下,我们可能希望根据某种模式生成值,如下所示:

parameters:
  - description: Password used for Redis authentication
    from: '[A-Z0-9]{8}'
    generate: expression
    name: REDIS_PASSWORD

在前面的示例中,实例化模板将生成一个随机密码,长度为八个字符,由所有大写和小写字母以及数字组成。尽管该语法与正则表达式非常相似,但它仅实现了Perl 兼容正则表达式PCRE)的一个小子集,因此你仍然可以使用\w\d\a修饰符:

  • [w]{10}:这会扩展为 10 个字母字符、数字和下划线。遵循 PCRE 标准,与[a-zA-Z0-9_]{10}相同。

  • [\d]{10}:这会扩展为 10 个数字。与[0-9]{10}相同。

  • [\a]{10}:这会扩展为 10 个字母字符。与[a-zA-Z]{10}相同。

这个功能对于生成随机密码非常有用。

需要理解的是,参数扩展过程发生在从模板创建资源时,而不是模板本身创建时;因此,每个生成的资源都会获得其独特的值。

添加模板

一旦模板开发完成,它可以像任何其他 YAML 或 JSON 定义的对象一样,通过oc create命令添加到 OpenShift 中。通常做法是使用专门的租户来存放模板,该租户将在多个项目间共享。Red Hat OpenShift 容器平台OCP)的默认安装在openshift项目中提供了许多模板。OpenShift 集群的所有用户都可以读取该项目,但只有集群管理员能够修改或创建项目中的模板。

以下示例展示了如何将模板添加到当前项目:

# oc create -f mytemplate.yaml
template "template1" created

# oc get template
NAME        DESCRIPTION    PARAMETERS    OBJECTS
template1                  1 (1 blank)   1

你需要成为system:admin用户才能在openshift租户中创建模板:

# oc login -u system:admin 
# oc create -f mytemplate.json -n openshift
template "template1" created

# oc get template -n openshift|grep temp
template1

显示模板参数

OpenShift 社区开发了许多有用的 OpenShift 模板,用于部署一些知名的服务。一旦确定了模板,你需要了解它接受哪些参数。

有几种方法可以列出所有参数:

  • 使用oc process --parameters命令(这是最简单的一种)

  • 在模板定义中查找parameters部分

在众多其他模板中,OpenShift 默认安装包含了mariadb-persistent模板,如下所示:

# oc get template mariadb-persistent -n openshift
NAME  DESCRIPTION  PARAMETERS OBJECTS
mariadb-persistent MariaDB database service, with persistent storage. For more information about... 8 (3 generated) 4

该模板有多个参数,列出如下:

# oc process --parameters -n openshift mariadb-persistent
NAME DESCRIPTION GENERATOR VALUE
...
<output omitted>
...
VOLUME_CAPACITY Volume space available for data, e.g. 512Mi, 2Gi. 1Gi

如果你不想将模板导入到 OpenShift 中,同样的方法也可以显示本地存储的 OpenShift 模板的参数:

# oc process --parameters -f mytemplate.yaml
NAME        DESCRIPTION  GENERATOR    VALUE
SHOW_DATA   Myapp configuration data

另一种方法是使用oc describe命令:

# oc describe template template1
Name:        template1
Namespace:    myproject
...
<output omitted>
...
Parameters:
    Name:        SHOW_DATA
    Description:    Myapp configuration data
    Required:        true
    Value:        <none>
...
<output omitted>
...

处理模板

以下是使用oc new-app命令从预定义模板部署应用程序的实际示例:

# oc new-app jenkins-persistent -p JENKINS_SERVICE_NAME=myjenkins
--> Deploying template "openshift/jenkins-persistent" to project myproject
...
<output omitted>
...
--> Creating resources ...
    route "myjenkins" created
    deploymentconfig "myjenkins" created
    serviceaccount "myjenkins" created
    rolebinding "myjenkins_edit" created
    service "jenkins-jnlp" created
    service "myjenkins" created
--> Success
...
<output omitted>
...
    Access your application via route 'myjenkins-myproject.127.0.0.1.nip.io'
    Run 'oc status' to view your app

在前面的示例中,我们将模板的名称作为参数传递给命令;oc 工具也可以从你指定的模板中构建应用程序。以下是 oc new-app 命令创建的对象列表:

# oc get all
NAME                         REVISION  DESIRED  CURRENT  TRIGGERED BY
deploymentconfigs/myjenkins  1         1         1       config,image(jenkins:latest)

NAME             HOST/PORT               PATH      SERVICES PORT   TERMINATION 
WILDCARD
routes/myjenkins myjenkins-templates.example.com myjenkins <all>    edge/Redirect 
None

NAME                      READY     STATUS    RESTARTS  AGE
po/myjenkins-1-h2mxx      1/1       Running   0         1m

NAME                      DESIRED   CURRENT   READY     AGE
rc/myjenkins-1            1         1         1         1m

NAME                      CLUSTER-IP     EXTERNAL-IP  PORT(S)    AGE
svc/jenkins-jnlp          172.30.33.180  <none>       50000/TCP  1m
svc/myjenkins             172.30.107.99  <none>       80/TCP     1m

这样,你可以在 OpenShift 中轻松创建一个完全功能的 Jenkins CI/CD 应用程序。

在我们继续之前,清理你的项目:

# oc delete all --all
deploymentconfig "myjenkins" deleted
route "myjenkins" deleted
pod "myjenkins-1-zg4km" deleted
service "jenkins-jnlp" deleted
service "myjenkins" deleted

创建自定义模板

在大多数情况下,开发人员使用 OpenShift 安装时自带的预定义模板;然而,有时这些模板并不适用于特定的情况,因此需要开发定制化的模板。在本节中,我们将向你介绍如何创建你自己的模板。

开发 YAML/JSON 模板定义

如果你熟悉模板布局,可能会想从头开始开发一个模板,使用标准的 YAML 或 JSON 文件。这种方法可以让你创建一个干净的模板,没有任何运行时数据。

OpenShift 的一些功能可能会加速模板开发的过程。例如,oc explain 允许你探索所有 OpenShift API 对象的语法,作为一种文档形式。

如果没有给定参数,oc explain 会列出当前版本 OpenShift 支持的所有资源类型:

# oc explain
You must specify the type of resource to explain. Valid resource types include:

    * all
    * buildconfigs (aka 'bc')
    * builds
    * certificatesigningrequests (aka 'csr')
    * clusterrolebindings
    * clusterroles
...
<output omitted>
...
    error: Required resource not specified.
See 'oc explain -h' for help and examples.

前面的命令接受资源类型作为参数,以显示其语法:

# oc explain svc
...
<output omitted>
...
FIELDS:
   metadata    <Object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
...
<output omitted>
...

一些 OpenShift 资源有多级结构。使用 .(点)作为级别分隔符,以了解此类属性的结构:

# oc explain svc.metadata
RESOURCE: metadata <Object>
 ...
 <output omitted>
 ...

FIELDS:
 ...
 <output omitted>
 ...

   uid    <string>
     UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

你可以在这个层次结构中进一步深入:

$ oc explain svc.metadata.uid
FIELD: uid <string>

DESCRIPTION:
     UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids

OpenShift 的文档在这方面非常好且有帮助。

将现有资源导出为模板

现有的 OpenShift 资源可以通过使用 oc export 命令导出为模板。让我们先使用 oc new-app 命令创建一个运行中的应用程序。

$ oc new-app httpd
--> Found image 9fd201d (10 days old) in image stream "openshift/httpd" under tag "2.4" for "httpd"
...
<output omitted>
...
--> Success
    Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
     'oc expose svc/httpd'
    Run 'oc status' to view your app.

$ oc get all
NAME             READY     STATUS    RESTARTS   AGE
httpd-1-dcm2d    1/1       Running   0          2s
httpd-1-deploy   1/1       Running   0          3s
[root@openshift ~]# oc get all
NAME                      REVISION   DESIRED   CURRENT   TRIGGERED BY
deploymentconfigs/httpd   1          1         1         config,image(httpd:2.4)

NAME                 DOCKER REPO                       TAGS      UPDATED
imagestreams/httpd   172.30.1.1:5000/myproject/httpd   2.4

NAME               READY     STATUS    RESTARTS   AGE
po/httpd-1-dcm2d   1/1       Running   0          15s

NAME         DESIRED   CURRENT   READY     AGE
rc/httpd-1   1         1         1         17s

NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
svc/httpd   ClusterIP   172.30.18.224   <none>        8080/TCP,8443/TCP   17s

$ oc export dc,svc,route --as-template=myhttpd > myhttpd_template.yaml 

模板创建后,你可以查看其内容:

$ cat myhttpd_template.yaml | head -n 20
apiVersion: v1
kind: Template
metadata:
  creationTimestamp: null
  name: myhttpd
objects:
- apiVersion: v1
  kind: DeploymentConfig
  metadata:
    annotations:
      openshift.io/generated-by: OpenShiftNewApp
    creationTimestamp: null
    generation: 1
    labels:
      app: httpd
    name: httpd
  spec:
    replicas: 1
    revisionHistoryLimit: 10
    selector:

这种创建模板的方法很快;然而,你必须从模板中删除所有运行时数据。例如,所有时间戳和状态记录应该被删除。

使用 oc new-app -o 命令

默认情况下,oc new-app 命令会创建项目所需的所有资源。你可以修改此行为,让工具创建资源定义文件,而不是创建资源:

$ oc new-app httpd -o yaml | head -n 20
apiVersion: v1
items:
- apiVersion: v1
  kind: DeploymentConfig
  metadata:
    annotations:
      openshift.io/generated-by: OpenShiftNewApp
    creationTimestamp: null
    labels:
      app: httpd
    name: httpd
  spec:
    replicas: 1
    selector:
      app: httpd
      deploymentconfig: httpd
    strategy:
      resources: {}
    template:
      metadata:

这不会创建一个 OpenShift 模板,但它可以用来创建模板结构的骨架。

在我们继续之前,清理你的项目环境。

# oc delete all --all
deploymentconfig "httpd" deleted
imagestream "httpd" deleted
pod "httpd-1-dcm2d" deleted
service "httpd" deleted

使用模板部署多层应用程序

在这个实验中,我们将部署Gogs(Git 仓库管理软件),并使用 PostgreSQL 后端,运用我们目前为止学到的所有知识。

Gogs 应用模板

我们将使用来自 OpenShift 演示的模板,该模板可以从raw.githubusercontent.com/OpenShiftDemos/gogs-openshift-docker/master/openshift/gogs-template.yaml获取:

使用以下命令将此模板下载到本地:

# curl -O https://raw.githubusercontent.com/OpenShiftDemos/gogs-openshift-docker/master/openshift/gogs-template.yaml

根据上述输出,大多数参数都有默认值(除了HOSTNAME参数)。如果需要单独列出参数,可以使用以下命令:

# oc process --parameters -f gogs-template.yaml
NAME DESCRIPTION GENERATOR VALUE
APPLICATION_NAME The name for the application. gogs
...
 <output omitted>
... 

创建 Gogs 应用

现在我们已经创建了模板,是时候使用它了:

  1. 首先,我们需要为本实验创建一个单独的项目:
# oc new-project gogs
Now using project "gogs" on server "https://localhost:8443".
  1. 让我们尝试在不指定必需的HOSTNAME参数的情况下部署 Gogs:
# oc new-app -f gogs-template.yaml
error: error processing template "templats/gogs": Template.template.openshift.io "gogs" is invalid: template.parameters[1]: Required value: template.parameters[1]: parameter HOSTNAME is required and must be specified

OpenShift 中止了模板的处理,因为未提供HOSTNAME

  1. 让我们重新尝试,并设置HOSTNAME
# oc new-app -f gogs-template.yaml -p HOSTNAME=gogs.example.com
--> Deploying template "gogs/gogs" for "gogs-template.yaml" to project gogs
...
<output omitted>
...
--> Success
    Access your application via route 'gogs.example.com'
    Run 'oc status' to view your app.

模板已由 OpenShift 处理。经过一段时间后,模板中指定的所有 OpenShift 对象应该被创建:

# oc get all
NAME DOCKER REPO TAGS        UPDATED
imagestreams/gogs 172.30.1.1:5000/gogs/gogs 0.9.97 About a minute ago

NAME REVISION DESIRED CURRENT TRIGGERED BY
deploymentconfigs/gogs 1 1 1 config,image(gogs:0.9.97)
deploymentconfigs/gogs-postgresql 1 1 1 config,image(postgresql:9.5)

NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
routes/gogs gogs.example.com gogs <all> None

NAME READY STATUS RESTARTS AGE
po/gogs-1-vc5g5 1/1 Running 1 1m
po/gogs-postgresql-1-hfxlf 1/1 Running 0 1m

NAME DESIRED CURRENT READY AGE
rc/gogs-1 1 1 1 1m
rc/gogs-postgresql-1 1 1 1 1m

NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/gogs 172.30.196.109 <none> 3000/TCP 1m
svc/gogs-postgresql 172.30.196.38 <none> 5432/TCP 1m

路由gogs使用了gogs.example.com主机名,这是我们在实例化模板时指定的。

将 gogs.example.com 记录添加到您的 hosts 文件

echo "127.0.0.1 gogs.example.com" >> /etc/hosts

Gogs 已成功部署。为了验证它是否正常工作,我们需要通过 Web 浏览器访问 http://gogs.example.com:1080/

清理您的环境。

$ oc delete all --all
deploymentconfig "mariadb" deleted
imagestream "mariadb" deleted
pod "mariadb-1-9qcsp" deleted
service "mariadb" deleted

$ oc delete project gogs
project "gogs" deleted

$ oc project myproject
Now using project "myproject" on server "https://localhost:8443".

如果您打算继续进行下一章的内容,可以保持 OpenShift 集群运行,否则可以关闭或删除 vagrant 虚拟机。

总结

在本章中,您学习了 OpenShift 模板,包括如何编写自己的模板并从模板部署应用。

在接下来的章节中,您将学习 OpenShift 如何通过提供 Docker 构建策略简化 Docker 镜像生命周期,如果有 Dockerfile 文件,OpenShift 会自动从源代码部署应用。

问题

  1. 以下哪个 OpenShift 项目包含默认模板?
    1. 默认

    2. openshift

    3. openshift-infra

    4. openshift-node

    5. 模板

  1. 以下哪个命令可以列出mytemplate模板的参数?选择三项:
    1. oc get template mytemplate -n openshift -o yaml

    2. oc process --parameters -f mytemplate.json

    3. oc describe template mytemplate -n openshift

    4. oc get parameters -t mytemplate

    5. oc get template mytemplate -n openshift

    6. oc new-app mytemplate

  1. 以下哪种 OpenShift 实体可以通过模板创建?
    1. Pod

    2. 服务

    3. 路由

    4. 部署配置

    5. 以上所有

进一步阅读

请参考以下链接以获取进一步的阅读资料:

第十四章:从 Dockerfile 构建应用程序镜像

在上一章中,您学习了 OpenShift 模板的概念,以及如何编写自己的模板并从模板部署应用程序。

本章将介绍 OpenShift 如何通过提供自动化应用程序从源代码部署的 Docker 构建策略来简化 Docker 镜像生命周期。本章是一个实践实验室,描述了如何使用 Docker 源代码到镜像S2I)策略进行应用程序交付。

完成本章后,您将学会如何从 Dockerfile 构建和部署应用程序。

本章将涵盖以下主题:

  • OpenShift 的 Dockerfile 开发

  • 从 Dockerfile 构建应用程序

  • Dockerfile 构建自定义

技术要求

本章没有严格的环境限制;支持任何 OpenShift 安装和开发环境——MiniShift、oc cluster up、标准的基于 Ansible 的生产就绪部署。您可以根据自己的需要选择使用哪个版本。然而,本章基于 oc cluster up 方法。以下 Vagrantfile 可用于部署开发环境:

Vagrant.configure(2) do |config| 
  config.vm.define "openshift" do |conf| 
    conf.vm.box = "centos/7" 
    conf.vm.network "private_network", ip: "172.24.0.11" 
    conf.vm.hostname = 'openshift.example.com' 
    conf.vm.network "forwarded_port", guest: 80, host: 980
    conf.vm.network "forwarded_port", guest: 443, host: 9443
    conf.vm.network "forwarded_port", guest: 8080, host: 8080
    conf.vm.network "forwarded_port", guest: 8443, host: 8443
    conf.vm.provider "virtualbox" do |v| 
      v.memory = 4096 
      v.cpus = 2 
    end
    conf.vm.provision "shell", inline: $lab_main 
  end 

$lab_main = <<SCRIPT
cat <<EOF >> /etc/hosts
172.24.0.11 openshift.example.com openshift
172.24.0.12 storage.example.com storage nfs
EOF
systemctl disable firewalld
systemctl stop firewalld
yum update -y
yum install -y epel-release git
yum install -y docker tree
cat << EOF >/etc/docker/daemon.json
{
   "insecure-registries": [
     "172.30.0.0/16"
   ]
}
EOF
systemctl start docker
systemctl enable docker
yum -y install centos-release-openshift-origin39
yum -y install origin-clients
oc cluster up
SCRIPT

环境可以按如下方式部署:

$ vagrant up

部署前面列出的 vagrant 机器后,您可以按如下方式连接到它:

$ vagrant ssh

最后,登录为开发者用户,以便能够执行操作:

$ oc login -u developer
Server [https://localhost:8443]:
The server uses a certificate signed by an unknown authority.
You can bypass the certificate check, but any data you send to the server could be intercepted by others.
Use insecure connections? (y/n): y

Authentication required for https://localhost:8443 (openshift)
Username: developer
Password: <ANY PASSWORD>
Login successful.

You have one project on this server: "myproject"

Using project "myproject".
Welcome! See 'oc help' to get started.

$ sudo -i
# 

OpenShift 的 Dockerfile 开发

本书的早期章节中,我们解释了如何通过 Dockerfile 开发将应用程序容器化。这涉及了 docker build 工具,它通过遵循 Dockerfile 指令创建一个可用的容器镜像。

一般来说,OpenShift 支持现有的应用程序 Dockerfile,但它有一些特殊的默认安全要求,需要您修改或调整应用程序的 Dockerfile 以符合 OpenShift 安全标准。

默认安全策略会以随机的用户 IDUID)运行任何容器,并忽略 USER Dockerfile 指令。应用程序总是以 root 用户组运行。

如果应用程序需要读写访问权限,您需要为 root 组配置 RW 访问权限,通常可以通过以下 Dockerfile 代码段来实现:

...
RUN chown -R 1001:0 /var/lib/myaplication /var/log/myapplication &amp;&amp; \
    chmod -R g=u /var/lib/myaplication /var/log/myapplication
...
USER 1001

上面的示例将目录和文件所有者更改为1001,并将组的权限设置为与所有者相同。这使得应用程序在任何 UID 下都能具有读写权限。

OpenShift 不允许应用程序绑定到小于 1024 的端口。因此,可能需要调整 Dockerfile 中的 EXPOSE 指令,以便能够在 OpenShift 基础设施中运行应用程序。

以下 Dockerfile 代码段提供了如何修改 HTTPD 镜像端口的示例:

EXPOSE 8080

在大多数情况下,您需要调整应用程序配置,以便监听新的端口。例如,对于 HTTPD,必须更改 Listen 指令选项。

从 Dockerfile 构建应用程序

在不同的命名空间中部署应用程序是一种良好的实践:

$ oc new-project dockerfile
Now using project "dockerfile" on server "https://localhost:8443".

对于这个实验,我们将使用redis容器。首先,我们需要一个包含额外文件的 Dockerfile,位于github.com/docker-library/redis.git。让我们克隆这个仓库到本地,以了解其结构:

$ git clone https://github.com/docker-library/redis.git
Cloning into 'redis'...
remote: Counting objects: 738, done.
remote: Compressing objects: 100% (15/15), done.
remote: Total 738 (delta 7), reused 13 (delta 4), pack-reused 719
Receiving objects: 100% (738/738), 108.56 KiB | 0 bytes/s, done.
Resolving deltas: 100% (323/323), done.

该仓库包含多个目录,表示 Redis 的不同版本:

$ tree redis/
redis/
├── 3.2
│   ├── 32bit
│   │   ├── docker-entrypoint.sh
│   │   └── Dockerfile
│   ├── alpine
│   │   ├── docker-entrypoint.sh
│   │   └── Dockerfile
│   ├── docker-entrypoint.sh
│   └── Dockerfile
├── 4.0
│   ├── 32bit
│   │   ├── docker-entrypoint.sh
│   │   └── Dockerfile
│   ├── alpine
│   │   ├── docker-entrypoint.sh
│   │   └── Dockerfile
│   ├── docker-entrypoint.sh
│   └── Dockerfile
├── 5.0-rc
│   ├── 32bit
│   │   ├── docker-entrypoint.sh
│   │   └── Dockerfile
│   ├── alpine
│   │   ├── docker-entrypoint.sh
│   │   └── Dockerfile
│   ├── docker-entrypoint.sh
│   └── Dockerfile
├── generate-stackbrew-library.sh
├── LICENSE
├── README.md
└── update.sh

仓库结构包含多个目录,表示特定版本。要构建应用程序,我们需要指定一个包含所需 Dockerfile 的目录。可以通过使用oc new-app--context-dir选项来实现。稍后会详细描述。

一个简单的 Dockerfile 构建

好的,我们了解了目录结构,并希望从现有的 Dockerfile 构建和部署 Redis 应用程序。我们专注于版本 3.2。oc new-app可以使用子目录从源代码启动构建。我们准备好启动一个简单的 Dockerfile 构建:

$ oc new-app https://github.com/docker-library/redis.git --context-dir=3.2
...
<OUTPUT OMITTED>
...
Run 'oc status' to view your app.

如我们所见,OpenShift 创建了以下多个对象:

  • 名为debianimagestream

  • 名为redisbuildconfig

  • 名为redisdeploymentconfig

  • 名为redisservice

你可以运行oc get all命令,以确保所有对象都已创建:

$ oc get all
NAME                  TYPE        FROM        LATEST
buildconfigs/redis    Docker      Git         1

NAME                TYPE    FROM        STATUS    STARTED        DURATION
builds/redis-1      Docker  Git@d24f2be Complete  39 seconds ago 6s

NAME                DOCKER REPO                       TAGS        UPDATED
imagestreams/debian 172.30.1.1:5000/dockerfile/debian jessie-slim 39 seconds ago
imagestreams/redis  172.30.1.1:5000/dockerfile/redis  latest      34 seconds ago

NAME                    REVISION DESIRED CURRENT TRIGGERED BY
deploymentconfigs/redis 1        1        1      config,image(redis:latest)

NAME                    READY      STATUS      RESTARTS    AGE
po/redis-1-build        0/1        Completed   0           40s
po/redis-1-js789        1/1        Running     0           33s

NAME                    DESIRED    CURRENT     READY       AGE
rc/redis-1              1           1          1           35s

NAME                    CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
svc/redis               172.30.165.223  <none>        6379/TCP   41s

上一个命令启动了一个构建。OpenShift 启动了一个-build的 Pod 来进行构建。你可能会暂时看到一个名字中带有-build的 Pod 处于Running状态:

$ oc get pod
NAME            READY    STATUS             RESTARTS    AGE
redis-1-build   1/1      Running            0           6s
redis-1-deploy  0/1      ContainerCreating  0           1s

Docker 构建由build配置对象控制。可以使用oc logs bc/<NAME>命令显示构建状态:

$ oc logs bc/redis|tail -n10
Successfully built b3b7a77f988a
Pushing image 172.30.1.1:5000/dockerfile/redis:latest ...
Pushed 0/6 layers, 7% complete
Pushed 1/6 layers, 30% complete
Pushed 2/6 layers, 43% complete
Pushed 3/6 layers, 57% complete
Pushed 4/6 layers, 75% complete
Pushed 5/6 layers, 87% complete
Pushed 6/6 layers, 100% complete
Push successful

解决构建过程故障的另一种方法是使用oc status

OpenShift 从 Dockerfile 构建了 Redis 镜像并上传到本地注册表。让我们确保容器正常工作:

$ oc get pod
NAME             READY    STATUS      RESTARTS  AGE
redis-1-build    0/1      Completed   0         2m
redis-1-js789    1/1      Running     0         1m

$ oc exec redis-1-8lf8h /usr/local/bin/redis-cli ping
PONG
$ oc rsh redis-1-js789 /usr/local/bin/redis-server --version
Redis server v=3.2.11 sha=00000000:0 malloc=jemalloc-4.0.3 bits=64 build=994283e2d09fba41

在这个实验中,我们使用一个简单的ping测试来确保基本的可达性。我们使用容器提供的redis-cli命令。

所以,看起来我们的 Redis 应用程序工作正常,版本是 3.2.11。

Dockerfile 构建定制

如前所述,OpenShift 可以从 Dockerfile 构建应用程序。有时,应用程序的源代码会更新,需要使用新源代码重新启动构建过程。OpenShift 通过oc start-build命令支持这一功能。

在这一部分中,我们将使用oc new-app命令最近创建的镜像流,启动一个构建过程,使用应用程序的新源代码。

我们使用特定目录 3.0 的源代码构建了 Redis 应用程序。

源代码包含另一个 Dockerfile,使用的是更新的版本 4.0:

$ tree redis/4.0/
redis/4.0/
├── 32bit
│   ├── docker-entrypoint.sh
│   └── Dockerfile
├── alpine
│   ├── docker-entrypoint.sh
│   └── Dockerfile
├── docker-entrypoint.sh
└── Dockerfile

现在,假设我们需要使用另一个仓库中的新代码,或现有仓库中的另一个上下文目录来更新应用程序。对于我们的特定情况,看起来我们需要将上下文目录从3.2改为4.0

oc new-app命令创建了多个控制应用程序构建和部署的实体。构建过程由build config对象控制。我们需要显示此对象以了解如何更改以指向仓库中的另一个目录:

$ oc get bc
NAME             TYPE         FROM     LATEST
redis            Docker       Git      1

$ oc get bc redis -o yaml
apiVersion: v1
kind: BuildConfig
metadata:
  annotations:
    openshift.io/generated-by: OpenShiftNewApp
  creationTimestamp: 2018-06-04T19:54:26Z
  labels:
    app: redis
  name: redis
  namespace: dockerfile
  resourceVersion: "256886"
  selfLink: /oapi/v1/namespaces/dockerfile/buildconfigs/redis
  uid: 16116413-6831-11e8-91ff-5254005f9478
spec:
  failedBuildsHistoryLimit: 5
  nodeSelector: null
  output:
    to:
      kind: ImageStreamTag
      name: redis:latest
  postCommit: {}
  resources: {}
  runPolicy: Serial
 source:
 contextDir: "3.2"
 git:
 uri: https://github.com/docker-library/redis.git
 type: Git <OMITTED>

我们突出显示了一个源元素,指定了构建过程中使用的目录。现在,如果有任务需要指向另一个上下文目录,我们只需要通过修改对象定义中的spec.source.contextDir来更新bc/redis对象。这可以通过几种方式实现:

  • 手动使用oc edit

  • 在使用oc patch的脚本中

oc edit bc/redis将启动一个文本编辑器来修改对象。以下是如何使用oc patch命令更新对象内容的示例:

$ oc patch bc/redis --patch '{"spec":{"source":{"contextDir":"4.0"}}}'
buildconfig "redis" patched

$ oc get bc/redis -o yaml|grep contextDir:
    contextDir: 4.0

好吧,我们更新了构建配置,但没有任何变化。我们的 Pod 没有改变。这表明构建过程没有被触发。你可以通过oc get pod来验证这一点。

如果需要启动应用程序重建过程,必须运行oc start-build。此命令将从现有的构建配置启动一个新构建。

让我们列出所有当前的构建:

$ oc get build
NAME         TYPE     FROM         STATUS     STARTED         DURATION
redis-1      Docker   Git@d24f2be  Complete   12 minutes ago  6s

所以,最近我们启动了一个构建,那个构建在一段时间前完成了。让我们尝试再次运行构建:

$ oc start-build bc/redis
build "redis-2" started

构建会创建多个表示新版本(版本 2)的新 Pod。过一段时间后,Pod 状态将会改变:

$ oc get pod
NAME              READY      STATUS             RESTARTS   AGE
redis-1-build     0/1        Completed          0          13m
redis-1-js789     1/1        Running            0          12m
redis-2-build     0/1        Completed          0          8s
redis-2-deploy    1/1        Running            0          2s
redis-2-l6bbl     0/1        ContainerCreating  0          0s

你可以看到redis现在正在经过构建和部署阶段,然后新的redis容器将会启动并运行。如果你等待一分钟左右,你应该能看到以下内容:

$ oc get pod
NAME              READY      STATUS            RESTARTS    AGE
redis-1-build     0/1        Completed         0           14m
redis-2-build     0/1        Completed         0           1m
redis-2-l6bbl     1/1        Running           0           57s

好吧,看起来这个构建已经完成:

$ oc get build
NAME      TYPE       FROM            STATUS     STARTED         DURATION
redis-1   Docker     Git@d24f2be     Complete   15 minutes ago  6s
redis-2   Docker     Git@d24f2be     Complete   2 minutes ago   6s

现在我们可以检查应用程序的版本:

$ oc rsh redis-2-l6bbl /usr/local/bin/redis-server --version
Redis server v=4.0.9 sha=00000000:0 malloc=jemalloc-4.0.3 bits=64 build=40ca48d6a92db598

最后,让我们确保应用程序已启动并运行:

$ oc rsh redis-2-l6bbl /usr/local/bin/redis-cli ping
PONG

我们能够从更新后的源代码启动构建。

清理你的实验环境。

$ oc delete all --all
deploymentconfig "redis" deleted
buildconfig "redis" deleted
imagestream "debian" deleted
imagestream "redis" deleted
pod "redis-2-vw92x" deleted
service "redis" deleted

$ oc delete project dockerfile
project "dockerfile" deleted

$ oc project myproject
Now using project "myproject" on server "https://localhost:8443".

如果你打算继续下一章,你可以保持你的 OpenShift 集群运行,否则你可以关闭或删除 vagrant 虚拟机。

概述

在本章中,你已经学习了如何调整 Dockerfile,以便在 OpenShift 中运行。我们还解释了如何从 Dockerfile 构建应用程序,如何使用oc new-app启动 Docker 构建,以及如何使用oc start-build从现有构建配置启动新构建。

在下一章中,我们将讨论已经在 Docker Hub 上可用的最常用应用程序镜像。但时不时地,需要构建一个包含自定义软件或符合公司安全政策/标准的自定义镜像。我们将学习 OpenShift 如何通过 S2I 构建策略自动化构建过程,这是 OpenShift 的主要优势之一,以及它如何允许你从应用程序的源代码构建镜像,然后将其作为容器运行。

问题

  1. 以下哪些 OpenShift 命令可以更新现有的 API 对象?请选择两个:
    1. oc edit bc/redis

    2. oc get bc redis

    3. oc patch bc/redis --patch ...

    4. oc update bc/redis

    5. oc build bc/redis

  1. 以下哪个命令可以启动新的构建?(选择一个):
    1. oc new-app

    2. oc new build

    3. oc start-build

    4. oc get build

  1. 在仓库中必须存在哪个文件才能执行 docker build(选择一个)?
    1. Jenkinsfile

    2. Dockerfile

    3. README.md

    4. index.php

    5. docker.info

深入阅读

以下是与本章节相关的主题链接,您可能希望深入了解:

第十五章:从源代码构建 PHP 应用程序

在上一章中,你学会了如何从 Dockerfile 构建应用程序,如何使用oc new-app启动 Docker 构建,最后如何使用oc start-build从现有的构建配置启动新构建。

在本章中,我们将讨论 Docker Hub 上已有的最常用的应用程序镜像。偶尔需要构建一个自定义镜像,包含定制的软件或符合公司安全政策/标准。你将学习 OpenShift 如何通过Source-to-ImageS2I)构建策略自动化构建过程,这是其主要优势之一。你还将学习如何从应用程序的源代码构建镜像,并将其作为容器运行。

本章将涵盖以下主题:

  • PHP S2I

  • 构建一个简单的 PHP 应用程序

  • 理解 PHP 构建过程

技术要求

本章在实验环境方面没有严格要求,因此任何 OpenShift 安装和开发环境都是支持的——Minishift、oc cluster up或基于 Ansible 的标准生产就绪部署。选择使用哪种方式由你决定。不过,本章将重点介绍oc cluster up方法。以下的 Vagrantfile 可以用来部署我们的虚拟实验室:

Vagrant.configure(2) do |config| 
  config.vm.define "openshift" do |conf| 
    conf.vm.box = "centos/7" 
    conf.vm.network "private_network", ip: "172.24.0.11" 
    conf.vm.hostname = 'openshift.example.com' 
    conf.vm.network "forwarded_port", guest: 80, host: 980
    conf.vm.network "forwarded_port", guest: 443, host: 9443
    conf.vm.network "forwarded_port", guest: 8080, host: 8080
    conf.vm.network "forwarded_port", guest: 8443, host: 8443
    conf.vm.provider "virtualbox" do |v| 
      v.memory = 4096 
      v.cpus = 2 
    end
    conf.vm.provision "shell", inline: $lab_main 
  end 

$lab_main = <<SCRIPT
cat <<EOF >> /etc/hosts
172.24.0.11 openshift.example.com openshift
172.24.0.12 storage.example.com storage nfs
EOF
systemctl disable firewalld
systemctl stop firewalld
yum update -y
yum install -y epel-release git
yum install -y docker
cat << EOF >/etc/docker/daemon.json
{
   "insecure-registries": [
     "172.30.0.0/16"
   ]
}
EOF
systemctl start docker
systemctl enable docker
yum -y install centos-release-openshift-origin39
yum -y install origin-clients
oc cluster up
SCRIPT

该环境可以通过运行单个命令来部署:

$ vagrant up

一旦虚拟机部署完成,你可以按以下方式连接到它:

$ vagrant ssh

最后,作为developer用户登录,以便能够执行没有特权的操作:

$ oc login -u developer
Server [https://localhost:8443]:
The server uses a certificate signed by an unknown authority.
You can bypass the certificate check, but any data you send to the server could be intercepted by others.
Use insecure connections? (y/n): y

Authentication required for https://localhost:8443 (openshift)
Username: developer
Password: <ANY PASSWORD>
Login successful.

You have one project on this server: "myproject"

Using project "myproject".
Welcome! See 'oc help' to get started.

PHP S2I

OpenShift 支持 PHP 的 S2I 构建,也支持许多其他运行时。S2I 过程通过将应用程序的源代码与基础构建器镜像结合,生成一个可运行的镜像,该镜像准备好应用程序。构建器是一个特殊的镜像,能够处理特定编程语言/框架的应用程序安装和配置。例如,PHP 构建器只能处理 PHP 源代码,并且默认不支持 Java。大多数常用的编程语言,如 Python、Ruby、Java 和 Node.js,已经由 OpenShift 内置的构建器覆盖。S2I 过程包括以下步骤:

  1. 确定正确的基础构建器镜像。此过程依赖于复杂的启发式算法,主要通过查找特定的文件和文件扩展名来实现,比如 Ruby on Rails 的Gemfile,或 Python 的requirements.txt。构建器镜像的运行时环境也可以通过 CLI 由用户覆盖

  2. 创建一个指向应用程序源代码仓库和构建器镜像的 ImageStream 的BuildConfig

  3. 从构建器镜像启动build pod

  4. 下载应用程序的源代码

  5. 使用tar工具将脚本和应用程序的源代码流式传输到构建器镜像容器中,适用于支持该工具的镜像

  6. 运行 assemble 脚本(由构建器镜像提供的脚本优先级最高)

  7. 将最终的镜像保存到内部注册表

  8. 创建支持应用程序所需的资源,包括但不限于 DeploymentConfigServicePod

PHP 构建器支持通过使用多个环境变量来更改默认的 PHP 配置。每个特定构建器的网页上有详细描述。

构建过程可以通过运行 oc new-app 命令来启动。此命令将仓库 URL 或本地路径作为参数,并同时创建所有必需的 OpenShift 实体来支持 S2I 构建和应用程序部署。

默认情况下,创建以下 OpenShift 实体:

Type Name Description
Pod <application name>-<build sequential number>-build 构建您的应用程序的构建器 Pod,生成应用程序镜像并可能生成一些供未来构建使用的工件
Pod <name>-<build sequential number>-<id> 构建过程中生成的应用程序 Pod
Service <name> 应用程序服务
Replication controller <name>-<build sequential number> 应用程序的复制控制器,默认情况下仅维持一个副本
Deployment config <name> 包含有关如何部署应用程序的所有信息的部署配置
Image stream <name> 构建的镜像路径
Build config <name> 包含如何构建应用程序的所有必要信息的构建配置
Build <name>-<build sequential number> 特定构建;可以多次运行

OpenShift 不会自动创建路由。如果需要暴露应用程序,应该通过运行 oc expose svc <name> 来创建路由。

构建一个简单的 PHP 应用程序

第一个 S2I 实验将使用一个非常简单的 PHP 应用程序,使用标准的 PHP 函数 phpinfo() 显示 PHP 配置信息。它由一个 index.php 文件组成,内容如下:

$ cat index.php
<?php
phpinfo();
?>

这个应用程序足以展示基本的 S2I 构建。我们已经在 GitHub 上准备好了一个包含我们应用程序代码的 Git 仓库。该仓库位于 github.com/neoncyrex/phpinfo.git,并将在本实验中使用。

首先,我们需要为我们的新实验创建一个单独的项目,如下所示:

$ oc new-project phpinfo
Now using project "phpinfo" on server "https://localhost:8443".
<OMITTED>

oc new-app 命令可以从源代码构建应用程序,使用本地或远程 Git 仓库。

让我们将仓库克隆到本地,以便能够对其进行更改。这将创建 phpinfo 目录,其中包含仓库文件:

$ git clone https://github.com/neoncyrex/phpinfo.git
Cloning into 'phpinfo'...
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 6 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), done.

构建和应用程序部署过程可以通过运行 oc new-app 命令来启动。此基本 S2I 策略可以如下触发:

$ oc new-app phpinfo
--> Found image 23e49b6 (17 hours old) in image stream "openshift/php" under tag "7.0" for "php"
...
<output omitted>
...
--> Success
    Build scheduled, use 'oc logs -f bc/phpinfo' to track its progress.
    Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
     'oc expose svc/phpinfo'
    Run 'oc status' to view your app.

前面的命令执行以下操作:

  • 使用 phpinfo 作为应用程序源代码的路径

  • 自动检测编程语言——PHP

  • 启动构建过程

  • 创建多个 OpenShift 资源

构建过程需要一些时间。在第一阶段,你可以看到一个名称中带有 -build 的容器。这个容器是从 PHP 构建器镜像部署的,负责执行构建操作:

$ oc get pod
NAME             READY  STATUS    RESTARTS  AGE
phpinfo-1-build  1/1    Running   0         23s

经过一段时间后,应用程序将可用。这意味着应用程序的 pod 应该处于 Running 状态:

$ oc get pod
NAME              READY  STATUS     RESTARTS  AGE
phpinfo-1-build   0/1    Completed  0         39s
phpinfo-1-h9xt5   1/1    Running    0         4s

OpenShift 已构建并部署了 phpinfo 应用程序,现在可以通过其服务 IP 进行访问。让我们尝试使用 curl 命令访问我们的应用程序:

$ oc get svc
NAME    CLUSTER-IP    EXTERNAL-IP PORT(S)            AGE
phpinfo 172.30.54.195 <none>      8080/TCP,8443/TCP  1h

$ curl -s http://172.30.54.195:8080 | head -n 10
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html ><head>
<style type="text/css">
body {background-color: #fff; color: #222; font-family: sans-serif;}
pre {margin: 0; font-family: monospace;}
a:link {color: #009; text-decoration: none; background-color: #fff;}
a:hover {text-decoration: underline;}
table {border-collapse: collapse; border: 0; width: 934px; box-shadow: 1px 2px 3px #ccc;}
.center {text-align: center;}
.center table {margin: 1em auto; text-align: left;}

phpinfo() 函数以 HTML 表格的形式显示 PHP 配置信息。

可以通过运行 oc statusoc status -v 命令显示构建过程的摘要:

$ oc status -v
In project phpinfo on server https://localhost:8443

svc/phpinfo - 172.30.54.195 ports 8080, 8443
  dc/phpinfo deploys istag/phpinfo:latest <-
    bc/phpinfo source builds https://github.com/neoncyrex/phpinfo.git#master on openshift/php:7.0
    deployment #1 deployed about an hour ago - 1 pod

Info:
  * pod/phpinfo-1-build has no liveness probe to verify pods are still running.
    try: oc set probe pod/phpinfo-1-build --liveness ...
  * dc/phpinfo has no readiness probe to verify pods are ready to accept traffic or ensure deployment is successful.
    try: oc set probe dc/phpinfo --readiness ...
  * dc/phpinfo has no liveness probe to verify pods are still running.
    try: oc set probe dc/phpinfo --liveness ...
View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.

上述命令显示了部署 #1 已经完成。它还可能包含一些有用的信息,用于在构建出现问题时进行故障排除。

还有另一种显示详细构建日志的方法——使用 oc logs 命令。我们需要显示 buildconfig(或简称 bc)实体的日志,可以通过如下命令显示:oc logs bc/phpinfo

$ oc logs bc/phpinfo
Cloning "https://github.com/neoncyrex/phpinfo.git" ...
  Commit: 638030df45052ad1d9300248babe0b141cf5dbed (initial commit)
  Author: vagrant <vagrant@openshift.example.com>
  Date: Sat Jun 2 04:22:59 2018 +0000
---> Installing application source...
=> sourcing 20-copy-config.sh ...
---> 05:00:11 Processing additional arbitrary httpd configuration provided by s2i ...
=> sourcing 00-documentroot.conf ...
=> sourcing 50-mpm-tuning.conf ...
=> sourcing 40-ssl-certs.sh ...
Pushing image 172.30.1.1:5000/phpinfo/phpinfo:latest ...
Pushed 0/10 layers, 23% complete
Pushed 1/10 layers, 30% complete
Pushed 2/10 layers, 21% complete
Pushed 3/10 layers, 30% complete
Pushed 4/10 layers, 40% complete
Pushed 5/10 layers, 51% complete
Pushed 6/10 layers, 60% complete
Pushed 7/10 layers, 76% complete
Pushed 8/10 layers, 87% complete
Pushed 9/10 layers, 95% complete
Pushed 10/10 layers, 100% complete
Push successful

上述输出给了我们一些关于构建如何工作的线索。

理解 PHP 构建过程

现在我们知道 phpinfo 应用程序按预期工作,让我们专注于理解构建过程所需的低级细节。OpenShift 创建了多个 API 资源来使构建成为可能。其中一些与部署过程相关,这部分内容我们在前面的章节已经学习过了。我们可以通过如下命令显示所有实体:

$ oc get all
NAME                 TYPE   FROM       LATEST
buildconfigs/phpinfo Source Git@master 1

NAME            TYPE    FROM       STATUS    STARTED           DURATION
builds/phpinfo-1 Source Git@638030d Complete About an hour ago 34s

NAME                 DOCKER REPO                     TAGS                      UPDATED
imagestreams/phpinfo 172.30.1.1:5000/phpinfo/phpinfo latest 
About an hour ago

NAME                     REVISION DESIRED CURRENT TRIGGERED BY
deploymentconfigs/phpinfo 1       1       1 config,image(phpinfo:latest)

NAME                READY  STATUS     RESTARTS  AGE
po/phpinfo-1-build  0/1    Completed  0         1h
po/phpinfo-1-h9xt5  1/1    Running    0         1h

NAME         DESIRED  CURRENT READY  AGE
rc/phpinfo-1 1        1       1      1h

NAME        CLUSTER-IP    EXTERNAL-IP PORT(S)           AGE
svc/phpinfo 172.30.54.195 <none>      8080/TCP,8443/TCP 1h

上述输出中的大多数实体(如 pod、服务、复制控制器和部署配置)在前面的章节中你应该已经熟悉了。

S2I 构建策略使用以下附加实体——构建配置、构建镜像流。构建配置包含构建应用程序所需的所有信息。与 OpenShift 其他 API 资源一样,其配置可以通过 oc get 命令获取:

$ oc get bc phpinfo -o yaml
apiVersion: v1
kind: BuildConfig
...
<output omitted>
...
spec:
...
<output omitted>
...
  source:
    git:
      ref: master
      uri: https://github.com/neoncyrex/phpinfo.git
    type: Git
  strategy:
    sourceStrategy:
      from:
        kind: ImageStreamTag
        name: php:7.0
        namespace: openshift
    type: Source
  successfulBuildsHistoryLimit: 5
  triggers:
  - github:
      secret: 5qFv8z-J1me1Mj7Q27rY
    type: GitHub
  - generic:
      secret: -g6nTMasd6TRCMxBvKWz
    type: Generic
  - type: ConfigChange
  - imageChange:
      lastTriggeredImageID: centos/php-70-centos7@sha256:eb2631e76762e7c489561488ac1eee966bf55601b6ab31d4fbf60315d99dc740
    type: ImageChange
status:
  lastVersion: 1

以下字段尤为重要:

  • spec.source.git: 应用程序源代码的仓库 URL

  • spec.strategy.sourceStrategy: 包含将使用哪个构建器的信息。

在我们的例子中,OpenShift 使用来自 openshift 命名空间中的内建构建器,基于镜像流 php:7.0。让我们看看它的配置:

$ oc get is php -o yaml -n openshift
apiVersion: v1
kind: ImageStream
<OMITTED>
spec:
  lookupPolicy:
    local: false
  tags:
<OMITTED>
  - annotations:
<OMITTED>
      openshift.io/display-name: PHP 7.0
      openshift.io/provider-display-name: Red Hat, Inc.
      sampleRepo: https://github.com/openshift/cakephp-ex.git
      supports: php:7.0,php
      tags: builder,php
      version: "7.0"
    from:
 kind: DockerImage
 name: centos/php-70-centos7:latest
<OMITTED>

用于构建我们应用程序的 PHP 构建器镜像是 centos/php-70-centos7:latest

我们现在已经掌握了从源代码构建应用程序的所有信息。OpenShift 将所有信息(包括你提供的和从源代码推断出的信息)组合起来,并启动新的构建。每个构建都有一个顺序编号,从 1 开始。你可以通过运行以下命令显示所有构建:

$ oc get build
NAME      TYPE   FROM        STATUS   STARTED     DURATION
phpinfo-1 Source Git@638030d Complete 2 hours ago 34s

该构建报告为 Complete,因为我们的应用程序已经启动并运行。

启动新构建

如果应用程序的源代码已更新,您可以通过运行oc start-build命令来触发重新构建过程。构建本身由构建配置管理。

首先,我们需要收集所有可用的构建配置的信息:

$ oc get bc
NAME    TYPE   FROM       LATEST
phpinfo Source Git@master 1

如您所见,我们只有一个构建,phpinfo,并且它只部署了一次;因此,版本号是1

让我们开始一个新构建,如下所示:

$ oc start-build phpinfo
build "phpinfo-2" started

$ oc get pod
NAME             READY  STATUS    RESTARTS AGE
phpinfo-1-build  0/1    Completed 0        2h
phpinfo-1-h9xt5  1/1    Running   0        2h
phpinfo-2-build  0/1    Init:0/2  0        3s

OpenShift 启动了一个新构建,版本号为2,该版本在新构建生成的 Pod 名称中出现。完成后,您的应用程序将重新部署:

$ oc get pod
NAME             READY  STATUS     RESTARTS AGE
phpinfo-1-build  0/1    Completed  0        2h
phpinfo-2-build  0/1    Completed  0        32s
phpinfo-2-zqtj6  1/1    Running    0        23s

最新版本的构建现在是2

$ oc get bc
NAME    TYPE   FROM       LATEST
phpinfo Source Git@master 2

OpenShift 会保存所有构建的列表,以便将来检查:

$ oc get build
NAME      TYPE   FROM        STATUS   STARTED            DURATION
phpinfo-1 Source Git@638030d Complete 2 hours ago        34s
phpinfo-2 Source Git@638030d Complete About a minute ago 7s

这个示例并不接近生产环境,因为构建是手动触发的,而不是由源代码的更改触发的。然而,它可以让您大致了解如何使用构建。

使用以下代码清理所有内容,为下一个实验做准备:

$ oc delete all --all deploymentconfig "phpinfo" deleted
buildconfig "phpinfo" deleted
imagestream "phpinfo" deleted
pod "phpinfo-2-fbd8l" deleted
service "phpinfo" deleted

$ oc delete project phpinfo
project "phpinfo" deleted

$ oc project myproject
Now using project "myproject" on server "https://127.0.0.1:8443".

概要

在本章中,您了解了 OpenShift 创建的构建实体,以及如何从源代码部署一个简单的 PHP 应用程序。我们向您展示了如何启动新构建以及如何自定义构建过程。

在接下来的章节中,您将从自定义模板构建并部署一个 WordPress 应用程序。您还将学习如何创建和部署 OpenShift 模板,以及如何从 OpenShift 模板部署应用程序。

问题

  1. 以下哪个 OpenShift 实体控制如何从源代码构建特定应用程序?选择两个:
    1. Pod

    2. 路由

    3. 复制控制器

    4. 构建配置

    5. 构建

    6. 部署配置

  1. 以下哪个命令启动一个新构建?选择一个:
    1. oc new-app

    2. oc new build

    3. oc start-build

    4. oc get build

  1. 以下哪个命令显示构建的信息?选择三个:
    1. oc status -v

    2. oc status

    3. oc logs build/phpdemo-2

    4. oc show logs

进一步阅读

S2I 是 OpenShift 最重要的功能之一。您可能希望深入了解这一过程。以下链接提供了更多信息:

第十六章:从源代码构建多层应用程序

在上一章中,我们学习了什么是由 OpenShift 创建的构建实体,以及如何从源代码部署一个简单的 PHP 应用程序。我们向你展示了如何启动一个新的构建以及如何自定义构建过程。

在本章中,你将从自定义模板构建并部署一个 WordPress 应用程序。你还将学习如何使用 OpenShift 模板,了解如何创建和部署 OpenShift 模板,并从 OpenShift 模板部署应用程序。

我们将讨论的主题是构建一个多层应用程序。

技术要求

本章依赖于已安装的 OpenShift 环境。我们假设 OpenShift 主节点的地址是openshift.example.com,默认子域名是example.com

一些实验项目需要自定义 DNS 记录,可以通过在/etc/hosts中设置记录来模拟。两种方法都是可以接受的。

本章要求你运行 minishift,为了避免在使用其他 OpenShift 部署方法时可能遇到的某些错误和重新配置,建议使用 minishift。

$ minishift start --openshift-version=v3.9.0 --vm-driver=virtualbox --memory 4GB ...
output truncated for brevity
...
-- Minishift VM will be configured with ...
   Memory: 4 GB
   vCPUs : 2
   Disk size: 20 GB
...
output truncated for brevity
...
OpenShift server started.

The server is accessible via web console at:
    https://192.168.99.110:8443

You are logged in as:
    User: developer
    Password: <any value>

To login as administrator:
    oc login -u system:admin

构建多层应用程序

我们之前向你解释了如何使用模板来部署简单和多层应用程序。这使得通过创建部署配置并部署多个 Pod、服务和路由来部署复杂的应用程序成为可能。但这种方法存在局限性,因为大多数多层应用程序需要从源代码构建。OpenShift 模板允许从源代码构建应用程序。本章将描述如何结合源代码构建应用程序和使用模板来部署和构建多层应用程序。这是一个实践章节,给你提供了实际示例,展示如何利用 OpenShift 模板在生产环境中部署应用程序。现在,让我们看看如何使用 MariaDB 作为数据库,从源代码构建一个 WordPress 应用程序。

WordPress 模板

WordPress 是一个免费的开源内容管理系统CMS),基于 PHP 和 MySQL。我们将演示使用github.com/openshift-evangelists/wordpress-quickstart中准备的模板,通过源代码到镜像S2I)构建过程来构建 WordPress。这个仓库包含了用于在 OpenShift 集群上部署 WordPress 的现成模板。该仓库中有两个示例模板。让我们先克隆该仓库:

$ git clone https://github.com/openshift-evangelists/wordpress-quickstart.git
Cloning into 'wordpress-quickstart'...
remote: Counting objects: 331, done.
remote: Total 331 (delta 0), reused 0 (delta 0), pack-reused 331
Receiving objects: 100% (331/331), 1.07 MiB | 1.96 MiB/s, done.
Resolving deltas: 100% (119/119), done.

我们将应用wordpress-quickstart/templates/classic-standalone.json WordPress 模板。为了简化,我们将模板从 JSON 格式转换为 YAML,并去除了与持久存储相关的实体。我们还移除了APPLICATION_NAME参数的默认值。

构建 WordPress 应用程序

首先,我们需要将应用程序放置到一个单独的命名空间中:

$ oc new-project wp
Now using project "wp" on server "https://openshift.example.com:8443".

首先,由于这是我们使用的新模板,我们希望收集一些关于可用参数的信息。正如之前所描述的,oc process --parameters可以提供帮助:

$ oc process --parameters -f  wordpress-quickstart/templates/classic-standalone.json 
NAME DESCRIPTION GENERATOR VALUE
APPLICATION_NAME The name of the WordPress instance.
QUICKSTART_REPOSITORY_URL The URL of the quickstart Git repository. https://github.com/openshift-evangelists/wordpress-quickstart
WORDPRESS_DEPLOYMENT_STRATEGY Type of the deployment strategy for Wordpress. Recreate
WORDPRESS_MEMORY_LIMIT Amount of memory available to WordPress. 512Mi
DATABASE_MEMORY_LIMIT Amount of memory available to the database. 512Mi
DATABASE_USERNAME The name of the database user. expression user[a-f0-9]{8}
DATABASE_PASSWORD The password for the database user. expression [a-zA-Z0-9]{12}
MYSQL_VERSION The version of the MySQL database. 5.7
PHP_VERSION The version of the PHP builder. 7.0

请注意,只有APPLICATION_NAME没有默认值。

让我们通过实例化模板并设置APPLICATION_NAME=wordpress来从源代码构建应用程序:

$ oc new-app -f wordpress-quickstart/templates/classic-standalone.json -p APPLICATION_NAME=wordpress
--> Deploying template "wp/wordpress-classic-standalone" for "wordpress.yaml" to project wp
...
<output omitted>
...
--> Success
    Build scheduled, use 'oc logs -f bc/wordpress' to track its progress.
    Access your application via route 'wordpress-wp.apps.kropachev.pro'
    Run 'oc status' to view your app.

你可能需要查看 WordPress 应用程序的构建日志:

$ oc logs bc/wordpress -f
Cloning "https://github.com/openshift-evangelists/wordpress-quickstart" ...
  Commit: 0f5076fbb3c898b77b820571fa30d1293c3ac33b (Update README with details on how to enable WebDav access.)
...
<output omitted>
...
Pushed 9/10 layers, 96% complete
Pushed 10/10 layers, 100% complete
Push successful

过了一段时间,所有 WordPress pod 都将启动并运行,如下所示:

$ oc get pod
NAME READY STATUS RESTARTS AGE
wordpress-1-build 0/1 Completed 0 2m
wordpress-1-zjfs2 1/1 Running 0 51s
wordpress-db-1-9mxgb 1/1 Running 0 2m

这表示我们的应用程序现在应该可以正常工作。让我们看看它通过什么 URL 被暴露出来,并尝试通过 Web 浏览器访问:

$ oc get route
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
wordpress wordpress-wp.127.0.0.1.nip.io wordpress 8080 edge/Allow None

一旦你打开浏览器并访问http://wordpress-wp.127.0.0.1.nip.io/,WordPress 应用程序应该会显示一个配置页面。选择你喜欢的语言并点击继续,在下一页面中,按照此处所示填写字段,然后点击安装 WordPress

下一窗口显示安装状态:

一旦你点击登录按钮,下面的页面将会显示:

你只需要输入在设置阶段提供的用户名和密码。此时,你应该看到 WordPress 工作空间:

这表示一切配置正确。

使用以下代码清理一切,为下一个实验做准备:

$ oc delete all --all deploymentconfig "wordpress" deleted
deploymentconfig "wordpress-db" deleted
buildconfig "wordpress" deleted
imagestream "wordpress-img" deleted
route "wordpress" deleted
pod "wordpress-1-tnvhl" deleted
pod "wordpress-db-1-pdsbb" deleted
service "wordpress" deleted
service "wordpress-db" deleted

$ oc delete project wp
project "wp" deleted

$ oc project myproject
Now using project "myproject" on server "https://127.0.0.1:8443".

如果今天的实验已经结束,你也可以通过运行minishift destroy命令删除你的 minishift 虚拟机,或者保持 minishift 运行并继续进行下一章。

总结

在这一章中,我们学习了如何从自定义模板构建和部署 WordPress 应用程序。你学习了如何使用 OpenShift 模板,如何创建和部署 OpenShift 模板,以及如何从 OpenShift 模板部署应用程序。

下一章将向读者介绍 CI/CD、Jenkins、OpenShift 管道,以及 Jenkins 与 OpenShift 的集成。我们将展示如何在 OpenShift 中创建示例 CI/CD 管道、编辑管道以及管理管道执行。

问题

  1. 以下哪个 OpenShift 实体控制构建过程(选择一个):

    1. buildconfig

    2. 部署配置

    3. 副本控制器

    4. service

    5. pod

    6. route

  2. 以下哪个 OpenShift 实体可以使用模板创建(选择一个):

    1. pod

    2. service

    3. route

    4. 部署配置

    5. buildconfig

    6. 上述所有内容

第十七章:OpenShift 中的 CI/CD 流水线

在上一章中,你学习了如何从自定义模板构建和部署 WordPress 应用程序。你还学习了如何创建和部署 OpenShift 模板,以及如何从 OpenShift 模板部署应用程序。

本章将向读者介绍持续集成/持续交付CI/CD)、Jenkins、OpenShift 流水线以及 Jenkins 与 OpenShift 的集成。我们将讨论 CI/CD、流水线以及作为 CI/CD 的 Jenkins,随后介绍 Jenkins 在 OpenShift 中的工作方式。我们还将向你展示如何在 OpenShift 中创建一个示例 CI/CD 流水线,如何编辑流水线,以及如何管理流水线执行。

阅读完本章后,你将了解以下内容:

  • CI/CD 和 CI/CD 流水线

  • Jenkins 作为 CI/CD

  • OpenShift 中的 Jenkins

技术要求

在本章中,我们将使用以下技术和软件:

  • 命令行界面

  • Minishift

  • GitHub

  • Web 浏览器

Vagrant 安装以及我们将在本章中使用的所有代码都可以在 GitHub 上找到,地址为github.com/PacktPublishing/Learn-OpenShift

你可以使用 Firefox 或其他任何浏览器浏览 Docker Hub。

作为前提,你需要确保你的笔记本电脑有稳定的互联网连接。

在本章中,我们将使用 Minishift 作为实验环境。在开始之前,请删除现有的 Minishift 虚拟机(如果有的话),因为我们需要创建一个新的虚拟机,具有非标准参数:

$ minishift start --openshift-version=v3.9.0 --vm-driver=virtualbox --memory 4GB
...
output truncated for brevity
...
-- Minishift VM will be configured with ...
 Memory: 4 GB
   vCPUs : 2
   Disk size: 20 GB
...
output truncated for brevity
...
OpenShift server started.

The server is accessible via web console at:
 https://192.168.99.110:8443

You are logged in as:
    User: developer
    Password: <any value>

To login as administrator:
    oc login -u system:admin

确保Memory至少显示为 4 GB,否则可能没有足够的内存来进行实验。你也可以通过停止 Minishift 虚拟机并通过 VirtualBox 控制台调整内存大小。但最安全的方法是删除现有的 Minishift 虚拟机并创建一个新的。

CI/CD 和 CI/CD 流水线

你可能已经听说过CI/CD这个术语。它包含了人们在谈论现代应用程序部署时使用的两个主要缩写。CI 代表持续集成,而 CD 有两种含义:一种是持续部署,另一种是持续交付。这三个术语都很容易理解,具体说明如下:

  • 持续集成:强调为应用程序构建创建和构建自动化测试,并尽可能频繁地将更新合并到单一分支中。它有助于尽早捕捉错误,避免开发人员在开发新代码并将更改合并到不同分支时遇到的集成困难。

  • 持续交付:帮助扩展持续集成流程,将新代码从开发阶段推送到生产阶段,并以可复制的方式进行。这意味着,如果在持续集成中自动化了构建和测试,那么持续交付会自动化应用程序发布过程,通常会使用审批程序。

  • 持续部署:进一步扩展持续交付,通过提供一个无缝且不中断的应用程序交付过程,且无需人工干预。这种应用部署方式需要大量努力来确保运行代码时不会破坏任何东西,并且能按预期工作。

CI 和 CD 过程被分成一系列步骤和程序,形成一个流水线,通常称为 CI/CD 流水线。下图展示了持续集成与持续交付和持续部署的关系:

CI/CD 流水线示例

流水线的各个阶段并不严格,但遵循最佳实践有助于使您的 CI/CD 流水线更加稳定和可重复。

有许多 CI/CD 工具可以帮助您自动化应用交付过程,但其中一个在业界最受欢迎的工具叫做 Jenkins。恰好 OpenShift 内建了与 Jenkins 的集成,我们将在接下来的章节中讨论这一点。

Jenkins 作为 CI/CD

Jenkins 是一个开源自动化工具,可用于通过 CI/CD 流水线加速应用程序开发过程。Jenkins 控制和管理应用程序开发生命周期过程,包括构建、测试、发布、阶段部署和冒烟测试等过程。

标准的 Jenkins 架构如下图所示:

Jenkins 架构

Jenkins 架构中有两个主要组件:

  • Jenkins 主节点:您的 CI/CD 控制器,负责处理各种任务,例如:

    • 任务调度

    • 在 Jenkins 从属节点之间分配工作负载

    • Jenkins 从属节点监控

    • 日志记录和展示任务结果

  • Jenkins 从属节点:一组基于 Java 的程序,运行在远程主机上。Jenkins 从属节点主要执行来自 Jenkins 主节点的指令。

Jenkins 主节点和 Jenkins 从属节点是功能上的区分,这意味着它们可以共存在同一主机上。在小型或实验室环境中,通常没有问题将 Jenkins 主节点和 Jenkins 从属节点都运行在同一主机上,但如果您的环境中有数百(或数千)个任务运行,那么将 Jenkins 主节点和 Jenkins 从属节点分布在不同节点上,并拥有多个 Jenkins 从属节点来分配负载是更合理的。

OpenShift 中的 Jenkins

OpenShift 利用 Jenkins CI/CD 模型,并通过以下组件的组合对其进行扩展:

  • OpenShift 领域特定语言(DSL):DSL 由运行在 Jenkins 主节点 Pod 上的 OpenShift Jenkins 客户端插件提供,并与 OpenShift API 服务器交互。OpenShift DSL 提供了一种控制应用程序生命周期的方法。

  • Jenkins 流水线构建策略:类似于其他 OpenShift 构建策略,它定义了构建的工作流。Jenkins 流水线构建策略允许开发人员创建一个由 OpenShift 监控和控制的 Jenkins 流水线。

  • Jenkinsfile:通过一系列步骤定义 CI/CD 流水线,应用于在 OpenShift 中部署应用程序,使用 Apache Groovy 编程语言。

在 OpenShift 中创建 Jenkins 流水线

一旦 Minishift 集群启动,打开你喜欢的网页浏览器,使用 minishift start 命令输出的 URL 访问 OpenShift。我们这边可以通过 https://192.168.99.110:8443 访问:

OpenShift 登录页面

使用开发者用户,输入任意密码,登录欢迎页面:

OpenShift 欢迎页面

点击“My Project”以访问 OpenShift 项目概览页面。从那里,点击“Builds | Pipelines”子菜单:

从这里,你应该能够点击“创建示例流水线”。然后,向下滚动到底部,点击“创建”按钮。它应该会告诉你Pipeline Build Example已创建:

OpenShift 创建了一组包括 Jenkins、mongodb 和 nodejs 的 Pod,并进一步与 OpenShift 集成。所有这些都只需要点击一下。这只是一个演示,但这不是很酷吗?现在返回到概览菜单检查整体进度:

OpenShift 项目菜单

你应该能够看到一些应用程序以及相关的 Pod。第一个是 Jenkins 应用程序,第二个是 MongoDB,作为我们测试应用的一部分。nodejs-mongodb-example 应该没有部署,因为它将由 Jenkins 流水线控制。

根据你的网络连接情况,拉取所有镜像并启动所有 Pod 可能需要一些时间,尤其是 Jenkins,因为它大约有 2.5 GB。你可以转到“监控”标签并点击“查看详情”来检查整体进度的当前状态。如果 Jenkins 最后的消息是拉取镜像 openshift/jenkins-2-centos7,那么请耐心等待,或者去泡杯咖啡。

启动 Jenkins 流水线

一旦 Jenkins 和 MongoDB Pod 启动完毕,返回到“Builds | Pipelines”子菜单:

点击“启动流水线”按钮。这将触发一个 CI/CD 流水线,开始构建。根据你的网络连接情况,这一步可能需要一些时间才能完成;完成后,你应该能够看到两个阶段成功完成:

点击“查看日志”应该会打开一个新标签,显示 Jenkins 登录页面的 Jenkins 控制台输出:

Jenkins 登录页面

点击“使用 OpenShift 登录”,并使用你在 OpenShift 中认证时使用的相同凭证(使用开发者用户,输入任意密码):

Jenkins 用户授权

你需要授权开发者用户访问 Jenkins,点击“允许所选权限”。这将带你进入 Jenkins 控制台输出:

Jenkins 控制台输出

向下滚动查看完整日志,关闭实验并返回 OpenShift 标签。

如果您对 Jenkins 的工作原理感兴趣并想深入了解,可以查看本章 Further reading 部分中的链接。

我们之前提到过,持续交付(Continuous Delivery)和持续部署(Continuous Deployment)模型之间是有区别的;其中一个模型包含人工干预,另一个则不包含。

接下来,我们将修改我们的测试管道并添加一个审批步骤。请返回管道子菜单:

点击 Edit Pipeline 应该会将我们重定向到编辑我们的第一个管道。

编辑 Jenkinsfile

您应该能看到 Jenkins 管道配置文件 Jenkinsfile,如下图所示:

编辑管道

Jenkins 使用 Apache Groovy 编程语言。它非常容易学习,我们不会深入讨论细节;相反,我们会提供一些基本技能。您可以在 Further reading 部分找到有关 Groovy 的更多信息。

我们只需添加一个新的阶段,名称可以自定义;我们称其为 approval 阶段,位于 build 阶段和 deploy 阶段之间。这个新的 approval 阶段将暂停部署阶段,直到您手动批准:

...
node('nodejs') {
 stage('build') {
 openshiftBuild(buildConfig: 'nodejs-mongodb-example', showBuildLogs: 'true')
 }
 stage('approval') {
 input "Approve moving to deploy stage?"
 }
 stage('deploy') {
 openshiftDeploy(deploymentConfig: 'nodejs-mongodb-example')
 }
}
...

点击 Save 并启动管道。

管理管道执行

添加一个名为 approval 的新阶段会触发第二次构建的开始,最终您应该能够看到新的审批阶段,并显示“Input Required”:

点击“Input Required”将打开一个新标签页并将您重定向到 Jenkins 审批阶段,询问:是否批准迁移到部署阶段?如下所示:

结果将根据您的回答而有所不同。在我们的案例中,我们将按下 Proceed 按钮。这将把我们重定向到 Jenkins 控制台输出,但我们可以直接关闭它并返回到 OpenShift 管道标签页。从以下截图中,我们可以看到 CI/CD 管道已经完成了部署阶段,现在我们有了三个阶段,而不是 Build #1 中的两个阶段。

结果,您应该能在 Overview 菜单中看到一个 nodejs-mongodb-example, #2 的 pod 正在运行。#2 表示这是第二次构建:

Jenkins 远比这复杂,但这应该为您提供了 OpenShift 中 CI/CD 的概述,以及在 OpenShift 中启动 Jenkins 的简易性。

您现在可以停止并删除运行 minishift delete 命令的 minishift 虚拟机,除非您打算做更多练习。

总结

在本章中,我们向您介绍了 CI/CD、Jenkins、OpenShift 管道以及 Jenkins 与 OpenShift 的集成。我们展示了如何在 OpenShift 中创建一个示例 CI/CD 管道,如何编辑管道,以及如何管理管道执行。

在下一章中,我们将简要讨论 HA 的基本知识,然后重点讲解 OpenShift HA。我们将讨论 OpenShift 如何在发生故障时提供冗余,以及如何通过正确设计 OpenShift 集群来防止这种情况发生。章节结束时,我们将讨论如何备份和恢复 OpenShift 集群数据,以防万一出现问题。

问题

  1. 哪种 CI/CD 方法允许你在没有人工干预的情况下自动化应用交付过程?请选择一个:

    1. 持续交付

    2. 持续部署

    3. 持续集成

    4. 持续自动化

  2. OpenShift 使用的三个 CI/CD 流水线组件是哪三个?请选择三个:

    1. OpenShift 领域特定语言

    2. Jenkins 流水线构建策略

    3. Jenkins Java 连接器

    4. Jenkinsfile

    5. Jenkins 构建策略

  3. 你必须手动配置 OpenShift 认证,才能使用 Jenkins 中的 OpenShift 流水线:

    1. 正确

    2. 错误

  4. 在 Jenkinsfile 中,哪个命令允许你实现手动审批过程?请选择一个:

    1. 输入

    2. 保留

    3. 批准

    4. 接受

  5. OpenShift GUI 中的哪个菜单项可以带你进入 OpenShift 流水线页面?请选择一个:

    1. 构建 | 流水线

    2. Jenkins | 流水线

    3. 构建 | Jenkins

    4. 概述

  6. Jenkinsfile 中的 CI/CD 流水线阶段是预定义的,无法更改:

    1. 正确

    2. 错误

深入阅读

本章简要介绍了有关 CI/CD 的多个不同话题。以下链接可以帮助你深入了解你可能感兴趣的主题:

第十八章:OpenShift 高可用性架构概述

在上一章中,我们介绍了 CI/CD、Jenkins、OpenShift 管道以及 Jenkins 与 OpenShift 的集成。我们还演示了如何在 OpenShift 中创建一个示例 CI/CD 管道,如何编辑管道以及如何管理管道执行。

在这一章中,我们将简要介绍高可用性HA)的一般概念,然后专注于 OpenShift HA。我们将讨论 OpenShift 如何在发生故障时提供冗余,并讨论如何通过正确设计你的 OpenShift 集群来避免故障发生。在本章结束时,我们将讨论如何在出现问题时备份和恢复 OpenShift 集群数据。

在本章中,我们将覆盖以下主题:

  • 什么是高可用性?

  • OpenShift 中的高可用性(HA)

  • OpenShift 备份与恢复

什么是高可用性?

在涉及真实客户和真实金钱时,高可用性是一个非常重要的话题。我们每个人都为不同的企业工作,而任何企业最不希望发生的事情就是出现停机。这是一个可能导致员工失业和公司破产的问题。这样的事情已经发生过,并且会继续发生。但如果你正确地规划 HA 设计并以正确的方式实施,你将有更好的机会保住工作,并维护公司良好的声誉。

高可用性通常指的是一种策略或概念,旨在使系统保持长时间运行。这就是可用性这两个术语结合的地方。当人们问,它支持 HA 吗?时,他们通常是在问系统是否具备冗余能力,并且在出现故障时是否能够持续运行。为了提供 HA,系统的每一个组件都需要具备容错能力,所有下层和上层组件及协议都必须具有高可用性。例如,如果你设计并实施了 OpenShift HA 模式,但你的网络存在单点故障,那么你的应用程序将停止工作。因此,正确规划是至关重要的,确保无论故障发生在哪里,你的应用程序都能持续运行。

OpenShift 中的高可用性(HA)

在之前的章节中,我们将应用程序运行在单个节点,或者有时是在两个节点上。有些人可能会说,如果集群中有超过一个 OpenShift 节点,那就算是冗余配置,但这远非事实。

如果我们比较标准的 OpenShift 架构和 OpenShift HA,你会看到它们之间的一些差异:

OpenShift 经典架构

在这里,我们有节点、主控、存储和由 infra 节点组成的路由层。OpenShift HA 架构非常相似,但有一个明显的区别——在路由层,我们有负载均衡器,使得整体解决方案始终可访问。所有其他组件天生是冗余的:

OpenShift 高可用性架构

虚拟 IP

我们可以看到,在 OpenShift 高可用性中,我们使用了所谓的企业级负载均衡器,并配置了两个虚拟 IP(VIPs)。我们需要一个 VIP 用于主节点的流量,另一个 VIP 用于实际的 OpenShift 应用程序流量,这些应用程序运行在 OpenShift 节点内的 Pod 中:

配备外部负载均衡器的 OpenShift

为什么不能只使用 DNS 负载均衡呢?原因是,如果我们使用 DNS 负载均衡,且某个 master 或节点发生故障,仍然会有一部分流量流向故障节点。负载均衡可以实现健康检查,停止将流量路由到故障端点。例如,如果其中一个 infra 节点发生故障,负载均衡器会检测到故障,移除该节点的服务器池,并停止向该节点发送流量。当该节点恢复时,负载均衡器会检测到这一点,并开始将流量分配到该节点。因此,拥有 VIP(虚拟 IP)对 OpenShift 外部高可用性至关重要。

节点名称 物理 IP 地址 虚拟 IP 地址 DNS
Infra1 10.0.0.1/24 10.0.0.11 *.apps.osp.com
Infra2 10.0.0.2/24 10.0.0.11 *.apps.osp.com
Infra3 10.0.0.3/24 10.0.0.11 *.apps.osp.com
Master1 10.0.1.4/24 10.0.0.14 console.osp.com
Master2 10.0.1.5/24 10.0.0.14 console.osp.com
Master3 10.0.1.6/24 10.0.0.14 console.osp.com

使用外部负载均衡器是构建 OpenShift 高可用性的理想选择,因为外部负载均衡器能够自动检测任何 OpenShift infra 或 master 节点的故障,并将负载分配到其他可用的节点上。

假设我们有三个 infra 节点,每个节点的流量处理速度为 50 Mbps。如果 Infra1 节点发生故障,外部负载均衡器会自动检测到故障,并停止向 Infra1 节点提供流量。因此,无论是最终用户还是应用程序都不会出现停机时间,负载均衡器会自动将负载分配到 Infra2Infra3,这样两个节点的流量处理速度都会提升至 75 Mbps。

这种方案的缺点是,我们必须使用外部负载均衡器,确保它们的高可用性,实施额外的健康检查,并进行进一步的配置。此外,如果使用 F5 或 A10 等商业负载均衡设备,它们的成本也非常高。然而,这仍然是确保 OpenShift 集群始终能够从外部访问的最具扩展性的解决方案。

IP 故障转移

确保你的 OpenShift 应用程序始终可以从外部访问的另一种方式是实现 IP 故障转移机制。当没有外部负载均衡器时,但你仍希望 OpenShift 始终能够从外部访问时,这种方法非常有用。OpenShift 的 IP 故障转移设计主要依赖于两种不同的技术:

  • Keepalived: 提供 OpenShift infra 节点之间的 VIP 高可用性。

  • DNS: 管理外部流量负载均衡。

OpenShift DNS 和 keepalived

在我们的示例中,keepalived 分别管理主节点和基础设施节点上的多个 VIP。使用两个 DNS 映射来负载均衡 OpenShift infra 节点和主节点之间的流量:

节点名称 物理 IP 地址 虚拟 IP 地址 DNS
Infra1 10.0.0.1/24 10.0.0.11 *.apps.osp.com
Infra2 10.0.0.2/24 10.0.0.12 *.apps.osp.com
Infra3 10.0.0.3/24 10.0.0.13 *.apps.osp.com
Master1 20.0.0.1/24 20.0.0.11 console.osp.com
Master2 20.0.0.2/24 20.0.0.12 console.osp.com
Master3 20.0.0.3/24 20.0.0.13 console.osp.com

在前面的示例中,我们不需要外部负载均衡器,如果其中一个 OpenShift 节点出现故障,虚拟 IP 会自动转移到另一个节点。根据我们配置的抢占选项,虚拟 IP 可能会在故障的 infra 节点恢复后重新回到该节点。

这个解决方案有一个缺点,这个缺点对于你的特定情况可能是关键,也可能不是。假设我们有三个 infra 节点,每个节点的流量处理速度为 50 Mbps。如果其中一个节点发生故障,那么来自 Infra1 的 VIP 会被转移到 Infra2。对最终用户或应用程序来说,不会有中断,但 Infra2 将以 100 Mbps 的速度处理流量,而 Infra3 仍然以 50 Mbps 的速度处理流量。

当工作负载不太高时,这是可以的,但如果流量过大,可能会通过过载 Infra2 引发问题。因此,你必须排查所有可能的场景;通过解决一个特定的故障场景,你可能会创造一个新的问题。

还有其他方法可以让你的 OpenShift 集群在外部可用。讨论像 DNS LB、GSLB、自定义脚本甚至手动切换等方法,可能会让这本书扩展到一千页。我们专注于那些经过验证并且 OpenShift 支持的方法。

OpenShift 基础设施节点

OpenShift 基础设施节点默认标记为 infra。它们运行两个主要的 OpenShift 组件:

  • OpenShift 内部注册表

  • OpenShift 路由器

OpenShift 基础设施节点最容易安装、操作和排除故障,因为 OpenShift 基础设施节点的结构简单、稳定且可预测。基础设施节点通常以高可用模式安装,是 OpenShift 初始安装的一部分;它们很少被修改。你唯一可能直接处理基础设施节点的情况是当大量流量通过基础设施节点时,而它们无法处理这些流量。

但是,当你遇到这种情况时,你将面临比仅仅扩展 infra 节点数量更严重的问题:

OpenShift infra 节点

最佳实践是安装至少三个基础设施节点,并为注册表和路由器启用 Pod 反亲和性特性。你需要启用反亲和性功能,因为如果你丢失了一个基础设施节点,并且新的路由器 Pod 启动在已经运行 OpenShift 路由器的节点上,你就会遇到问题。如果你只有两个基础设施节点,并且没有启用 Pod 反亲和性功能,在发生故障时,两个路由器和两个注册表可能会运行在同一个基础设施节点上,并监听相同的端口。Pod 反亲和性规则防止一个 Pod 与另一个 Pod 在同一主机上运行,从而避免两个注册表(或两个路由器)在同一个 OpenShift 基础设施节点上运行。

OpenShift 主节点

OpenShift 主节点是控制平面节点,是 OpenShift 解决方案的核心控制点。在早期版本中,你需要使用 Pacemaker 安装 OpenShift 主节点(以提供故障转移),但在最新版本中,这由 keepalived 和外部存储来处理。如果你的 OpenShift 主节点完全故障,你可以直接删除该节点并重新安装。

为了从 OpenShift 集群中移除一个节点,你可以使用 oc delete node 命令,然后从 openshift-ansible Git 项目中运行 scaleup.yml

openshift-ansible 项目可在 github.com/openshift/openshift-ansible 上找到。

scaleup.yml 文件位于 openshift-ansible/playbooks/byo/openshift-node/scaleup.yml,当你下载了 openshift-ansible 项目后可以找到该文件。

你需要调整你的 Ansible 库存文件,并在 [new_masters] 部分添加一个新节点。

如果在某些时候,你失去了所有 OpenShift 主节点,它不会影响最终用户,客户与应用程序之间的流量将继续流动;但是,你将无法对 OpenShift 集群进行任何新的更改。到那时,除了从最后的备份恢复 OpenShift 主节点外,你几乎无能为力。我们将在本章稍后讨论 OpenShift 的备份和恢复。

OpenShift etcd

OpenShift etcd 键值存储是 OpenShift 中最关键和最敏感的组件,因为所有 OpenShift 主节点的持久数据都保存在 etcd 集群中。好消息是,etcd 本身支持主动/主动配置,并且在初始安装时就会安装。你需要正确设计你的 etcd 集群,以避免出现需要重新安装 etcd 来应对更大负载的情况。一般建议将 etcd 集群安装并配置在专用节点上,与 OpenShift 主节点分开,并且节点数量为三、五或七个。

OpenShift 将所有配置数据保存在 etcd 键值存储中,因此定期备份你的 etcd 非常重要——在某些时候,你将需要恢复它。

OpenShift 节点

在高可用性方面,OpenShift 节点最容易处理。由于 OpenShift Pod 本质上是无状态的,因此我们不需要直接处理 OpenShift 节点的高可用性;我们只需确保应用程序 Pod 在不同的 OpenShift 节点上运行,这样如果某个 OpenShift 节点发生故障,最终用户就不会有停机时间,且复制控制器会启动一个新的应用程序 Pod。如果 OpenShift 节点完全故障,您只需删除该节点并重新安装它。

为了从 OpenShift 集群中移除节点,您可以使用 oc delete node 命令,然后从 openshift-ansible Git 项目中运行 scaleup.yml

openshift-ansible 项目可在 github.com/openshift/openshift-ansible 上找到。

scaleup.yml 文件位于 openshift-ansible/playbooks/byo/openshift-node/scaleup.yml,一旦您下载了 openshift-ansible 项目。

您需要调整 Ansible 库存文件,并在 [new_nodes] 部分添加新节点。

无需备份 OpenShift 节点上的任何数据,因为节点上没有状态数据。在大多数情况下,您将希望从 OpenShift 集群中删除 OpenShift 节点,重新安装它,并让它重新投入使用,焕然一新。

OpenShift 持久数据的外部存储

OpenShift 持久数据的外部存储配置和设计超出了本书的范围,但有一些常规建议是确保外部存储具备冗余和可扩展性,这意味着如果某些组件发生故障,整体存储性能不会受到影响,并且 OpenShift 总是可以访问该存储。

您需要单独处理定期的外部存储备份和恢复程序,并且需要有一个经过测试和验证的程序,以防丢失持久存储数据。

OpenShift 备份和恢复

无论您做什么,OpenShift 集群总会有出现问题的时刻,可能会丢失部分(或全部)数据。这就是为什么您需要知道何时以及如何进行 OpenShift 备份,以及如何将 OpenShift 恢复到正常运行状态。OpenShift 安装过程包括以下需要备份的组件:

  • Etcd 键值存储数据

  • 主节点配置数据

  • Ansible 主机安装剧本

  • Pod 数据

  • 注册表数据

  • 项目配置数据

  • 另外安装的软件

根据故障情况,您可能需要重新安装整个 OpenShift 集群,或者单独重新安装某些组件。在大多数情况下,您需要完全重新安装 OpenShift 集群。

Etcd 键值存储备份

etcd 备份过程可以在任何 etcd 节点上执行,步骤如下:

  1. 停止 etcd 服务:systemctl stop etcd

  2. 创建 etcd 备份:etcdctl backup --data-dir /var/lib/etcd --backup-dir ~/etcd.back

  3. 复制 etcd db 文件:cp /var/lib/etcd/member/snap/db ~/etcd/member/snap/db

  4. 启动 etcd 服务:systemctl start etcd

etcd 键值存储恢复过程在 etcd 节点上执行,步骤如下:

  1. 创建单节点集群

  2. 在 etcd 不运行时,从备份中恢复数据到/var/lib/etcd/目录

  3. 从备份中恢复/etc/etcd/etcd.conf文件

  4. 重启 etcd

  5. 向 etcd 集群添加新节点

OpenShift 主节点

OpenShift 主节点备份过程可以在所有主节点上执行,步骤如下:

  1. 备份主证书和密钥cd /etc/origin/master; tar cf /tmp/certs-and-keys-$(hostname).tar *.key *.crt

  2. 备份注册证书cd /etc/docker/certs.d/; tar cf /tmp/docker-registry-certs-$(hostname).tar *

主节点恢复过程可以在所有主节点上执行,步骤如下:

  1. 在每个主节点上将之前保存的数据恢复到 /etc/sysconfig//etc/origin//etc/docker/ 目录。

  2. 重启 OpenShift 所有服务

OpenShift 节点

在 OpenShift 节点上没有特定的需求保存任何数据,因为没有有状态的数据;你可以轻松地一个一个地重新安装所有节点,或者在重新安装 OpenShift 集群时一起重新安装。

持久化存储

在许多情况下,OpenShift pod 的持久化数据可以通过 oc rsync 命令进行保存和恢复,但这不是最可靠和高效的方法。不同存储类型的持久化存储备份程序各不相同,必须单独考虑。

总结

本章简要介绍了 OpenShift 高可用性和高可用性的一般概念。我们讨论了 OpenShift 如何在发生故障时提供冗余,并且如何通过正确设计 OpenShift 集群来防止这种情况发生。我们在本章最后讨论了 OpenShift 中的备份和恢复方法。

在下一章中,我们将讨论 OpenShift 在单数据中心和多数据中心中的 DC 配置。OpenShift 多数据中心是 OpenShift 设计和实现中最难的主题之一,尤其是在可扩展和分布式环境中。下一章将说明如何正确设计 OpenShift,以便在一个或多个数据中心的分布式和冗余配置中正常工作。

问题

  1. 以下哪种高可用性方法通过外部负载均衡器提供对 OpenShift 集群的外部访问?选择一个:

    1. 虚拟 IP

    2. IP 故障转移

    3. GSLB

    4. DNS 负载均衡

  2. 哪两种有效的高可用性方法可以提供外部对 OpenShift 的访问?选择两个:

    1. 虚拟 IP

    2. IP 故障转移

    3. GSLB

    4. DNS 负载均衡

  3. Etcd 是一个键值存储,用于存储 OpenShift 中系统的配置和状态:

    1. 正确

    2. 错误

  4. 什么命令可以用于备份和恢复 OpenShift 中的应用数据?选择一个:

    1. oc rsync

    2. `oc backup`

    3. oc save

    4. oc load

  5. 在 OpenShift 主节点灾难恢复过程中,无需恢复任何数据:

    1. 正确

    2. 错误

进一步阅读

以下链接将帮助您深入了解本章的一些主题:

第十九章:OpenShift 单数据中心和多数据中心的高可用性设计

在前一章节中,我们简要提到了 OpenShift 高可用性和一般的高可用性HA)概念。我们讨论了 OpenShift 如何在发生故障时提供冗余,以及如何通过正确设计 OpenShift 集群来防止这种情况的发生。最后,我们通过备份和恢复方法和流程结束了章节。

在本章中,我们将讨论 OpenShift 在单一和多个数据中心的应用场景。本章还将解释如何在一个或多个数据中心中以分布式和冗余的配置正确设计 OpenShift。

阅读完本章后,你将了解以下主题:

  • OpenShift 单数据中心高可用性设计

  • OpenShift 多数据中心高可用性设计

OpenShift 单数据中心高可用性设计

在前一章节中,我们简要讨论了高可用性和 OpenShift 高可用性的一般概念,但我们没有深入探讨如何在你的数据中心环境中实际设计 OpenShift。

让我们回顾一下 OpenShift 的主要组件,并了解它们如何提供冗余:

  • OpenShift 基础设施节点

  • OpenShift master 节点

  • OpenShift 节点

  • Etcd 键值存储

  • 持久化存储

OpenShift 基础设施节点

OpenShift 基础设施节点是提供外部访问 OpenShift 集群的关键组件。OpenShift 基础设施节点横向扩展,这意味着我们可以根据需要添加任意数量的节点以增加网络吞吐量。如果你记得前一章节的内容,我们需要考虑使用哪种 VIP 方法。我们有两种主要的 VIP 方法:

  • 使用外部负载均衡器的 VIP

  • 使用 keepalived 的 IP 故障转移

这两种方法各有优缺点,但提供更好可扩展性并支持平滑迁移到多数据中心设计的是使用外部负载均衡器的 VIP 方法。该方法允许你动态地在所有基础设施节点之间分配负载,并能在不发生中断的情况下动态添加它们。如果你打算在多个数据中心之间分配负载,虚拟 IP 和外部负载均衡器的方法允许你以最小的停机时间实现这些更改。我们将在本章后面讨论多数据中心设计。

OpenShift master 节点

类似于 OpenShift 基础设施节点,OpenShift master 节点也需要冗余和高可用性。冗余通过横向扩展的 master 节点数量轻松实现,而高可用性则通过我们之前讨论过的 VIP 方法之一来实现。出于同样的原因,使用外部负载均衡器相比 keepalived 和 DNS 方法,是一种更好的、可扩展的解决方案。

OpenShift 节点

OpenShift 节点没有特定的高可用要求,因为它们以冗余方式运行无状态 pod。唯一需要考虑的高可用性和冗余问题是在数据中心发生故障时,是否有足够的 OpenShift 节点来处理额外的工作负载,无论是服务器、机架还是整个机架行。你需要分发工作负载,并确保无论发生何种故障,你都有冗余的组件和 pod 运行。

Etcd 键值存储

OpenShift etcd 是一个高度分布式的键值存储,其中保留了所有关键的 OpenShift 集群相关数据。Etcd 默认以活动/活动配置工作,这意味着它提供了默认的冗余和高可用性。一般建议将您的 etcd 集群安装和配置在专用节点上,与 OpenShift 主节点分开,数量为三个、五个或七个成员。

持久存储

外部存储配置和设计对于 OpenShift 持久数据超出了本书的范围,但一般建议确保您的外部存储以冗余和可扩展的方式可用,这意味着如果一个或多个组件出现故障,不会影响整体存储性能,并且始终可以被 OpenShift 访问。

物理位置考虑

考虑到我们的 OpenShift 集群将在单个数据中心内运行,我们需要额外小心,并确保遵循一些简单的规则:

  • 相同的 OpenShift 组件需要连接到不同的交换机和电源电路,并尽可能放置在不同的机架或服务器房中。

  • 所有硬件应该在使用接口组网的 OpenShift 组件上运行,使用网络交换机上的 LACP 和 MC-LAG 进行连接。

  • 用于持久数据的外部存储集群应遵循相同的规则,并应连接到不同的交换机和电源电路,并尽可能放置在不同的机架或服务器房中。

  • 使用不同的负载均衡器集群,彼此独立运行,不形成单点故障。

  • 如果您希望为 OpenShift 解决方案提供额外的可靠性,您还可以使用服务器硬件 RAID 用于 OpenShift 操作系统、ECC 启用的 RAM、多个网络卡、双插槽主板和 SSD 磁盘。

设计考虑

考虑这样的设计,你需要问自己几个问题:

  • 如果任何关键组件(如 OpenStack、网络或存储)宕机,会发生什么?

  • 如果 OpenShift 集群需要升级,我该怎么办?

  • 如果 OpenShift 持久数据的外部存储不可用,我该怎么办?

  • 如果整个 OpenShift 集群崩溃,恢复需要多长时间?

当然,还有其他问题,你需要问问自己,但如果你能毫不犹豫地回答这些问题,那么你就在正确的轨道上。

OpenShift 多数据中心高可用设计

OpenShift 多数据中心是 OpenShift 设计和在可扩展分布式环境中实施时最困难的话题之一。这主要是因为在这个话题周围没有太多的部署和最佳实践。单一数据中心环境中部署 OpenShift 集群可能相对容易,但当涉及到多数据中心设计时,事情就变得复杂。原因是,现在我们还需要考虑所有 OpenShift 及其相邻组件(如网络和存储)在多个数据中心之间的可扩展性和高可用性。对于涉及多个数据中心的设计,主要有两种高可用性策略:

  • 跨所有数据中心使用一个 OpenShift 集群(例如,三个数据中心对应一个集群)

  • 每个数据中心一个 OpenShift 集群(例如,三个数据中心对应三个集群)

对于所有这些策略,我们需要使用主动/主动场景,因为主动/被动场景是资源和金钱的浪费。尽管仍有许多公司使用主动/被动场景,但它们通常有计划迁移到主动/主动场景。

在所有数据中心之间使用一个 OpenShift 集群

这种设计选项是最自然、最容易操作的,但在所有其他选项中也是最危险的。一个数据中心环境带来一套问题,而如果再增加一个数据中心,问题就会翻倍。如果你有一个不可靠的数据中心互联链接,它将增加故障风险。你们中一些人可能不同意,但如果不正确规划和设计解决方案,通常会发生以下情况:

考虑前述设计,你将面临以下挑战:

  • 我如何在这些数据中心之间平衡来自互联网的流量?

  • 我如何在这些数据中心之间分布每一个 OpenShift 组件,以便在发生故障时,OpenShift 仍然能够承载所有负载?

  • 我的存储解决方案如何在所有三个数据中心之间工作?

  • 我需要在所有数据中心之间扩展相同的网络子网吗?

  • 我如何解决非对称路由问题?

  • 如果发生脑裂现象,OpenShift 集群会怎样?

  • 如果其中一个数据中心的任何关键组件(OpenStack、网络或存储)发生故障,会发生什么?

  • 如果整个 OpenShift 集群宕机,恢复需要多长时间?

  • 如果一个新数据中心上线,我该如何扩展这个解决方案?

  • 如果 OpenShift 集群需要升级,我该怎么办?

  • 如果 OpenShift 持久化数据的外部存储不可用,我该怎么办?

如你所见,在所有数据中心只使用一个 OpenShift 集群,相较于在单个数据中心使用一个集群,会增加更多的问题和疑问。这个解决方案有一个主要优点——拥有一个单独的 OpenShift 集群更容易操作。但你需要问自己,是否在构建一个易于操作的解决方案,还是一个可靠且稳定的解决方案,能够在最困难甚至灾难性的事件中保持运行。

每个数据中心一个 OpenShift 集群

尽管前一种解决方案有许多缺点,但还有另一种不那么流行但非常稳定、可预测且具有良好扩展性的解决方案。该解决方案是每个数据中心都使用一个 OpenShift 集群:

这个解决方案的主要优点是,所有的 OpenShift 集群彼此独立,一个集群出现问题不会影响其他集群。你仍然会遇到挑战,但这些挑战与跨所有数据中心使用单个 OpenShift 集群的挑战有所不同,而且更容易解决。此外,这个解决方案的扩展性比其他方案更好。然而,在实施此解决方案之前,你应该回答以下问题:

  • 我如何在所有这些数据中心之间进行负载均衡?

  • 如果出现脑裂(split brain)情况,OpenShift 集群会发生什么?

  • 我的存储数据如何在三个数据中心之间工作?

  • 我是否需要在所有数据中心之间进行数据库复制?如果是,如何实现?

  • 如果某个数据中心的关键组件(如 OpenStack、网络或存储)出现故障,怎么办?

  • 如果整个 OpenShift 集群出现故障,需要多长时间恢复?

  • 如果有新数据中心上线,我该如何扩展这个解决方案?

  • 关于 OpenShift 集群升级,我该怎么办?

  • 如果 OpenShift 持久数据的外部存储不可用,我该怎么办?

以下是一个对比表,总结了我们刚刚讨论的所有 OpenShift 容器平台OCP)高可用性解决方案的主要差异:

名称 1xOCP-1xDC 1xOCP-3xDC 3xOCP-3xDC
数据中心冗余
集群间冗余
集群间存储隔离
扩展性 有限 有限 无限
解决方案实现 简单 中等 困难
操作 简单 简单 中等
故障排除 简单 困难 中等
集群无缝升级与恢复 中等 困难 简单
应用开发 简单 简单 简单
应用部署 简单 简单 中等
是否需要外部自定义工具

如你所见,每个高可用性解决方案都有其优缺点:

  • 1xOCP-1xDC:这是最容易实现、操作和故障排除的方案,但它容易受到数据中心或 OpenShift 集群故障的影响,且扩展性有限。

  • 1xOCP-3xDC:除了前一个解决方案的所有好处外,它具有更好的冗余性,但如果出现问题,故障排除的工作量会大大增加。此解决方案也难以进行无缝升级和恢复。

  • 3xOCP-3xDC:这是一个更高层次的解决方案架构,实施、运营和故障排除更加困难,但它是最稳定、可扩展的解决方案。这个解决方案需要丰富的经验和专业知识,但可以确保您的应用程序始终保持运行。

为了成功实现最后一个解决方案,其中每个数据中心都有一个 OpenShift 集群,我们需要仔细关注以下几个主题:

  • 网络

  • 存储

  • 应用部署

网络

在构建这样的解决方案时,网络的主要问题是如何正确地负载均衡流量,以及当故障发生时,如何将流量重新路由到其他 OpenShift 集群。我们可以使用的主要技术如下:

  • Anycast IP 地址:为了有效地在所有数据中心之间进行流量负载均衡,我们可以使用 Anycast IP 地址。这不仅有助于负载均衡流量,还能在某个数据中心的应用程序不可用时提供 IP 故障转移。

  • 应用程序健康检查:应用程序健康检查是这个解决方案中的必备内容。它们将帮助您识别故障并将流量重新路由到其他数据中心。

  • 动态路由协议:确保负载均衡器与网络硬件之间有 IGP/BGP 连接。当发生故障时,IGP/BGP 将撤回其 IP Anycast 地址,使流量转到其他数据中心。

  • SSL 卸载:根据实现的不同,您可能需要在负载均衡器上配置 SSL 卸载。这将使流量解密从 OpenShift 集群中分离出来:

存储

存储一直是跨多个平台进行地理分布式应用部署时的一个问题。即使是主要的云服务提供商也尚未解决这个问题。这是应用程序实例必须能够相互独立运行并保持无状态的主要原因。然而,我们可以建议一些架构,指引您走向正确的方向并帮助解决这个问题:

存储地理复制

您可以设置多数据中心存储复制,以确保所有数据中心之间的数据一致性。一个例子是 GlusterFS 地理复制,它支持不同的场景,应该适合您的需求。正如本书早些时候讨论的,GlusterFS 作为 OpenShift 的持久存储是一个完美的匹配。

数据库地理复制

在大多数情况下,您在 OpenShift 中的唯一有状态信息将保存在数据库中。现代数据库支持多站点、多数据中心和多区域复制架构,如 Cassandra、MongoDB、Kafka 和 Hazelcast。

如果数据库损坏,你仍然需要处理备份和恢复过程。

应用部署

一旦完成网络和存储设计,最后一步应该是关于应用部署流程的。由于我们有多个集群,因此必须有一个流程,确保如何在所有 OpenShift 集群和所有数据中心中一致地交付你的应用:

这是外部工具发挥作用的地方。我们可以使用外部 CI/CD 软件来自动化整个 OpenShift 集群中的应用部署过程,或者我们可以构建一个单独的 OpenShift 集群,配合 CI/CD 来开发、构建、测试并将应用发布到生产 OpenShift 集群。

总结

本章讨论了单一和多个数据中心中的 OpenShift 场景。本章还解释了如何在一个或多个数据中心中以分布式和冗余的配置正确设计 OpenShift。

在下一章中,我们将讨论在设计一个或多个数据中心的 OpenShift 集群时的主要网络方面。我们还将讨论常见的错误、解决方案以及从网络角度出发的总体指导。

问题

  1. 哪个 OpenShift 组件内置高可用性并在主动/主动模式下工作? 选择一个:

    1. OpenShift Etcd 键值存储

    2. OpenShift 主节点

    3. OpenShift 基础设施节点

    4. OpenShift 节点

  2. 以下哪种 OpenShift 高可用性解决方案支持无限扩展性? 选择一个:

    1. 1xOSP - 3xDC

    2. 3xOSP - 1xDC

    3. 3xOSP - 3xDC

    4. 1xOSP - 1xDC

  3. Anycast IP 地址确保应用流量在多个数据中心之间负载均衡:

    1. 正确

    2. 错误

  4. 在地理复制的 OpenShift 部署中,确保应用数据一致性的两个选项是什么? 选择两个:

    1. 持久存储复制

    2. 应用数据库复制

    3. OpenShift 集群复制

    4. OpenShift etcd 键值存储复制

进一步阅读

这里列出了一些与本章节相关的主题链接,你可能想深入了解:

第二十章:OpenShift 高可用性网络设计

在上一章中,我们讨论了单数据中心和多个数据中心中的 OpenShift 场景,并解释了如何在一个或多个数据中心中以分布式和冗余的配置正确设计 OpenShift。

在本章中,我们将介绍设计 OpenShift 集群时的主要网络方面,无论是在单一数据中心还是跨多个数据中心的设计。我们还将涵盖常见的错误、解决方案和从网络角度的整体指导。

阅读完本章后,您将了解以下主题:

  • OpenShift 部署的常见网络拓扑

  • 设计 OpenShift 网络时常见的错误

  • OpenShift 部署的一般网络需求和设计指南

OpenShift 部署的常见网络拓扑

尽管每个网络基础设施在某种程度上都是独一无二的,但所有这些网络有很多共同点,可以分为两种类型:

  • 物理或数据中心网络

  • 虚拟或云网络

数据中心网络

大多数物理和数据中心网络具有类似的结构和组件,如下所示:

  • 核心/脊层交换机:用于互联网络的不同部分,包括聚合/叶子交换机,以及网络边缘和/或数据中心互联链接

  • 接入/叶子交换机:将物理服务器连接到产生收入的网络

  • 边缘防火墙:用于过滤外部流量进入网络内部

  • 边界路由器:用于连接互联网或其他外部网络

  • 负载均衡器:用于在应用服务器组之间平衡传入流量

如下图所示:

这些组件中的一些可以合并,如防火墙和边界路由器。某些组件是可选的,如负载均衡器。可能还会有额外的组件,但这些是每个数据中心的基本构建块。

接入层交换机

网络接入层是一个至关重要的网络组件,通常具有几个已配置的功能,例如:

  • 多机箱链路聚合 (MC-LAG):允许服务器链路连接到不同的接入层交换机,并在主动/主动模式下启用绑定

  • 虚拟局域网 (VLAN):一种非常古老但稳定的技术,用于将一个广播域与另一个广播域隔离开

  • L3 网关:一个 IP 网关,允许 VLAN 流量进出

  • 动态路由协议内部网关协议IGP)/边界网关协议BGP)允许服务器的 IP 地址/子网动态交换,并能在不同传输链路之间进行故障切换,且不会导致任何停机

  • 控制平面流量控制 (CoPP):这是一种常见的方式,用于保护设备的管理访问

核心层交换机

网络核心层是整个数据中心组件网络的核心,并且配置了非常有限的功能,例如以下内容:

  • 动态路由协议:IGP/BGP 允许动态交换服务器的 IP 地址/子网,并在不中断的情况下实现从一个传输链路到另一个的故障转移

  • CoPP:这是一种常见的方式,用于保护设备的管理访问

边缘防火墙

边缘防火墙是抵御来自互联网攻击的第一道防线。边缘防火墙还可以逻辑上将不同的网络段彼此隔离,并通常启用以下功能:

  • 有状态检查:跟踪进出流量,动态打开和关闭请求的端口,以便数据中心内部的服务器可以访问

  • 应用防火墙:通过检查应用程序签名动态识别应用程序,并采取适当的措施,如允许或拒绝

  • 分布式拒绝服务(DDoS)保护:识别并阻止恶意流量,同时允许合法流量得到处理并到达数据中心内部的应用程序

  • 入侵防御系统(IPS)和入侵检测系统(IDS):这些系统提供了针对利用数据中心应用程序漏洞的攻击的防护技术

  • 网络地址转换(NAT):NAT 允许数据中心网络内部的主机通过将源 IP 地址更改为公共可用的 IP 地址,从而访问互联网

  • CoPP:这是一种常见的方式,用于保护设备的管理访问

负载均衡器

负载均衡器是可选的网络组件之一,具备附加功能,可以跟踪应用程序的可用性,并动态地将来自互联网的请求在数据中心内部的服务器之间进行负载均衡。负载均衡器配置了一定的功能集,例如以下内容:

  • 目标网络地址转换(DNAT):DNAT 允许来自数据中心网络外部的主机,通过将目标 IP 地址从公共可用的 IP 地址更改为池中适当服务器的私有 IP 地址,从而访问数据中心网络内部的服务器

  • 负载均衡:与 DNAT 一起工作,动态跟踪应用程序的可用性,并动态从服务器组中移除服务器

  • SSL 卸载:通过在负载均衡器上终止 SSL 流量,处理进出不安全网络的 Web 流量的加密/解密

  • CoPP:这是一种常见的方式,用于保护设备的管理访问

边界路由器

边界路由器是数据中心网络与互联网之间的通信节点,并且配置了非常有限的功能,例如以下内容:

  • 动态路由协议:BGP 保持整个互联网路由表,以计算到最终目的地的最佳路径,并向互联网通告数据中心的公共 IP 地址池。

  • CoPP:这是一种常见的保护设备管理访问的方式,确保进出设备的流量安全。

云网络

类似于物理和数据中心网络,云网络有其自己的结构和组件,如下所示:

  • 软件定义网络(SDN):SDN 通过使用流行的 SDN 传输和封装协议,以按需实现网络,从而取代了传统的网络架构。

  • 安全组:灵活且可编程的防火墙过滤器,允许在大规模环境下进行流量控制,避免单点故障

  • NAT 网关:为了高效地路由进出云网络的流量,并在必要时提供 NAT 服务。

  • 负载均衡器:在一组应用服务器之间对流入流量进行负载均衡

尽管不同的云服务提供商使用不同的协议和实现方式来处理某些组件,但主要功能保持一致,从一个云服务提供商到另一个云服务提供商的工作原理类似:

SDN

SDN 通过使用现代流行的通信和传输协议,使应用程序能够抽象化并与底层网络实现完全独立,以下是一些例子:

  • OpenFlow:这是一种协议,允许通过网络访问网络交换机或路由器的转发平面。OpenFlow 允许网络控制器计算数据包在网络中的路径。许多产品和公司,包括 OpenStack,正在使用 OpenFlow 来构建其 SDN 网络。OpenFlow 得到了领先网络制造商的支持,包括思科系统、瞻博网络、阿尔卡特朗讯、戴尔、Arista 等。

  • 虚拟可扩展局域网(VXLAN):这是一种封装协议,允许在现有路由网络上运行覆盖网络。VXLAN 是当前最受欢迎的协议之一,用于将不同的 SDN 网络段互联成一个单一解决方案。

安全组

安全组是控制虚拟机VMs)进出流量的主要安全方法,虚拟机内运行着应用程序,它们的主要功能是有状态检查。它会跟踪进出流量,动态地为虚拟机内的应用程序打开和关闭所需的端口。

负载均衡器

如果数据中心网络通常使用物理负载均衡器,那么云网络则倾向于使用软件负载均衡器,提供相同的功能,包括 DNAT、负载均衡,甚至 SSL 卸载。

所以,云网络的强大功能不容小觑。

网络地址转换(NAT)网关

NAT 网关是云提供商内部应用与互联网之间的通信点,并且配置的功能非常有限,如 NAT。它允许数据中心网络内的主机通过更改源 IP 地址为公共 IP 地址来访问互联网。

在为 OpenShift 设计网络时常见的错误

虽然网络设计通常很简单,但有很多方法可能会导致整个网络成为单点故障。最常见的错误如下:

  • 在接入层和核心网络层,人们经常使用静态的 链路聚合LAG)方法,而不是使用动态链路控制,如 链路聚合控制协议LACP)802.3ad。MTU 值在整个网络中未正确设置,且 CoPP 过滤器阻止了网络设备之间的协议通信,导致故障时故障切换无法正常工作。

  • 在使用负载均衡器时,通常情况下,主动/被动部署无法正确地从一个节点切换到另一个节点。这通常是由于负载均衡器集群节点之间的配置不一致所导致的。

  • 防火墙和安全组往往是瓶颈,而一个由两个或更多节点组成的防火墙集群,在使用单一控制平面的情况下,可能成为单点故障。我们曾见过使用无状态过滤器代替有状态过滤器,这会导致双向流量被阻塞。最后,当启用故障转移抢占时,会导致防火墙集群无限制地切换。

  • 在边界路由器上,拥有多个完整的互联网路由表需要很长时间才能让 BGP 重新收敛,这常常会导致流量被黑洞化。唯一的默认网关导致 BGP 无法选择最优的路由,等成本多路径ECMP)会引发非对称路由和数据包丢失。

OpenShift 部署的一般网络要求和设计指南

网络是 OpenShift 的一个关键组件,因为 OpenShift 解决方案的每一部分都依赖于网络的可用性、性能、可扩展性和稳定性。控制平面和数据平面流量使用网络的不同部分进行通信。为了使 OpenShift 解决方案大部分时间可用,并避免计划外的网络中断,需要考虑以下几点:

  • 所有运行 OpenShift 的物理主机都需要通过 MC-LAG 或类似技术,拥有冗余的物理连接来访问接入层交换机。

  • 实现冗余的负载均衡器集,分别处理数据平面和控制平面

  • 实现冗余的防火墙集,分别处理数据平面和控制平面

  • 在适用情况下,为了安全原因,使用专用的管理网络

  • 如果没有使用外部负载均衡器,且基础设施节点上有 keepalived 实现的 VIP,且上游网络没有单点故障

  • 如果有多个数据中心,应使用 AnyCast IP

  • 网络收敛通过其他机制来处理,例如双向转发检测BFD)、额外的调整设置(如在生成树协议STP)中的 PortFast)以及调整路由协议中的协议定时器

  • MTU 参数在 OpenShift 节点上正确设置,并与网络对齐,以确保不会发生数据包分段

  • 如果被防火墙或任何类型的访问控制列表ACL)隔离,必须打开适当的端口,以便 OpenShift 组件之间能够进行通信

考虑到以上所有因素将帮助你避免在设计 OpenShift 集群时遇到 99% 的问题。

总结

在本章中,我们介绍了在设计 OpenShift 集群时涉及的主要网络方面,包括在一个或多个数据中心的设计。我们还讨论了常见的错误、解决方案和从网络角度出发的总体指导。

在下一章,我们将简要概述我们在本书中已覆盖和未覆盖的 OpenShift 3.9 新特性。我们将简要讨论预计在今年晚些时候(2018 年)发布的 OpenShift 后续版本中的内容。

问题

  1. 在标准数据中心部署中,哪个网络组件是可选的?选择一个:
    1. 防火墙

    2. 负载均衡器

    3. 核心交换机

    4. 边界路由器

  1. 防火墙中常见的两个错误是什么?选择两个:
    1. 故障切换抢占

    2. 无状态过滤器

    3. 有状态过滤器

    4. 拥有一个或多个完整的互联网表

  1. 物理数据中心和云网络具有完全相同的组件:
    1. 正确

    2. 错误

  1. 可以用哪个命令在 OpenShift 中备份和恢复应用数据?选择一个:
    1. oc rsync

    2. oc backup

    3. oc save

    4. oc load

  1. 在云网络中,哪个网络组件负责让应用程序访问互联网?选择一个:
    1. 负载均衡器

    2. NAT 网关

    3. 边界路由器

    4. 边缘防火墙

进一步阅读

以下是与本章相关的主题及其链接,您可能想深入了解:

第二十一章:OpenShift 3.9 中的新特性是什么?

在上一章中,我们讨论了在一个或多个数据中心设计 OpenShift 集群时的主要网络方面。我们还讨论了常见的错误、解决方案和从网络角度的总体指导。

在本章中,我们将简要概述本书中已涵盖和未涵盖的 OpenShift 3.9 新特性。我们还将简要讨论 2018 年晚些时候对后续 OpenShift 版本的预期。

在本章中,我们将涵盖以下主题:

  • OpenShift 3.9 版本中的重大变化

  • 预期的 OpenShift 版本更新内容

OpenShift 3.9 版本中的重大变化

在撰写本书时,OpenShift 容器平台OCP)3.9 刚刚发布。OCP 3.9 与其他版本有显著不同,早期版本的 OpenShift 与 Kubernetes 的版本不同步,这意味着 OpenShift 3.5 使用的是 Kubernetes 1.6,而 OpenShift 3.7 使用的是 Kubernetes 1.8。这也是红帽跳过 3.8 版本直接发布 3.9 版本并与 Kubernetes 1.9 同步的主要原因。这并不意味着 OpenShift 3.8 被重命名为 3.9,事实上,正好相反。OpenShift 3.9 包括了 3.8 和 3.9 的所有新特性,其中包括以下内容:

  • CRI-O:一个轻量级的容器运行时接口CRI),原生支持 Kubernetes。它旨在为 Kubernetes 带来更多功能,包括以下内容:

    • 增强的安全性

    • 相较于传统的容器运行时接口,提供更好的可扩展性和性能

    • 与任何符合开放容器倡议OCI)的兼容性,包括 Docker

  • 红帽 CloudForms 管理引擎 4.6 容器管理:这是一种易于使用和配置的集成方式,通过 Ansible playbook 安装与红帽 CloudForms 集成,其中包括以下新特性、更新和旧版本的增强:

    • OpenShift 模板配置

    • 警报管理

    • 用户体验(UX)的增强

    • 计费回收(chargeback)的增强

    • 提供商更新

  • 端到端在线扩展和容器化 GlusterFS PV 调整大小:此功能允许 OpenShift 用户在线扩展持久卷PV)声明。最初,这只能通过 CLI 完成。

  • 自动化容器原生存储部署与 OpenShift 容器平台进阶安装:这允许与由最流行的云提供商(包括亚马逊 AWS、谷歌 GCE 和微软 Azure)提供的文件和块存储设备进行集成。

  • 设备插件:一种特殊的插件,允许你在 OCP 中使用特殊设备,无需编写任何自定义代码。设备插件是一个便捷的解决方案,用于跨 OpenShift 集群使用硬件资源。

  • 支持我们自己的 HAProxy RPM 供路由器使用: 在重负载下升级 HAProxy 路由器而不导致任何中断的能力。OpenShift 3.9 允许您进行无缝更新和升级。

  • 快速安装: 该方法现已被弃用。您将能够使用此方法安装新的集群,但无法执行任何更新或升级。此方法将在未来版本中移除。应使用高级安装方法。

  • 自动化 3.7 到 3.9 的控制平面升级: 通过 Ansible 使用高级 OpenShift 安装程序,可以从 3.7 升级到 3.8,再到 3.9。这包括以下内容:

    • 控制平面上的 API、控制器和节点,支持无缝的一跳升级

    • 路由器、注册表、服务目录和代理直接升级

    • 节点、Docker、Open vSwitchOVS)直接升级

    • 日志和度量直接升级

  • Prometheus: 这是一个新的产品套件,未来将替代 OpenShift 中的标准日志记录和度量功能。

  • CLI 插件: 一个新功能,允许您通过新功能扩展 oc 命令的能力。

期待以下 OpenShift 版本的内容

尽管很难预测未来 OpenShift 版本的具体内容,但我们可以从技术预览功能中推测出一些信息,您可以在 OCP 3.9 版本说明中找到这些功能,也可以在最新的红帽活动中找到,例如 2018 年 5 月 7 日至 5 月 11 日举行的红帽峰会。好消息是,OCP 的软件发布周期为 3 个月,这意味着当您阅读本书时,可能会有一个新的 OpenShift 版本发布。下表显示了 OCP 版本之间的差异,主要体现在 技术预览TP)和 一般可用性GA)方面:

功能 OCP 3.7 OCP 3.9 OCP 3.10, OCP 3.11
Prometheus 集群监控 技术预览 技术预览 一般可用性*
CRI-O 技术预览 一般可用性*
CLI 插件 技术预览 技术预览 一般可用性*
设备插件 技术预览 一般可用性
CPU 管理器 技术预览 技术预览*
大页内存 技术预览 一般可用性*
PV 扩容 技术预览 技术预览*
本地存储 PVs 技术预览 技术预览 一般可用性*

*数据基于我们的预测提供。

除了以上所列的所有数据外,我们还将看到其他 OpenShift 增强功能,例如以下内容:

  • 与 Admin Web Console 和操作员 生命周期管理器LCM)功能结合使用的 Prometheus 集群监控,将使 懒惰 的 OpenShift 管理员的工作变得更加轻松,提供一个易于使用和配置的网页控制台,功能足够完成大多数从网页界面执行的任务。这只是这些项目将一起添加的功能之一,但未来还会有更多功能。

  • Istio 和 OpenShift 服务网格产品将通过使用按需服务连接,将 OpenShift 混合网格提升到新的水平。因此,将不再需要手动配置这些服务。

  • 我们将在未来版本中看到的一个最棒的特性是无缝且容易执行的集群升级和集群扩展。这意味着,目前我们只能在不重启或重新启动主节点的情况下,从集群中移除 OpenShift 节点,但添加新节点的过程并不那么快速,也不总是容易的。所以,在即将发布的版本中,我们将看到使用 黄金 镜像的功能,并能够动态地将其添加到集群中。

  • 与 OpenStack 基础设施即服务IaaS)平台的集成将更接近现实。我们将看到 OpenShift 在 OpenStack 上的部署如何利用此类部署,通过利用底层 OpenStack 平台服务,如 负载均衡服务LBaaS)、防火墙服务FWaaS)、DNS 服务DNSaaS)等,同时进一步与经过测试和认证的 Neutron 插件集成,以及通过 Ironic 实现裸金属服务器的配置和管理。

  • 另一个我们在未来将看到的巨大增强功能是 OpenShift 与 CoreOS 产品(如 CoreOS、Quay 和 Tectonic)的集成。在思考如何与 OCP 配合使用时,这些产品都是非常独特且有用的:

    • CoreOS: 这是一个专为大规模运行容器而设计和构建的操作系统。CoreOS 占用的资源非常小,轻量级且功能最小,足以运行容器和类似 Kubernetes 这样的容器编排平台。

    • Quay: 这是一个高度分布式且可靠的容器注册中心,适用于容器和 Kubernetes,它允许你运行多个 Quay 实例来确保开箱即用的冗余。Quay 还具备地理复制功能,能够在不同的数据中心之间无缝同步容器镜像。Quay 的另一个重要特点是自动化容器扫描,以检测安全漏洞。这是容器行业中需求量最大的安全功能之一。Quay 还具备持续集成功能,能够在开发者提交代码时自动构建并推送镜像。

    • Tectonic: 这是 CoreOS 提供的一个容器编排平台,它的工作方式类似于 OCP,但与 OpenShift 相比,功能要少得多。Tectonic 的独特之处在于,它的控制平面服务运行在 Tectonic 管理的 Kubernetes 服务内部。这是一个经典的“先有鸡还是先有蛋”的问题,CoreOS 成功地解决了这个问题。

我们将看到这些功能中的大多数,或者是用户请求最多的功能,最终会迁移到或与 OCP 合并。由于最近收购了 CoreOS,2018 年整年对于 OCP 开发团队来说可能会有些忙碌。这些团队将共同决定哪些功能继续推进,哪些功能永远放弃。但无论如何,这一切都将使 OpenShift 受益,并使其成为史上最成功的容器管理平台。

总结

在本章中,我们简要概述了本书中涉及和未涉及的 OpenShift 3.9 新特性。我们还简要讨论了 2018 年稍后的 OpenShift 版本中可以期待的内容。

这是本书的最后一章,因此感谢您阅读本书,并希望您获得了有关 OpenShift 背后概念的有用知识,并获得了一些安装 OpenShift 和管理其资源的实践技能。

问题

  1. 以下哪一项不是设计用来通过 CRI-O 向 Kubernetes 添加额外功能的?请选择一项:
    1. Docker 替代

    2. 增强的安全性

    3. 更好的可扩展性和性能

    4. 兼容任何符合 OCI 规范的容器

  1. 最近收购的 CoreOS 带来了哪些主要产品,并且未来需要与 OpenShift 集成?请选择两个:
    1. Quay

    2. Mesos

    3. Tectonik

    4. Kubernetes

  1. CoreOS 是一个设计和构建用于大规模运行容器的操作系统:
    1. 正确

    2. 错误

  1. 以下哪一项 OpenShift 功能可以扩展 OpenShift oc 命令的功能?请选择一项:
    1. CLI 插件

    2. OCI 插件

    3. 设备插件

    4. CLI 增强

进一步阅读

下面是与本章相关的主题列表,您可能希望深入研究:

第二十二章:评估

第一章

  1. 答案是 1(Docker 容器,Docker 镜像,Docker 注册表)

  2. 答案是 2 和 3(私有注册表和公共注册表)

  3. 答案是 正确。

  4. 答案是 1(Cgroups)

  5. 答案是 1 和 4(docker build -t new_httpd_image .docker build -t new_httpd_image ./

  6. 答案是 错误

第二章

  1. 答案是 1 和 4(节点和主机)

  2. 答案是 1 和 3(Docker 和 Rkt)

  3. 答案是 正确

  4. 答案是 2 和 3(kubelet 和 kube-proxy)

  5. 答案是 1 和 5(JSON 和 YAML)

  6. 答案是 错误

第三章

  1. 答案是 2(CRI)

  2. 答案是 1 和 3(Docker 和 Rkt)

  3. 答案是 正确

  4. 答案是 1(kubectl describe pods/httpd)

  5. 答案是 2 和 3(CRI-O 直接与容器运行时通信,且 CRI-O 遵循 OCI 标准)

  6. 答案是 正确

第四章

  1. 答案是 PaaS

  2. 答案是 1 和 3(OpenShift Origin 和 OpenShift Enterprise)

  3. 答案是 错误

  4. 答案是 4(持久存储)

  5. 答案是 1 和 2(路由作为入口流量控制和 OpenShift 内部注册表)

  6. 答案是 正确

第五章

  1. 答案是 1(Docker)

  2. 答案是 1(8443)

  3. 答案是 错误

  4. 答案是 2(oc login -u system:admin)

  5. 答案是 1 和 3(oc login -u system:admin 和 oc cluster down)

  6. 答案是 正确

第六章

  1. 答案是 1 和 4(主节点和 etcd)

  2. 答案是 3(infra)

  3. 答案是 2(playbooks/byo/config.yml)

第七章

  1. 答案是 2(用于生产的 MariaDB 数据库)

  2. 答案是 1 和 4(NFS 和 GlusterFS)

  3. 答案是 3(任何项目)

  4. 答案是 2(1950 M)

  5. 答案是 2 和 3(Service 和 Endpoint)

第八章

  1. 答案是 1 和 4(节点和主机)

  2. 答案是 2 和 5(管理员用户和服务用户)

  3. 答案是 正确

  4. 答案是 2(Route)

  5. 答案是 1 和 4(oc 获取 pod 和 oc 获取路由)

  6. 答案是 错误

第九章

  1. 答案是 1 和 4(为了保护应用程序免于在镜像指向的镜像发生更改时意外中断,和 为了在镜像更改时实现自动构建和部署。)

  2. 答案是 1 和 3(oc create configmap my-configmap --from-file=nginx.confoc create -f configmap_definition.yaml

  3. 答案是 3 和 4(oc create resourcequota my-quota --hard=cpu=4,services=5oc create quota another-quota --hard=pods=8,secrets=4

  4. 答案是 2 和 4(ConfigMap 和 Service)

  5. 答案是 3(${VARIABLE})

  6. 答案是 3(请求)

  7. 答案是 3(v2alpha1)

第十章

  1. 答案是 2 和 4(生成和声明)

  2. 答案是 4(默认)

  3. 答案是 2 和 4(admin 和 edit)

  4. 答案是 3(ProjectRequestLimit)

  5. 答案是 1 和 4(anyuid 和 privileged)

  6. 答案是 2(数据)

第十一章

  1. 答案是 4(vxlan_sys_4789

  2. 答案是 3(使用 ovs-multitenant 插件,根据需要加入和隔离项目)

  3. 答案是 3(来自项目的外部流量的静态 IP)

  4. 答案是 3:

- type: Allow
 to:
 dnsName: rubygems.org
- type: Allow
 to:
 dnsName: launchpad.net
- type: Deny
 to:
 cidrSelector: 0.0.0.0/0 
  1. 答案是 5(web.dev.svc.cluster.local

第十二章

  1. 答案是 2(Route

  2. 答案是 1、2、3(Pod、Service、Route)

  3. 答案是 3(oc expose svc httpd --hostname myservice.example.com

  4. 答案是 1 和 4(oc 获取所有,oc 获取路由)

第十三章

  1. 答案是 2(openshift)

  2. 答案是 1、2、3:

    1. oc get template mytemplate -n openshift -o yaml

    2. oc process --parameters -f mytemplate.json

    3. oc describe template mytemplate -n openshift

  3. 答案是 5(以上所有)

第十四章

  1. 答案是 1, 3(oc edit bc/redisoc patch bc/redis --patch ...

  2. 答案是 3 oc start-build

  3. 答案是 2 Dockerfile

第十五章

  1. 答案是 3, 4(复制控制器、构建配置)

  2. 答案是 3(oc start-build

  3. 答案是 1, 2, 3:

    1. oc status -v

    2. oc status

    3. oc logs build/phpdemo-2

第十六章

  1. 答案是 1(buildconfig)

  2. 答案是 6(以上所有)

第十七章

  1. 答案是 2(持续部署)

  2. 答案是 1, 2, 4(OpenShift 域特定语言、Jenkins 管道构建策略、Jenkinsfile)

  3. 答案是 错

  4. 答案是 1(输入)

  5. 答案是 1(构建 | 管道)

  6. 答案是 错

第十八章

  1. 答案是 1(虚拟 IP)

  2. 答案是 1, 2(虚拟 IP、IP 故障转移)

  3. 答案是 对

  4. 答案是 1(oc rsync)

  5. 答案是 错

第十九章

  1. 答案是 1(OpenShift Etcd 键值存储

  2. 答案是 3(3xOSP - 3xDC)

  3. 答案是 对

  4. 答案是 1, 2(持久存储复制、应用数据库复制)

第二十章

  1. 答案是 2(负载均衡器)

  2. 答案是 1, 2(故障转移抢占、无状态过滤器)

  3. 答案是 错

  4. 答案是 oc rsync

  5. 答案是 2(NAT 网关)

第二十一章

  1. 答案是 1(Docker 替代)

  2. 答案是 1, 3(Quay, Tectonik)

  3. 答案是 对

  4. 答案是 1(CLI 插件)

posted @ 2025-06-29 10:39  绝不原创的飞龙  阅读(13)  评论(0)    收藏  举报