Kubernetes-无服务器架构-全-

Kubernetes 无服务器架构(全)

原文:zh.annas-archive.org/md5/36BD40FEB49D3928DE19F4A0B653CB1B

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

关于

本节简要介绍了作者、本书的覆盖范围、开始所需的技术技能,以及完成所有包含的活动和练习所需的硬件和软件要求。

关于本书

Kubernetes 已经确立了自己作为容器管理、编排和部署的标准平台。通过学习 Kubernetes,您将能够通过实施函数即服务(FaaS)模型来设计自己的无服务器架构。

在对无服务器架构和各种 Kubernetes 概念进行加速、实践性概述之后,您将涵盖真实开发人员面临的各种真实开发挑战,并探索克服这些挑战的各种技术。您将学习如何创建可投入生产的 Kubernetes 集群,并在其上运行无服务器应用程序。您将了解 Kubernetes 平台和无服务器框架(如 Kubeless、Apache OpenWhisk 和 OpenFaaS)如何提供您在 Kubernetes 上开发无服务器应用程序所需的工具。您还将学习如何为即将到来的项目选择适当的框架。

通过本书,您将具备技能和信心,能够利用 Kubernetes 的强大和灵活性设计自己的无服务器应用程序。

关于作者

Onur Yılmaz是一家跨国企业软件公司的高级软件工程师。他是一名持有认证的 Kubernetes 管理员(CKA),并致力于 Kubernetes 和云管理系统。他是 Docker、Kubernetes 和云原生应用等尖端技术的热情支持者。他在工程领域拥有一个硕士学位和两个学士学位。

Sathsara Sarathchandra是一名 DevOps 工程师,具有在云端和本地构建和管理基于 Kubernetes 的生产部署的经验。他拥有 8 年以上的经验,曾在从小型初创公司到企业的多家公司工作。他是一名持有认证的 Kubernetes 管理员(CKA)和认证的 Kubernetes 应用开发者(CKAD)。他拥有工商管理硕士学位和计算机科学学士学位。

学习目标

通过本书,您将能够:

  • 使用 Minikube 在本地部署 Kubernetes 集群

  • 使用 AWS Lambda 和 Google Cloud Functions

  • 在云中创建、构建和部署由无服务器函数生成的网页。

  • 创建在虚拟 kubelet 硬件抽象上运行的 Kubernetes 集群

  • 创建、测试、排除 OpenFass 函数

  • 使用 Apache OpenWhisk 操作创建一个示例 Slackbot

受众

本书适用于具有关于 Kubernetes 的基本或中级知识并希望学习如何创建在 Kubernetes 上运行的无服务器应用程序的软件开发人员和 DevOps 工程师。那些希望设计和创建在云上或本地 Kubernetes 集群上运行的无服务器应用程序的人也会发现本书有用。

方法

本书提供了与无服务器开发人员在实际工作中与 Kubernetes 集群一起工作的相关项目示例。您将构建示例应用程序并解决编程挑战,这将使您能够应对大型、复杂的工程问题。每个组件都设计为吸引和激发您,以便您可以在实际环境中以最大的影响力保留和应用所学到的知识。通过完成本书,您将能够处理真实世界的无服务器 Kubernetes 应用程序开发。

硬件要求

为了获得最佳的学生体验,我们建议以下硬件配置:

  • 处理器:Intel Core i5 或同等级别

  • 内存:8 GB RAM(建议 16 GB)

  • 硬盘:10 GB 可用空间

  • 互联网连接

软件要求

我们还建议您提前安装以下软件:

  • Sublime Text(最新版本)、Atom IDE(最新版本)或其他类似的文本编辑应用程序

  • Git

额外要求

  • Azure 账户

  • Google 云账户

  • AWS 账户

  • Docker Hub 账户

  • Slack 账户

约定

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:

“将hello-from-lambda作为函数名称,Python 3.7作为运行时。”

新术语和重要单词以粗体显示。例如,屏幕上看到的单词,比如菜单或对话框中的单词,会以这样的方式出现在文本中:“打开 AWS 管理控制台,在查找服务搜索框中输入Lambda,然后单击Lambda - Run Code without Thinking about Servers。”

代码块设置如下:

import json
def lambda_handler(event, context):
    return {
        'statusCode': '200',
        'body': json.dumps({"message": "hello", "platform": "lambda"}),
        'headers': {
            'Content-Type': 'application/json',
        }
    }

安装和设置

在我们可以对数据进行出色处理之前,我们需要准备好最高效的环境。在这个简短的部分中,我们将看到如何做到这一点。以下是需要满足的先决条件:

其他资源

本书的代码包也托管在 GitHub 上,网址为github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes。我们还有其他代码包来自我们丰富的图书和视频目录,可在github.com/PacktPublishing/找到。快去看看吧!

第一章: 无服务器介绍

学习目标

本章结束时,您将能够:

  • 识别无服务器架构的好处

  • 在无服务器平台上创建和调用简单函数

  • 使用 Kubernetes 创建云原生无服务器函数并将其打包为容器

  • 创建 Twitter Bot 后端应用程序并将其打包到 Docker 容器中

在本章中,我们将解释无服务器架构,然后创建我们的第一个无服务器函数并将其打包为容器。

无服务器介绍

当前,云技术正处于不断变革的状态,以创建可扩展、可靠和强大的环境。为了创建这样的环境,云技术的每一项改进都旨在提高最终用户体验和开发人员体验。最终用户要求快速、强大的应用程序,可以从世界各地访问。与此同时,开发人员要求更好的开发环境来设计、部署和维护他们的应用程序。在过去的十年中,云技术的旅程始于云计算,其中服务器在云数据中心中进行配置,并在服务器上部署应用程序。向云数据中心的过渡降低了成本,并消除了对数据中心的责任。然而,随着数十亿人访问互联网并要求更多服务,可扩展性已成为必需。为了扩展应用程序,开发人员创建了可以独立扩展的较小的微服务。微服务被打包成容器,作为软件架构的构建块,以改善开发人员和最终用户的体验。微服务通过提供更好的可维护性来增强开发人员体验,同时为最终用户提供高可扩展性。然而,微服务的灵活性和可扩展性无法满足巨大的用户需求。例如,今天,每天进行数百万笔银行交易,并向后端系统发出数百万笔业务请求。

最后,无服务器开始引起人们的关注,用于创建“未来可靠”和“即时可扩展”的应用程序。无服务器设计专注于创建比微服务更小的服务,并且它们被设计为更持久地存在于未来。这些“纳米服务”或函数帮助开发人员创建更灵活、更易于维护的应用程序。另一方面,无服务器设计是即时可扩展的,这意味着如果您采用无服务器设计,您的服务会随着用户请求自然地扩展或缩小。无服务器的这些特性使其成为行业中最新的大趋势,它现在正在塑造云技术的格局。在本节中,将介绍无服务器技术的概述,重点关注无服务器的演变、起源和用例。

在深入研究无服务器设计之前,让我们了解一下云技术的演变。在过去,部署应用程序的预期流程始于硬件的采购和部署,即服务器。随后,在服务器上安装操作系统,然后部署应用程序包。最后,执行应用程序包中的实际代码以实现业务需求。这四个步骤如图 1.1所示:

图 1.1:传统软件开发

图 1.1:传统软件开发

组织开始将其数据中心运营外包给云提供商,以改善服务器的可伸缩性和利用率。例如,如果您正在开发一个在线购物应用程序,您首先需要购买一些服务器,等待它们的安装,并每天操作它们并处理由电力、网络和错误配置引起的潜在问题。很难预测服务器的使用水平,也不可行大规模投资于服务器来运行应用程序。因此,初创公司和大型企业开始将数据中心运营外包给云提供商。这清除了与硬件部署的第一步相关的问题,如图 1.2所示:

图 1.2:云计算软件开发

图 1.2:云计算软件开发

随着云计算中虚拟化的开始,操作系统变得虚拟化,以便多个虚拟机(VMs)可以在同一台裸机上运行。这种转变消除了第二步,服务提供商按照图 1.3所示提供 VMs。在同一硬件上运行多个 VMs,服务器运行成本降低,操作灵活性增加。换句话说,软件开发人员的底层问题得到解决,因为硬件和操作系统现在都是别人的问题:

图 1.3:虚拟化软件开发

图 1.3:虚拟化软件开发

VMs 使得在同一硬件上运行多个实例成为可能。然而,使用 VMs 需要为每个应用程序安装完整的操作系统。即使对于基本的前端应用程序,您也需要安装操作系统,这导致操作系统管理的开销,从而导致可扩展性受限。应用程序开发人员和现代应用程序的高级使用需要比创建和管理 VMs 更快速、更简单、更具隔离性的解决方案。容器化技术通过在同一操作系统上运行多个“容器化”应用程序来解决这个问题。通过这种抽象级别,与操作系统相关的问题也被解决,容器被作为应用程序包交付,如图 1.4所示。容器化技术实现了微服务架构,其中软件被设计为小型且可扩展的服务,彼此之间进行交互。

这种架构方法使得能够运行现代应用程序,如 Google Drive 中的协作电子表格,YouTube 上的体育赛事直播,Skype 上的视频会议等等:

图 1.4:容器化软件开发

图 1.4:容器化软件开发

下一个架构现象,无服务器,消除了管理容器的负担,专注于运行实际的代码本身。无服务器架构的基本特征是临时可伸缩性。无服务器架构中的应用程序是临时可伸缩的,这意味着它们在需要时会自动扩展或缩减。它们也可以缩减到零,这意味着没有硬件、网络或运营成本。在无服务器应用程序中,所有低级别的问题都被外包和管理,重点是最后一步——运行代码——如图 1.5 所示。无服务器设计的重点是传统软件开发的最后一步。在接下来的部分中,我们将专注于无服务器的起源和宣言,以便更深入地介绍:

图 1.5:使用无服务器进行软件开发

图 1.5:使用无服务器进行软件开发

无服务器起源和宣言

无服务器是一个令人困惑的术语,因为在会议、书籍和博客中有各种定义。尽管从理论上讲它意味着没有任何服务器,但实际上它意味着将服务器的责任留给第三方组织。换句话说,它并不意味着摆脱服务器,而是服务器操作。当你运行无服务器时,其他人会处理服务器操作的采购、运输和安装。这降低了你的成本,因为你不需要运营服务器甚至数据中心;此外,它让你专注于实现核心业务功能的应用逻辑。

无服务器的最初用途出现在 2010 年左右与持续集成相关的文章中。当它首次讨论时,无服务器被认为是用于在云服务提供商的服务器上构建和打包应用程序。随着 2014 年亚马逊网络服务AWS)推出Lambda,其受欢迎程度急剧增加。此外,2015 年,AWS 推出了API Gateway用于管理和触发 Lambda 函数,作为多个函数的单一入口点。因此,无服务器函数在 2014 年开始受到关注,并且在 2015 年可以使用AWS API Gateway创建无服务器架构应用程序。

然而,对无服务器的最明确和完整的解释是在 2016 年在 AWS 开发者大会上提出的,称为无服务器计算宣言。它包括八条严格的规则,定义了无服务器架构背后的核心思想:

注意

尽管在 AWS Summit 2016 年会议的各种讨论中讨论过,但无服务器计算宣言没有官方网站或文档。宣言详细内容的完整列表可以在 Tim Wagner 博士的演示中看到:www.slideshare.net/AmazonWebServices/getting-started-with-aws-lambda-and-the-serverless-cloud

  • 作为构建块的功能:在无服务器架构中,开发、部署和扩展的构建块应该是函数。每个函数应该独立部署和扩展,与其他函数无关。

  • 没有服务器、虚拟机或容器:服务提供商应该为无服务器函数操作所有计算抽象,包括服务器、虚拟机和容器。无服务器架构的用户不需要了解底层基础设施的任何进一步信息。

  • 没有存储:无服务器应用程序应设计为临时工作负载,每个请求都有一个新鲜的环境。如果它们需要保留一些数据,它们应该使用远程服务,如数据库即服务DbaaS)。

  • 隐式容错函数:无服务器基础架构和部署的应用程序都应该是容错的,以创建一个强大、可扩展和可靠的应用程序环境。

  • 请求的可伸缩性:包括计算和网络资源在内的基础架构应该能够实现高度的可伸缩性。换句话说,当请求增加时,无服务器环境不应该无法扩展。

  • 空闲时间没有成本:无服务器提供商只有在无服务器工作负载运行时才会产生成本。如果您的函数长时间没有收到 HTTP 请求,您不应该为空闲支付任何费用。

  • 自带代码BYOC):无服务器架构应该能够运行由最终用户开发和打包的任何代码。如果您是 Node.Js 或 Go 开发人员,应该可以在您喜欢的语言中部署您的函数到无服务器基础架构中。

  • 仪器仪表:应该向开发人员提供有关函数日志和函数调用收集的指标。这使得能够调试和解决与函数相关的问题。由于它们已经在远程服务器上运行,仪器仪表不应该在分析潜在问题方面产生进一步的负担。

原始宣言介绍了一些最佳实践和限制;然而,随着云技术的发展,无服务器应用程序的世界也在不断发展。这种演变将使宣言中的一些规则过时,并增加新规则。在接下来的部分中,讨论了无服务器应用程序的用例,以解释无服务器在行业中的应用情况。

无服务器用例

无服务器应用程序和设计似乎是前卫的技术;然而,它们在行业中被广泛采用,用于可靠、强大和可伸缩的应用程序。如果您希望获得无服务器设计的好处,任何在 VM、Docker 容器或 Kubernetes 上运行的传统应用程序都可以设计为无服务器运行。以下是一些无服务器架构的知名用例:

  • 数据处理:解释、分析、清洗和格式化数据是大数据应用中必不可少的步骤。借助无服务器架构的可伸缩性,您可以快速过滤数百万张照片并计算其中的人数,例如,而无需购买任何昂贵的服务器。根据一份案例报告(azure.microsoft.com/en-in/blog/a-fast-serverless-big-data-pipeline-powered-by-a-single-azure-function/),可以使用 Azure Functions 创建一个无服务器应用程序,以检测来自多个来源的欺诈交易。为了处理 800 万个数据处理请求,无服务器平台将是适当的选择,因为它们具有临时可伸缩性。

  • Webhooks:Webhooks 是向第三方服务发送实时数据的 HTTP API 调用。与为 Webhook 后端运行服务器不同,可以利用无服务器基础架构以更低的成本和更少的维护。

  • 结账和付款:可以将购物系统创建为无服务器应用程序,其中每个核心功能都设计为独立的组件。例如,您可以集成 Stripe API 作为远程支付服务,并在无服务器后端中使用 Shopify 服务进行购物车管理。

  • 实时聊天应用程序:集成到 Facebook Messenger、Telegram 或 Slack 等应用程序的实时聊天应用程序非常受欢迎,用于处理客户操作、分发新闻、跟踪体育比赛结果,或者只是用于娱乐。可以创建临时无服务器函数来响应消息或根据消息内容采取行动。无服务器实时聊天的主要优势在于,当许多人在使用时,它可以扩展。当没有人使用聊天应用程序时,它也可以缩减到零成本。

这些用例说明了无服务器架构可以用于设计任何现代应用程序。还可以将单体应用程序的某些部分移动并转换为无服务器函数。如果您当前的在线商店是打包为 JAR 文件的单个 Java Web 应用程序,可以将其业务功能分离并转换为无服务器组件。将巨大的单体应用程序分解为小的无服务器函数有助于同时解决多个问题。首先,无服务器应用程序的可扩展性永远不会成为问题。例如,如果在假期期间无法处理大量付款,无服务器平台将根据使用水平自动扩展付款功能。其次,您不需要局限于单体的编程语言;您可以使用任何编程语言开发函数。例如,如果您的数据库客户端最好使用 Node.js 实现,您可以使用 Node.js 编写在线商店的数据库操作。

最后,您可以重用在您的单体中实现的逻辑,因为现在它是一个共享的无服务器服务。例如,如果您将在线商店的支付操作分离出来并创建无服务器支付功能,您可以在下一个项目中重用这些支付功能。所有这些好处使得创业公司和大型企业都愿意采用无服务器架构。在接下来的部分中,将更深入地讨论无服务器架构,特别关注一些实现。

可能的答案:

  • 具有高延迟的应用程序

  • 当可观察性和指标对业务至关重要时

  • 当供应商锁定和生态系统依赖成为问题时

无服务器架构和函数即服务(FaaS)

无服务器是一种云计算设计,云提供商处理服务器的供应。在前一节中,我们讨论了操作方面的分层和移交。在本节中,我们将重点讨论无服务器架构和使用无服务器架构的应用程序设计。

在传统软件架构中,应用程序的所有组件都安装在服务器上。例如,假设您正在用 Java 开发一个电子商务网站,您的产品信息存储在MySQL中。在这种情况下,前端、后端和数据库都安装在同一台服务器上。预期最终用户将通过服务器的 IP 地址访问购物网站,因此服务器上应该运行应用服务器,比如Apache Tomcat。此外,用户信息和安全组件也包含在安装在服务器上的软件包中。图 1.6 显示了一个单体电子商务应用程序,包括前端、后端、安全和数据库部分:

图 1.6:传统软件架构

图 1.6:传统软件架构

微服务架构侧重于创建松散耦合且可独立部署的服务集合。对于同一个电子商务系统,您仍然会有前端、后端、数据库和安全组件,但它们将是隔离的单元。此外,这些组件将被打包为容器,并由诸如 Kubernetes 之类的容器编排器进行管理。这使得能够独立安装和扩展组件,因为它们分布在多台服务器上。在图 1.7 中,相同的四个组件安装在服务器上,并通过 Kubernetes 网络相互通信:

图 1.7:微服务软件架构

图 1.7:微服务软件架构

微服务部署到服务器上,仍然由运维团队管理。采用无服务器架构后,这些组件将转换为第三方服务或函数。例如,电子商务网站的安全性可以由诸如Auth0之类的身份验证即服务提供商来处理。AWS 关系型数据库服务(RDS)可以用作系统的数据库。后端逻辑的最佳选择是将其转换为函数,并部署到无服务器平台,如AWS LambdaGoogle Cloud Functions。最后,前端可以由存储服务提供,如AWS 简单存储服务(S3)Google Cloud 存储

采用无服务器设计,只需为您定义这些服务,您就可以拥有可扩展、强大和管理良好的应用程序,如图 1.8 所示。

注意

Auth0是一个用于为 Web、移动和传统应用程序提供身份验证和授权的平台。简而言之,它提供身份验证和授权即服务,您可以连接任何使用任何语言编写的应用程序。更多详细信息可以在其官方网站上找到:auth0.com

图 1.8:无服务器软件架构

图 1.8:无服务器软件架构

从单体架构开始,首先将其分解为微服务,然后再转换为无服务器组件,这样做有多种好处:

  • 成本:无服务器架构有助于通过两种关键方式降低成本。首先,服务器的管理被外包,其次,只有在使用无服务器应用程序时才会产生费用。

  • 可扩展性:如果预计应用程序会增长,当前最佳选择是将其设计为无服务器应用程序,因为这样可以消除与基础设施相关的可扩展性约束。

  • 灵活性:当部署单元的范围减少时,无服务器提供更多灵活性,可以选择更好的编程语言,并且可以用更小的团队进行管理。

这些维度以及它们在软件架构之间的变化在图 1.9中可视化。

图 1.9:从成本到无服务器过渡的好处

图 1.9:从成本到无服务器过渡的好处

当您从传统的软件开发架构开始时,转向微服务会增加可扩展性和灵活性。然而,它并没有直接降低运行应用程序的成本,因为您仍然在处理服务器。进一步转向无服务器可以提高可扩展性和灵活性,同时降低成本。因此,了解并实施无服务器架构对于未来的应用程序至关重要。在接下来的部分中,将介绍无服务器架构的实现,即函数即服务FaaS)。

函数即服务(FaaS)

FaaS是最受欢迎和广泛采用的无服务器架构实现。所有主要的云提供商都有 FaaS 产品,如 AWS Lambda、Google Cloud Functions 和 Azure Functions。顾名思义,在 FaaS 中,部署和管理的单位是函数。在这种情况下,函数与任何其他编程语言中的函数没有区别。它们预期接受一些参数并返回值以实现业务需求。FaaS 平台处理服务器的管理,并且可以运行事件驱动的可扩展函数。FaaS 提供的基本属性如下:

  • 无状态:函数被设计为无状态和短暂的操作,不会将文件保存到磁盘,也不会管理缓存。每次调用函数时,它都会快速启动一个新环境,并在完成时被移除。

  • 事件触发:函数设计为直接触发,并基于事件,如cron时间表达式、HTTP 请求、消息队列和数据库操作。例如,可以在启动新聊天时通过 HTTP 请求调用startConversation函数。同样,可以在向数据库添加新用户时启动syncUsers函数。

  • 可扩展:函数被设计为能够并行运行,以便每个传入请求都得到响应,每个事件都得到处理。

  • 托管:函数受其平台管理,因此服务器和基础设施不是 FaaS 用户的关注点。

这些函数的属性由云提供商的产品提供,如AWS LambdaGoogle Cloud FunctionsAzure Functions;以及本地产品,如KubelessApache OpenWhiskOpenFass。由于其高度的流行度,术语 FaaS 通常与无服务器术语互换使用。在接下来的练习中,我们将创建一个处理 HTTP 请求的函数,并说明如何开发无服务器函数。

练习 1:创建一个 HTTP 函数

在这个练习中,我们将创建一个 HTTP 函数,作为无服务器平台的一部分,然后通过 HTTP 请求来调用它。为了执行练习的步骤,您将使用 Docker、文本编辑器和终端。

注意

本章练习的代码文件可以在这里找到:github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson01/Exercise1

要成功完成这个练习,我们需要确保执行以下步骤:

  1. 在您喜欢的文本编辑器中创建一个名为function.go的文件,并包含以下内容:
package main
import (
    "fmt"
    "net/http"
)
func WelcomeServerless(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello Serverless World!")
}

在这个文件中,我们已经创建了一个实际的函数处理程序,以便在调用该函数时做出响应。

  1. 创建一个名为main.go的文件,并包含以下内容:
package main
import (
    "fmt"
    "net/http"
)
func main() {
    fmt.Println("Starting the serverless environment..")
    http.HandleFunc("/", WelcomeServerless)
    fmt.Println("Function handlers are registered.")
    http.ListenAndServe(":8080", nil)
}

在这个文件中,我们已经创建了用于提供该函数的环境。一般来说,这部分应该由无服务器平台来处理。

  1. 在您的终端中使用以下命令启动一个 Go 开发环境:
docker run -it --rm -p 8080:8080 -v "$(pwd)":/go/src --workdir=/go/src golang:1.12.5

通过该命令,将在 Go 版本1.12.5的 Docker 容器内启动一个 shell 提示符。此外,主机系统的端口8080被映射到容器,并且当前工作目录被映射到/go/src。您将能够在启动的 Docker 容器内运行命令:

图 1.10:容器内的 Go 开发环境

图 1.10:容器内的 Go 开发环境
  1. 步骤 3中打开的 shell 提示符中使用以下命令启动函数处理程序:go run *.go

随着应用程序的启动,您将看到以下行:

图 1.11:函数服务器的开始

图 1.11:函数服务器的开始

这些行表明main.go文件中的main函数正在运行

预期。

  1. 在浏览器中打开http://localhost:8080图 1.12:WelcomeServerless 输出
图 1.12:WelcomeServerless 输出

网页上显示的消息显示WelcomeServerless函数通过 HTTP 请求成功调用,并且已检索到响应。

  1. Ctrl + C退出函数处理程序,然后输入exit停止容器:图 1.13:退出函数处理程序和容器
图 1.13:退出函数处理程序和容器

通过这个练习,我们演示了如何创建一个简单的函数。此外,展示了无服务器环境如何提供和调用函数。在接下来的部分中,将介绍 Kubernetes 和无服务器环境,以连接这两个云计算现象。

Kubernetes 和无服务器

无服务器和 Kubernetes 大约在 2014 年同时出现在云计算领域。AWS 通过 AWS Lambda 支持无服务器,而 Kubernetes 在 Google 的支持下成为开源,并在容器管理方面拥有悠久而成功的历史。组织开始为他们的短暂临时任务创建 AWS Lambda 函数,许多初创公司专注于在无服务器基础设施上运行的产品。另一方面,Kubernetes 在行业中获得了巨大的采用,并成为事实上的容器管理系统。它能够在容器内运行无状态应用程序,如 Web 前端和数据分析工具,以及有状态应用程序,如数据库。应用程序和微服务架构的容器化已被证明对大型企业和初创公司都是有效的。

因此,运行微服务和容器化应用是成功、可扩展和可靠的云原生应用的关键因素。此外,以下两个重要元素加强了 Kubernetes 和无服务器架构之间的联系:

  • 供应商锁定:Kubernetes 隔离了云提供商,并为运行无服务器工作负载创建了托管环境。换句话说,如果您想明年转移到新的提供商,要在 Google Cloud Functions 中运行您的 AWS Lambda 函数并不是一件简单的事情。然而,如果您使用基于 Kubernetes 的无服务器平台,您将能够快速在云提供商之间甚至本地系统之间进行迁移。

  • 服务重用:作为主流的容器管理系统,Kubernetes 在您的云环境中运行大部分工作负载。它提供了一个机会,可以将无服务器函数与现有服务并行部署。这使得操作、安装、连接和管理无服务器和容器化应用变得更加容易。

云计算和部署策略一直在不断发展,以创造更具开发者友好性和更低成本的环境。Kubernetes 和容器化的采用已经赢得了市场和开发者的喜爱,以至于在很长一段时间内,没有 Kubernetes 的云计算将不会被看到。通过提供相同的好处,无服务器架构正在变得越来越受欢迎;然而,这并不构成对 Kubernetes 的威胁。相反,无服务器应用程序将使容器化更易于访问,因此 Kubernetes 将受益。因此,学习如何在 Kubernetes 上运行无服务器架构以创建未来可靠、云原生、可扩展的应用程序至关重要。在接下来的练习中,我们将结合函数和容器,并将我们的函数打包为容器。

可能的答案:

  • 无服务器 – 数据准备

  • 无服务器 – 短暂的 API 操作

  • Kubernetes – 数据库

  • Kubernetes – 与服务器相关的操作

练习 2:将 HTTP 函数打包为容器

在这个练习中,我们将把练习 1中的 HTTP 函数打包为一个容器,作为 Kubernetes 工作负载的一部分。此外,我们将运行容器,并通过容器触发函数。

注意

本章练习的代码文件可以在此处找到:github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson01/Exercise2

要成功完成练习,我们需要确保执行以下步骤:

  1. 在与练习 1相同的文件夹中创建一个名为Dockerfile的文件。
FROM golang:1.12.5-alpine3.9 AS builder
ADD . .
RUN go build *.go
FROM alpine:3.9
COPY --from=builder /go/function ./function
RUN chmod +x ./function
ENTRYPOINT ["./function"]

在这个多阶段的Dockerfile中,函数是在golang:1.12.5-alpine3.9容器内构建的。然后,将二进制文件复制到alpine:3.9容器中作为最终的应用程序包。

  1. 在终端中使用以下命令构建 Docker 镜像:docker build . -t hello-serverless

Dockerfile的每一行都是按顺序执行的,最后,通过最后一步,Docker 镜像被构建并标记为:Successfully tagged hello-serverless:latest

图 1.14:Docker 容器的构建

图 1.14:Docker 容器的构建
  1. 使用以下命令在终端中从hello-serverless镜像启动 Docker 容器:docker run -it --rm -p 8080:8080 hello-serverless

通过该命令,使用端口8080实例化 Docker 镜像,将主机系统映射到容器。此外,--rm标志将在退出时删除容器。日志行表明函数的容器正在按预期运行:

图 1.15:函数容器的启动

图 1.15:函数容器的启动
  1. 在浏览器中打开http://localhost:8080图 1.16:WelcomeServerless 输出
图 1.16:WelcomeServerless 输出

它显示了在容器中运行的WelcomeServerless函数通过 HTTP 请求成功调用,并且已检索到响应。

  1. 按下Ctrl + C退出容器:图 1.17:退出容器
图 1.17:退出容器

在这个练习中,我们看到了如何将一个简单的函数打包为一个容器。此外,容器已启动,并且借助 Docker 的网络功能触发了函数。在接下来的练习中,我们将实现一个参数化函数,以展示如何向函数传递值并返回不同的响应。

练习 3:参数化 HTTP 函数

在这个练习中,我们将把Exercise 2中的WelcomeServerless函数转换为参数化的 HTTP 函数。此外,我们将运行容器,并通过容器触发函数。

注意

本章练习的代码文件可以在这里找到:github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson01/Exercise3

为了成功完成练习,我们需要确保执行以下步骤:

  1. function.go的内容从Exercise 2更改为以下内容:
package main
import (
	"fmt"
	"net/http"
)
func WelcomeServerless(w http.ResponseWriter, r *http.Request) {
	names, ok := r.URL.Query()["name"]

    if ok && len(names[0]) > 0 {
        fmt.Fprintf(w, names[0] + ", Hello Serverless World!")
	} else {
		fmt.Fprintf(w, "Hello Serverless World!")
	}
}

在新版本的WelcomeServerless函数中,我们现在接受 URL 参数并相应返回响应。

  1. 在终端中使用以下命令构建 Docker 镜像:docker build . -t hello-serverless

Dockerfile的每一行都按顺序执行,最后一步,Docker 镜像被构建并标记为:Successfully tagged hello-serverless:latest

图 1.18:Docker 容器的构建

图 1.18:Docker 容器的构建
  1. 在终端中使用以下命令从hello-serverless镜像启动 Docker 容器:docker run -it –rm -p 8080:8080 hello-serverless

使用该命令,函数处理程序将在主机系统的端口8080上启动:

图 1.19:函数容器的开始

图 1.19:函数容器的开始
  1. 在浏览器中打开http://localhost:8080图 1.20:WelcomeServerless 输出
图 1.20:WelcomeServerless 输出

它显示与上一个练习中相同的响应。如果我们提供 URL 参数,我们应该会得到个性化的Hello Serverless World消息。

  1. 将地址更改为http://localhost:8080?name=Ece并重新加载页面。我们现在期望看到一个带有 URL 参数中提供的名称的个性化Hello Serverless World消息:图 1.21:个性化的 WelcomeServerless 输出
图 1.21:个性化的 WelcomeServerless 输出
  1. 按下Ctrl + C退出容器:图 1.22:退出容器
图 1.22:退出容器

在这个练习中,展示了如何使用不同参数的通用函数。我们部署的单个函数返回了基于输入值的个人消息。在接下来的活动中,将创建一个更复杂的函数,并将其作为容器进行管理,以展示它们在现实生活中的实现方式。

活动 1:伦敦自行车点的 Twitter 机器人后端

这项活动的目的是为 Twitter 机器人后端创建一个真实的功能。Twitter 机器人将用于搜索伦敦的可用自行车点和相应位置的可用自行车数量。机器人将以自然语言形式回答;因此,您的函数将接受街道名称或地标的输入,并输出完整的人类可读句子。

伦敦的交通数据是公开可用的,并且可以通过伦敦交通局TFL统一 APIapi.tfl.gov.uk)进行访问。您需要使用 TFL API 并在容器内运行您的函数。

完成后,您将有一个运行函数的容器:

图 1.23:容器内运行的函数

图 1.23:容器内运行的函数

当您通过 HTTP REST API 查询时,如果找到可用的自行车点,它应返回类似以下的句子:

图 1.24:当自行车可用时的功能响应

图 1.24:当自行车可用时的功能响应

当找不到自行车点或这些位置没有可用的自行车时,函数将返回类似以下的响应:

图 1.25:当找到自行车点但没有找到自行车时的功能响应

图 1.25:当找到自行车点但没有找到自行车时的功能响应

函数还可能提供以下响应:

图 1.26:当找不到自行车点或自行车时的功能响应

图 1.26:当找不到自行车点或自行车时的功能响应

执行以下步骤完成此活动:

  1. 创建一个main.go文件来注册函数处理程序,就像练习 1中一样。

  2. FindBikes函数创建一个function.go文件。

  3. 为构建和打包函数创建一个Dockerfile,就像练习 2中一样。

  4. 使用 Docker 命令构建容器映像。

  5. 作为 Docker 容器运行容器映像,并使端口从主机系统可用。

  6. 使用不同的查询测试函数的 HTTP 端点。

  7. 退出容器。

注意

文件main.gofunction.goDockerfile可以在这里找到:github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson01/Activity1

活动的解决方案可以在第 372 页找到。

在这个活动中,我们构建了 Twitter 机器人的后端。我们首先定义了mainFindBikes函数。然后我们将这个无服务器后端构建和打包为 Docker 容器。最后,我们用各种输入测试它,以找到最近的自行车站。通过这个现实生活中的例子,展示了无服务器平台的后台操作以及如何编写无服务器函数。

摘要

在本章中,我们首先描述了从传统软件开发到无服务器软件开发的过程。我们讨论了多年来软件开发如何改变,以创造一个更加开发者友好的环境。在此之后,我们介绍了无服务器技术的起源及其官方宣言。由于无服务器是行业中流行的术语,定义一些规则有助于设计更好的无服务器应用程序,使其能够轻松集成到各种平台中。然后,我们列举了无服务器技术的用例,以说明无服务器架构如何用于创建任何现代应用程序。

在介绍无服务器之后,探讨了 FaaS 作为无服务器架构的一种实现。我们展示了应用程序在传统、微服务和无服务器设计中的设计方式。此外,还详细讨论了过渡到无服务器架构的好处。

最后,讨论了 Kubernetes 和无服务器技术,以展示它们如何相互支持。作为主流的容器管理系统,介绍了 Kubernetes,涉及了在其上运行无服务器平台的优势。容器化和微服务在工业界得到了广泛采用,因此涵盖了作为容器运行无服务器工作负载的内容,并提供了相关练习。最后,探讨了将函数作为 Twitter 机器人的后端的真实案例。在这个活动中,函数被打包为容器,以展示基于微服务、容器化和 FaaS 支持设计之间的关系。

在下一章中,我们将介绍云中的无服务器架构,并使用云服务进行工作。

第二章:介绍云中的无服务器

学习目标

在本章结束时,您将能够:

  • 评估选择最佳无服务器 FaaS 提供商的标准

  • 识别主要云服务提供商支持的语言、触发类型和成本结构

  • 将无服务器函数部署到云提供商并将函数与其他云服务集成

在本章中,我们将解释云提供商的无服务器 FaaS 产品,创建我们在云中的第一个无服务器函数,并与其他云服务集成。

介绍

在上一章中,讨论了传统架构向无服务器设计的架构演变。此外,介绍了无服务器的起源和好处,以解释其在行业中的高采用率和成功。在本章中,重点将放在云提供商的无服务器平台上。让我们从多年来云技术提供的演变开始。

在云计算开始时,云提供商的主要提供是其预配和可立即使用的硬件,即基础设施。云提供商管理硬件和网络操作,因此,他们提供的产品是基础设施即服务IaaS),如下图所示。所有云提供商仍然将 IaaS 产品作为其核心功能,比如 AWS 的Amazon 弹性计算云(Amazon EC2)和 GCP 的Google 计算引擎

在接下来的几年里,云提供商开始提供平台,开发人员只能在其上运行他们的应用程序。通过这种抽象,手动服务器配置、安全更新和服务器故障成为了云提供商的关注点。这些提供被称为平台即服务PaaS),因为它们只专注于在其平台上运行应用程序和数据。Heroku是最受欢迎的 PaaS 提供商,尽管每个云提供商都有自己的 PaaS 产品,比如AWS 弹性 BeanstalkGoogle App Engine。与 IaaS 类似,PaaS 在软件开发中仍在使用。

在顶层抽象中,应用程序的功能作为无服务器架构中的控制单元。这被称为函数即服务FaaS),近年来所有重要的云提供商都提供了这种抽象。从 IaaS 到 PaaS,最终到 FaaS 的抽象层次可以在下图中看到:

图 2.1:从 IaaS 到 PaaS 和 FaaS 的转变

图 2.1:从 IaaS 到 PaaS 和 FaaS 的转变

无服务器和云评估标准

为了分析市场上的 FaaS 产品,定义一些标准是有益的,这样我们就可以以结构化的方式比较产品。在选择云提供商之前,以下主题对于每个 FaaS 平台都是必不可少的,并需要进行详细调查:

  • 编程语言: 函数部署和管理在云提供商的环境中。因此,云提供商定义支持的编程语言。这是最重要的决策因素之一,因为在大多数情况下,使用其他语言实现函数是不可行的。

  • 功能触发器: 函数设计为由云提供商服务和外部方法触发。传统的技术包括定时调用、按需调用以及与其他云服务(如数据库、队列和 API 网关)的集成。

  • 成本: 无服务器架构最具吸引力的特点是其成本效益和主流的价格计算方式,即按请求付费。对于长期运行的项目的可行性,计算实际和预期成本是至关重要的。

云提供商应该具有成本效益,尽可能提供多种编程语言,并支持各种功能触发器。还有其他标准,如监控、运维和内部知识水平,但这些与云提供商的无服务器产品并不直接相关。在接下来的章节中,将讨论三个最主要的云提供商的无服务器平台:亚马逊云服务、谷歌云平台和微软 Azure。

AWS Lambda

AWS Lambda 是第一个 FaaS 提供,也在行业中引起了无服务器的热潮。它于 2014 年公开,并被各级组织广泛采用于云计算世界。它使初创公司能够在短时间内创建新产品。它还使像 Netflix 这样的大型企业能够将基于事件的触发器转移到无服务器功能上。通过消除服务器操作负担的机会,AWS Lambda 和无服务器成为了行业的下一个趋势。在本节中,我们将讨论 AWS Lambda 的编程语言支持、触发器类型和成本结构。此外,我们将部署我们的第一个无服务器函数。

注意

如果您想了解更多信息,可以在此处找到 AWS Lambda 的官方网站:aws.amazon.com/lambda

AWS Lambda 在无服务器函数方面支持 Java、Python、Node.js、C#、Ruby 和 Go 编程语言。此外,AWS Lambda 提供了一个名为 AWS Lambda Runtime Interface 的 API,以实现任何语言作为自定义运行时的集成。因此,可以说 AWS Lambda 本地支持一组流行的语言,同时允许扩展到其他编程语言。

AWS Lambda 旨在具有事件触发功能。这是函数处理从事件源检索到的事件的地方。在 AWS 生态系统中,各种服务都可以是事件源,包括以下内容:

  • 亚马逊 S3 文件存储用于添加新文件时

  • 亚马逊 Alexa 用于实现语音助手的新技能

  • 亚马逊 CloudWatch Events 用于云资源状态更改时发生的事件

  • 亚马逊 CodeCommit 用于开发人员向代码存储库推送新提交时

除了这些服务之外,无服务器事件源的基本 AWS 服务是Amazon API Gateway。它具有通过 HTTPS 调用 Lambda 函数的 REST API 功能,并允许管理多个 Lambda 函数以用于不同的方法,如GETPOSTPATCHDELETE。换句话说,API Gateway 在无服务器函数和外部世界之间创建了一个层。这一层还通过保护 Lambda 函数免受分布式拒绝服务DDoS)攻击和定义节流来处理 Lambda 函数的安全性。如果要与其他 AWS 服务集成或通过 API Gateway 公开它们,AWS Lambda 函数的触发器类型和环境是高度可配置的。

对于 AWS Lambda 的定价,有两个关键点需要注意:第一个是请求费用,第二个是计算费用。请求费用是基于函数调用次数计算的,而计算费用是按每秒 GB 计算的。计算费用是内存大小和执行时间的乘积:

  • 内存大小(GB):这是函数配置的分配内存。

  • 执行时间(毫秒):这是函数实际运行的执行时间。

此外,还有一个免费套餐,其中每月免除前 100 万次请求费用和每秒 400,000 GB 的计算费用。包括免费套餐在内的简单计算可以显示运行无服务器函数的成本是多么便宜。

假设您的函数一个月被调用了 3000 万次。您已经分配了 128 MB 的内存,平均来说,函数运行了 200 毫秒:

请求费用:

价格:每 100 万次请求$0.20

免费套餐:100 万次

月请求:30 M

月请求费用:29 M x $0.20 / M = $5.80

计算费用:

价格:每 GB 每秒$0.0000166667

免费套餐:每秒 400,000 GB

月计算:30 M x 0.2 秒 x 128 MB / 1024 = 750,000 GB 每秒

月计算费用:350,000 x $0.0000166667 = $5.83

月总成本:$5.80 + $5.83 = $11.63

这个计算表明,在运行一个无服务器的 AWS Lambda 环境中,每天接收100 万次函数调用的月成本为 11.63 美元。这表明了运行无服务器工作负载的成本是多么便宜,以及在无服务器经济中需要考虑的基本特征。在接下来的练习中,我们的第一个无服务器函数将部署到 AWS Lambda,并将被调用以显示平台的操作视图。

注意

为了完成这个练习,您需要拥有一个活跃的亚马逊网络服务账户。您可以在aws.amazon.com/上创建一个账户。

练习 4:在 AWS Lambda 中创建函数并通过 AWS Gateway API 调用它

在这个练习中,我们将创建我们的第一个 AWS Lambda 函数,并将其连接到 AWS Gateway API,以便我们可以通过其 HTTP 端点调用。

要成功完成此练习,我们需要确保执行以下步骤:

  1. 打开 AWS 管理控制台,在查找服务搜索框中输入Lambda,然后单击Lambda - Run Code without Thinking about Servers。控制台将如下所示:图 2.2:AWS 管理控制台
图 2.2:AWS 管理控制台
  1. 单击 Lambda 函数列表中的创建函数,如下截图所示:图 2.3:AWS Lambda - 函数列表
图 2.3:AWS Lambda - 函数列表
  1. 创建函数视图中选择从头开始。将hello-from-lambda作为函数名称,Python 3.7作为运行时。单击屏幕底部的创建函数,如下截图所示:图 2.4:AWS Lambda - 创建函数视图
图 2.4:AWS Lambda - 创建函数视图
  1. 您将被引导到hello-from-lambda函数视图,这是您

  2. 可以编辑函数代码,如下截图所示:图 2.5:AWS Lambda - hello-from-lambda

图 2.5:AWS Lambda - hello-from-lambda
  1. 更改lambda_handler函数如下:
import json
def lambda_handler(event, context):
    return {
        'statusCode': '200',
        'body': json.dumps({"message": "hello", "platform": "lambda"}),
        'headers': {
            'Content-Type': 'application/json',
        }
    }
  1. 单击屏幕顶部的保存,如下截图所示:图 2.6:AWS Lambda - hello-from-lambda 函数代码
图 2.6:AWS Lambda - hello-from-lambda 函数代码
  1. 打开设计视图,点击添加触发器,如下面的屏幕截图所示:图 2.7:AWS Lambda – hello-from-lambda 设计视图
图 2.7:AWS Lambda – hello-from-lambda 设计视图
  1. 从触发器列表中选择API Gateway,如下面的屏幕截图所示:图 2.8:AWS Lambda – 触发器列表
图 2.8:AWS Lambda – 触发器列表
  1. 在触发器配置屏幕上选择创建新的 API作为 API,并且选择开放作为安全配置,如下面的屏幕截图所示:图 2.9:AWS Lambda – 触发器配置
图 2.9:AWS Lambda – 触发器配置

在这个屏幕上,已经在 API Gateway 中为hello-from-lambda函数定义了一个新的 API,并且开放了安全性。这个配置确保了一个端点将被创建,并且它将可以在没有任何身份验证的情况下访问。

  1. 在屏幕底部点击添加

您将被重定向到hello-from-lambda函数,通知显示该函数现在正在接收来自触发器的事件。在设计视图中,Lambda 函数连接到 API Gateway 以进行触发,并连接到 Amazon CloudWatch Logs 以进行日志记录。换句话说,现在可以通过 API Gateway 端点触发函数,并在 CloudWatch 中检查它们的输出,如下面的屏幕截图所示:

图 2.10:AWS Lambda – 添加触发器

图 2.10:AWS Lambda – 添加触发器
  1. 从 API Gateway 部分获取 API Gateway 端点,如下面的屏幕截图所示:图 2.11:AWS Lambda – 触发器 URL
图 2.11:AWS Lambda – 触发器 URL
  1. 在新标签页中打开 URL 来触发函数并获取响应,如下面的屏幕截图所示:图 2.12:AWS Lambda – 函数响应
图 2.12:AWS Lambda – 函数响应

这个 JSON 响应表明 AWS Lambda 函数通过 API Gateway 连接并且按预期工作。

  1. 从第 2 步返回到函数列表,选择hello-from-lambda,并从操作中选择删除。然后,点击弹出窗口中的删除以从 Lambda 中删除该函数,如下面的屏幕截图所示:图 2.13:AWS Lambda – 函数删除
图 2.13:AWS Lambda – 函数删除

在这个练习中,展示了创建 AWS Lambda 函数并连接到 AWS Gateway API 以进行 HTTP 访问的一般流程。在不到 10 个步骤的时间内,就可以在 AWS Lambda 云环境中运行生产就绪的服务。这个练习向您展示了无服务器平台如何使软件开发变得快速简单。在接下来的部分中,将继续分析云提供商的无服务器平台,其中包括 Microsoft 的 Azure Functions。

Azure Functions

微软于 2016 年宣布了Azure Functions,作为Microsoft Azure云中的无服务器平台。Azure Functions 通过来自 Azure 或外部服务的事件触发器来运行无服务器工作负载,从而扩展了其云平台。它的特色在于专注于行业中广泛使用的 Microsoft 支持的编程语言和工具。在本节中,将从支持的编程语言、触发器类型和成本方面讨论 Azure Functions。最后,我们将部署一个从端点接收参数的函数到 Azure Functions,以说明其操作方面。

注意

如果您想了解更多信息,可以在这里找到 Azure Functions 的官方网站:azure.microsoft.com/en-us/services/functions/

Azure Functions 的最新版本支持C#JavaScript(在Node.js运行时),F#JavaPowerShellPythonTypescript,它会被转译成JavaScript。此外,提供了一种语言可扩展性接口,用于在gRPC作为消息层的函数运行时和工作进程之间进行通信。在开始使用之前,了解 Azure Functions 支持的普遍可用的、实验性的和可扩展的编程语言是很有价值的。

注意

gRPC是一个最初由 Google 开发的远程过程调用RPC)系统。它是一个开源系统,可以实现跨平台通信,没有语言或平台限制。

Azure Functions 旨在由各种类型触发,例如定时器、HTTP、文件操作、队列消息和事件。此外,可以为函数指定输入和输出绑定。这些绑定定义了函数的输入参数和要发送到其他服务的输出值。例如,可以创建一个定时函数来从 Blob Storage 中读取文件,并将 Cosmos DB 文档创建为输出。在这个例子中,函数可以使用定时器触发器Blob Storage输入绑定和Cosmos DB输出绑定进行定义。触发器和绑定使 Azure Functions 轻松集成到 Azure 服务和外部世界中。

与 AWS Lambda 相比,Azure Functions 的成本计算方法和当前价格有两个不同之处。第一个区别是 Azure Functions 的当前计算价格略低,为每秒$0.000016/GB。第二个区别是 Azure Functions 使用观察到的内存消耗进行计算,而 AWS Lambda 中内存限制是预先配置的。

在接下来的练习中,第一个无服务器函数将被部署到 Azure Functions,并将被调用以显示平台的操作视图。

注意

为了完成这项练习,您需要拥有一个活跃的 Azure 账户。您可以在signup.azure.com/上创建一个账户。

练习 5:在 Azure Functions 中创建一个带参数的函数

在这个练习中,我们的目标是在 Azure 中创建一个带参数的函数,并通过其 HTTP 端点以不同的参数进行调用。

为了成功完成这项练习,我们需要确保执行以下步骤:

  1. Azure首页的左侧菜单中点击Function App,如下截图所示:图 2.14:Azure 首页
图 2.14:Azure 首页
  1. Function App列表中点击创建函数应用,如下截图所示:图 2.15:函数应用列表
图 2.15:函数应用列表
  1. 给应用取一个唯一的名称,比如hello-from-azure,并选择Node.js作为Runtime Stack。点击页面底部的创建,如下截图所示:图 2.16:创建一个函数应用
图 2.16:创建一个函数应用
  1. 您将被重定向到函数应用列表视图。检查菜单顶部是否有通知。您将看到正在部署到资源组'hello-from-azure',如下图所示:图 2.17:部署正在进行中
图 2.17:部署正在进行中

等待几分钟,直到部署完成:

图 2.18:成功部署

图 2.18:成功部署
  1. hello-from-azure函数应用视图中点击+新函数,如下图所示:图 2.19:hello-from-azure 函数应用
图 2.19:hello-from-azure 函数应用
  1. 选择In-portal作为开发环境,在 Azure Web 门户内创建函数,并单击继续,如下图所示:图 2.20:函数开发环境
图 2.20:函数开发环境
  1. 选择Webhook + API,然后单击创建,如下图所示:图 2.21:函数触发器类型
图 2.21:函数触发器类型

在此视图中,可以从模板创建函数,例如 webhooks、定时器或来自市场的协作模板。

  1. 将以下函数写入index.js,然后单击保存
module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');
    if (req.query.name || (req.body && req.body.name)) {
        context.res = {
            status: 200,
            body: "Hello " + (req.query.name || req.body.name) +", it is your function in Azure!"
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body."
        };
    }
};

此代码导出一个接受来自请求的参数的函数。该函数创建一个个性化消息,并将其作为输出发送给用户。代码应该插入到代码编辑器中,如下图所示:

图 2.22:hello-from-azure 函数的 index.js

图 2.22:hello-from-azure 函数的 index.js
  1. 点击获取函数 URL并复制弹出窗口中的 URL,如下图所示

  2. 下面的屏幕截图:图 2.23:函数 URL

图 2.23:函数 URL
  1. 在浏览器中打开您在步骤 7中复制的 URL,如下面的屏幕截图所示:图 2.24:没有参数的函数响应
图 2.24:没有参数的函数响应

在 URL 的末尾添加&name=和您的名字,然后重新加载选项卡,例如https://hello-from-azure.azurewebsites.net/api/HttpTrigger?code=nNrck...&name=Onur,如下面的屏幕截图所示:

图 2.25:带参数的函数响应

图 2.25:带参数的函数响应

这些响应表明可以验证和传递参数给函数。对于无服务器函数以及考虑各种触发器和绑定的可能性时,传递参数及其验证是至关重要的。

  1. 步骤 2返回函数应用列表,单击我们创建的新函数旁边的...,然后选择删除,如下面的屏幕截图所示:图 2.26:删除函数
图 2.26:删除函数

在弹出视图中输入函数名称,然后单击删除以删除所有资源。在确认视图中,警告指示函数应用的删除是不可逆的,如下面的屏幕截图所示:

图 2.27:删除函数及其资源

图 2.27:删除函数及其资源

在下一节中,将以类似的方式讨论谷歌云函数,并将更复杂的函数部署到云提供商。

谷歌云函数

谷歌云函数于 2017 年公开发布,就在 AWS Lambda 和 Azure Functions 之后。在谷歌云函数发布之前,PaaS 产品谷歌的Firebase已经支持无服务器函数。然而,谷歌云函数作为其核心无服务器云产品,已经对谷歌云平台内的所有服务开放。在本节中,将讨论谷歌云函数支持的编程语言、触发器类型和成本。最后,我们将部署一个定期被云服务调用的函数到谷歌云函数,以展示其操作方面。

注意

如果您想了解更多信息,可以在谷歌云函数的官方网站找到:cloud.google.com/functions/

谷歌云函数GCF)可以使用Node.jsPythonGo进行开发。与其他主要云提供商相比,GCF 支持的语言范围较小。此外,GCF 不支持公开可用的语言扩展或 API。因此,评估 GCF 支持的语言是否适用于您将开发的函数至关重要。

Google Cloud Functions 旨在与触发器和事件相关联。事件发生在您的云服务中,例如数据库更改、存储系统中的新文件,或者在提供新虚拟机时。触发器是将服务和相关事件声明为函数输入的声明。可以创建触发器作为HTTP端点、Cloud Pub/Sub队列消息,或存储服务,如Cloud StorageCloud Firestore。此外,函数可以连接到 Google Cloud Platform 提供的大数据和机器学习服务。

与其他云提供商相比,Google Cloud Platform 的成本计算略微复杂。这是因为它考虑了调用、计算时间和出站网络数据,而其他云提供商只关注调用和计算时间:

  • 调用:每一百万请求收取 0.40 美元。

  • 计算时间:函数的计算时间从调用开始到完成,以 100 毫秒为增量计算。例如,如果您的函数完成需要 240 毫秒,您将被收取 300 毫秒的计算时间费用。在这个计算中使用了两个单位 - 每秒 GB每秒 GHz。为运行 1 秒的函数提供 1GB 内存,每秒 1GB 的价格为 0.0000025 美元。此外,为运行 1 秒的函数提供 1GHz 的 CPU,每秒 1GHz 的价格为 0.0000100 美元。

  • 出站网络数据:从函数传输到外部的数据以 GB 计量,每 GB 数据收取 0.12 美元。

GCF 的免费套餐提供了 200 万次调用、每秒 400,000GB、每秒 200,000GHz 的计算时间,以及每月 5GB 的出站网络流量。与 AWS 或 Azure 相比,GCP 的成本会略高,因为它的价格更高,计算方法更复杂。

假设您的函数一个月被调用了 3000 万次。您已经分配了 128MB 内存,200MHz 的 CPU,并且平均来说,函数运行时间为 200 毫秒,类似于 AWS Lambda 的例子:

请求费用

价格:每 1 百万请求 0.40 美元

免费套餐:2 百万

每月请求:30 百万

每月请求费用 = 28 百万 x 0.40 / 百万 = 11.2 美元

计算费用 - 内存:

价格:每 GB 秒 0.0000025 美元

免费套餐:400,000GB 秒

每月计算:30 M x 0.2 秒 x 128 MB / 1024 = 750,000 GB 秒

每月内存费用:350,000 x $0.0000025 = $0.875

计算费用 - CPU:

价格:每 GHz 秒$0.0000100

免费套餐:200,000 GB 秒

每月计算:30 M x 0.2 秒 x 200 MHz / 1000 GHz = 1,200,000 GHz 秒

每月 CPU 费用:1,000,000 x $0.0000100 = $10

每月总费用= $11.2 + $0.875 + $10 = $22.075

由于单位价格略高于 AWS 和 Azure,运行相同函数的总月费用在 GCP 中超过$22,而在 AWS 和 Azure 中约为$11。此外,从函数到外部世界的任何出站网络在潜在的额外成本方面都是至关重要的。因此,在选择无服务器云平台之前,应深入分析定价方法和单位价格。

在下面的练习中,我们的第一个无服务器函数将部署到 GCF,并将被定时触发器调用,以显示平台的运行视图。

注意

为了完成这个练习,您需要拥有一个活跃的 Google 账户。您可以在console.cloud.google.com/start上创建一个账户。

练习 6:在 GCF 中创建一个定时函数

在这个练习中,我们的目标是在 Google Cloud Platform 中创建一个定时函数,并使用云调度器服务来检查其调用。

要成功完成这个练习,我们需要确保执行以下步骤:

  1. 点击左侧菜单中的云函数,它可以在 Google Cloud Platform 主页的计算组中找到,如下面的屏幕截图所示:图 2.28:Google Cloud Platform 主页
图 2.28:Google Cloud Platform 主页
  1. 点击Cloud Functions页面上的创建函数,如下面的屏幕截图所示:图 2.29:云函数页面
图 2.29:云函数页面
  1. 在函数创建表单中,将函数名称更改为HelloWorld,并选择 128 MB 的内存分配。确保选择HTTP作为触发方法,并选择Go 1.11作为运行时,如下面的屏幕截图所示:图 2.30:函数创建表单
图 2.30:函数创建表单
  1. 使用浏览器内联编辑器更改function.go,使其具有以下内容:
package p
import (
	"fmt"
	"net/http"
)
func HelloWorld(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello World from Google Cloud Functions!")
	return
}

此代码段创建一个带有静态消息打印到输出的HelloWorld函数。代码应插入到代码编辑器中的function.go中,如下截图所示:

图 2.31:函数内联编辑器

图 2.31:函数内联编辑器
  1. 复制“触发器”选择框下方表单中的 URL 以调用函数,如下截图所示:图 2.32:函数触发 URL
图 2.32:函数触发 URL
  1. 单击表单末尾的“创建”按钮。使用此配置,将打包并部署第 4 步的代码到 Google Cloud Platform。此外,将为函数分配一个触发器 URL,以便从外部访问,如下截图所示:图 2.33:函数创建
图 2.33:函数创建

等待几分钟,直到函数列表中的HelloWorld函数旁边有一个绿色的勾号图标,如下截图所示:

图 2.34:功能部署

图 2.34:功能部署
  1. 在浏览器中打开您在第 5 步中复制的 URL,如下截图所示:图 2.35:函数响应
图 2.35:函数响应

响应显示函数已成功部署并按预期运行。

  1. 在左侧菜单中单击“TOOLS”下的“Cloud Scheduler”,如下截图所示:图 2.36:Google Cloud 工具菜单
图 2.36:Google Cloud 工具菜单
  1. 在“Cloud Scheduler”页面上单击“创建作业”,如下截图所示:图 2.37:Cloud Scheduler 页面
图 2.37:Cloud Scheduler 页面
  1. 如果您在 Google Cloud 项目中首次使用 Cloud Scheduler,请选择一个区域,然后单击“下一步”,如下截图所示:图 2.38:Cloud Scheduler – 区域选择
图 2.38:Cloud Scheduler – 区域选择

如果看到以下通知,请等待几分钟:

我们正在初始化您选择的区域中的 Cloud Scheduler。这通常需要大约一分钟

  1. 将作业名称设置为HelloWorldEveryMinute,频率设置为* * * * *,这意味着作业将每分钟触发一次。选择 HTTP 作为目标,并将在步骤 5 中复制的 URL 粘贴到 URL 框中,如下面的屏幕截图所示:图 2.39:调度程序作业创建
图 2.39:调度程序作业创建
  1. 您将被重定向到Cloud Scheduler列表,如下面的屏幕截图所示:图 2.40:云调度程序页面
图 2.40:云调度程序页面

等待几分钟,然后单击刷新按钮。列表将显示HelloWorldEveryMinute最后运行时间戳及其结果,如下面的屏幕截图所示:

图 2.41:带有运行信息的云调度程序页面

图 2.41:带有运行信息的云调度程序页面

这表明云调度程序在2019 年 8 月 13 日下午 3:44:00触发了我们的函数,并且结果是成功的。

  1. 从第 7 步返回到函数列表,然后单击HelloWorld函数的...,然后单击日志,如下面的屏幕截图所示:图 2.42:函数的设置菜单
图 2.42:函数的设置菜单

您将被重定向到函数的日志,您将看到,每分钟,函数执行开始和相应的成功日志被列出,如下面的屏幕截图所示:

图 2.43:函数日志

图 2.43:函数日志

正如您所看到的,云调度程序正在按计划调用函数,并且函数正在成功运行。

  1. 从第 13 步返回到云调度程序页面,选择HelloWorldEveryMinute,在菜单上单击删除,然后在弹出窗口中确认,如下面的屏幕截图所示:图 2.44:云调度程序-作业删除
图 2.44:云调度程序-作业删除
  1. 从第 7 步返回到Cloud Functions页面,选择HelloWorld,在菜单上单击删除,然后在弹出窗口中确认,如下面的屏幕截图所示:图 2.45:云函数-函数删除
图 2.45:云函数-函数删除

在这个练习中,我们创建了一个Hello World功能并将其部署到 GCF。此外,还创建了一个云调度程序作业,以特定的间隔触发该功能,比如每分钟一次。现在,该功能已连接到另一个云服务,以便该功能可以触发该服务。在选择云 FaaS 提供商之前,将功能与其他云服务集成并评估其集成能力是至关重要的。

在以下活动中,您将开发一个真实的每日站立提醒功能。您将连接一个您希望在特定站立会议时间调用的功能和功能触发服务。此外,这个提醒将发送一个特定的消息到一个基于云的协作工具,即Slack

活动 2:Slack 每日站立会议提醒功能

这个活动的目的是在 Slack 中创建一个真实的站立会议提醒功能。这个提醒功能将在特定时间被调用,以提醒您团队中的每个人下一次站立会议。提醒将与 Slack 一起工作,因为它是一种受到全球许多组织采用的流行协作工具。

注意

为了完成这个活动,您需要访问 Slack 的工作区。您可以在slack.com/create免费使用现有的 Slack 工作区或创建一个新的。

完成后,您将部署每日站立提醒功能到 GCF,如下截图所示:

图 2.46:每日提醒功能

图 2.46:每日提醒功能

此外,您还需要一个集成环境来在指定的会议时间调用该功能。站立会议通常在工作日的特定时间举行。因此,调度程序作业将被连接以根据您的会议时间触发您的功能,如下截图所示:

图 2.47:每日提醒调度程序

图 2.47:每日提醒调度程序

最后,当调度程序调用该功能时,您将在 Slack 频道中收到提醒消息,如下截图所示:

图 2.48:Slack 会议提醒消息

图 2.48:Slack 会议提醒消息

注意

为了完成这个活动,您应该按照 Slack 设置步骤配置 Slack。

Slack 设置

执行以下步骤配置 Slack:

  1. 在 Slack 工作区中,点击您的用户名,然后选择自定义 Slack。

  2. 在打开的窗口中点击配置应用

  3. 点击浏览应用目录以从目录中添加新应用。

  4. 应用目录的搜索框中找到传入 WebHooks

  5. 点击添加配置以添加传入 WebHooks应用。

  6. 填写传入 Webhook 的配置,包括您特定的频道名称和图标。

  7. 打开您的 Slack 工作区和频道。您会看到一个集成消息。

注意

在第 376 页可以找到 Slack 设置步骤的详细截图。

执行以下步骤完成此活动:

  1. 在 GCF 中创建一个新函数,在调用时调用 Slack Webhook。

代码应该向 Slack Webhook URL 发送一个类似的 JSON 请求对象:{"text": "Time for a stand-up meeting"}。您可以使用 GCF 支持的任何语言来实现代码。代码片段如下:

package p
import (
    "bytes"
    "net/http"
)
func Reminder(http.ResponseWriter, *http.Request) {
    url := "https://hooks.slack.com/services/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"

    var jsonStr = []byte('{"text": "Time for a stand-up meeting!"}')
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))

    client := &http.Client{}
    _, err = client.Do(req)
    if err != nil {
        panic(err)
    }
}
  1. 在 GCP 中使用函数的触发 URL 创建一个调度程序作业,并根据您的站立会议时间指定调度。

在提醒消息的预定时间到达时,检查 Slack 频道。

  1. 从云提供商中删除调度作业和函数。

注意

此活动的解决方案可在第 376 页找到。

摘要

在本章中,我们描述了云技术产品的演变,包括云产品多年来的变化以及责任如何在组织之间分配,从 IaaS 和 PaaS 开始,最终到 FaaS。随后,介绍了评估无服务器云产品的标准。

编程语言支持、函数触发器和无服务器产品的成本结构被列出,以便我们可以比较各个云提供商,即 AWS Lambda、Azure Functions 和 GCF。此外,我们将无服务器函数部署到了所有三个云提供商。这展示了云函数如何与其他云服务集成,比如用于 REST API 操作的 AWS API Gateway。此外,我们部署了一个参数化函数到 Azure Functions,以展示我们如何处理来自用户或其他系统的输入。最后,我们部署了一个定时函数到 GCF,以展示与其他云服务的集成。在本章末尾,我们使用无服务器函数和云调度程序实现了一个真实的 Slack 提醒。

在下一章中,我们将介绍无服务器框架,并学习如何与它们一起工作。

第三章: 无服务器框架简介

学习目标

通过本章的学习,你将能够:

  • 比较并有效地利用不同的无服务器函数

  • 建立一个与云无关且容器本地的无服务器框架

  • 使用 Fn 框架创建、部署和调用一个函数

  • 使用无服务器框架将无服务器函数部署到云提供商

  • 在未来在多个云平台上创建一个真实的无服务器应用程序并运行它

在本章中,我们将解释无服务器框架,使用这些框架创建我们的第一个无服务器函数,并将它们部署到各种云提供商。

介绍

让我们想象一下,你正在开发一个在一个云提供商中有许多函数的复杂应用程序。即使新的云提供商更便宜、更快或更安全,也可能无法迁移。这种供应商依赖的情况在行业中被称为供应商锁定,这在长期来看是一个非常关键的决策因素。幸运的是,无服务器框架是供应商锁定的一个简单而有效的解决方案。

在上一章中,讨论了所有三个主要的云提供商及其无服务器产品。这些产品是基于它们的编程语言支持、触发能力和成本结构进行比较的。然而,所有三个产品之间仍然存在一个看不见的关键差异:运维。在每个云提供商中,创建函数、部署它们以及它们的管理都是不同的。换句话说,你不能在 AWS Lambda、Google Cloud Functions 和 Azure Functions 中使用相同的函数。需要进行各种更改,以满足云提供商及其运行时的要求。

无服务器框架是用于运行无服务器应用程序的开源、与云无关的平台。云提供商和无服务器产品之间的第一个区别是,它们的无服务器框架是开源和公开的。它们可以免费安装在云上或本地系统上,并且可以独立运行。第二个特点是无服务器框架是与云无关的。这意味着可以在不同的云提供商或自己的系统上运行相同的无服务器函数。换句话说,函数将在哪个云提供商上执行只是无服务器框架中的一个配置参数。所有云提供商都在共享 API 后面被平等化,以便无服务器框架可以开发和部署与云无关的函数。

像 AWS Lambda 这样的云无服务器平台增加了无服务器架构的热度,并促进了其在行业中的采用。在前一章中,深入讨论了多年来云技术产品的演变和重要的云无服务器平台。在本章中,我们将讨论开源无服务器框架,并谈论它们的特色和功能。市场上有许多受欢迎的和即将推出的无服务器框架。然而,我们将重点关注两个在优先级和架构方面有所不同的杰出框架。在本章中,将介绍一个容器本地化的无服务器框架,即 Fn。随后,将深入讨论一个具有多个云提供商支持的更全面的框架,即 Serverless Framework。尽管这两个框架都为运行无服务器应用程序创建了一个与云无关且开源的环境,但它们在实施和开发者体验方面的差异将被说明。

Fn 框架

Fn 是由 Oracle 在 2017 年的 JavaOne 2017 大会上宣布的,是一个面向事件驱动和开源的函数即服务(FaaS)平台。该框架的关键特点如下:

  • 开源:Fn 项目的所有源代码都可以在github.com/fnproject/fn上公开获取,并且该项目托管在fnproject.io上。它在 GitHub 上有一个活跃的社区,有超过 3300 次提交和 1100 次发布,如下面的截图所示:

图 3.1:Fn 在 GitHub 上

图 3.1:Fn 在 GitHub 上
  • 容器本地: 容器和微服务改变了软件开发和运维的方式。Fn是容器本地的,意味着每个函数都被打包并部署为 Docker 容器。此外,您可以创建自己的 Docker 容器并将其作为函数运行。

  • 语言支持: 该框架正式支持GoJavaNode.jsRubyPython。此外,C#由社区支持。

  • 与云无关: 只要安装并运行 Docker,Fn就可以在每个云提供商或本地系统上运行。这是Fn最关键的特性,因为它完全避免了供应商锁定问题。如果函数不依赖于任何特定于云的服务,就可以快速在云提供商和本地系统之间移动。

作为一个与云无关和容器本地的平台,Fn是一个面向开发人员的框架。它增强了开发人员的体验和灵活性,因为您可以在本地开发、测试和调试,并使用相同的工具部署到云端。在接下来的练习中,我们将安装和配置Fn,以便开始使用该框架。

注意

在开始下一个练习之前,您的计算机上应安装并运行 Docker 17.10.0-ce或更高版本,因为这是Fn的先决条件。

练习 7:使用 Fn 框架入门

在这个练习中,您将在本地计算机上安装和配置一个与云无关和容器本地的无服务器框架。此练习的目的是演示配置和安装 Fn 框架是多么简单,以便您可以开始使用无服务器框架。

要成功完成此练习,我们需要确保执行以下步骤:

  1. 在您的终端中,键入以下命令:
curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh

此命令将下载并安装 Fn 框架。完成后,版本号将被打印出来,如下截图所示:

图 3.2:Fn 的安装

图 3.2:Fn 的安装
  1. 使用以下命令在您的终端中启动Fn服务器:
fn start -d

此命令将下载Fn服务器的 Docker 镜像,并在容器内启动,如下截图所示:

图 3.3:启动 Fn 服务器

图 3.3:启动 Fn 服务器
  1. 使用以下命令在您的终端中检查客户端和服务器版本:
fn version

输出应如下所示:

图 3.4:Fn 服务器和客户端版本

图 3.4:Fn 服务器和客户端版本

这个输出显示客户端和服务器端都在运行并相互交互。

  1. 更新当前的 Fn 上下文并设置本地开发注册表:
fn use context default && fn update context registry serverless

输出如下截图所示:

图 3.5:当前上下文的注册表设置

图 3.5:当前上下文的注册表设置

如输出所示,设置了default上下文,并将注册表更新为serverless

  1. 使用以下命令在终端中启动Fn仪表板:
docker run -d --link fnserver:api -p 4000:4000 -e "FN_API_URL=http://api:8080" fnproject/ui

这个命令下载fnproject/ui镜像并以detached模式启动。此外,它将fnserver:api链接到自身并发布4000端口,如下截图所示:

图 3.6:启动 Fn UI

图 3.6:启动 Fn UI
  1. 使用以下命令检查正在运行的 Docker 容器:
docker ps

如预期的那样,有两个运行中的Fn容器,分别使用镜像名称fnproject/uifnproject/fnserver:latest,如下截图所示:

图 3.7:Docker 容器

图 3.7:Docker 容器
  1. 在浏览器中打开http://localhost:4000来检查 Fn UI。

Fn 仪表板列出了应用程序和函数统计信息,作为一个 web 应用程序,如下截图所示:

图 3.8:Fn 仪表板

图 3.8:Fn 仪表板

通过这个练习,我们已经安装了Fn框架,以及它的客户端、服务器和仪表板。由于Fn是一个与云无关的框架,可以使用所示步骤安装任何云或本地系统。我们将继续讨论Fn框架,讨论函数如何配置和部署。

Fn框架设计用于处理应用程序,其中每个应用程序都是一组具有自己路由映射的函数。例如,假设您已将函数分组到一个文件夹中,如下所示:

- app.yaml
- func.yaml
- func.go
- go.mod
- products/
  - func.yaml
  - func.js
- suppliers/
  - func.yaml
  - func.rb

在每个文件夹中,都有一个func.yaml文件,用于定义对应的RubyNode.js或其他支持的语言的函数实现。此外,根文件夹中还有一个app.yaml文件用于定义应用程序。

让我们从检查app.yaml的内容开始:

name: serverless-app

app.yaml用于定义无服务器应用程序的根目录,并包括应用程序的名称。根文件夹中还有三个额外的文件用于函数:

  • func.go:Go 实现代码

  • go.mod:Go 依赖定义

  • func.yaml:函数定义和触发器信息

对于带有 HTTP 触发器和 Go 运行时的函数,定义了以下func.yaml文件:

name: serverless-app
version: 0.0.1
runtime: go
entrypoint: ./func
triggers:
- name: serverless-app
  type: http
  source: /serverless-app

当您将所有这些函数部署到 Fn 时,它们将通过以下 URL 可访问:

http://serverless-kubernetes.io/ 		-> root function
http://serverless-kubernetes.io/products 	-> function in products/ directory
http://serverless-kubernetes.io/suppliers 	-> function in suppliers/ directory

在下一个练习中,将用一个真实的例子来说明app.yamlfunc.yaml文件的内容以及它们的函数实现。

练习 8:在 Fn 框架中运行函数

在这个练习中,我们的目标是使用Fn框架创建、部署和调用一个函数。

要成功完成这个练习,我们需要确保执行以下步骤:

  1. 在您的终端中,运行以下命令来创建一个应用程序:
mkdir serverless-app
cd serverless-app
echo "name: serverless-app" > app.yaml
cat app.yaml

输出应该如下所示:

图 3.9:创建应用程序

图 3.9:创建应用程序

这些命令创建一个名为serverless-app的文件夹,然后更改目录,使其位于此文件夹中。最后,创建一个名为app.yaml的文件,其中包含内容name: serverless-app,用于定义应用程序的根目录。

  1. 在您的终端中运行以下命令,以创建一个在应用程序 URL 的"/"处可用的根函数:
fn init --runtime ruby --trigger http

该命令将在应用程序的根目录创建一个带有 HTTP 触发器的 Ruby 函数,如下面的屏幕截图所示:

图 3.10:Ruby 函数创建

图 3.10:Ruby 函数创建
  1. 使用以下命令在终端中创建一个子函数:
fn init --runtime go --trigger http hello-world

该命令在应用程序的hello-world文件夹中初始化一个带有 HTTP 触发器的 Go 函数,如下面的屏幕截图所示:

图 3.11:Go 函数创建

图 3.11:Go 函数创建
  1. 在终端中使用以下命令检查应用程序的目录:
ls -l ./*

该命令列出了根文件夹和子文件夹中的文件,如下面的屏幕截图所示:

图 3.12:文件夹结构

图 3.12:文件夹结构

如预期的那样,在根文件夹中有一个 Ruby 函数,包含三个文件:func.rb用于实现,func.yaml用于函数定义,Gemfile用于定义 Ruby 函数依赖。

同样,在hello-world文件夹中有一个 Go 函数,包含三个文件:func.go用于实现,func.yaml用于函数定义,go.mod用于 Go 依赖。

  1. 使用以下命令在终端中部署整个应用程序:
fn deploy --create-app --all --local

此命令通过创建应用程序并使用本地开发环境部署所有函数,如下截图所示:

图 3.13:应用程序部署到 Fn

图 3.13:应用程序部署到 Fn

首先,构建serverless-app的函数,然后创建函数和触发器。同样,构建并部署hello-world函数以及相应的函数和触发器。

  1. 使用以下命令列出应用程序的触发器,并复制serverless-app-triggerhello-world-triggerEndpoints
fn list triggers serverless-app

此命令列出了serverless-app的触发器,以及函数、类型、源和端点信息,如下截图所示:

图 3.14:触发器列表

图 3.14:触发器列表
  1. 使用以下命令在终端中触发端点:

注意

对于curl命令,请不要忘记使用我们在步骤 5中复制的端点。

curl -d Ece http://localhost:8080/t/serverless-app/serverless-app

输出应该如下所示:

图 3.15:调用 serverless-app 触发器

图 3.15:调用 serverless-app 触发器

此命令将调用位于应用程序root处的serverless-app触发器。由于它是以name负载触发的,它会响应个人消息:Hello Ece!

curl http://localhost:8080/t/serverless-app/hello-world

此命令将调用hello-world触发器,没有任何负载,如预期的那样,它会响应Hello World,如下截图所示:

图 3.16:调用 hello-world 触发器

图 3.16:调用 hello-world 触发器
  1. 通过在浏览器中打开http://localhost:4000,从Fn仪表板中检查应用程序和函数统计信息。

在主屏幕上,可以看到您的应用程序及其整体统计信息,以及自动刷新的图表,如下截图所示:

图 3.17:Fn 仪表板-主页

图 3.17:Fn 仪表板-主页

单击应用程序列表中的serverless-app以查看有关应用程序功能的更多信息,如下截图所示:

图 3.18:Fn 仪表板-应用程序

图 3.18:Fn 仪表板-应用程序
  1. 在终端中使用以下命令停止Fn服务器:
fn stop

此命令将停止Fn服务器,包括所有函数实例,如下截图所示:

图 3.19:Fn 服务器停止

图 3.19:Fn 服务器停止

在本练习中,我们在Fn框架中创建了一个双函数应用程序并部署了它。我们向您展示了如何使用fn客户端将函数构建为 Docker 容器,并通过创建函数来调用函数的触发器。此外,还从Fn仪表板检查了函数的统计信息。作为一个面向容器的、与云无关的框架,该框架的函数是 Docker 容器,可以在任何云提供商或本地系统上运行。在下一节中,将介绍另一个无服务器框架,即Serverless Framework,它更专注于云提供商集成。

Serverless Framework

Serverless Framework 在 2015 年以JavaScript Amazon Web Services (JAWS)的名字宣布。最初是在 Node.js 中开发的,以使人们更容易开发 AWS Lambda 函数。同年,它将名称更改为Serverless Framework,并将其范围扩大到其他云提供商和无服务器框架,包括Google Cloud FunctionsAzure FunctionsApache OpenWhiskFn等等。

Serverless Framework 是开源的,其源代码可在 GitHub 上找到:github.com/serverless/serverless。如下截图所示,这是一个非常受欢迎的存储库,拥有超过 31,000 颗星:

图 3.20:Serverless Framework GitHub 存储库

图 3.20:Serverless Framework GitHub 存储库

该框架的官方网站可在serverless.com上找到,并提供广泛的文档、用例和示例。Serverless Framework 的主要特点可以分为四个主要主题:

  • 与云无关:Serverless Framework 旨在创建一个与云无关的无服务器应用程序开发环境,因此供应商锁定不是一个问题。

  • 可重用组件:在 Serverless Framework 中开发的无服务器函数是开源的并可用。这些组件帮助我们快速创建复杂的应用程序。

  • 基础设施即代码:在 Serverless Framework 中开发的所有配置和源代码都是明确定义的,并且可以通过单个命令部署。

  • 开发者体验:Serverless Framework 旨在通过其 CLI、配置参数和活跃的社区来增强开发者体验。

Serverless Framework 的这四个特点使其成为创建云中无服务器应用程序最知名的框架。此外,该框架专注于管理无服务器应用程序的完整生命周期:

  • 开发:可以在本地开发应用程序,并通过框架 CLI 重用开源插件。

  • 部署:Serverless Framework 可以部署到多个云平台,并从开发到生产中推出和回滚版本。

  • 测试:该框架支持使用命令行客户端函数直接测试函数。

  • 安全性:该框架处理运行函数的秘密和部署的特定于云的身份验证密钥。

  • 监控:无服务器应用程序的指标和日志可通过无服务器运行时和客户端工具获得。

在接下来的练习中,将使用 Serverless Framework 在 Docker 容器内创建、配置和部署一个无服务器应用程序到 AWS,以展示使用无服务器应用程序有多么容易。

注意

Serverless Framework 可以通过npm下载并安装到本地计算机上。在接下来的练习中,将使用包含 Serverless Framework 安装的 Docker 容器,以便我们拥有快速且可重复的设置。

在接下来的练习中,将使用 Serverless Framework 将hello-world函数部署到 AWS Lambda。为了完成这个练习,您需要拥有一个活跃的亚马逊网络服务账户。您可以在aws.amazon.com/上创建一个账户。

练习 9:使用 Serverless Framework 运行函数

在这个练习中,我们的目标是配置 Serverless 框架并使用它部署我们的第一个函数。使用 Serverless 框架,可以创建与云无关的无服务器应用程序。在这个练习中,我们将把函数部署到 AWS Lambda。但是,也可以将相同的函数部署到不同的云提供商。

要成功完成这个练习,我们需要确保执行以下步骤:

  1. 在你的终端中,运行以下命令启动 Serverless 框架开发环境:
docker run -it --entrypoint=bash onuryilmaz/serverless

该命令将以交互模式启动一个 Docker 容器。在接下来的步骤中,将在这个 Docker 容器内执行操作,如下截图所示:

图 3.21:启动无服务器的 Docker 容器

图 3.21:启动无服务器的 Docker 容器
  1. 运行以下命令检查框架版本:
serverless version

该命令列出了框架、插件和 SDK 版本,并且完整的输出表明一切都设置正确,如下截图所示:

图 3.22:框架版本

图 3.22:框架版本
  1. 运行以下命令以交互方式使用框架:
serverless

按下Y创建一个新项目,并从下拉菜单中选择AWS Node.js,如下截图所示:

图 3.23:在框架中创建一个新项目

图 3.23:在框架中创建一个新项目
  1. 将项目名称设置为hello-world,然后按下Enter。输出如下:图 3.24:成功创建项目
图 3.24:成功创建项目
  1. 按下Y回答 AWS 凭证设置问题,然后再次按下Y回答您是否有 AWS 账户?的问题。输出如下:图 3.25:AWS 账户设置
图 3.25:AWS 账户设置

现在你有一个用于创建无服务器用户的 URL。复制并保存这个 URL;我们以后会用到它。

  1. 在浏览器中打开步骤 4中的 URL,并开始向 AWS 控制台添加用户。该 URL 将打开预定义选择的添加用户屏幕。点击屏幕末尾的下一步:权限,如下截图所示:
图 3.26:AWS 添加用户
  1. AdministratorAccess策略应该会自动选择。如下面的屏幕截图所示,单击屏幕底部的“下一步:标签”:图 3.27:AWS 添加用户-权限
图 3.27:AWS 添加用户-权限
  1. 如果您想要给用户打标签,您可以在此视图中添加可选标签。单击“下一步:审核”,如下面的屏幕截图所示:图 3.28:AWS 添加用户-标签
图 3.28:AWS 添加用户-标签
  1. 此视图显示了新用户的摘要。如下面的屏幕截图所示,单击“创建用户”:图 3.29:AWS 添加用户-审核
图 3.29:AWS 添加用户-审核

您将被重定向到一个成功页面,显示访问密钥 ID秘密,如下面的屏幕截图所示:

图 3.30:AWS 添加用户-成功

图 3.30:AWS 添加用户-成功
  1. 复制密钥 ID 和秘密访问密钥,以便您可以在本练习的后续步骤和本章的活动中使用它。您需要单击“显示”以显示秘密访问密钥。

  2. 返回到您的终端并按Enter输入密钥 ID 和秘密信息,如下面的屏幕截图所示:图 3.31:框架中的 AWS 凭据

图 3.31:框架中的 AWS 凭据
  1. 按下Y回答 Serverless 账户启用问题,并从下拉菜单中选择注册,如下面的屏幕截图所示:图 3.32:Serverless 账户已启用
图 3.32:Serverless 账户已启用
  1. 输入您的电子邮件和密码以创建 Serverless 框架账户,如下面的屏幕截图所示:图 3.33:Serverless 账户注册
图 3.33:Serverless 账户注册
  1. 运行以下命令以更改目录并部署函数:
cd hello-world
serverless deploy -v 

这些命令将使 Serverless Framework 将函数部署到 AWS,如下面的屏幕截图所示:

图 3.34:Serverless 框架部署输出

图 3.34:Serverless 框架部署输出

注意

输出日志从打包服务和为源代码、工件和函数创建 AWS 资源开始。在创建了所有资源之后,“服务信息”部分将提供函数和 URL 的摘要。

在屏幕底部,您将找到部署函数的无服务器仪表板 URL,如下截图所示:

图 3.35:堆栈输出

图 3.35:堆栈输出

复制仪表板 URL,以便在接下来的步骤中检查函数指标。

  1. 使用以下命令在终端中调用函数:
 serverless invoke --function hello

此命令调用部署的函数并打印出响应,如下截图所示:

图 3.36:函数输出

图 3.36:函数输出

如输出所示,statusCode200,响应的正文表明函数已成功响应。

  1. 在浏览器中打开您在第 8 步末尾复制的无服务器仪表板 URL,如下截图所示:图 3.37:无服务器仪表板登录
图 3.37:无服务器仪表板登录
  1. 使用您在步骤 5中创建的电子邮件和密码登录。

您将被重定向到应用程序列表。展开hello-world-app并点击成功部署行,如下截图所示:

图 3.38:无服务器仪表板应用程序列表

图 3.38:无服务器仪表板应用程序列表

在函数视图中,所有运行时信息,包括 API 端点、变量、警报和指标都可用。向下滚动以查看调用次数。输出应如下所示:

图 3.39:无服务器仪表板函数视图

图 3.39:无服务器仪表板函数视图

由于我们只调用了函数一次,因此在图表中只会看到1

  1. 返回到您的终端,并使用以下命令删除函数:
serverless remove

此命令将删除部署的函数及其所有依赖项,如下截图所示:

图 3.40:移除函数

图 3.40:移除函数

通过在终端中输入exit退出无服务器框架开发环境容器,如下截图所示:

图 3.41:退出容器

在这个练习中,我们使用 Serverless Framework 创建、配置和部署了一个无服务器函数。此外,该函数是通过 CLI 调用的,并且可以从 Serverless Dashboard 检查其指标。Serverless Framework 为云提供商创建了一个全面的抽象,因此它只作为凭据传递给平台。换句话说,部署在哪里只是借助无服务器框架的帮助进行配置的问题。

在接下来的活动中,将开发一个真实的无服务器每日天气应用程序。您将创建一个带有调用计划的无服务器框架应用程序,并将其部署到云提供商。此外,天气状况消息将发送到一个名为Slack的基于云的协作工具。

注意

为了完成接下来的活动,您需要能够访问 Slack 工作区。您可以使用现有的 Slack 工作区,也可以免费创建一个新的工作区,网址为slack.com/create

活动 3:Slack 的每日天气状况功能

本活动的目的是创建一个真实的无服务器应用程序,可以在特定的Slack频道中发送天气状况消息。该函数将使用Serverless Framework开发,以便将来可以在多个云平台上运行。该函数将被设计为在团队特定时间运行,以便他们了解天气状况,比如在早上上班前。这些消息将发布在Slack频道上,这是团队内的主要沟通工具。

为了获取天气状况以在团队内共享,您可以使用wttr.ingithub.com/chubin/wttr.in),这是一个免费使用的天气数据提供商。完成后,您将已经将一个函数部署到了云提供商,即AWS Lambda

图 3.42:每日天气功能

图 3.42:每日天气功能

最后,当调度程序调用该函数,或者当您手动调用它时,您将在 Slack 频道中收到有关当前天气状况的消息:

图 3.43:Slack 消息显示当前天气状况

图 3.43:Slack 消息显示当前天气状况

注意

为了完成这个活动,您应该按照 Slack 设置步骤配置 Slack。

Slack 设置

执行以下步骤配置 Slack:

  1. 在您的 Slack 工作区中,单击您的用户名,然后选择自定义 Slack

  2. 在打开的窗口中单击配置应用程序

  3. 单击浏览应用程序目录以从目录中添加新应用程序。

  4. 在应用程序目录的搜索框中找到传入 WebHooks

  5. 单击设置以设置传入 WebHooks应用程序。

  6. 使用您特定的频道名称和图标填写传入 Webhooks 的配置。

  7. 打开您的 Slack 工作区和您在第 6 步中配置的频道,以便检查集成消息。

注意

在第 387 页可以找到 Slack 设置步骤的详细截图。

执行以下步骤以完成此活动。

  1. 在您的终端中,在名为daily-weather的文件夹中创建一个 Serverless Framework 应用程序结构。

  2. 创建一个package.json文件来定义daily-weather文件夹中的 Node.js 环境。

  3. 创建一个handler.js文件来实现daily-weather文件夹中的实际功能。

  4. 为无服务器应用程序安装 Node.js 依赖项。

  5. 将 AWS 凭据导出为环境变量。

  6. 使用 Serverless Framework 将无服务器应用程序部署到 AWS。

  7. 在 AWS 控制台中检查已部署函数的 AWS Lambda。

  8. 使用 Serverless Framework 客户端工具调用函数。

  9. 检查发布的天气状态的 Slack 频道。

  10. 返回到您的终端并使用 Serverless Framework 删除函数。

  11. 退出 Serverless Framework 开发环境容器。

注意

此活动的解决方案可以在第 387 页找到。

摘要

在本章中,我们通过讨论云提供商的无服务器产品之间的差异,提供了无服务器框架的概述。在此之后,我们深入讨论了一个基于容器的原生框架和一个基于云的原生框架。首先讨论了Fn框架,这是一个开源的、基于容器的、与云无关的平台。其次介绍了 Serverless Framework,这是一个更加注重云和更全面的框架。此外,我们还在本地安装和配置了这两个框架。在两个无服务器框架中创建、部署和运行了无服务器应用程序。使用无服务器框架的功能进行调用,并检查必要的指标以进行进一步分析。在本章的最后,我们实现了一个真实的、每日天气 Slack 机器人,作为一个明确定义的、与云无关的应用程序,使用了无服务器框架。无服务器框架以其与云无关和开发者友好的特性,对无服务器开发世界至关重要。

第四章: Kubernetes 深入探讨

学习目标

到本章结束时,您将能够:

  • 在计算机上设置本地 Kubernetes 集群

  • 使用仪表板和终端访问 Kubernetes 集群

  • 识别基本的 Kubernetes 资源,Kubernetes 应用程序的构建模块

  • 在 Kubernetes 集群上安装复杂的应用程序

在本章中,我们将解释 Kubernetes 架构的基础知识,访问 Kubernetes API 的方法以及基本的 Kubernetes 资源。除此之外,我们还将在 Kubernetes 中部署一个真实的应用程序。

Kubernetes 简介

在上一章中,我们学习了无服务器框架,使用这些框架创建了无服务器应用程序,并将这些应用程序部署到主要的云提供商。

正如我们在前几章中所看到的,Kubernetes 和无服务器架构在行业中开始同时受到关注。Kubernetes 以其基于可扩展性、高可用性和可移植性的设计原则获得了高度的采用,并成为事实上的容器管理系统。对于无服务器应用程序,Kubernetes 提供了两个基本的好处:消除供应商锁定服务的重复使用

Kubernetes 创建了一个基础设施抽象层,以消除供应商锁定。供应商锁定是指从一个服务提供商转移到另一个服务提供商非常困难甚至不可行的情况。在上一章中,我们学习了无服务器框架如何轻松开发与云无关的无服务器应用程序。假设您正在AWS EC2实例上运行您的无服务器框架,并希望迁移到Google Cloud。尽管您的无服务器框架在云提供商和无服务器应用程序之间创建了一层,但您仍然对基础设施的云提供商有很深的依赖。Kubernetes 通过在基础设施和云提供商之间创建一个抽象来打破这种联系。换句话说,在 Kubernetes 上运行的无服务器框架对基础设施一无所知。如果您的无服务器框架在 AWS 上运行 Kubernetes,则预计它也可以在Google Cloud PlatformGCP)或 Azure 上运行。

作为事实上的容器管理系统,Kubernetes 管理云中和本地系统中的大多数微服务应用程序。假设您已经将大型单体应用程序转换为云原生微服务,并在 Kubernetes 上运行它们。现在,您已经开始开发无服务器应用程序或将一些微服务转换为无服务器纳米服务。在这个阶段,您的无服务器应用程序将需要访问数据和其他服务。如果您可以在 Kubernetes 集群中运行您的无服务器应用程序,您将有机会重复使用服务并接近您的数据。此外,管理和操作微服务和无服务器应用程序将更容易。

作为解决供应商锁定问题,并为了数据和服务的潜在重复使用,学习如何在 Kubernetes 上运行无服务器架构至关重要。在本章中,将介绍 Kubernetes 的概述,介绍 Kubernetes 的起源和设计。接下来,我们将安装一个本地 Kubernetes 集群,您将能够通过仪表板或使用kubectl等客户端工具访问集群。除此之外,我们还将讨论 Kubernetes 应用程序的构建模块,最后,我们将在集群中部署一个真实的应用程序。

Kubernetes 设计和组件

Kubernetes,也被称为k8s,是一个用于管理容器的平台。它是一个复杂的系统,专注于容器的完整生命周期,包括配置、安装、健康检查、故障排除和扩展。通过 Kubernetes,可以以可伸缩、灵活和可靠的方式运行微服务。假设您是一家金融科技公司的 DevOps 工程师,专注于为客户提供在线银行服务。

您可以以安全和云原生的方式配置和安装在线银行应用程序的完整后端和前端到 Kubernetes。通过 Kubernetes 控制器,您可以手动或自动地扩展服务,以满足客户需求。此外,您可以检查日志,对每个服务执行健康检查,甚至可以 SSH 到应用程序的容器中。

在本节中,我们将重点关注 Kubernetes 的设计以及其组件如何和谐地工作。

Kubernetes 集群由一个或多个服务器组成,每个服务器分配了一组逻辑角色。集群的服务器分配了两个基本角色:masternode。如果服务器处于master角色,则 Kubernetes 的控制平面组件运行在这些节点上。控制平面组件是用于运行 Kubernetes API 的主要服务集,包括 REST 操作、身份验证、授权、调度和云操作。在最新版本的 Kubernetes 中,有四个服务作为控制平面运行:

  • etcdetcd是一个开源的键/值存储,它是所有 Kubernetes 资源的数据库。

  • kube-apiserver:API 服务器是运行 Kubernetes REST API 的组件。这是与飞机其他部分和客户端工具交互的最关键组件。

  • kube-scheduler:调度程序根据工作负载的要求和节点状态将工作负载分配给节点。

  • kube-controller-managerkube-controller-manager是用于管理 Kubernetes 资源的核心控制器的控制平面组件。控制器是 Kubernetes 资源的主要生命周期管理器。对于每个 Kubernetes 资源,都有一个或多个控制器在图 4.1中的观察决策执行循环中工作。控制器在观察阶段检查资源的当前状态,然后分析并决定达到所需状态所需的操作。在执行阶段,它们执行操作并继续观察资源。图 4.1:Kubernetes 中的控制器循环

图 4.1:Kubernetes 中的控制器循环

具有node角色的服务器负责在 Kubernetes 中运行工作负载。因此,每个节点都需要两个基本的 Kubernetes 组件:

  • kubeletkubelet是节点中控制平面的管理网关。kubelet与 API 服务器通信并在节点上实施所需的操作。例如,当将新的工作负载分配给节点时,kubelet通过与容器运行时(如 Docker)交互来创建容器。

  • kube-proxy:容器在服务器节点上运行,但它们在统一的网络设置中相互交互。kube-proxy使容器能够通信,尽管它们在不同的节点上运行。

控制平面和角色(如主节点和工作节点)是组件的逻辑分组。然而,建议使用具有多个主节点角色服务器的高可用控制平面。此外,具有节点角色的服务器连接到控制平面,以创建可扩展和云原生环境。控制平面与主节点服务器和节点服务器的关系和交互如下图所示:

图 4.2:Kubernetes 集群中的控制平面、主节点和节点服务器

图 4.2:Kubernetes 集群中的控制平面、主节点和节点服务器

在接下来的练习中,将在本地创建一个 Kubernetes 集群,并检查 Kubernetes 组件。Kubernetes 集群是具有主节点或工作节点的服务器集合。在这些节点上,控制平面组件和用户应用程序以可扩展和高可用的方式运行。借助本地 Kubernetes 集群工具,可以创建用于开发和测试的单节点集群。minikube是官方支持和维护的本地 Kubernetes 解决方案,并将在接下来的练习中使用。

注意:...]

在接下来的练习中,您将使用minikube作为官方本地 Kubernetes 解决方案,并在虚拟化程序上运行 Kubernetes 组件。因此,您必须安装虚拟化程序,如VirtualboxParallelsVMWareFusionHyperkitVMWare。有关更多信息,请参阅此链接:

kubernetes.io/docs/tasks/tools/install-minikube/#install-a-hypervisor

练习 10:启动本地 Kubernetes 集群

在本练习中,我们将安装minikube并使用它启动一个单节点 Kubernetes 集群。当集群启动并运行时,将可以检查主节点和节点组件。

为完成练习,需要确保执行以下步骤:

  1. 在终端中运行以下命令将minikube安装到本地系统:
# Linux
curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
# MacOS
curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64 
chmod +x minikube 
sudo mv minikube /usr/local/bin

这些命令下载minikube的二进制文件,使其可执行,并将其移动到bin文件夹以供终端访问。

  1. 通过运行以下命令启动minikube集群:
minikube start

此命令下载镜像并创建单节点虚拟机。随后,它配置该机器并等待 Kubernetes 控制平面启动,如下图所示:

图 4.3:在 minikube 中启动新集群

图 4.3:在 minikube 中启动新集群
  1. 检查 Kubernetes 集群的状态:

minikube status

如下图中的输出所示,主机系统、kubeletapiserver正在运行:

图 4.4:Kubernetes 集群状态

图 4.4:Kubernetes 集群状态
  1. 通过运行以下命令连接到minikube的虚拟机:
minikube ssh

您应该看到以下图中显示的输出:

图 4.5:minikube 虚拟机

图 4.5:minikube 虚拟机
  1. 使用以下命令检查四个控制平面组件:
pgrep -l etcd && pgrep -l kube-apiserver && pgrep -l kube-scheduler && pgrep -l controller

此命令列出进程并捕获所提到的命令名称。每个控制平面组件及其进程 ID 对应四行,如下图所示:

图 4.6:控制平面组件

图 4.6:控制平面组件
  1. 使用以下命令检查节点组件:
pgrep -l kubelet && pgrep -l kube-proxy

此命令列出了在节点角色中运行的两个组件及其进程 ID,如下图所示:

图 4.7:节点组件

图 4.7:节点组件
  1. 使用以下命令退出步骤 4中启动的终端:
exit

您应该看到以下图中显示的输出:

图 4.8:退出 minikube 虚拟机

图 4.8:退出 minikube 虚拟机

在本练习中,我们使用minikube安装了单节点 Kubernetes 集群。在下一节中,我们将讨论使用 Kubernetes 的官方客户端工具连接到并操作前面练习中的集群。

Kubernetes 客户端工具:kubectl

Kubernetes 控制平面运行一个 REST API 服务器,用于访问 Kubernetes 资源和进行操作活动。Kubernetes 配备了一个名为kubectl的官方开源命令行工具,以便消费 REST API。它安装在本地系统上,并配置为安全可靠地连接远程集群。kubectl是 Kubernetes 中运行应用程序的完整生命周期的主要工具。例如,假设您在集群中部署了一个WordPress博客。首先,您可以使用kubectl创建数据库密码作为 secrets。然后,您部署博客应用程序并检查其状态。除此之外,您还可以跟踪应用程序的日志,甚至可以 SSH 到容器进行进一步分析。因此,它是一个强大的 CLI 工具,可以处理基本的创建、读取、更新和删除(CRUD)操作和故障排除。

除了应用程序管理外,kubectl还是集群操作的强大工具。可以使用kubectl检查 Kubernetes API 状态或集群中服务器的状态。假设您需要重新启动集群中的服务器,并且需要将工作负载移动到其他节点。使用kubectl命令,您可以将节点标记为不可调度,并让 Kubernetes 调度程序将工作负载移动到其他节点。完成维护后,您可以将节点标记为Ready,并让 Kubernetes 调度程序分配工作负载。

kubectl是日常 Kubernetes 操作的重要命令行工具。因此,学习基础知识并获得kubectl的实际经验至关重要。在接下来的练习中,您将安装和配置kubectl以连接到本地 Kubernetes 集群。

练习 11:使用客户端工具 kubectl 访问 Kubernetes 集群

在这个练习中,我们旨在使用kubectl访问 Kubernetes API 并探索其功能。

为了完成练习,我们需要确保执行以下步骤:

  1. 通过在终端中运行以下命令下载kubectl可执行文件:
# Linux
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.15.0/bin/linux/amd64/kubectl
# MacOS
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.15.0/bin/darwin/amd64/kubectl
chmod +x kubectl
sudo mv kubectl /usr/local/bin

这些命令下载kubectl的二进制文件,使其可执行,并将其移动到bin文件夹以供终端访问。

  1. 配置kubectl以连接到minikube集群:
kubectl config use-context minikube

此命令配置kubectl以使用minikube上下文,该上下文是用于连接到kubectl集群的一组凭据,如下图所示:

图 4.9:kubectl 上下文设置

图 4.9:kubectl 上下文设置
  1. 使用以下命令检查可用节点:
 kubectl get nodes

此命令列出了连接到集群的所有节点。作为单节点集群,只有一个名为minikube的节点,如下图所示:

图 4.10:kubectl get nodes

图 4.10:kubectl get nodes
  1. 使用以下命令获取有关minikube节点的更多信息:

kubectl describe node minikube

此命令列出有关节点的所有信息,从其元数据开始,例如RolesLabelsAnnotations。此节点的角色在Roles部分中被指定为master,如下图所示:

图 4.11:节点元数据

图 4.11:节点元数据

在元数据之后,Conditions 列出了节点的健康状态。可以以表格形式检查可用内存、磁盘和进程 ID,如下图所示。

图 4.12:节点条件

图 4.12:节点条件

然后,列出可用和可分配的容量以及系统信息,如下图所示:

图 4.13:节点容量信息

图 4.13:节点容量信息

最后,列出了节点上运行的工作负载和分配的资源,如下图所示:

图 4.14:节点工作负载信息

图 4.14:节点工作负载信息
  1. 使用以下命令获取支持的 API 资源:
kubectl api-resources -o name

您应该看到以下图中显示的输出:

图 4.15:kubectl api-resources 的输出

图 4.15:kubectl api-resources 的输出

此命令列出 Kubernetes 集群支持的所有资源。列表的长度表示了 Kubernetes 在应用程序管理方面的强大和全面性。在本练习中,安装、配置和探索了官方 Kubernetes 客户端工具。在接下来的部分中,将介绍资源列表中的核心构建块资源。

Kubernetes 资源

Kubernetes 配备了丰富的资源来定义和管理云原生应用程序作为容器。在 Kubernetes API 中,每个容器、秘钥、配置或自定义定义都被定义为资源。控制平面管理这些资源,而节点组件则尝试实现应用程序的期望状态。期望状态可能是运行 10 个应用程序实例或者挂载磁盘卷到数据库应用程序。控制平面和节点组件协同工作,使集群中的所有资源达到其期望状态。

在本节中,我们将学习用于运行无服务器应用程序的基本 Kubernetes 资源。

Pod

pod是 Kubernetes 中用于计算的基本资源。一个 pod 由安排在同一节点上运行的容器组成,作为单个应用程序。同一 pod 中的容器共享相同的资源,如网络和内存资源。此外,pod 中的容器共享生命周期事件,如扩展或缩减。可以使用ubuntu镜像和echo命令定义一个 pod,如下所示:

apiVersion: v1
kind: Pod
metadata:
 name: echo
spec:
 containers:
 - name: main
   image: ubuntu
   command: ['sh', '-c', 'echo Serverless World! && sleep 3600']

当在 Kubernetes API 中创建echo pod 时,调度程序将其分配给一个可用节点。然后,相应节点中的kubelet将创建一个容器并将网络连接到它。最后,容器将开始运行echosleep命令。Pod 是创建应用程序的基本 Kubernetes 资源,并且 Kubernetes 将它们用作更复杂资源的构建块。在接下来的资源中,pod 将被封装以创建更复杂的云原生应用程序。

部署

部署是管理高可用应用程序的最常用的 Kubernetes 资源。部署通过扩展 pod,使其能够进行扩展、缩减或者部署新版本。部署定义看起来类似于一个带有两个重要附加项的 pod:标签和副本。

考虑以下代码:

apiVersion: apps/v1
kind: Deployment
metadata:
 name: webserver
 labels:
   app: nginx
spec:
 replicas: 5
 selector:
   matchLabels:
     app: server
 template:
   metadata:
     labels:
       app: server
   spec:
     containers:
     - name: nginx
       image: nginx:1.7.9
       ports:
       - containerPort: 80 

名为webserver的部署定义了应用程序的五个副本,这些副本带有标签app:server。在模板部分,应用程序使用完全相同的标签和一个nginx容器进行定义。控制平面中的部署控制器确保集群内运行着这个应用程序的五个实例。假设你有三个节点,A、B 和 C,分别运行着一个、两个和两个 webserver 应用程序的实例。如果节点 C 下线,部署控制器将确保丢失的两个实例在节点 A 和 B 中重新创建。Kubernetes 确保可伸缩和高可用的应用程序作为部署可靠地运行。在接下来的部分中,将介绍用于有状态应用程序的 Kubernetes 资源,如数据库。

有状态集

Kubernetes 支持运行无状态的短暂应用程序和有状态的应用程序。换句话说,可以以可伸缩的方式在集群内运行数据库应用程序或面向磁盘的应用程序。StatefulSet的定义与部署类似,但有与卷相关的附加内容。

考虑以下代码片段:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "root"
        ports:
        - name: mysql
          containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

mysql StatefulSet状态创建了一个带有 1GB 卷数据的 MySQL 数据库。卷是由 Kubernetes 创建并附加到容器的/var/lib/mysql目录。通过StatefulSet控制器,可以以可伸缩和可靠的方式创建需要磁盘访问的应用程序。在接下来的部分中,我们将讨论如何在 Kubernetes 集群中连接应用程序。

服务

在 Kubernetes 中,多个应用程序在同一个集群中运行并相互连接。由于每个应用程序在不同节点上运行着多个 pod,因此连接应用程序并不是一件简单的事情。在 Kubernetes 中,Service是用于定义一组 pod 的资源,并且可以通过Service的名称来访问它们。Service 资源是使用 pod 的标签来定义的。

考虑以下代码片段:

apiVersion: v1
kind: Service
metadata:
  name: my-database
spec:
  selector:
    app: mysql
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306

使用my-database服务,具有标签app: mysql的 pod 被分组在一起。当调用my-database地址的3306端口时,Kubernetes 网络将连接到具有标签app:mysql的 pod 的3306端口。服务资源在应用程序之间创建了一个抽象层,并实现了解耦。假设您的应用程序中有三个后端实例和三个前端实例。前端 pod 可以使用Service资源轻松连接到后端实例,而无需知道后端实例运行在何处。它在集群中运行的应用程序之间创建了抽象和解耦。在接下来的部分中,将介绍关注任务和定时任务的资源。

Job 和 CronJob

Kubernetes 资源,如deploymentsStatefulSets,专注于运行应用程序并保持其运行。但是,Kubernetes 还提供了JobCronJob资源来完成应用程序的运行。例如,如果您的应用程序需要执行一次性任务,可以创建一个Job资源,如下所示:

apiVersion: batch/v1
kind: Job
metadata:
  name: echo
spec:
  template:
    spec:
      restartPolicy: OnFailure
      containers:
      - name: echo
        image: busybox
        args:
         - /bin/sh
         - -c
         - echo Hello from the echo Job!

当创建echo Job 时,Kubernetes 将创建一个 pod,对其进行调度并运行。当容器在执行echo命令后终止时,Kubernetes 不会尝试重新启动它或保持其运行。

除了一次性任务外,还可以使用CronJob资源来运行定时作业,如下面的代码片段所示。

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hourly-echo
spec:
  schedule: "0 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          restartPolicy: OnFailure
          - name: hello
            image: busybox
            args:
            - /bin/sh
            - -c
            - date; echo It is time to say echo!

使用hourly-echo CronJob,提供了一个额外的schedule参数。使用"0 * * * *"的计划,Kubernetes 将创建此 CronJob 的新 Job 实例,并每小时运行一次。Job 和 CronJob 是处理应用程序所需的手动和自动化任务的 Kubernetes 本机方式。在接下来的练习中,将使用kubectl和本地 Kubernetes 集群来探索 Kubernetes 资源。

练习 12:在 Kubernetes 内部安装有状态的 MySQL 数据库并进行连接

在这个练习中,我们将安装一个 MySQL 数据库作为StatefulSet,检查其状态,并使用一个用于创建表的作业连接到数据库。

要完成练习,我们需要确保执行以下步骤:

  1. 在本地计算机上创建一个名为mysql.yaml的文件,并包含以下内容:
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "root"
        - name: MYSQL_DATABASE
          value: "db"
        - name: MYSQL_USER
          value: "user"
        - name: MYSQL_PASSWORD
          value: "password"
        ports:
        - name: mysql
          containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

注意

mysql.yaml可在 GitHub 上找到github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson04/Exercise12/mysql.yaml

  1. 在终端中使用以下命令部署StatefulSet MySQL 数据库:
kubectl apply -f mysql.yaml

这个命令提交了mysql.yaml文件,其中包括一个名为mysqlStatefulSet和一个 1GB 的卷索赔。输出如下:

图 4.16:StatefulSet 创建

图 4.16:StatefulSet 创建
  1. 使用以下命令检查 pod:

kubectl get pods

这个命令列出了运行中的 pod,我们期望看到一个mysql实例,如下图所示:

图 4.17:Pod 列表

图 4.17:Pod 列表

注意

如果 pod 状态为Pending,请等待几分钟直到变为Running,然后再继续下一步。

  1. 使用以下命令检查持久卷:
kubectl get persistentvolumes

这个命令列出了持久卷,我们期望看到为StatefulSet创建的单卷实例,如下图所示:

图 4.18:持久卷列表

图 4.18:持久卷列表
  1. 使用以下内容创建service.yaml文件:
apiVersion: v1
kind: Service
metadata:
  name: my-database
spec:
  selector:
    app: mysql
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306

注意

service.yaml可在 GitHub 上找到github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson04/Exercise12/service.yaml

  1. 使用以下命令在终端中部署my-database服务:

kubectl apply -f service.yaml

这个命令提交了名为my-databaseService,以便将标签为app:mysql的 pod 分组:

图 4.19:服务创建

图 4.19:服务创建
  1. 使用以下内容创建create-table.yaml文件:
apiVersion: batch/v1
kind: Job
metadata:
  name: create-table
spec:
  template:
    spec:
      restartPolicy: OnFailure
      containers:
      - name: create
        image: mysql:5.7
        args:
         - /bin/sh
         - -c
         - mysql -h my-database -u user -ppassword db -e 'CREATE TABLE IF NOT EXISTS messages (id INT)';

注意

create-table.yaml可在 GitHub 上找到github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson04/Exercise12/create-table.yaml

  1. 使用以下命令部署作业:
kubectl apply -f create-table.yaml

此命令提交名为create-table的作业,并在几分钟内,将创建一个 pod 来运行CREATE TABLE命令,如下图所示:

图 4.20:作业创建

图 4.20:作业创建
  1. 使用以下命令检查 pod:

kubectl get pods

此命令列出正在运行的 pod,我们期望看到一个create-table的实例,如下图所示:

图 4.21:Pod 清单

图 4.21:Pod 清单

注意

如果 pod 状态为PendingRunning,请等待几分钟,直到它变为Completed,然后再继续下一步。

  1. 运行以下命令来检查 MySQL 数据库中的表格:
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never \
-- mysql -h my-database -u user -ppassword  db -e "show tables;"

此命令运行一个临时实例的mysql:5.7镜像,并运行mysql命令,如下图所示:

图 4.22:表格清单

图 4.22:表格清单

在 MySQL 数据库中,有一个名为messages的表格,如前面的输出所示。它显示MySQL StatefulSet已经成功运行数据库。此外,create-table作业已经创建了一个连接到数据库的 pod,并创建了表格。

  1. 通过运行以下命令清理资源:
kubectl delete -f create-table.yaml,service.yaml,mysql.yaml

您应该看到下图所示的输出:

图 4.23:清理

图 4.23:清理

在接下来的活动中,数据库将被自动化任务在 Kubernetes 中检索到的信息填充。

注意

在接下来的活动中,您将需要一个 Docker Hub 账户将图像推送到注册表中。Docker Hub 是一个免费的服务,您可以在hub.docker.com/signup注册。

活动 4:在 Kubernetes 中收集 MySQL 数据库中的黄金价格

这个活动的目的是创建一个在 Kubernetes 集群中运行的真实无服务器应用程序,使用 Kubernetes 本地资源。无服务器函数将从实时市场获取黄金价格,并将数据推送到数据库。该函数将以预定义的间隔运行,以保留历史记录并进行统计分析。黄金价格可以从CurrencyLayer API 中检索,该 API 提供免费的汇率 API。完成后,您将拥有一个每分钟运行的 CronJob:

注意

为了完成以下活动,您需要拥有 CurrencyLayer API 访问密钥。这是一个免费的货币和汇率服务,您可以在官方网站上注册。

图 4.24:用于黄金价格的 Kubernetes 作业

图 4.24:用于黄金价格的 Kubernetes 作业

最后,每次运行 Kubernetes 作业时,您将在数据库中获得实时黄金价格:

图 4.25:数据库中的价格数据

图 4.25:数据库中的价格数据

执行以下步骤来完成这个活动:

  1. 创建一个应用程序,从CurrencyLayer检索黄金价格并将其插入到 MySQL 数据库中。可以在main.go文件中使用以下结构来实现这个功能:
//only displaying the function here//
func main() {
    db, err := sql.Open("mysql", ...
    r, err := http.Get(fmt.Sprintf(„http://apilayer.net/api/...
    stmt, err := db.Prepare("INSERT INTO GoldPrices(price) VALUES(?)")_,       err = stmt.Exec(target.Quotes.USDXAU)
    log.Printf("Successfully inserted the price: %v", target.Quotes.
USDXAU)
}

main函数中,首先需要连接到数据库,然后从CurrencyLayer检索价格。然后需要创建一个 SQL 语句并在数据库连接上执行。main.go 的完整代码可以在这里找到:github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson04/Activity4/main.go

  1. 将应用程序构建为 Docker 容器。

  2. 将 Docker 容器推送到 Docker 注册表。

  3. 将 MySQL 数据库部署到 Kubernetes 集群中。

  4. 部署一个 Kubernetes 服务来暴露 MySQL 数据库。

  5. 部署一个CronJob,每分钟运行一次。

  6. 等待几分钟并检查CronJob的实例。

  7. 连接到数据库并检查条目。

  8. 从 Kubernetes 中清除数据库和自动化任务。

注意

活动的解决方案可以在第 403 页找到。

总结

在本章中,我们首先描述了 Kubernetes 的起源和特点。接着,我们研究了 Kubernetes 的设计和组件,包括主控组件和节点组件的细节。然后,我们安装了一个本地单节点的 Kubernetes 集群,并检查了 Kubernetes 的组件。在集群设置之后,我们学习了官方的 Kubernetes 客户端工具kubectl,它用于连接到集群。我们还看到了kubectl如何用于管理集群和应用程序的生命周期。最后,我们讨论了用于无服务器应用程序的基本 Kubernetes 资源,包括 pod、部署和StatefulSets。除此之外,我们还学习了如何使用服务在集群中连接应用程序。使用JobsCronJobs来呈现一次性和自动化任务的 Kubernetes 资源。在本章的最后,我们使用 Kubernetes 本地资源开发了一个实时数据收集功能。

在下一章中,我们将学习 Kubernetes 集群的特性,并使用流行的云平台来部署它们。

第五章: 生产就绪的 Kubernetes 集群

学习目标

本章结束时,您将能够:

  • 识别 Kubernetes 集群设置的要求

  • 在 Google Cloud Platform(GCP)中创建一个生产就绪的 Kubernetes 集群

  • 管理集群自动缩放以向 Kubernetes 集群添加新服务器

  • 迁移生产集群中的应用程序

在本章中,我们将学习关于设置 Kubernetes 的关键考虑因素。随后,我们还将研究不同的 Kubernetes 平台选项。然后,我们将继续在云平台上创建一个生产就绪的 Kubernetes 集群,并执行管理任务。

介绍

在上一章中,我们为开发环境创建了 Kubernetes 集群,并将应用程序安装到其中。在本章中,重点将放在生产就绪的 Kubernetes 集群上,以及如何管理它们以获得更好的可用性、可靠性和成本优化。

Kubernetes 是在云中管理作为容器运行的微服务的事实标准系统。它被行业广泛采用,包括初创公司和大型企业,用于运行各种类型的应用程序,包括数据分析工具、无服务器应用程序和数据库。可伸缩性、高可用性、可靠性和安全性是 Kubernetes 的关键特性,使其能够被广泛采用。假设您已决定使用 Kubernetes,因此您需要一个可靠且可观察的集群设置用于开发和生产。在选择 Kubernetes 提供商以及如何操作应用程序之前,有一些关键的考虑因素取决于您的需求、预算和团队。有四个关键考虑因素需要分析:

  • 服务质量: Kubernetes 以高可用和可靠的方式运行微服务。然而,安装和可靠地操作 Kubernetes 至关重要。假设您已将 Kubernetes 控制平面安装到集群中的单个节点,并且由于网络问题而断开连接。由于您已经失去了 Kubernetes API 服务器的连接,您将无法检查应用程序的状态和操作它们。因此,评估您在生产环境中所需的 Kubernetes 集群的服务质量至关重要。

  • 监控: Kubernetes 运行分布到节点的容器,并能够检查它们的日志和状态。假设您昨天推出了应用程序的新版本。今天,您想要检查最新版本的运行情况,是否有错误、崩溃和响应时间。因此,您需要一个集成到 Kubernetes 集群中的监控系统来捕获日志和指标。收集的数据对于生产就绪的集群中的故障排除和诊断至关重要。

  • 安全性: Kubernetes 组件和客户端工具以安全的方式工作,以管理集群中运行的应用程序。然而,您需要为您的组织定义特定的角色和授权级别,以安全地操作 Kubernetes 集群。因此,选择一个可以安全连接并与客户和同事共享的 Kubernetes 提供者平台至关重要。

  • 运维: Kubernetes 是所有应用程序的主机,包括具有数据合规性、审计和企业级要求的服务。假设您正在 Kubernetes 上运行在线银行应用系统的后端和前端。对于您所在国家的特许银行,应用程序的审计日志应该是可访问的。由于您已经在 Kubernetes 上部署了整个系统,平台应该能够获取审计日志、存档和存储它们。因此,Kubernetes 平台的运维能力对于生产就绪的集群设置至关重要。

为了决定如何安装和操作您的 Kubernetes 集群,本章将讨论这些考虑因素,以选择 Kubernetes 平台选项。

Kubernetes 设置

Kubernetes 是一个灵活的系统,可以安装在各种平台上,从树莓派数据中心中的高端服务器。每个平台在服务质量、监控、安全性和运营方面都有其优势和劣势。Kubernetes 将应用程序作为容器进行管理,并在基础架构上创建一个抽象层。假设你在地下室的三台旧服务器上安装了 Kubernetes,然后安装了你的新项目的概念验证PoC)。当项目取得成功后,你想要扩展你的应用程序并迁移到亚马逊网络服务AWS)等云服务提供商。由于你的应用程序是设计运行在 Kubernetes 上,并且不依赖于基础设施,因此迁移到另一个 Kubernetes 安装是直接的。

在上一章中,我们学习了使用minikube作为 Kubernetes 的官方方法来设置开发环境。在本节中,将介绍生产级别的 Kubernetes 平台。生产级别的 Kubernetes 平台可以分为三种,具有以下抽象层:

图 5.1:Kubernetes 平台

图 5.1:Kubernetes 平台

现在让我们逐个看看这些类型。

托管平台

托管平台提供Kubernetes 作为服务,所有底层服务都在云提供商的控制下运行。由于云提供商处理所有基础设施操作,因此设置和扩展这些集群非常容易。领先的云提供商,如 GCP、AWS 和 Microsoft Azure,都提供了托管的 Kubernetes 解决方案应用程序,旨在集成其他云服务,如容器注册表、身份服务和存储服务。最受欢迎的托管 Kubernetes 解决方案如下:

  • Google Kubernetes Engine (GKE): GKE 是市场上最成熟的托管服务,谷歌将其作为 GCP 的一部分提供。

  • Azure Kubernetes Service (AKS): AKS 是微软提供的作为 Azure 平台一部分的 Kubernetes 解决方案。

  • Amazon 弹性容器服务(EKS): EKS 是 AWS 的托管 Kubernetes。

即插即用平台

即插即用解决方案专注于在云端或内部系统中安装和操作 Kubernetes 控制平面。即插即用平台的用户提供有关基础设施的信息,即插即用平台处理 Kubernetes 设置。即插即用平台在设置配置和基础设施选项方面提供更好的灵活性。这些平台大多由在 Kubernetes 和云系统方面拥有丰富经验的组织设计,如HeptioCoreOS

如果将即插即用平台安装在 AWS 等云提供商上,基础设施由云提供商管理,即插即用平台管理 Kubernetes。然而,当即插即用平台安装在内部系统上时,内部团队应处理基础设施运营。

自定义平台

如果您的用例不适用于任何托管或即插即用解决方案,则可以进行自定义安装 Kubernetes。例如,您可以使用Gardenerhttps://gardener.cloud)或OpenShifthttps://www.openshift.com)在云提供商、内部数据中心、内部虚拟机(VM)或裸金属服务器上安装 Kubernetes 集群。虽然自定义平台提供更灵活的 Kubernetes 安装,但也需要特殊的运营和维护工作。

在接下来的章节中,我们将在 GKE 中创建一个托管的 Kubernetes 集群并对其进行管理。GKE 提供了市场上最成熟的平台和卓越的客户体验。

Google Kubernetes Engine

GKE 提供了一个由 Google 在运行容器化服务方面拥有十多年经验支持的托管 Kubernetes 平台。GKE 集群已经准备就绪并且可扩展,并支持上游 Kubernetes 版本。此外,GKE 专注于通过消除 Kubernetes 集群的安装、管理和运营需求来改善开发体验。

虽然 GKE 改善了开发者体验,但它试图最小化运行 Kubernetes 集群的成本。它只收取集群中的节点费用,并免费提供 Kubernetes 控制平面。换句话说,GKE 提供了一个可靠、可扩展和强大的 Kubernetes 控制平面,而没有任何费用。对于运行应用程序工作负载的服务器,通常适用 GCP 计算引擎定价。例如,假设您将从两个n1-standard-1 (vCPUs:1,RAM:3.75 GB)节点开始:

计算如下:

每月总计 1,460 小时

实例类型:n1-standard-1

GCE 实例成本:48.54 美元

Kubernetes Engine 成本:0.00 美元

预估组件成本:每月 48.54 美元

如果您的应用程序需要随着更高的使用量而扩展,如果您需要 10 台服务器而不是 2 台,成本也会线性增加:

每月总共 7300 小时

实例类型:n1-standard-1

GCE 实例成本:242.72 美元

Kubernetes Engine Cost: USD 0.00

预估组件成本:每月 242.72 美元

这个计算表明,GKE 不会为 Kubernetes 控制平面收费,并为每个集群提供可靠、可扩展和强大的 Kubernetes API。此外,扩展集群的成本是线性增加的,这使得规划和操作 Kubernetes 集群变得更加容易。

在接下来的练习中,您将在 GKE 中创建一个托管的 Kubernetes 集群并连接到它。

注意

为了完成这个练习,您需要有一个活跃的 GCP 账户。您可以在其官方网站上创建一个账户:https://console.cloud.google.com/start。

练习 13:在 GCP 上创建 Kubernetes 集群

在这个练习中,我们将在 GKE 中创建一个 Kubernetes 集群,并安全地连接到它以检查节点状态。Google Cloud Platform 的仪表板和 CLI 工具保持了高水平的开发者体验。因此,如果您需要一个生产就绪的 Kubernetes 集群,您将在不到 10 分钟内拥有一个完全运行的控制平面和服务器节点。

为了完成练习,我们需要确保执行以下步骤:

  1. 在 Google Cloud Platform 主页的计算下的左侧菜单中点击Kubernetes Engine,如下图所示:图 5.2:Google Cloud Platform 主页
图 5.2:Google Cloud Platform 主页
  1. 集群页面上点击创建集群,如下图所示:图 5.3:集群视图
图 5.3:集群视图
  1. 集群模板中从左侧选择您的第一个集群,并将serverless作为名称。点击页面底部的创建,如下图所示:图 5.4:集群创建
图 5.4:集群创建
  1. 等待几分钟,直到集群图标变成绿色,然后点击连接按钮,如下图所示:图 5.5:集群列表
图 5.5:集群列表
  1. 点击在云 shell 中运行连接到集群窗口中,如下图所示:图 5.6:连接到集群视图
图 5.6:连接到集群视图
  1. 等到云 shell 打开并可用时,按下Enter,当命令显示时,如下图所示:图 5.7:云 shell
图 5.7:云 shell

输出显示,集群的认证数据已被获取,kubeconfig条目已准备就绪。

  1. 在云 shell 中使用以下命令检查节点:
kubectl get nodes

由于集群是使用一个节点池创建的,只有一个节点连接到集群,如下图所示:

图 5.8:节点列表

图 5.8:节点列表
  1. 在云 shell 中使用以下命令检查集群中运行的 pod:
kubectl get pods --all-namespaces

由于 GKE 管理控制平面,在kube-system命名空间中没有api-serveretcdscheduler的 pod。集群中只有网络和指标的 pod 在运行,如下截图所示:

图 5.9:Pod 列表

图 5.9:Pod 列表

通过这个练习,您已经在 GKE 上创建了一个生产就绪的 Kubernetes 集群。在几分钟内,GKE 创建了一个托管的 Kubernetes 控制平面,并将服务器连接到了集群。在接下来的章节中,将讨论管理生产环境中的集群,并扩展这个练习中的 Kubernetes 集群。

自动缩放 Kubernetes 集群

Kubernetes 集群旨在可靠地运行可扩展的应用程序。换句话说,如果 Kubernetes 集群今天运行您的应用程序的10 个实例,它也应该支持在未来运行100 个实例。有两种主流方法可以达到这种灵活性水平:冗余自动缩放。假设您的应用程序的 10 个实例正在集群中的 3 台服务器上运行。通过冗余,您至少需要 27 台额外的空闲服务器来在未来运行 100 个实例。这也意味着支付空闲服务器的费用以及运营和维护成本。通过自动缩放,您需要自动化程序来创建或删除服务器。自动缩放确保没有过多的空闲服务器,并最大程度地减少成本,同时满足可扩展性要求。

GKE 集群自动缩放器是处理 Kubernetes 集群中自动缩放的开箱即用解决方案。启用后,如果工作负载没有剩余容量,它会自动添加新服务器。同样,当服务器利用率不足时,自动缩放器会删除多余的服务器。此外,自动缩放器还定义了服务器的最小和最大数量,以避免无限增加或减少。在以下练习中,将为 Kubernetes 集群启用 GKE 集群自动缩放器。然后通过更改集群中的工作负载来演示服务器的自动缩放。

练习 14:在生产环境中为 GKE 集群启用自动缩放

在本练习中,我们将在生产集群中启用并利用 GKE 集群自动缩放器。假设您需要在集群中运行大量应用的副本。但是,由于服务器数量较少,目前不可能实现。因此,您需要启用自动缩放,并查看如何自动创建新服务器。

要成功完成练习,我们需要确保执行以下步骤:

  1. 通过在云 shell 中运行以下命令在集群中安装nginx
kubectl create deployment workload --image=nginx 

此命令从nginx镜像创建名为workload的部署,如下图所示:

图 5.10:部署创建

图 5.10:部署创建
  1. 通过在云 shell 中运行以下命令将workload部署扩展到 25 个副本:
kubectl scale deployment workload --replicas=25

此命令增加了 workload 部署的副本数量,如下图所示:

图 5.11:部署扩展

图 5.11:部署扩展
  1. 使用以下命令检查运行中的 pod 数量:
kubectl get deployment workload

由于集群中只有 1 个节点,因此无法在集群中运行 25 个nginx的副本。相反,目前只有 5 个实例正在运行,如下图所示:

图 5.12:部署状态

图 5.12:部署状态
  1. 使用以下命令为集群的节点池启用自动扩展:
gcloud container clusters update serverless --enable-autoscaling  \
 --min-nodes 1 --max-nodes 10 --zone us-central1-a  \
 --node-pool pool-1

注意

如果您的集群在另一个区域运行,请更改zone参数。

此命令启用了 Kubernetes 集群的自动扩展,最小节点数为 1,最大节点数为 10,如下图所示:

图 5.13:启用自动缩放器

图 5.13:启用自动缩放器

此命令可能需要几分钟的时间来创建所需的资源,并显示“正在更新无服务器...”提示。

  1. 等待几分钟,然后使用以下命令检查节点数:
kubectl get nodes

启用自动缩放后,GKE 确保集群中有足够的节点来运行工作负载。节点池扩展到四个节点,如下图所示:

图 5.14:节点列表

图 5.14:节点列表
  1. 使用以下命令检查运行中的 pod 数量:
kubectl get deployment workload

由于集群中有 4 个节点,因此可以在集群中运行 25 个nginx的副本,如下图所示:

图 5.15:部署状态

图 5.15:部署状态
  1. 使用以下命令删除部署:
kubectl delete deployment workload

输出应该如下所示:

图 5.16:部署删除

图 5.16:部署删除
  1. 使用以下命令禁用集群的节点池的自动缩放:
gcloud container clusters update serverless --no-enable-autoscaling \
--node-pool pool-1 --zone us-central1-a

注意

如果您的集群在另一个区域运行,请更改zone参数。

您应该看到以下图中显示的输出:

图 5.17:禁用自动缩放

图 5.17:禁用自动缩放

在这个练习中,我们看到了 GKE 集群自动缩放器的运行情况。当自动缩放器启用时,它会在集群对当前工作负载容量不足时增加服务器数量。尽管看起来很简单,但这是 Kubernetes 平台的一个引人注目的特性。它消除了手动操作的负担,以检查集群利用率并采取行动。对于用户需求变化很大的无服务器应用程序来说,这一点甚至更为关键。

假设您已经在 Kubernetes 集群中部署了一个启用了自动缩放的无服务器函数。当您的函数频繁调用时,集群自动缩放器将自动增加节点数量,然后在您的函数不被调用时删除节点。因此,检查 Kubernetes 平台对无服务器应用程序的自动缩放能力是至关重要的。在接下来的部分中,将讨论在生产环境中迁移应用程序,这是另一个重要的集群管理任务。

Kubernetes 集群中的应用迁移

Kubernetes 将应用程序分发到服务器并保持它们可靠和稳健地运行。集群中的服务器可以是具有不同技术规格的 VM 或裸金属服务器实例。假设您只连接了标准 VM 到您的 Kubernetes 集群,并且它们正在运行各种类型的应用程序。如果您即将使用的数据分析库需要 GPU 来更快地运行,您需要连接具有 GPU 的服务器。同样,如果您的数据库应用程序需要 SSD 磁盘来进行更快的 I/O 操作,您需要连接具有 SSD 访问权限的服务器。这些应用程序要求导致在集群中有不同的节点池。此外,您需要配置 Kubernetes 工作负载在特定节点上运行。除了标记一些节点保留给特殊类型的工作负载外,还使用了污点。同样,如果 pod 运行特定类型的工作负载,它们将被标记为容忍。Kubernetes 支持使用污点和容忍度协同工作来将工作负载分发到特殊节点。

  • 污点是应用于节点的,表示该节点不应该有任何不容忍污点的 pod。

  • 容忍度被应用于 pod,允许 pod 被调度到具有污点的节点上。

例如,如果您只想在具有 SSD 的节点上运行数据库实例,您需要首先对节点进行污点处理:

kubectl taint nodes disk-node-1 ssd=true:NoSchedule

使用这个命令,disk-node-1将只接受具有以下容忍度的 pod:

tolerations:
- key: "ssd"
  operator: "Equal"
  value: "true"
  effect: "NoSchedule"

污点和容忍度协同工作,作为 Kubernetes 调度器的一部分,将 pod 分配给特定的节点。此外,Kubernetes 支持使用kubectl drain命令安全地从集群中移除服务器。如果您想对一些节点进行维护或退役,这将非常有帮助。在下面的练习中,运行在 Kubernetes 集群中的应用程序将迁移到一组特定的新节点。

练习 15:迁移在 GKE 集群中运行的应用程序

这个练习旨在教我们在生产集群中执行迁移活动。假设您在 Kubernetes 集群中运行一个后端应用程序。随着最近的变化,您已经改进了应用程序的内存管理,并希望在具有更高内存优化的服务器上运行。因此,您将创建一个新的节点池,并将应用程序实例迁移到其中。

为了成功完成练习,我们需要确保执行以下步骤:

  1. 通过在云 shell 中运行以下命令将后端应用程序安装到集群中:
kubectl create deployment backend --image=nginx 

此命令从nginx镜像创建名为backend的部署,如下图所示:

图 5.18:部署创建

图 5.18:部署创建
  1. 通过在云 shell 中运行以下命令,将backend部署的副本数扩展到10
    kubectl scale deployment backend --replicas=10    

此命令增加了后端部署的副本数,如下图所示:

图 5.19:部署扩展

图 5.19:部署扩展
  1. 使用以下命令检查正在运行的pods数量及其节点:
kubectl get pods -o wide

部署的所有 10 个副本都在 4 个节点上成功运行,如下图所示:

图 5.20:部署状态

图 5.20:部署状态
  1. 在 GCP 中创建一个具有更高内存的节点池:
gcloud container node-pools create high-memory-pool --cluster=serverless \
--zone us-central1-a --machine-type=n1-highmem-2 --num-nodes=2

注意

如果您的集群在另一个区域运行,请更改zone参数。

此命令在无服务器集群中创建了一个名为high-memory-pool的新节点池,机器类型为n1-highmem-2,有两个服务器,如下图所示:

图 5.21:节点池创建

图 5.21:节点池创建

此命令可能需要几分钟来创建所需的资源,并显示创建节点池高内存池提示。

  1. 等待几分钟并检查集群中的节点:
kubectl get nodes

此命令列出了集群中的节点,我们期望看到两个额外的high-memory节点,如下图所示:

图 5.22:集群节点

图 5.22:集群节点
  1. 排空旧节点,以便 Kubernetes 将应用程序迁移到新节点:
kubectl drain -l cloud.google.com/gke-nodepool=pool-1

此命令从所有带有标签cloud.google.com/gke-nodepool=pool-1的节点中删除工作负载,如下图所示:

图 5.23:节点移除

图 5.23:节点移除
  1. 使用以下命令检查正在运行的 pods 及其节点:
kubectl get pods -o wide

部署的所有 10 个副本都成功运行在新的high-memory节点上,如下图所示:

图 5.24:部署状态

图 5.24:部署状态
  1. 使用以下命令删除旧的节点池:
gcloud container node-pools delete pool-1 --cluster serverless --zone us-central1-a 

注意

更改zone参数,如果您的集群在另一个区域运行。

此命令将删除未使用的旧节点池,如下图所示:

图 5.25:节点池删除

图 5.25:节点池删除

在这个练习中,我们已经将正在运行的应用迁移到了具有更好技术规格的新节点。使用 Kubernetes 原语和 GKE 节点池,可以在没有停机时间的情况下将应用迁移到特定的节点集。在接下来的活动中,您将使用自动缩放和 Kubernetes 污点来运行无服务器函数,同时最大限度地降低成本。

活动 5:在 GKE 集群中最大限度地降低无服务器函数的成本

本活动的目的是在生产集群上执行管理任务,以运行无服务器函数,同时最大限度地降低成本。假设您的后端应用已经在 Kubernetes 集群中运行。现在,您希望安装一些无服务器函数来连接后端。然而,后端实例正在运行内存优化的服务器,这对于运行无服务器函数也是昂贵的。因此,您需要添加可抢占服务器,这些服务器更便宜。可抢占 VM 已经在 GCP 中可用;然而,它们具有较低的服务质量和最长寿命为 24 小时。因此,您应该配置节点池为自动缩放,并且只运行无服务器函数。否则,您的后端实例也可能被调度到可抢占 VM 上,并降低整体性能。

活动结束时,您将拥有连接到后端实例的函数,如下图所示:

图 5.26:后端检查器功能

图 5.26:后端检查器功能

后端实例将在高内存节点上运行,功能实例将在可抢占服务器上运行,如下图所示:

图 5.27:Kubernetes pods 和相应的节点

图 5.27:Kubernetes pods 和相应的节点

注意

为了完成活动,您应该使用来自练习 15的集群,其中运行着后端部署。

执行以下步骤完成活动:

  1. 创建一个具有可抢占服务器的新节点池。

  2. 给可抢占服务器打上标记,只运行无服务器函数。

  3. 创建一个 Kubernetes 服务以访问后端 pod。

  4. 创建一个 CronJob,每分钟连接到后端服务。CronJob 定义应该具有容忍性,可以在可抢占服务器上运行。

  5. 检查 CronJob 函数的节点分配。

  6. 检查 CronJob 函数实例的日志。

  7. 清理后端部署和无服务器函数。

  8. 如果不再需要 Kubernetes 集群,请将其删除。

注意

活动的解决方案可以在第 412 页找到。

摘要

在本章中,我们首先描述了分析 Kubernetes 集群设置要求的四个关键考虑因素。然后我们研究了三组 Kubernetes 平台:托管、即插即用和定制。每个 Kubernetes 平台都有解释,以及它们在基础设施、Kubernetes 和应用程序上的责任水平。在那之后,我们在 GKE 上创建了一个可投入生产的 Kubernetes 集群。由于 Kubernetes 旨在运行可扩展的应用程序,我们研究了如何通过自动缩放来处理工作负载的增加或减少。此外,我们还研究了在生产集群中无需停机的应用程序迁移,以说明如何将应用程序移动到具有更高内存的服务器。最后,我们在生产集群中运行无服务器函数来执行自动缩放和迁移活动,以最大程度地降低成本。Kubernetes 和无服务器应用程序共同工作,创建可靠、强大和可扩展的未来环境。因此,了解如何安装和操作生产环境的 Kubernetes 集群至关重要。

在下一章中,我们将研究 Kubernetes 中即将推出的无服务器功能。我们还将详细研究虚拟 kubelet,并在 GKE 上部署无状态容器。

第六章: Kubernetes 中即将推出的无服务器功能

学习目标

在本章结束时,您将能够:

  • 利用 Knative 的概念和组件部署应用程序

  • 在 GKE 集群上设置 Knative

  • 在 Knative 上部署应用程序并配置自动缩放

  • 在谷歌云运行上部署应用程序

  • 在 Azure 上设置虚拟 Kubelet

  • 使用虚拟 Kubelet 部署应用程序

本章涵盖了 Knative、谷歌云运行和虚拟 Kubelet,它们在 Kubernetes 集群之上提供了无服务器的优势。

介绍 Kubernetes 的无服务器功能

在上一章中,我们广泛研究了 Kubernetes 中使用的各种设置选项和平台。我们还涵盖了 Kubernetes 的自动缩放功能,并在集群上部署的应用程序中实施了它。

Kubernetes 和无服务器是 IT 行业中的两个热门话题,但这两个话题经常被独立讨论。Kubernetes 是一个管理容器化应用程序的平台,而无服务器是一种执行模型,它抽象了基础设施,使软件开发人员可以专注于他们的应用逻辑。然而,这两个概念的结合将实现同样的目标,使软件开发人员的生活变得更加轻松。

最近出现了一些平台,通过抽象管理容器和任何基础架构的复杂性,为容器带来了无服务器特性。这些平台在 Kubernetes 集群上运行无服务器工作负载,并提供许多好处,包括自动缩放、零缩放、按使用量计费、事件驱动功能、集成监控和集成日志记录功能。

在本章中,我们将讨论三种技术,它们在 Kubernetes 集群之上提供了无服务器的好处:

  • Knative

  • 谷歌云运行

  • 虚拟 Kubelet

Knative 简介

Knative 是由谷歌发起的开源项目,得到了包括 Pivotal、Red Hat、IBM 和 SAP 在内的 50 多家其他公司的贡献。Knative 通过引入一组组件来扩展 Kubernetes,从而构建和运行无服务器应用程序。这个框架非常适合已经在使用 Kubernetes 的应用开发人员。Knative 为他们提供了工具,让他们专注于他们的代码,而不用担心 Kubernetes 的底层架构。它引入了自动化容器构建、自动缩放、零缩放和事件框架等功能,使开发人员能够在 Kubernetes 之上获得无服务器的好处。

Knative 框架在 Knative 网站上被描述为“基于 Kubernetes 的平台,用于部署和管理现代无服务器工作负载”。该框架通过引入无服务器特性,如自动缩放和零缩放,来弥合容器化应用程序和无服务器应用程序之间的差距。

Knative 由三个主要组件组成:

  • 构建

  • 服务

  • 事件

注意

在最新版本的 Knative 中,构建组件已被弃用,而是更倾向于使用 Tekton Pipelines。Knative 构建组件的最终版本可在 0.7 版本中获得。

构建是从源代码构建容器映像并在 Kubernetes 集群上运行它们的过程。Knative Serving 组件允许部署无服务器应用程序和函数。这使得可以向容器提供流量,并根据请求的数量进行自动缩放。该服务组件还负责在对代码和配置进行更改时进行快照。Knative Eventing 组件帮助我们构建事件驱动的应用程序。该组件允许应用程序为事件流产生事件,并从事件流中消费事件。

以下图示了 Knative 框架及其依赖项以及每个组件的利益相关者:

图 6.1:Knative 依赖项和利益相关者

图 6.1:Knative 依赖项和利益相关者

底层代表了 Kubernetes 框架,它作为 Knative 框架的容器编排层使用。Kubernetes 可以部署在任何基础设施上,例如 Google Cloud Platform 或本地系统。接下来,我们有Istio服务网格层,它管理集群内的网络路由。这一层提供了许多好处,包括流量管理、可观察性和安全性。在顶层,Knative 在Istio上运行在 Kubernetes 集群上。在 Knative 层,一端我们可以看到通过 GitHub 项目向 Knative 框架贡献代码的贡献者,另一端我们可以看到构建和部署应用程序在 Knative 框架之上的应用程序开发人员。

注意

有关 Istio 的更多信息,请参阅istio.io/

现在我们对 Knative 有了这样的理解,让我们在下一节中看看如何在 Kubernetes 集群上安装 Knative。

在 GKE 上开始使用 Knative

在本节中,我们将带您完成在 Kubernetes 集群上安装 Knative 的过程。我们将使用 Google Kubernetes Engine(GKE)来设置一个 Kubernetes 集群。GKE 是 Google 云中的托管 Kubernetes 集群服务。它允许我们在不安装、管理和操作自己的集群的负担下运行 Kubernetes 集群。

我们需要安装和配置以下先决条件才能继续本节:

  • 一个 Google Cloud 账户

  • gcloud CLI

  • kubectl CLI(v1.10 或更新版本)

首先,我们需要设置一些环境变量,这些变量将与gcloud CLI 一起使用。您应该使用您的 GCP 项目的名称更新<your-gcp-project-name>。我们将使用us-central1-a作为 GCP 区域。在您的终端窗口中执行以下命令以设置所需的环境变量:

$ export GCP_PROJECT=<your-gcp-project-name>
$ export GCP_ZONE=us-central1-a
$ export GKE_CLUSTER=knative-cluster

输出应该如下:

图 6.2:设置环境变量

图 6.2:设置环境变量

将我们的 GCP 项目设置为gcloud CLI 命令要使用的默认项目:

$ gcloud config set core/project $GCP_PROJECT

输出应该如下:

图 6.3:设置默认的 GCP 项目

图 6.3:设置默认的 GCP 项目

现在我们可以使用gcloud命令创建 GKE 集群。Knative 需要一个版本为 1.11 或更新的 Kubernetes 集群。我们将使用 GKE 提供的Istio插件来为这个集群提供支持。以下是运行 Knative 组件所需的 Kubernetes 集群的推荐配置:

  • Kubernetes 版本 1.11 或更新

  • 具有四个 vCPU(n1-standard-4)的 Kubernetes 节点

  • 启用最多 10 个节点的节点自动缩放

  • cloud-platform的 API 范围

执行以下命令来创建一个符合这些要求的 GKE 集群:

     $ gcloud beta container clusters create $GKE_CLUSTER \
    --zone=$GCP_ZONE \
    --machine-type=n1-standard-4 \
    --cluster-version=latest \
    --addons=HorizontalPodAutoscaling,HttpLoadBalancing,Istio \
    --enable-stackdriver-kubernetes \
    --enable-ip-alias \
    --enable-autoscaling --min-nodes=1 --max-nodes=10 \
    --enable-autorepair \
    --scopes cloud-platform

输出应该如下:

图 6.4:创建一个 GKE 集群

图 6.4:创建一个 GKE 集群

设置 Kubernetes 集群可能需要几分钟的时间。一旦集群准备好,我们将使用命令gcloud container clusters get-credentials来获取新集群的凭据,并配置kubectl CLI,如下面的代码片段所示:

$ gcloud container clusters get-credentials $GKE_CLUSTER --zone $GCP_ZONE --project $GCP_PROJECT

输出应该如下:

图 6.5:获取 GKE 集群的凭据

图 6.5:获取 GKE 集群的凭据

现在您已成功创建了带有Istio的 GKE 集群,并配置了kubectl以访问新创建的集群。我们现在可以继续进行下一步,安装 Knative。我们将安装 Knative 版本 0.8,这是撰写本书时可用的最新版本。

我们将使用kubectl CLI 将 Knative 组件应用到 Kubernetes 集群上。首先,运行kubectl apply命令,并使用-l knative.dev/crd-install=true标志来防止安装过程中的竞争条件:

$ kubectl apply --selector knative.dev/crd-install=true \
   -f https://github.com/knative/serving/releases/download/v0.8.0/serving.yaml \
   -f https://github.com/knative/eventing/releases/download/v0.8.0/release.yaml \
   -f https://github.com/knative/serving/releases/download/v0.8.0/monitoring.yaml

接下来,再次运行命令,不带-l knative.dev/crd-install=true标志来完成安装:

$ kubectl apply -f https://github.com/knative/serving/releases/download/v0.8.0/serving.yaml \
   -f https://github.com/knative/eventing/releases/download/v0.8.0/release.yaml \
   -f https://github.com/knative/serving/releases/download/v0.8.0/monitoring.yaml

一旦命令完成,执行以下命令来检查安装的状态。确保所有的 pod 都有Running的状态:

$ kubectl get pods --namespace knative-serving
$ kubectl get pods --namespace knative-eventing
$ kubectl get pods --namespace knative-monitoring

输出应该如下:

图 6.6:验证 Knative 安装

在这个阶段,您已经在 GKE 上设置了一个 Kubernetes 集群并安装了 Knative。现在我们准备在 Knative 上部署我们的第一个应用程序。

练习 16:在 Knative 上部署一个示例应用程序

在前面的部分中,我们成功在 Kubernetes 和Istio之上部署了 Knative。在这个练习中,我们将在 Knative 框架上部署我们的第一个应用程序。为了进行这次部署,我们将使用一个用 Node.js 编写的示例 Web 应用程序。这个应用程序的 Docker 镜像可以在 Google 容器注册表中找到,地址为gcr.io/knative-samples/helloworld-nodejs。这些步骤可以适应部署我们自己的 Docker 镜像到 Docker Hub 或任何其他容器注册表。

这个示例的“hello world”应用程序将读取一个名为TARGET的环境变量,并打印Hello <VALUE_OF_TARGET>!作为输出。如果未为TARGET环境变量定义值,则它将打印NOT SPECIFIED作为输出。

让我们首先创建应用程序的服务定义文件。这个文件定义了与应用程序相关的信息,包括应用程序名称和应用程序 Docker 镜像:

注意

Knative 服务对象和 Kubernetes 服务对象是两种不同的类型。

  1. 创建一个名为hello-world.yaml的文件,其中包含以下内容。这个 Knative 服务对象定义了部署此服务的命名空间、用于容器的 Docker 镜像以及任何环境变量等数值:
          apiVersion: serving.knative.dev/v1alpha1 
kind: Service
metadata:
  name: helloworld-nodejs 
  namespace: default 
spec:
  runLatest:
    configuration:
      revisionTemplate:
        spec:
          container:
            image: gcr.io/knative-samples/helloworld-nodejs 
            env:
              - name: TARGET 
                value: "Knative NodeJS App"
  1. 一旦hello-world.yaml文件准备好,我们可以使用kubectl apply命令部署我们的应用程序:
$ kubectl apply -f hello-world.yaml

输出应该如下所示:

图 6.7:部署 helloworld-nodejs 应用程序

图 6.7:部署 helloworld-nodejs 应用程序
  1. 上一个命令将创建多个对象,包括 Knative 服务、配置、修订、路由和 Kubernetes 部署。我们可以通过列出新创建的对象来验证应用程序,就像以下命令一样:
$ kubectl get ksvc
$ kubectl get configuration
$ kubectl get revision
$ kubectl get route
$ kubectl get deployments

输出应该如下所示:

图 6.8:验证 helloworld-nodejs 应用程序部署

图 6.8:验证 helloworld-nodejs 应用程序部署
  1. 一旦我们的应用程序成功部署,我们可以使用 HTTP 请求调用这个应用程序。为此,我们需要确定 Kubernetes 集群的外部 IP 地址。执行以下命令将EXTERNAL-IP的值导出到名为EXTERNAL_IP的环境变量中:
$ export EXTERNAL_IP=$(kubectl get svc istio-ingressgateway --namespace istio-system --output 'jsonpath={.status.loadBalancer.ingress[0].ip}')

输出应该如下所示:

图 6.9:导出 istio-ingressgateway 服务的外部 IP

图 6.9:导出 istio-ingressgateway 服务的外部 IP

接下来,我们需要找到helloworld-nodejs应用程序的主机 URL。执行以下命令并注意URL列的值。此 URL 采用以下形式:http://<application-name>.<namespace>.example.com:

$ kubectl get route helloworld-nodejs

输出应该如下所示:

图 6.10:列出 helloworld-nodejs 路由

图 6.10:列出 helloworld-nodejs 路由
  1. 现在我们可以使用我们在之前步骤中记录的EXTERNAL_IPURL值来调用我们的应用程序。让我们使用以下命令进行curl请求:
$ curl -H "Host: helloworld-nodejs.default.example.com" http://${EXTERNAL_IP}

输出应该如下所示:

图 6.11:调用 helloworld-nodejs 应用程序

图 6.11:调用 helloworld-nodejs 应用程序

您应该收到预期的输出为Hello Knative NodeJS App!。这表明我们已成功在 Knative 平台上部署和调用了我们的第一个应用程序。

Knative 服务组件

在前一节中,我们使用服务类型的 YAML 文件部署了我们的第一个 Knative 应用程序。在部署服务时,它创建了多个其他对象,包括配置、修订和路由对象。在本节中,让我们讨论每个这些对象:

Knative 服务组件中有四种资源类型:

  • 配置:定义应用程序的期望状态

  • 修订:只读快照,跟踪配置的更改

  • 路由:提供到修订的流量路由

  • 服务:路由和配置的顶层容器

以下图示说明了每个这些组件之间的关系:

图 6.12:Knative 服务、路由、配置和修订之间的关系

图 6.12:Knative 服务、路由、配置和修订之间的关系

配置用于定义应用程序的期望状态。这将定义用于应用程序的容器映像和任何其他所需的配置参数。每次更新配置时都会创建一个新的修订版修订版指的是代码和配置的快照。这用于记录配置更改的历史。路由用于定义应用程序的流量路由策略,并为应用程序提供 HTTP 端点。默认情况下,路由将流量发送到配置创建的最新修订版路由还可以配置更高级的场景,包括将流量发送到特定的修订版或根据定义的百分比将流量分配到不同的修订版。服务对象用于管理应用程序的整个生命周期。在部署新应用程序时,需要手动创建配置路由对象,但服务可以通过自动创建和管理配置路由对象来简化这一过程。

在接下来的部分,我们将使用金丝雀部署来部署 Knative 应用程序。让我们首先了解一下金丝雀部署到底是什么。

金丝雀部署

金丝雀部署是一种部署策略,用于在生产环境中推出新版本的代码。这是一种安全的部署新版本代码到生产环境并将一小部分流量切换到新版本的过程。这样,开发和部署团队可以在对生产流量影响最小的情况下验证新版本的代码。一旦验证完成,所有流量将切换到新版本。除了金丝雀部署之外,还有几种其他部署类型,例如大爆炸部署、滚动部署和蓝绿部署。

在我们在练习 16中部署的helloworld-nodejs应用程序中,我们使用了带有spec.runLatest字段的服务对象,该字段将所有流量定向到最新可用的修订版。在接下来的练习中,我们将使用单独的配置和路由对象,而不是服务对象。

注意:

有关金丝雀部署技术的更多信息,请参阅dev.to/mostlyjason/intro-to-deployment-strategies-blue-green-canary-and-more-3a3

练习 17:Knative 的金丝雀部署

在这个练习中,我们将实施金丝雀部署策略来部署 Knative 应用程序。首先,我们将部署应用程序的初始版本(版本 1),并将 100%的流量路由到该版本。接下来,我们将创建应用程序的第 2 个版本,并将 50%的流量路由到版本 1,剩下的 50%路由到版本 2。最后,我们将更新路由,将 100%的流量发送到版本 2。

以下步骤将帮助您完成练习:

  1. 首先,从创建应用程序的初始版本(v1)开始。创建一个名为canary-deployment.yaml的文件,内容如下。这个应用程序使用与我们之前使用的相同的 Docker 镜像(gcr.io/knative-samples/helloworld-nodejs),并将TARGET环境变量设置为This is the first version - v1
apiVersion: serving.knative.dev/v1alpha1
kind: Configuration
metadata:
  name: canary-deployment
  namespace: default
spec:
  template:
    spec:
      containers:
        - image: gcr.io/knative-samples/helloworld-nodejs
          env:
            - name: TARGET
              value: "This is the first version - v1"
  1. 使用在上一步中创建的 YAML 文件,使用kubectl apply命令部署应用程序的第一个版本:
$ kubectl apply -f canary-deployment.yaml

输出应该如下所示:

图 6.13:创建金丝雀部署

图 6.13:创建金丝雀部署
  1. 让我们获取此配置创建的修订名称,因为我们在下一步中需要这个值。执行kubectl get configurations命令,并检索latestCreatedRevisionName字段的值:
$ kubectl get configurations canary-deployment -o=jsonpath='{.status.latestCreatedRevisionName}'

输出应该如下所示:

图 6.14:获取金丝雀部署配置的最新修订版本

图 6.14:获取金丝雀部署配置的最新修订版本

对我来说,从前面的命令返回的值是canary-deployment-xgvl8。请注意,你的值将会不同。

  1. 接下来的步骤是创建路由对象。让我们创建一个名为canary-deployment-route.yaml的文件,内容如下(请记得用你在上一步中记录的修订名称替换canary-deployment-xgvl8)。在spec.traffic部分下,你可以看到 100%的流量被路由到我们之前创建的修订版本:
apiVersion: serving.knative.dev/v1alpha1
kind: Route
metadata:
  name: canary-deployment
  namespace: default 
spec:
  traffic:
    - revisionName: canary-deployment-xgvl8
      percent: 100 
  1. 使用kubectl apply命令创建路由对象:
$ kubectl apply -f canary-deployment-route.yaml

输出应该如下所示:

图 6.15:创建金丝雀部署路由

图 6.15:创建 canary-deployment 路由
  1. 对应用程序发出请求,并观察Hello This is the first version - v1!的预期输出:
$ curl -H "Host: canary-deployment.default.example.com" "http://${EXTERNAL_IP}"

输出应如下所示:

图 6.16:调用 canary-deployment

图 6.16:调用 canary-deployment
  1. 一旦应用程序成功调用,我们可以部署应用程序的第 2 个版本。使用以下内容更新canary-deployment.yaml。在应用程序的第 2 个版本中,我们只需要将TARGET环境变量的值从This is the first version - v1更新为This is the second version - v2
apiVersion: serving.knative.dev/v1alpha1
kind: Configuration
metadata:
  name: canary-deployment
  namespace: default
spec:
  template:
    spec:
      containers:
        - image: gcr.io/knative-samples/helloworld-nodejs
          env:
            - name: TARGET
              value: "This is the second version - v2"
  1. 使用kubectl apply应用更新的配置:
$ kubectl apply -f canary-deployment.yaml

输出应如下所示:

图 6.17:将 canary-deployment 更新为版本 2

图 6.17:将 canary-deployment 更新为版本 2
  1. 现在我们可以使用kubectl get revisions命令检查创建的修订版本,同时更新配置:
$ kubectl get revisions

输出应如下所示:

图 6.18:获取 canary-deployment 的修订版本

图 6.18:获取 canary-deployment 的修订版本
  1. 让我们获取由canary-deployment配置创建的最新修订版本:
$ kubectl get configurations canary-deployment -o=jsonpath='{.status.latestCreatedRevisionName}'

输出应如下所示:

图 6.19:获取 canary-deployment 配置的最新修订版本

图 6.19:获取 canary-deployment 配置的最新修订版本
  1. 现在是时候向我们应用程序的新版本发送一些流量了。更新canary-deployment-route.yamlspec.traffic部分,将 50%的流量发送到旧修订版本,50%发送到新修订版本:
apiVersion: serving.knative.dev/v1alpha1
kind: Route
metadata:
  name: canary-deployment
  namespace: default 
spec:
  traffic:
    - revisionName: canary-deployment-xgvl8
      percent: 50 
    - revisionName: canary-deployment-8pp4s
      percent: 50 
  1. 使用以下命令对路由进行更改:
$ kubectl apply -f canary-deployment-route.yaml

输出应如下所示:

图 6.20:更新 canary-deployment 路由

图 6.20:更新 canary-deployment 路由
  1. 现在我们可以多次调用应用程序,观察流量如何在两个修订版本之间分配:
$ curl -H "Host: canary-deployment.default.example.com" "http://${EXTERNAL_IP}" 
  1. 一旦我们成功验证了应用程序的第 2 个版本,我们可以将canary-deployment-route.yaml更新为将 100%的流量路由到最新的修订版本:
apiVersion: serving.knative.dev/v1alpha1
kind: Route
metadata:
  name: canary-deployment
  namespace: default 
spec:
  traffic:
    - revisionName: canary-deployment-xgvl8
      percent: 0 
    - revisionName: canary-deployment-8pp4s
      percent: 100 
  1. 使用以下命令对路由进行更改:
$ kubectl apply -f canary-deployment-route.yaml

输出应如下所示:

图 6.21:更新 canary-deployment 路由

图 6.21:更新 canary-deployment 路由
  1. 现在多次调用应用程序,以验证所有流量都流向应用程序的第 2 个版本:
$ curl -H "Host: blue-green-deployment.default.example.com" "http://${EXTERNAL_IP}" 

在这个练习中,我们成功地使用配置和路由对象来执行 Knative 的金丝雀部署。

Knative 监控

Knative 预先安装了 Grafana,这是一个开源的度量分析和可视化工具。Grafana pod 可在knative-monitoring命名空间中找到,并且可以使用以下命令列出:

$ kubectl get pods -l app=grafana -n knative-monitoring

输出应该如下所示:

图 6.22:列出 Grafana pod

图 6.22:列出 Grafana pod

我们可以使用kubectl port-forward命令暴露 Grafana UI,该命令将本地端口3000转发到 Grafana pod 的端口3000。打开一个新的终端并执行以下命令:

$ kubectl port-forward $(kubectl get pod -n knative-monitoring -l app=grafana -o jsonpath='{.items[0].metadata.name}') -n knative-monitoring 3000:3000

输出应该如下所示:

图 6.23:将端口转发到 Grafana pod

现在我们可以从我们的网络浏览器上的http://127.0.0.1:3000导航到 Grafana UI。

输出应该如下所示:

图 6.24:Grafana UI

图 6.24:Grafana UI

Knative 的 Grafana 仪表板带有多个仪表板,包括以下内容:

图 6.25:仪表板

图 6.25:仪表板

Knative 自动缩放器

Knative 具有内置的自动缩放功能,根据接收到的 HTTP 请求的数量自动调整应用程序 pod 的数量。当需求增加时,它将增加 pod 数量,当需求减少时,它将减少 pod 数量。当 pod 处于空闲状态且没有传入请求时,pod 数量将缩减为零。

Knative 使用两个组件,自动缩放器和激活器,来实现前面提到的功能。这些组件部署为knative-serving命名空间中的 pod,如下面的代码片段所示:

NAME                          READY   STATUS    RESTARTS   AGE
activator-7c8b59d78-9kgk5     2/2     Running   0          15h
autoscaler-666c9bfcc6-vwrj6   2/2     Running   0          15h
controller-799cd5c6dc-p47qn   1/1     Running   0          15h
webhook-5b66fdf6b9-cbllh      1/1     Running   0          15h

激活器组件负责收集有关修订版的并发请求数量的信息,并将这些值报告给自动缩放器。自动缩放器组件将根据激活器报告的指标增加或减少 pod 的数量。默认情况下,自动缩放器将尝试通过扩展或缩减 pod 来维持每个 pod 的 100 个并发请求。所有 Knative 与自动缩放器相关的配置都存储在knative-serving命名空间中名为config-autoscaler的配置映射中。Knative 还可以配置为使用 Kubernetes 提供的水平 Pod 自动缩放器HPA),HPA 将根据 CPU 使用情况自动调整 pod 的数量。

练习 18:使用 Knative 进行自动缩放

在这个练习中,我们将通过部署一个示例应用程序来执行 Knative pod 自动缩放:

  1. 创建一个名为autoscale-app.yaml的服务定义文件,内容如下。该文件定义了一个名为autoscale-app的服务,该服务将使用gcr.io/knative-samples/autoscale-go:0.1示例 Docker 镜像。autoscaling.knative.dev/target用于配置每个 pod 的目标并发请求数量:
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
  name: autoscale-app
spec:
  runLatest:
    configuration:
      revisionTemplate:
        metadata:
          annotations:
            autoscaling.knative.dev/target: "10"
        spec:
          container:
            image: "gcr.io/knative-samples/autoscale-go:0.1"
  1. 使用kubectl apply命令应用服务定义:
$ kubectl apply -f autoscale-app.yaml

输出应如下所示:

图 6.26:创建 autoscale-app

图 6.26:创建 autoscale-app
  1. 一旦应用程序准备就绪,我们可以生成一个负载到autoscale-app应用程序以观察自动缩放。为此,我们将使用一个名为hey的负载生成器。使用以下curl命令下载hey二进制文件。
$ curl -Lo hey https://storage.googleapis.com/hey-release/hey_linux_amd64

输出应如下所示:

图 6.27:安装 hey

图 6.27:安装 hey
  1. hey二进制文件添加执行权限,并将其移动到/usr/local/bin/路径中:
$ chmod +x hey
$ sudo mv hey /usr/local/bin/

输出应如下所示:

图 6.28:将 hey 移动到/usr/local/bin

图 6.28:将 hey 移动到/usr/local/bin
  1. 现在我们准备使用hey工具生成负载。hey工具在生成负载时支持多个选项。对于这种情况,我们将使用并发数为 50(使用-c标志)持续 60 秒(使用-z标志)的负载:
$ hey -z 60s -c 50 \
   -host "autoscale-app.default.example.com" \
   "http://${EXTERNAL_IP?}?sleep=1000" 
  1. 在单独的终端中,观察负载期间创建的 pod 数量:
$ kubectl get pods --watch

您将看到类似以下的输出:

     NAME                                             READY   STATUS    RESTARTS   AGE
autoscale-app-7jt29-deployment-9c9c4b474-4ttl2   3/3     Running   0          58s
autoscale-app-7jt29-deployment-9c9c4b474-6pmjs   3/3     Running   0          60s
autoscale-app-7jt29-deployment-9c9c4b474-7j52p   3/3     Running   0          63s
autoscale-app-7jt29-deployment-9c9c4b474-dvcs6   3/3     Running   0          56s
autoscale-app-7jt29-deployment-9c9c4b474-hmkzf   3/3     Running   0          62s
  1. 打开 Grafana 中的Knative Serving - Scaling Debugging仪表板,观察自动缩放如何在负载期间增加了 pod 数量,并在负载停止后将 pod 数量减少到零,如下面的截图所示:

图 6.29:修订 pod 计数指标

图 6.29:修订 pod 计数指标

图 6.30:观察并发度指标

图 6.30:观察并发度指标

我们已成功配置了 Knative 的自动缩放器,并通过 Grafana 仪表板观察到了自动缩放。

Google Cloud Run

在前面的部分中,我们讨论了 Knative。我们学习了如何在 Kubernetes 集群上安装 Istio 和 Knative,以及如何使用 Knative 运行 Docker 镜像。但是 Knative 平台的优势伴随着管理底层 Kubernetes 集群和 Istio 的运营开销。来自 Google Cloud 的托管 Kubernetes 服务 GKE 将帮助我们管理 Kubernetes 主控组件,但是我们仍然必须自己管理所有的 Kubernetes 节点。

为了将开发人员的所有基础设施管理任务抽象出来,Google 推出了一个名为 Cloud Run 的新服务。这是一个完全托管的平台,建立在 Knative 项目之上,用于运行无状态的 HTTP 驱动容器。Cloud Run 提供与 Knative 相同的功能集,包括自动缩放、零缩放、版本控制和事件。Cloud Run 在 Google Cloud Next '19 大会上作为 Google Cloud 无服务器计算堆栈的最新成员推出。在撰写本书时,Cloud Run 服务仍处于测试阶段,仅在有限数量的地区提供。

现在让我们进行一个练习,在 Google Cloud Run 上部署容器。

练习 19:在 Google Cloud Run 上部署容器

在这个练习中,我们将在 Google Cloud Run 平台上部署一个预构建的 Docker 镜像。

以下步骤将帮助您完成练习:

  1. 从浏览器导航到您的 GCP 控制台,并从菜单中选择Cloud Run(在计算类别中),如下图所示:图 6.31:Cloud Run 的 GCP 菜单
图 6.31:Cloud Run 的 GCP 菜单
  1. 单击创建服务按钮以创建新服务。

  2. 使用以下值填写创建服务表单:

容器镜像 URL:gcr.io/knative-samples/helloworld-nodejs

部署平台:Cloud Run(完全托管)

位置:从选项中选择任何您喜欢的地区

服务名称:hello-world

身份验证:允许未经身份验证的调用

图 6.32:Cloud Run 创建服务表单

图 6.32:Cloud Run 创建服务表单
  1. 单击创建按钮。

  2. 现在我们将被重定向到部署的服务页面,其中包括关于新部署的hello-world服务的详细信息。我们可以看到已创建一个名为hello-world-00001的修订版本,如下图所示:图 6.33:服务详细信息页面

图 6.33:服务详细信息页面
  1. 点击显示的 URL 链接来运行容器。请注意,每个新实例的 URL 都会有所不同:图 6.34:调用 hello-world 应用程序
图 6.34:调用 hello-world 应用程序
  1. 接下来,我们将通过更新TARGET环境变量来部署应用程序的新修订版。返回GCP控制台,点击部署新修订版按钮。

  2. 部署修订版到 hello-world(us-central1)表单中,点击显示可选修订设置链接,这将指向我们到附加设置部分:图 6.35:可选修订设置

图 6.35:可选修订设置
  1. 在环境变量部分,创建一个名为TARGET的新环境变量,值为Cloud Run Deployment图 6.36:设置 TARGET 环境变量
图 6.36:设置 TARGET 环境变量
  1. 点击部署按钮。

  2. 现在我们可以看到hello-world应用程序的新修订版名为hello-world-00002,100%的流量被路由到最新的修订版:图 6.37:hello-world 应用程序的新修订版

图 6.37:hello-world 应用程序的新修订版
  1. 再次点击 URL 来运行更新的修订版:

图 6.38:调用 hello-world 应用程序

图 6.38:调用 hello-world 应用程序

我们已成功在 Google Cloud Run 平台上部署了预构建的 Docker 镜像。

介绍 Virtual Kubelet

Virtual Kubelet 是 Kubernetes kubelet 的开源实现,充当 kubelet。这是来自Cloud Native Computing FoundationCNCF)的沙箱项目,Virtual Kubelet 的第一个主要版本(v 1.0)于 2019 年 7 月 8 日发布。

在进一步深入 Virtual Kubelet 之前,让我们回顾一下 Kubernetes 架构中的 kubelet 是什么。kubelet 是在 Kubernetes 集群中每个节点上运行的代理,负责管理节点内的 pod。kubelet 从 Kubernetes API 接收指令,以识别要在节点上调度的 pod,并与节点的底层容器运行时(例如 Docker)交互,以确保所需数量的 pod 正在运行并且它们是健康的。

除了管理 pod 外,kubelet 还执行几项其他任务:

  • 更新 Kubernetes API 与 pod 的当前状态

  • 监控和报告节点的健康指标,如 CPU、内存和磁盘利用率,到 Kubernetes 主节点

  • 从 Docker 注册表中拉取分配的 pod 的 Docker 镜像

  • 为 pod 创建和挂载卷

  • 为 API 服务器提供一个接口,以执行诸如kubectl logskubectl execkubectl attach等命令,用于 pod

以下图显示了一个具有标准和虚拟 kubelet 的 Kubernetes 集群:

图 6.39:具有标准 kubelet 和虚拟 kubelet 的 Kubernetes 集群

图 6.39:具有标准 kubelet 和虚拟 kubelet 的 Kubernetes 集群

从 Kubernetes API 的视角来看,Virtual Kubelet 将会像传统的 kubelet 一样。它将在现有的 Kubernetes 集群中运行,并在 Kubernetes API 中注册自己为一个节点。Virtual Kubelet 将以与 kubelet 相同的方式运行和管理 pod。但与在节点内运行 pod 的 kubelet 相反,Virtual Kubelet 将利用外部服务来运行 pod。这将把 Kubernetes 集群连接到其他服务,如无服务器容器平台。Virtual Kubelet 支持越来越多的提供者,包括以下:

  • 阿里巴巴云弹性容器实例(ECI)

  • AWS Fargate

  • Azure Batch

  • Azure 容器实例(ACI)

  • Kubernetes 容器运行时接口(CRI)

  • 华为云容器实例(CCI)

  • HashiCorp Nomad

  • OpenStack Zun

在这些平台上运行 pod 带来了无服务器世界的好处。我们不必担心基础架构,因为它由云提供商管理。Pod 将根据收到的请求数量自动扩展和缩减。此外,我们只需为使用的资源付费。

练习 20:在 AKS 上部署 Virtual Kubelet

在这个练习中,我们将在 Azure Kubernetes Service(AKS)上配置 Virtual Kubelet,并使用 ACI 提供程序。在这个练习中,我们将使用 Azure 中提供的以下服务。

  • AKS:AKS 是 Azure 上的托管 Kubernetes 服务。

  • ACI:ACI 提供了在 Azure 上运行容器的托管服务。

  • Azure Cloud Shell:一个交互式的基于浏览器的 shell,支持 Bash 和 PowerShell。

您需要具备以下先决条件才能进行这个练习:

  • Microsoft Azure 账户

  • Azure CLI

  • kubectl CLI

  • Helm

我们将使用 Azure Cloud Shell,其中预先安装了所有先前提到的 CLI:

  1. 转到shell.azure.com/在浏览器窗口中打开 Cloud Shell。从“欢迎使用 Azure Cloud Shell”窗口中选择Bash图 6.40:欢迎使用 Azure Cloud Shell 窗口
图 6.40:欢迎使用 Azure Cloud Shell 窗口
  1. 单击“创建存储”按钮以为 Cloud Shell 创建存储账户。请注意,这是一个一次性任务,仅在我们第一次使用 Cloud Shell 时需要执行:图 6.41:为 Cloud Shell 挂载存储
图 6.41:为 Cloud Shell 挂载存储

Cloud Shell 窗口将如下所示:

图 6.42:Cloud Shell 窗口

图 6.42:Cloud Shell 窗口
  1. 一旦 Cloud Shell 准备就绪,我们就可以开始创建 AKS 集群。

首先,我们需要创建一个 Azure 资源组,以便逻辑上将相关的 Azure 资源分组。执行以下命令,在 West US(westus)地区创建一个名为serverless-kubernetes-group的资源组:

$ az group create --name serverless-kubernetes-group --location westus

输出应该如下所示:

图 6.43:创建 Azure 资源组

图 6.43:创建 Azure 资源组
  1. 注册您的订阅以使用Microsoft.Network命名空间:
$ az provider register --namespace Microsoft.Networks

输出应该如下所示:

图 6.44:注册订阅

图 6.44:注册订阅
  1. 接下来,我们将创建一个 Azure Kubernetes 集群。以下命令将创建一个名为virtual-kubelet-cluster的 AKS 集群,其中包含一个节点。此命令将需要几分钟来执行:
$ az aks create --resource-group serverless-kubernetes-group --name virtual-kubelet-cluster --node-count 1 --node-vm-size Standard_D2 --network-plugin azure --generate-ssh-keys

AKS 集群创建成功后,上述命令将返回一些 JSON 输出,其中包含集群的详细信息:

图 6.45:创建 AKS 集群

图 6.45:创建 AKS 集群
  1. 接下来,我们需要配置 kubectl CLI 以与新创建的 AKS 集群通信。执行az aks get-credentials命令来下载凭据并配置 kubectl CLI 以与virtual-kubelet-cluster集群一起工作的命令如下:

注意

我们不需要安装 kubectl CLI,因为 Cloud Shell 已经预装了 kubectl。

$ az aks get-credentials --resource-group serverless-kubernetes-group --name virtual-kubelet-cluster

输出应该如下所示:

图 6.46:配置 kubectl

图 6.46:配置 kubectl
  1. 现在我们可以通过执行 kubectl get nodes 命令来验证从 Cloud Shell 到集群的连接,该命令将列出 AKS 集群中可用的节点:
$ kubectl get nodes

输出应如下所示:

图 6.47:列出 Kubernetes 节点

图 6.47:列出 Kubernetes 节点
  1. 如果这是您第一次使用 ACI 服务,您需要在订阅中注册 Microsoft.ContainerInstance 提供程序。我们可以使用以下命令检查 Microsoft.ContainerInstance 提供程序的注册状态:
$ az provider list --query "[?contains(namespace,'Microsoft.ContainerInstance')]" -o table

输出应如下所示:

图 6.48:检查 Microsoft.ContainerInstace 提供程序的注册状态

图 6.48:检查 Microsoft.ContainerInstace 提供程序的注册状态
  1. 如果 RegistrationStatus 列包含值 NotRegistered,则执行 az provider register 命令来注册 Microsoft.ContainerInstance 提供程序。如果 RegistrationStatus 列包含值 Registered,则可以继续下一步:
$ az provider register --namespace Microsoft.ContainerInstance

输出应如下所示:

图 6.49:注册 Microsoft.ContainerInstance 提供程序

图 6.49:注册 Microsoft.ContainerInstance 提供程序
  1. 下一步是为 tiller 创建必要的 ServiceAccountServiceAccount 对象。创建一个名为 tiller-rbac.yaml 的文件,其中包含以下代码:
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system
  1. 然后执行 kubectl apply 命令来创建必要的 ServiceAccountClusterRoleBinding 对象:
$ kubectl apply -f tiller-rbac.yaml

输出应如下所示:

图 6.50:创建 ServiceAccount 和 ClusterRoleBinding 对象

图 6.50:创建 ServiceAccount 和 ClusterRoleBinding 对象
  1. 现在我们可以配置 Helm 使用我们在上一步中创建的 tiller 服务账户:
$ helm init --service-account tiller

输出应如下所示:

图 6.51:配置 tiller

图 6.51:配置 tiller
  1. 一旦所有配置都完成,我们可以使用 az aks install-connector 命令安装虚拟 Kubelet。我们将使用以下命令部署 Linux 和 Windows 连接器:
$ az aks install-connector \
    --resource-group serverless-kubernetes-group \
    --name virtual-kubelet-cluster \
    --connector-name virtual-kubelet \
    --os-type Both

输出应如下所示:

图 6.52:安装虚拟 Kubelet

图 6.52:安装虚拟 Kubelet
  1. 安装完成后,我们可以通过列出 Kubernetes 节点来验证它。将会有两个新节点,一个用于 Windows,一个用于 Linux:
$ kubectl get nodes

输出应如下所示:

图 6.53:列出 Kubernetes 节点

图 6.53:列出 Kubernetes 节点
  1. 现在我们已经在 AKS 集群中安装了 Virtual Kubelet。我们可以将一个应用程序部署到 Virtual Kubelet 引入的新节点上。我们将创建一个名为hello-world的 Kubernetes Deployment,使用microsoft/aci-helloworld Docker 镜像。

我们需要添加一个nodeSelector,将此 pod 专门分配给 Virtual Kubelet 节点。请注意,Virtual Kubelet 节点默认会被标记,以防止意外的 pod 在其上运行。我们需要为 pod 添加 tolerations,以允许它们被调度到这些节点上。

让我们创建一个名为hello-world.yaml的文件,内容如下:

     apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
      - name: hello-world
        image: microsoft/aci-helloworld
        ports:
        - containerPort: 80
      nodeSelector:
        kubernetes.io/role: agent
        type: virtual-kubelet
        beta.kubernetes.io/os: linux
      tolerations:
      - key: virtual-kubelet.io/provider
        operator: Equal
        value: azure
        effect: NoSchedule
  1. 使用kubectl apply命令部署hello-world应用程序:
$ kubectl apply -f hello-world.yaml

输出应如下所示:

图 6.54:创建 hello-world 部署

图 6.54:创建 hello-world 部署
  1. 使用kubectl get pods命令和-o wide标志执行,以输出一个包含各个 pod 及其相应节点的列表。请注意,hello-world-57f597bc59-q9w9k pod 已被调度到virtual-kubelet-virtual-kubelet-linux-westus节点上:
$ kubectl get pods -o wide

输出应如下所示:

图 6.55:使用-o wide 标志列出所有 pod

图 6.55:使用-o wide 标志列出所有 pod

因此,我们已成功在 AKS 上配置了带有 ACI 的 Virtual Kubelet,并在 Virtual Kubelet 节点上部署了一个 pod。

现在让我们完成一个活动,我们将在无服务器环境中部署一个容器化应用程序。

活动 6:在无服务器环境中部署容器化应用程序

假设你在一家初创公司工作,你的经理希望你创建一个可以根据给定时区返回当前日期和时间的应用程序。在初始阶段,预计该应用程序只会收到少量请求,但从长远来看将收到数百万个请求。该应用程序应能根据收到的请求数量自动扩展,无需进行任何修改。此外,你的经理不希望承担管理基础设施的负担,并希望该应用程序以尽可能低的成本运行。

执行以下步骤完成此活动:

  1. 创建一个应用程序(使用任何你想要的语言),可以根据给定的timezone值提供当前日期和时间。

以下是用 PHP 编写的一些示例应用程序代码:

     <?php
if ( !isset ( $_GET['timezone'] ) ) {
    // Returns error if the timezone parameter is not provided
    $output_message = "Error: Timezone not provided"; 
} else if ( empty ( $_GET['timezone'] ) ) {
    // Returns error if the timezone parameter value is empty
    $output_message = "Error: Timezone cannot be empty"; 
} else {
    // Save the timezone parameter value to a variable
    $timezone = $_GET['timezone'];

    try {
        // Generates the current time for the provided timezone
        $date = new DateTime("now", new DateTimeZone($timezone) );
        $formatted_date_time = $date->format('Y-m-d H:i:s');
        $output_message = "Current date and time for $timezone is $formatted_date_time";
    } catch(Exception $e) {
        // Returns error if the timezone is invalid
        $output_message = "Error: Invalid timezone value"; 
    }
}
// Return the output message
echo $output_message;
  1. 根据 Google Cloud Run 提供的指南对应用程序进行容器化。

以下是一个示例 Dockerfile 的内容:

# Use official PHP 7.3 image as base image
FROM php:7.3-apache
# Copy index.php file to the docker image
COPY index.php /var/www/html/
# Replace port 80 with the value from PORT environment variable in apache2 configuration files
RUN sed -i 's/80/${PORT}/g' /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf
# Use the default production configuration file
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
  1. 将 Docker 镜像推送到 Docker 注册表。

  2. 使用 Cloud Run 运行应用程序。

输出应该如下所示:

图 6.56:在无服务器环境中部署应用程序

图 6.56:在无服务器环境中部署应用程序

注意

活动的解决方案可以在第 417 页找到。

摘要

在本章中,我们讨论了在 Kubernetes 上使用无服务器的优势。我们讨论了三种技术,它们在 Kubernetes 集群之上提供了无服务器的好处。这些技术是 Knative、Google Cloud Run 和 Virtual Kubelet。

首先,我们创建了一个带有 Istio 的 GKE 集群,并在其上部署了 Knative。然后我们学习了如何在 Knative 上部署应用程序。接下来,我们讨论了 Knative 的 serving 组件,以及如何使用配置和路由对象执行金丝雀部署。然后我们讨论了 Knative 上的监控,并观察了 Knative 根据收到的请求数进行自动扩展的工作原理。

我们还讨论了 Google Cloud Run,这是一个完全托管的平台,建立在 Knative 项目之上,用于运行无状态的 HTTP 驱动容器。然后我们学习了如何使用 Cloud Run 服务部署应用程序。

在最后一节中,我们学习了 Virtual Kubelet,这是 Kubernetes kubelet 的开源实现。我们了解了普通 kubelet 和 Virtual Kubelet 之间的区别。最后,我们在 AKS 集群上部署了 Virtual Kubelet,并将应用程序部署到了 Virtual Kubelet 节点。

在接下来的三章中,我们将专注于三种不同的 Kubernetes 无服务器框架,分别是 Kubeless、OpenWhisk 和 OpenFaaS。

第七章: 使用 Kubeless 的 Kubernetes 无服务器

学习目标

到本章结束时,您将能够:

  • 使用 Minikube 创建 Kubernetes 集群

  • 在 Kubernetes 上安装 Kubeless 框架

  • 创建、更新、调用和删除 Kubeless 函数

  • 列出、描述、调试和监视 Kubeless 函数

  • 为 Kubeless 函数创建 HTTP 和 PubSub 触发器

在本章中,我们将首先了解 Kubeless 架构。然后,我们将创建我们的第一个 Kubeless 函数,部署它并调用它。您还将学习如何在 Kubeless 函数失败的情况下进行调试。

Kubeless 简介

Kubeless是一个开源的、基于 Kubernetes 的无服务器框架,运行在 Kubernetes 之上。这使软件开发人员可以将代码部署到 Kubernetes 集群中,而不必担心底层基础设施。Kubeless是 Bitnami 的一个项目,Bitnami 是任何平台上打包应用程序的提供商。Bitnami 为超过 130 个应用程序提供软件安装程序,这使您可以快速高效地将这些软件应用程序部署到任何平台。

Kubeless函数支持多种编程语言,包括 Python、PHP、Ruby、Node.js、Golang、Java、.NET、Ballerina 和自定义运行时。这些函数可以通过 HTTP(S)调用以及使用 Kafka 或 NATS 消息系统的事件触发器来调用。Kubeless 还支持 Kinesis 触发器,将函数与 AWS Kinesis 服务关联起来,这是 AWS 提供的托管数据流服务。Kubeless 函数甚至可以使用定时触发器在指定的时间间隔内被调用。

Kubeless 配备了自己的命令行界面(CLI),名为kubeless,类似于 Kubernetes 提供的kubectl CLI。我们可以使用这个kubeless CLI 来创建、部署、列出和删除 Kubeless 函数。Kubeless 还有一个图形用户界面,使函数的管理更加容易。

在本章中,我们将使用 Kubeless 在 Kubernetes 上创建我们的第一个无服务器函数。然后,我们将使用多种机制调用此函数,包括 HTTP 和 PubSub 触发器。一旦我们熟悉了 Kubeless 的基础知识,我们将创建一个更高级的函数,可以向 Slack 发布消息。

Kubeless 架构

Kubeless 框架是 Kubernetes 框架的扩展,利用了原生 Kubernetes 概念,如自定义资源定义CRDs)和自定义控制器。由于 Kubeless 是建立在 Kubernetes 之上的,它可以利用 Kubernetes 中可用的所有出色功能,如自愈、自动扩展、负载平衡和服务发现。

注意

自定义资源是 Kubernetes API 的扩展。您可以在官方 Kubernetes 文档中找到有关 Kubernetes 自定义资源的更多信息,网址为kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/

让我们来看看 Kubernetes 架构,以了解其背后的核心概念:

图 7.1:Kubeless 架构图

图 7.1:Kubeless 架构图

前面的图表类似于标准的 Kubernetes 架构,包括 Kubernetes 主节点和节点。可以有一个或多个负责集群整体决策的 Kubernetes 主节点。Kubernetes 节点用于托管 Kubernetes pod。这些 pod 包含软件开发人员编写的函数。函数的源代码将由控制器使用ConfigMaps注入到 pod 中。

这些 pod 将由Kubeless 控制器管理。在 Kubeless 框架安装过程中,它将启动一个集群内控制器,该控制器将持续监视函数资源。当部署函数时,该控制器将使用提供的运行时创建相关的服务、部署和 pod。

Kubeless 框架有三个核心概念:

  • 功能

  • 触发器

  • 运行时

函数代表 Kubeless 框架执行的代码块。在安装过程中,将创建一个名为functions.kubeless.io的 CRD 来表示 Kubeless 函数。

触发器代表函数的调用机制。当接收到触发器时,Kubeless 函数将被调用。一个触发器可以关联一个或多个函数。在 Kubeless 上部署的函数可以使用五种可能的机制进行触发:

  • HTTP 触发器:这是通过基于 HTTP(S)的调用执行的,比如 HTTP GET 或 POST 请求。

  • CronJob 触发器:这是通过预定义的时间表执行的。

  • Kafka 触发器:当消息发布到 Kafka 主题时执行。

  • NATS 触发器:当消息发布到 NATS 主题时执行。

  • Kinesis 触发器:当记录发布到 AWS Kinesis 数据流时执行。

运行时代表可以用于编写和执行 Kubeless 函数的不同编程语言。单个编程语言将根据版本进一步分为多个运行时。例如,Python 2.7、Python 3.4、Python 3.6 和 Python 3.7 是支持 Python 编程语言的运行时。Kubeless 支持稳定阶段和孵化器阶段的运行时。一旦满足 Kubeless 指定的某些技术要求,运行时将被视为稳定。孵化器运行时被视为处于开发阶段。一旦满足指定的技术要求,运行时维护者可以在 Kubeless GitHub 存储库中创建一个“pull”请求,将运行时从孵化器阶段移至稳定阶段。在撰写本书时,Ballerina、.NET、Golang、Java、Node.js、PHP 和 Python 运行时在稳定阶段可用,JVM 和 Vertx 运行时在孵化器阶段可用。

注意

以下文档定义了稳定运行时的技术要求:github.com/kubeless/runtimes/blob/master/DEVELOPER_GUIDE.md#runtime-image-requirements

创建 Kubernetes 集群

我们需要一个工作的 Kubernetes 集群才能安装 Kubeless 框架。您可以使用 Minikube、Kubeadm 和 Kops 等工具创建自己的 Kubernetes 集群。您还可以使用公共云提供商提供的托管 Kubernetes 集群服务,如 Google Kubernetes Engine(GKE)、Microsoft 的 Azure Kubernetes Service(AKS)和 Amazon Elastic Kubernetes Service(Amazon EKS)来创建 Kubernetes 集群。在接下来的章节中,我们将使用 Minikube 创建自己的 Kubernetes 集群。

使用 Minikube 创建 Kubernetes 集群

首先,我们将使用 Minikube 创建我们的 Kubernetes 集群。Minikube 是一个工具,可以在您的个人电脑上安装和运行 Kubernetes。这将在虚拟机VM)内创建一个单节点 Kubernetes 集群。Minikube 被软件开发人员用来在本地尝试 Kubernetes,但不建议用于运行生产级别的 Kubernetes 集群。我们将通过以下步骤开始创建我们的 Kubernetes 集群:

  1. 安装 VirtualBox。

由于 Minikube 作为虚拟机运行,我们需要安装一个支持虚拟机的 hypervisor。我们将安装由 Oracle Corporation 开发的免费虚拟化软件 Oracle VirtualBox。

注意

可以通过在终端中执行以下命令在 Ubuntu 18.04 上使用 APT 软件包管理器安装 VirtualBox:

$ sudo apt install virtualbox -y

  1. 执行virtualbox命令启动Oracle VM VirtualBox Manager,如下截图所示:
$ virtualbox

图 7.2:Oracle VM VirtualBox Manager

图 7.2:Oracle VM VirtualBox Manager
  1. 安装minikube

现在,我们将安装Minikube版本 1.2.0,这是撰写本书时的最新版本。首先,将minikube二进制文件下载到本地机器:

$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v1.2.0/minikube-linux-amd64

输出如下:

图 7.3:下载 Minikube 二进制文件

图 7.3:下载 Minikube 二进制文件
  1. 然后,为minikube二进制文件添加执行权限:
$ chmod +x minikube 

输出如下:

图 7.4:为 Minikube 二进制文件添加执行权限

图 7.4:为 Minikube 二进制文件添加执行权限
  1. 最后,将 Minikube 二进制文件移动到/usr/local/bin/路径位置:
$ sudo mv minikube /usr/local/bin/

结果如下截图所示:

图 7.5:将 Minikube 二进制文件移动到路径

图 7.5:将 Minikube 二进制文件移动到路径
  1. 验证安装:
$ minikube version

结果如下截图所示:

图 7.6:验证 Minikube 版本

图 7.6:验证 Minikube 版本
  1. 使用minikube start命令启动 Minikube 集群:
$ minikube start

这将在 VirtualBox 中为 Minikube 创建一个虚拟机,如下所示:

图 7.7:启动 Minikube

图 7.7:启动 Minikube

现在,在VirtualBox Manager窗口中,您可以看到一个名为minikube的虚拟机处于运行状态:

图 7.8:带有 Minikube VM 的 Oracle VirtualBox

图 7.8:带有 Minikube VM 的 Oracle VirtualBox
  1. 安装kubectl

现在,我们将安装kubectl版本 1.15.0,这是撰写本书时可用的最新版本。首先,将kubectl二进制文件下载到您的本地机器:

$ curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.15.0/bin/linux/amd64/kubectl

这将显示以下输出:

图 7.9:下载 kubectl 二进制文件

图 7.9:下载 kubectl 二进制文件
  1. 然后,为 Minikube 二进制文件添加执行权限:
$ chmod +x kubectl

以下截图显示了结果:

图 7.10:为 kubectl 二进制文件添加执行权限

图 7.10:为 kubectl 二进制文件添加执行权限
  1. 最后,将 Minikube 二进制文件移动到/usr/local/bin/路径位置:
$ sudo mv kubectl /usr/local/bin/kubectl

输出如下:

图 7.11:将 kubectl 二进制文件移动到路径

图 7.11:将 kubectl 二进制文件移动到路径
  1. 验证安装:
$ kubectl version

屏幕上将显示以下内容:

图 7.12:验证 kubectl 版本

图 7.12:验证 kubectl 版本
  1. 验证kubectl CLI 是否正确指向 Minikube 集群:
$ kubectl get pods

您应该看到以下输出:

图 7.13:验证 kubectl 是否指向 Minikube 集群

图 7.13:验证 kubectl 是否指向 Minikube 集群

安装 Kubeless

一旦 Minikube Kubernetes 环境准备就绪,我们可以在 Kubernetes 集群之上安装 Kubeless。安装 Kubeless 包括安装三个组件:

  • Kubeless 框架

  • Kubeless CLI

  • Kubeless UI

Kubeless 框架将在 Kubernetes 之上安装所有扩展以支持 Kubeless 功能。这包括 CRD、自定义控制器和部署。Kubeless CLI 用于与 Kubeless 框架交互,执行任务如部署函数、调用函数和创建触发器。Kubeless UI 是 Kubeless 框架的 GUI,可帮助您查看、编辑和运行函数。

安装 Kubeless 框架

我们将安装 Kubeless 版本 1.0.3,这是撰写本书时可用的最新版本。

首先,我们需要使用kubectl create namespace创建kubeless命名空间。这是 Kubeless 使用的默认命名空间,用于存储所有对象:

$ kubectl create namespace kubeless

结果如下:

图 7.14:创建 kubeless 命名空间

图 7.14:创建 kubeless 命名空间

在下一步中,我们将安装 Kubeless 框架。我们将使用 Kubeless 提供的 YAML 清单之一来安装框架。Kubeless 提供了多个yaml文件,我们必须根据 Kubernetes 环境(例如rbacnon-rbacopenshift)选择正确的yaml文件:

$ kubectl create -f https://github.com/kubeless/kubeless/releases/download/v1.0.3/kubeless-v1.0.3.yaml 

屏幕将显示以下内容:

图 7.15:安装 Kubeless 框架

图 7.15:安装 Kubeless 框架

上一步将在kubeless命名空间中创建多个 Kubernetes 对象。这将创建一个函数对象作为自定义资源定义和 Kubeless 控制器作为部署。您可以通过执行以下命令验证这些对象是否正在运行:

$ kubectl get pods -n kubeless
$ kubectl get deployment -n kubeless
$ kubectl get customresourcedefinition

您将在屏幕上看到以下内容:

图 7.16:验证 Kubeless 安装

图 7.16:验证 Kubeless 安装

现在,我们已成功完成了 Kubeless 框架的安装。在下一节中,我们将安装 Kubeless CLI。

安装 Kubeless CLI

Kubeless CLI是针对 Kubeless 框架运行命令的命令行界面。kubeless function是最常见的命令,因为它允许您执行诸如部署、调用、更新或删除函数等任务。此外,您还可以通过kubeless function命令列出和描述函数。还支持通过kubeless function命令检查日志或指标。您还可以通过 Kubeless CLI 管理 Kubeless 触发器、主题和自动缩放。

安装成功 Kubeless 框架后,下一步是安装 Kubeless CLI。我们将使用 Kubeless CLI 版本 1.0.3,这与我们在上一节中安装的 Kubeless 框架版本相同。

首先,我们需要下载 Kubeless CLI zip 文件:

$ curl -OL https://github.com/kubeless/kubeless/releases/download/v1.0.3/kubeless_linux-amd64.zip 

结果如下:

图 7.17:下载 Kubeless 二进制文件

图 7.17:下载 Kubeless 二进制文件

接下来,我们将提取 zip 文件:

$ unzip kubeless_linux-amd64.zip

要更好地理解这一点,请参考以下输出:

图 7.18:提取 Kubeless 二进制文件

图 7.18:提取 Kubeless 二进制文件

然后,将 Kubeless 可执行文件移动到/usr/local/bin/路径位置:

$ sudo mv bundles/kubeless_linux-amd64/kubeless /usr/local/bin/

以下是您在屏幕上看到的内容:

图 7.19:将 Kubeless 二进制文件移动到路径

图 7.19:将 Kubeless 二进制文件移动到路径

现在,我们已经成功安装了 Kubeless CLI。您可以通过运行以下命令来验证:

$ kubeless version

参考以下截图:

图 7.20:验证 Kubeless 版本

图 7.20:验证 Kubeless 版本

Kubeless UI

Kubeless UI 是 Kubeless 的图形用户界面。它允许您使用易于使用的 UI 创建、编辑、删除和执行 Kubeless 函数。执行以下命令在 Kubernetes 集群中安装 Kubeless UI:

$ kubectl create -f https://raw.githubusercontent.com/kubeless/kubeless-ui/master/k8s.yaml

这将给你以下输出:

图 7.21:安装 Kubeless UI

图 7.21:安装 Kubeless UI

安装成功后,执行以下命令在浏览器窗口中打开 Kubeless UI。如果 Kubeless UI 没有显示出来,可以重新加载浏览器窗口,因为创建服务可能需要几分钟时间:

$ minikube service ui --namespace kubeless

如下所示:

图 7.22:Kubeless GUI

图 7.22:Kubeless GUI

我们刚刚完成了 Kubeless UI 的安装,它可以用来创建、编辑、删除和执行类似于 Kubeless CLI 的 Kubeless 函数。

Kubeless 函数

一旦 Kubeless 成功安装,您现在可以忘记底层基础设施,包括虚拟机和容器,只专注于您的函数逻辑。Kubeless 函数是用其中一种支持的语言编写的代码片段。正如我们之前讨论的,Kubeless 支持多种编程语言和版本。您可以执行 kubeless get-server-config 命令来获取您的 Kubeless 版本支持的语言运行时列表:

$ kubeless get-server-config 

结果如下截图所示:

图 7.23:Kubeless 服务器配置

图 7.23:Kubeless 服务器配置

在接下来的章节中,我们将创建、部署、列出、调用、更新和删除 Kubeless 函数。

创建 Kubeless 函数

每个 Kubeless 函数,无论语言运行时如何,都具有相同的格式。它接收两个参数作为输入,并返回一个字符串或对象作为响应。函数的第一个参数是一个事件,其中包括有关事件源的所有信息,例如事件 ID、事件时间和事件类型。event对象内的data字段包含函数请求的主体。函数的第二个参数命名为context,其中包含有关函数的一般信息,例如其名称、超时、运行时和内存限制。

以下是一个返回文本Welcome to Kubeless World作为响应的示例 Python 函数:

def main(event, context):
    return "Welcome to Kubeless World"  

您可以将文件保存为hello.py

部署 Kubeless 函数

一旦函数准备就绪,您可以将其部署到 Kubeless 框架中。您可以使用kubeless function deploy命令将函数注册到 Kubeless 框架中。为了部署函数,您需要提供一些信息,包括函数名称、函数的运行时、包含函数源代码的文件以及在调用函数时要执行的方法名称:

kubeless function deploy hello --runtime python3.7 \
                           --from-file hello.py \
                           --handler hello.main

输出如下:

图 7.24:部署 Kubeless 函数

图 7.24:部署 Kubeless 函数

让我们将这个命令分解成几个部分,以便了解命令的每个部分的作用:

  • kubeless function deploy hello:这告诉 Kubeless 注册一个名为hello的新函数。我们可以在以后使用这个名称来调用这个函数。

  • --runtime python3.7:这告诉 Kubeless 使用 Python 3.7 运行时来运行此函数。

  • --from-file hello.py:这告诉 Kubeless 使用hello.py文件中可用的代码来创建hello函数。如果在执行命令时不在当前文件路径中,需要指定完整的文件路径。

  • --handler hello.main:这指定了在调用此函数时要执行的代码文件的名称和方法的名称。这应该是<file-name>.<function-name>的格式。在我们的情况下,文件名是hello,文件内的函数名是main

您可以通过执行kubeless function deploy --help命令找到在部署函数时可用的其他选项。

列出 Kubeless 函数

部署函数后,您可以使用kubeless function list命令列出函数,以验证函数是否成功部署。您应该看到所有注册函数的详细信息如下:

$ kubeless function list

以下截图反映了结果:

图 7.25:使用 Kubeless CLI 列出 Kubeless 函数

图 7.25:使用 Kubeless CLI 列出 Kubeless 函数

注意

同样可以使用kubeless function ls命令实现。

如果您希望获取有关特定函数的更详细信息,可以使用kubeless function describe命令:

$ kubeless function describe hello

它产生以下输出:

图 7.26:描述 Kubeless 函数

图 7.26:描述 Kubeless 函数

由于 Kubeless 函数被创建为 Kubernetes 对象(即自定义资源),您还可以使用 Kubectl CLI 获取有关可用函数的信息。以下是kubectl get functions命令的输出:

$ kubectl get functions

您将得到以下输出:

图 7.27:使用 kubectl CLI 列出 Kubeless 函数

图 7.27:使用 kubectl CLI 列出 Kubeless 函数

调用 Kubeless 函数

现在是时候调用我们的hello函数了。您可以使用kubeless function call方法来调用 Kubeless 函数。hello函数将返回文本Welcome to Kubeless World作为响应:

$ kubeless function call hello

输出如下:

图 7.28:使用 kubeless CLI 调用 Kubeless 函数

图 7.28:使用 kubeless CLI 调用 Kubeless 函数

恭喜!您已成功执行了您的第一个 Kubeless 函数。

您还可以使用 Kubeless UI 调用 Kubeless 函数。打开 Kubeless UI 后,您可以在左侧看到可用函数的列表。您可以点击hello函数以打开它。然后,点击Run函数按钮来执行函数。您可以在Response部分下看到预期的Welcome to Kubeless World响应:

图 7.29:使用 Kubeless UI 调用 Kubeless 函数

图 7.29:使用 Kubeless UI 调用 Kubeless 函数

注意

Kubeless 函数也可以使用 Kubeless UI 进行更新或删除。

更新 Kubeless 函数

成功调用我们的hello函数后,现在我们将对其进行更新,以向任何人说hello。您可以按照以下方式更新hello.py文件:

def main(event, context):
   name = event['data']['name']
   return "Hello " +  name

然后,您可以执行kubeless function update命令来更新我们之前创建的hello函数:

$ kubeless function update hello --from-file hello.py

这将产生以下输出:

图 7.30:使用 Kubeless CLI 更新 Kubeless 函数

图 7.30:使用 Kubeless CLI 更新 Kubeless 函数

现在在调用hello函数时,您必须传递所需的数据:

$ kubeless function call hello --data '{"name":"Kubeless World!"}'

这是上述代码的输出:

图 7.31:调用更新后的 Kubeless 函数

图 7.31:调用更新后的 Kubeless 函数

您应该能够在上述命令的输出中看到Hello Kubeless World!

删除 Kubeless 函数

如果您想要删除该函数,可以执行kubeless function delete命令:

$ kubeless function delete hello

这将产生以下结果:

图 7.32:删除 kubeless 函数

图 7.32:删除 kubeless 函数

一旦函数被删除,尝试再次列出函数。它应该会抛出一个错误,如下所示:

$ kubeless function list hello

我们将看到以下结果:

图 7.33:验证删除 kubeless 函数

图 7.33:验证删除 kubeless 函数

上述的kubeless function delete命令不仅会删除kubeless函数,而且在创建 Kubeless 函数时,框架会创建诸如 pod 和 deployment 之类的 Kubernetes 对象。当我们删除 kubeless 函数时,这些对象也将被删除。您可以使用以下命令进行验证:

$ kubectl get pods -l function=hello

您可以按照以下方式查看结果:

图 7.34:验证删除

图 7.34:验证删除

现在我们已经学会了如何创建、部署、列出、调用、更新和删除 Kubeless 函数。让我们继续进行一个关于创建您的第一个 Kubeless 函数的练习。

练习 21:创建您的第一个 Kubeless 函数

在这个练习中,我们将创建、部署、调用,然后删除一个 Kubeless 函数。执行以下步骤来完成练习:

注意

此练习的代码文件可以在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson07/Exercise21找到。

  1. 创建一个带有示例hello函数的文件:
$ cat <<EOF >my-function.py
def main(event, context):
    return "Welcome to Serverless Architectures with Kubernetes"
EOF

这将呈现以下输出:

图 7.35:创建 my-function.py 文件

图 7.35:创建 my-function.py 文件
  1. 创建lesson-7命名空间并部署之前创建的my-function.py文件:
$ kubectl create namespace lesson-7
$ kubeless function deploy my-function --runtime python3.7 \
                                  --from-file my-function.py \
                                  --handler my-function.main \
                                  --namespace lesson-7

输出如下:

图 7.36:部署 my-function

图 7.36:部署 my-function
  1. 验证my-function是否已正确部署:
$ kubeless function list my-function --namespace lesson-7

输出如下:

图 7.37:验证 my-function 已成功部署

图 7.37:验证 my-function 已成功部署
  1. 使用kubeless CLI 调用my-function
$ kubeless function call my-function --namespace lesson-7

它看起来像这样:

图 7.38:使用 Kubeless CLI 调用 my-function

图 7.38:使用 Kubeless CLI 调用 my-function
  1. 删除my-functionlesson-7命名空间:
$ kubeless function delete my-function --namespace lesson-7
$ kubectl delete namespace lesson-7

以下是我们得到的:

图 7.39:使用 Kubeless CLI 删除 my-function

图 7.39:使用 Kubeless CLI 删除 my-function

在这个练习中,首先,我们创建了一个简单的 Python 函数,它返回Welcome to Serverless Architectures with Kubernetes字符串作为输出,并将其部署到 Kubeless。然后,我们列出函数以确保它已成功创建。然后,我们调用了my-function并成功返回了预期的响应Welcome to Serverless Architectures with Kubernetes。最后,我们通过删除函数进行清理。

Kubeless HTTP 触发器

在前面的部分中,我们讨论了如何使用 Kubeless CLI 调用 Kubeless 函数。在本节中,我们将演示如何通过创建 HTTP 触发器向所有人公开这些函数。

HTTP 触发器用于通过基于 HTTP(S)的调用(如 HTTP GETPOST请求)执行 Kubeless 函数。当函数部署时,Kubeless 将创建一个与函数关联的 Kubernetes 服务,服务类型为ClusterIP;然而,这些服务是不可公开访问的。为了使函数公开可用,我们需要创建一个 Kubeless HTTP 触发器。这将通过使用 Kubernetes 入口规则向所有人公开 Kubeless 函数。

为了运行 HTTP 触发器,您的 Kubernetes 集群必须有一个运行中的入口控制器。一旦入口控制器在 Kubernetes 集群中运行,您可以使用kubeless trigger http create命令创建一个 HTTP 触发器:

$ kubeless trigger http create <trigger-name> --function-name <function-name>

--function-name标志用于指定将与 HTTP 触发器关联的函数的名称。

注意

Kubernetes 有许多可用的 ingress 控制器插件,包括 NGINX、Kong、Traefik、F5、Contour 等。您可以在kubernetes.io/docs/concepts/services-networking/ingress-controllers/找到它们。

练习 22:为 Kubeless 函数创建 HTTP 触发器

在这个练习中,我们将首先为 Minikube 启用 ingress 插件。然后,我们将创建一个要与 HTTP 触发器一起执行的函数。最后,我们将创建一个 HTTP 触发器并使用 HTTP 触发器调用此函数。

注意

此练习的代码文件可以在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson07/Exercise22找到。

执行以下步骤完成练习:

  1. 首先,我们需要在 Minikube 集群中启用ingress插件:
$ minikube addons enable ingress

这显示以下输出:

图 7.40:启用 Minikube 插件

图 7.40:启用 Minikube 插件
  1. 几分钟后,您应该能够看到kube-system命名空间中已创建了nginx-ingress-controller容器,这是 Kubernetes 系统创建的对象的命名空间:
$ kubectl get pod -n kube-system -l app.kubernetes.io/name=nginx-ingress-controller

它显示如下:

图 7.41:列出 nginx-ingress-controller pod

图 7.41:列出 nginx-ingress-controller pod
  1. 一旦nginx-ingress-controller容器处于运行状态,我们将创建要与 HTTP 触发器一起执行的函数。创建一个名为greetings.py的 Python 文件,内容如下:
import datetime as dt
def main(event, context):
    currentHour = dt.datetime.now().hour
    greetingMessage = ''
    if currentHour < 12:
        greetingMessage = 'Hello, Good morning!'
    elif currentHour < 18:
        greetingMessage = 'Hello, Good afternoon!'
    else:
        greetingMessage = 'Hello, Good evening!'
    return greetingMessage
  1. 创建lesson-7命名空间并部署之前创建的greetings.py
$ kubectl create namespace lesson-7
$ kubeless function deploy greetings --runtime python3.7 \
                                  --from-file greetings.py \
                                  --handler greetings.main \
                                  --namespace lesson-7

参考以下输出:

图 7.42:使用 HTTP 触发器执行函数

图 7.42:使用 HTTP 触发器执行函数
  1. 调用函数并验证函数是否提供了预期的输出:
$ kubeless function call greetings --namespace lesson-7

一旦调用,屏幕将显示以下内容:

图 7.43:函数输出

图 7.43:函数输出
  1. 现在我们可以为hello函数创建http触发器:
$ kubeless trigger http create greetings \
                       --function-name greetings \
                       --namespace lesson-7

结果如下:

图 7.44:创建 HTTP 触发器

图 7.44:创建 HTTP 触发器
  1. 列出http触发器;您应该能够看到hello函数的http触发器:
$ kubeless trigger http list --namespace lesson-7

列表将看起来像这样:

图 7.45:列出 HTTP 触发器

图 7.45:列出 HTTP 触发器
  1. 这将在 Kubernetes 层创建一个ingress对象。我们可以使用kubectl CLI 列出ingress对象:
$ kubectl get ingress --namespace lesson-7

这将返回以下内容:

图 7.46:列出入口对象

图 7.46:列出入口对象
  1. 您可以看到带有.nip.io域名的主机名,我们可以使用它通过 HTTP 访问greetings函数。

在这种情况下,主机名是greetings.192.168.99.100.nip.io。一旦在 Web 浏览器中打开此主机名,您应该能够在浏览器窗口中看到问候消息(请注意,根据您的本地时间,您的输出可能会有所不同):

图 7.47:使用 HTTP GET 请求调用函数

图 7.47:使用 HTTP GET 请求调用函数

Kubeless PubSub 触发器

Kubeless 函数可以通过向消息系统中的topics发送输入消息来调用。这种方法称为 PubSub 机制。目前,Kubeless 支持两种消息系统,即 Kafka 和 NATS。

为了在 Kubeless 中创建 PubSub 触发器,我们需要运行 Kafka 集群或 NATS 集群。一旦 Kafka 或 NATS 集群准备就绪,我们可以使用kubeless trigger kafka create来创建 Kafka 触发器,或者使用kubeless trigger nats create来创建 NATS 触发器,并将我们的 PubSub 函数与新触发器关联:

$ kubeless trigger <trigger-type> create <trigger-name> \
                             --function-selector <label-query> \
                             --trigger-topic <topic-name>

让我们讨论命令的每个部分都做了什么:

  • kubeless trigger <trigger-type> create <trigger-name>:这告诉 Kubeless 使用提供的名称和触发器类型创建一个 PubSub 触发器。有效的触发器类型是kafkanats

  • --function-selector <label-query>:这告诉我们应该将哪个函数与此触发器关联。Kubernetes 标签用于定义这种关系(例如,--function-selector key1=value1,key2=value2)。

  • --trigger-topic <topic-name>:Kafka 代理将监听此主题,并在向其发布消息时触发函数。

主题是生产者发布消息的地方。Kubeless CLI 允许我们使用kubeless topic命令创建主题。这使我们可以轻松地创建、删除、列出主题,并向主题发布消息。

练习 23:为 Kubeless 函数创建 PubSub 触发器

在这个练习中,我们将首先在 Minikube 环境中创建一个 Kafka 和 Zookeeper 集群。一旦 Kafka 和 Zookeeper 集群准备就绪,我们将创建一个要执行的函数,并使用 PubSub 触发器。接下来,我们将创建 PubSub 主题。发布消息到创建的主题将执行 Kubeless 函数。执行以下步骤完成练习。

让我们使用 Kafka 的PubSub机制调用 Kubeless 函数:

  1. 首先,我们将在 Kubernetes 集群中部署KafkaZookeeper
$ kubectl create -f https://github.com/kubeless/kafka-trigger/releases/download/v1.0.2/kafka-zookeeper-v1.0.2.yaml

输出将如下所示:

图 7.48:安装 Kafka 和 Zookeeper

图 7.48:安装 Kafka 和 Zookeeper
  1. 验证在kubeless命名空间中是否运行了名为kafkazoo的两个statefulset,用于 Kafka 和 Zookeeper:
$ kubectl get statefulset -n kubeless
$ kubectl get services -n kubeless
$ kubectl get deployment -n kubeless

将看到以下输出:

图 7.49:验证 Kafka 和 Zookeeper 安装

图 7.49:验证 Kafka 和 Zookeeper 安装
  1. 一旦我们的 Kafka 和 Zookeeper 部署准备就绪,我们可以创建并部署要由PubSub触发器触发的函数。创建一个名为pubsub.py的文件,并添加以下内容:
def main(event, context): 
    return "Invoked with Kubeless PubSub Trigger"  
  1. 现在让我们部署我们的函数:
$ kubeless function deploy pubsub --runtime python3.7 \
                           --from-file pubsub.py \
                           --handler pubsub.main

部署将产生以下结果:

图 7.50:部署 pubsub 函数

图 7.50:部署 pubsub 函数
  1. 一旦函数部署完成,我们可以通过列出函数来验证函数是否成功:
$ kubeless function list pubsub 

列出的函数将如下所示:

图 7.51:验证 pubsub 函数

图 7.51:验证 pubsub 函数
  1. 现在,让我们使用kubeless trigger kafka create命令创建kafka触发器,并将我们的pubsub函数与新触发器关联起来:
$ kubeless trigger kafka create my-trigger \
                             --function-selector function=pubsub \
                             --trigger-topic pubsub-topic

它将如下所示:

图 7.52:为 pubsub 函数创建 kafka 触发器

图 7.52:为 pubsub 函数创建 kafka 触发器
  1. 现在我们需要一个 Kubeless 主题来发布消息。让我们使用kubeless topic create命令创建一个主题。我们需要确保主题名称与我们在上一步中创建kafka触发器时提供的--trigger-topic相似:
$ kubeless topic create pubsub-topic
  1. 好的。现在是时候测试我们的pubsub函数,通过发布事件到pubsub-topic来测试:
$ kubeless topic publish --topic pubsub-topic --data "My first message"
  1. 检查logs函数以验证pubsub函数是否成功调用:
$ kubectl logs -l function=pubsub

您应该在output日志中看到已发布的消息:

...
My first message
...

要更好地理解这一点,请查看以下输出:

图 7.53:pubsub 函数的日志

图 7.53:pubsub 函数的日志

监视 Kubeless 函数

当我们成功部署了 Kubeless 函数后,我们需要监视我们的函数。可以使用kubeless function top命令来实现。此命令将为我们提供以下信息:

  • NAME:Kubeless 函数的名称

  • NAMESPACE:函数的命名空间

  • METHOD:调用函数时的 HTTP 方法类型(例如,GET/POST)

  • TOTAL_CALLS:调用次数

  • TOTAL_FAILURES:函数失败的次数

  • TOTAL_DURATION_SECONDS:此函数执行的总秒数

  • AVG_DURATION_SECONDS:此函数执行的平均秒数

  • MESSAGE:任何其他消息

以下是hello函数的kubeless function top输出:

$ kubeless function top hello

输出将如下所示:

图 7.54:查看 hello 函数的指标

图 7.54:查看 hello 函数的指标

现在我们已经监视了函数,是时候开始调试了。

调试 Kubeless 函数

Kubeless 函数可能在函数生命周期的不同阶段失败(例如,从部署时间到函数执行时间),原因有很多。在本节中,我们将调试一个函数,以确定失败的原因。

为了演示多个错误场景,首先,我们将在debug.py文件中创建一个包含以下代码块的示例函数:

def main(event, context)
    name = event['data']['name']
    return "Hello " +  name

错误场景 01

现在,让我们尝试使用kubeless function deploy命令部署此函数:

$ kubeless function deploy debug --runtime python \
                           --from-file debug.py \
                           --handler debug.main

这将导致Invalid runtime error,Kubeless 将显示支持的运行时。经过进一步检查,我们可以看到kubeless function deploy命令的--runtime参数中存在拼写错误。

结果输出将如下所示:

图 7.55:部署调试函数-错误

图 7.55:部署调试函数-错误

让我们纠正这个拼写错误,并使用python3.7运行时重新运行kubeless function deploy命令:

$ kubeless function deploy debug --runtime python3.7 \
                           --from-file debug.py \
                           --handler debug.main

这次,函数将成功部署到 Kubeless 环境中。它应该看起来像下面这样:

图 7.56:部署调试函数-成功

图 7.56:部署 debug 函数-成功

错误场景 02

现在,让我们使用kubeless function ls命令来检查函数的状态:

$ kubeless function ls debug

为了更好地理解这一点,请参考以下输出:

图 7.57:列出 debug 函数

图 7.57:列出 debug 函数

您可以看到状态为0/1 NOT READY。现在,让我们使用kubectl get pods命令来检查 debug pod 的状态:

$ kubectl get pods -l function=debug

现在,参考以下截图输出:

图 7.58:列出 debug 函数 pods

图 7.58:列出 debug 函数 pods

在这里,debug pod 处于CrashLoopBackOff状态。这种错误通常是由函数中的语法错误或我们指定的依赖关系引起的。

仔细检查后,我们发现函数头部缺少一个冒号(:)来标记函数头部的结束。

让我们纠正这个问题并更新我们的函数。

打开debug.py文件,在函数头部添加一个冒号:

def main(event, context):
    name = event['data']['name']
    return  "Hello " +  name

我们现在将执行kubeless function update命令来使用新的代码文件更新函数:

$ kubeless function update debug --from-file debug.py

输出如下:

图 7.59:更新 debug 函数

图 7.59:更新 debug 函数

当再次执行kubeless function ls debug 时,您应该能够看到函数现在处于1/1 READY状态:

图 7.60:列出 debug 函数

图 7.60:列出 debug 函数

错误场景 03

让我们创建一个带有hello函数的示例错误场景。为此,您可以通过将data部分的键名替换为username来调用hello函数:

$ kubeless function call debug --data '{"username":"Kubeless"}'

现在,让我们看看它在屏幕上的样子:

图 7.61:调用 debug 函数-错误

图 7.61:调用 debug 函数-错误

为了找到此失败的可能原因,我们需要检查函数日志。您可以执行kubeless function logs命令来查看hello函数的日志:

$ kubeless function logs debug 

输出如下:

图 7.62:检查 debug 函数日志

图 7.62:检查 debug 函数日志

输出的前几行显示类似于以下代码块的行,这些是内部健康检查。根据日志,我们可以看到对/healthz端点的所有调用都成功返回了200 HTTP 成功响应代码:

10.56.0.1 - - [03/Jul/2019:13:36:17 +0000] "GET /healthz HTTP/1.1" 200 2 "" "kube-probe/1.12+" 0/120

接下来,您可以看到错误消息的堆栈跟踪,如下所示,可能的原因是KeyError: 'name'错误。函数期望一个'name'键,在函数执行期间未找到:

Traceback (most recent call last):
  File "/usr/local/lib/python3.7/dist-packages/bottle.py", line 862, in _handle
    return route.call(**args)
  File "/usr/local/lib/python3.7/dist-packages/bottle.py", line 1740, in wrapper
    rv = callback(*a, **ka)
  File "/kubeless.py", line 86, in handler
    raise res
KeyError: 'name'

错误消息的最后一行表示函数调用返回了 HTTP 错误500

10.56.0.1 - - [03/Jul/2019:13:37:29 +0000] "POST / HTTP/1.1" 500 739 "" "kubeless/v0.0.0 (linux/amd64) kubernetes/$Format" 0/10944

注意

HTTP 500是 HTTP 协议返回的错误代码,表示内部服务器错误。这意味着服务器由于意外情况而无法满足请求。

除了kubeless function logs之外,您还可以使用kubectl logs命令,它将返回类似的输出。您需要传递-l参数,表示标签,以便仅获取特定函数的日志:

$ kubectl logs -l function=hello

以下将是输出:

图 7.63:检查调试函数日志

图 7.63:检查调试函数日志

使用kubectl get functions --show-labels命令查看与 Kubeless 函数关联的标签。

这将产生以下结果:

图 7.64:列出函数标签

图 7.64:列出函数标签

让我们纠正错误,并向debug函数传递正确的参数:

$ kubeless function call debug --data '{"name":"Kubeless"}'

现在我们的函数已成功运行,并生成了Hello Kubeless作为其输出:

图 7.65:调用调试函数-成功

图 7.65:调用调试函数-成功

Kubeless 的 Serverless 插件

Serverless Framework 是一个通用的框架,用于在不同的无服务器提供商上部署无服务器应用程序。Kubeless 的无服务器插件支持部署 Kubeless 函数。除了 Kubeless 的插件之外,Serverless Framework 还支持 AWS Lambda、Azure Functions、Google Cloud Functions、Apache OpenWhisk 和 Kubeless 等无服务器应用程序。

在本节中,我们将安装无服务器框架,并使用无服务器框架提供的 CLI 创建 Kubeless 函数。

在我们开始安装无服务器框架之前,我们需要安装 Node.js 版本 6.5.0 或更高版本作为先决条件。所以,首先让我们安装 Node.js:

$ curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
$ sudo apt-get install nodejs -y

输出如下:

图 7.66:安装 Node.js 版本 6.5.0

](image/C12607_07_66.jpg)

图 7.66:安装 Node.js 版本 6.5.0

安装后,通过执行以下命令验证 Node.js 版本:

$ nodejs -v

以下是输出:

图 7.67:Node.js 版本验证

图 7.67:Node.js 版本验证

一旦 Node.js 安装成功,我们将通过执行以下命令安装 Serverless 框架:

$ sudo npm install -g serverless

接下来,我们将验证 serverless 版本:

$ serverless -v

检查输出,如下所示:

图 7.68:Serverless 版本验证

图 7.68:Serverless 版本验证

我们已经成功完成了 Serverless 框架的安装。现在我们可以开始使用它创建函数。

我们可以使用serverless create命令从模板创建一个基本服务。让我们创建一个名为my-kubeless-project的项目,如下所示:

$ serverless create --template kubeless-python --path my-kubeless-project

让我们把命令拆分成几部分以便理解:

  • --template kubeless-python:目前,Kubeless 框架有两个模板可用。kubeless-python创建一个 Python 函数,kubeless-nodejs创建一个 Node.js 函数。

  • --path my-kubeless-project:这定义了该函数应该在my-kubeless-project目录下创建。查看输出以更好地理解它:

图 7.69:创建 my-kubeless-project

图 7.69:创建 my-kubeless-project

这个命令将创建一个名为my-kubeless-project的目录,并在该目录中创建几个文件。首先,让我们通过执行以下命令进入my-kubeless-project目录:

$ cd my-kubeless-project

以下文件位于my-kubeless-project目录中:

  • handler.py

  • serverless.yml

  • package.json

handler.py文件包含一个示例 Python 函数,如下所示。这是一个简单的函数,返回一个 JSON 对象和状态码 200:

import json
def hello(event, context):
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event['data']
    }
    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }
    return response

它还创建了一个serverless.yml文件,告诉 serverless 框架在handler.py文件中执行hello函数。在provider部分中,提到这是一个带有python2.7运行时的 Kubeless 函数。在plugins部分中,它定义了所需的自定义插件,比如serverless-kubeless插件:

# Welcome to Serverless!
#
# For full config options, check the kubeless plugin docs:
#    https://github.com/serverless/serverless-kubeless
#
# For documentation on kubeless itself:
#    http://kubeless.io
# Update the service name below with your own service name
service: my-kubeless-project
# Please ensure the serverless-kubeless provider plugin is installed globally.
# $ npm install -g serverless-kubeless
#
# ...before installing project dependencies to register this provider.
# $ npm install
provider:
  name: kubeless
  runtime: python2.7
plugins:
  - serverless-kubeless
functions:
  hello:
    handler: handler.hello

最后,package.json文件包含了npm打包信息,比如dependencies

{
  "name": "my-kubeless-project",
  "version": "1.0.0",
  "description": "Sample Kubeless Python serverless framework service.",
  "dependencies": {
    "serverless-kubeless": "⁰.4.0"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "serverless",
    "kubeless"
  ],
  "author": "The Kubeless Authors",
  "license": "Apache-2.0"
}

您可以根据需要更新这些文件以匹配您的业务需求。在本例中,我们不会更改这些文件。

现在,我们将执行npm install命令,安装所有npm依赖,比如kubeless-serverless插件:

$ npm install

这个输出如下:

图 7.70:安装 npm 依赖

图 7.70:安装 npm 依赖项

一旦依赖项准备好,让我们部署服务:

$ serverless deploy -v

部署服务会为我们提供以下输出:

图 7.71:部署服务

图 7.71:部署服务

然后,我们可以使用以下命令部署函数:

$ serverless deploy function -f hello

以下截图显示了输出:

图 7.72:部署函数

图 7.72:部署函数

当函数成功部署后,我们可以使用serverless invoke命令调用函数:

$ serverless invoke --function hello -l

调用函数会产生以下输出:

图 7.73:调用函数

图 7.73:调用函数

您还可以使用kubeless function call命令来调用此函数:

$ kubeless function call hello

这样做将提供以下输出:

图 7.74:使用 kubeless 函数调用来调用函数

图 7.74:使用 kubeless 函数调用来调用函数

完成函数后,使用serverless remove来删除函数。

$ serverless remove

以下是前面代码的输出:

图 7.75:删除函数

图 7.75:删除函数

注意

如果在调用函数时遇到任何错误,请执行serverless logs -f hello命令。

活动 7:使用 Kubeless 向 Slack 发布消息

想象一下,您需要一个 Slackbot 来向您的 Slack 频道发布消息。这个 Slackbot 应该能够使用传入的 webhook 集成方法向特定的 Slack 频道发布消息。如果成功向 Slack 发布消息,此机器人将打印成功消息;否则,如果在向 Slack 发送消息时出现任何错误,它将打印错误消息。在这个活动中,我们将创建一个能够向特定 Slack 频道发布消息的 Kubeless 函数。

作为此活动的先决条件,我们需要一个 Slack 工作区,并集成传入的 webhook。执行以下步骤创建一个 Slack 工作区并集成传入的 webhook:

解决方案-Slack 设置

  1. 创建一个 Slack 工作区。

  2. 访问slack.com/create创建一个工作区。输入您的电子邮件地址,然后单击创建

  3. 您应该收到一个六位数的确认码,发送到您在上一页输入的电子邮件中。在工作区中输入收到的代码。

  4. 为我们的工作区和 Slack 频道添加合适的名称。

  5. 您将被要求填写其他与您合作在同一项目上的人的电子邮件 ID。您可以跳过此部分,或者填写详细信息然后继续。

  6. 现在您的 Slack 频道已准备就绪,请点击“在 Slack 中查看您的频道”。

  7. 一旦点击,我们应该看到我们的频道。

  8. 现在我们将向 Slack 添加Incoming Webhook应用程序。从左侧菜单中,在“应用程序”部分下选择“添加应用程序”。

  9. 在搜索字段中输入“传入 Webhooks”,然后点击“安装”以安装Incoming Webhook应用程序。

  10. 点击“添加配置”。

  11. 点击“添加传入 WebHooks 集成”。

  12. 保存 webhook URL。在编写 Kubeless 函数时,我们会需要它。

注意

有关使用传入 webhook 集成创建 Slack 工作区的详细步骤,以及相应的屏幕截图,请参阅第 422 页。

现在我们准备开始这项活动。执行以下步骤完成此活动:

活动解决方案

  1. 在任何语言中创建一个函数(由 Kubeless 支持),可以将消息发布到 Slack。在这个活动中,我们将编写一个 Python 函数,执行以下步骤。

  2. 使用requests库作为依赖项。

  3. 向传入的 webhook(在步骤 2 中创建)发送一个POST请求,带有输入消息。

  4. 打印 post 请求的响应,

  5. 将该函数部署到 Kubeless 框架中。

  6. 调用该函数。

  7. 转到您的 Slack 工作区,验证消息是否成功发布到 Slack 频道。最终输出应如下所示:

图 7.76:验证消息是否成功发布

图 7.76:验证消息是否成功发布

注意

活动的解决方案可以在第 422 页找到。

摘要

在本章中,我们学习了如何使用 Minikube 部署单节点 Kubernetes 集群。然后,我们在 Minikube 集群上安装了 Kubeless 框架、Kubeless CLI 和 Kubeless UI。一旦 Kubernetes 集群和 Kubeless 框架准备就绪,我们就用 Python 创建了我们的第一个 Kubeless 函数,并将其部署到 Kubeless 上。然后,我们讨论了多种调用 Kubeless 函数的方式,包括使用 Kubeless CLI、Kubeless UI、HTTP 触发器、定时触发器和 PubSub 触发器。接下来,我们讨论了在部署 Kubeless 函数时遇到的常见错误场景的调试方法。然后,我们讨论了如何使用无服务器框架部署 Kubeless 函数。最后,在活动中,我们学习了如何使用 Kubeless 函数向 Slack 频道发送消息。

在下一章中,我们将介绍 OpenWhisk,并涵盖 OpenWhisk 动作和触发器。

第八章: Apache OpenWhisk 简介

学习目标

在本章结束时,您将能够:

  • 在 IBM Cloud Functions 中运行 OpenWhisk

  • 创建、列出、调用、更新和删除 OpenWhisk 操作

  • 利用和调用 OpenWhisk web 操作和序列

  • 使用 feeds、触发器和规则自动化 OpenWhisk 操作调用

本章涵盖了 Apache OpenWhisk 以及如何处理其操作、触发器和包。

OpenWhisk 简介

到目前为止,在本书中,我们已经了解了Kubeless框架,这是一个开源的基于 Kubernetes 的无服务器框架。我们讨论了Kubeless架构,并创建并使用了Kubeless函数和触发器。在本章中,我们将学习OpenWhisk,这是另一个可以部署在 Kubernetes 之上的开源无服务器框架。

OpenWhisk是 Apache 软件基金会的一部分的开源无服务器框架。最初在 IBM 开发,项目代号为 Whisk,后来在源代码开源后更名为OpenWhiskApache OpenWhisk支持许多编程语言,包括 Ballerina、Go、Java、JavaScript、PHP、Python、Ruby、Swift 和.NET Core。它允许我们调用用这些编程语言编写的函数来响应事件。OpenWhisk 支持许多部署选项,例如本地部署和云基础设施。

OpenWhisk 有四个核心组件:

  • Actions:这些包含用支持的语言编写的应用程序逻辑,将在响应事件时执行。

  • 序列:这些将多个操作链接在一起,以创建更复杂的处理流水线。

  • 触发器和规则:这些通过将它们绑定到外部事件源来自动调用操作。

  • Packages:这些将相关的操作组合在一起进行分发。

以下图表说明了这些组件如何相互交互:

图 8.1:OpenWhisk 核心组件

图 8.1:OpenWhisk 核心组件

在下一节中,我们将学习如何在 IBM Cloud Functions 中运行 Apache OpenWhisk。

在 IBM Cloud Functions 中运行 OpenWhisk

OpenWhisk 是一个可以部署在本地或云基础设施上的框架。然而,OpenWhisk 也可以作为 IBM 的托管服务提供,IBM 是 OpenWhisk 项目的创建者。IBM Cloud Functions是 IBM Cloud 基础设施上托管 OpenWhisk 实现的名称。本书将使用此服务来部署我们的无服务器函数,因为 IBM Cloud Functions 是开始使用 OpenWhisk 的最简单方式。我们首先要设置一个 IBM Cloud 账户。

练习 24:设置 IBM Cloud 账户

在这个练习中,我们将在 IBM Cloud 上设置一个账户。

注意

注册 IBM Cloud 不需要信用卡。

以下步骤将帮助您完成练习:

  1. 首先,我们需要在 IBM Cloud 上注册cloud.ibm.com/registration。然后,填写必需的数据并提交表单。它应该类似于以下截图:
图 8.2:IBM Cloud 注册页面

注册完成后,您应该会看到以下内容:

图 8.3:IBM Cloud 注册完成页面
  1. 此时,我们将收到一封带有激活链接的电子邮件。点击确认账户按钮来激活您的账户,如下图所示:
图 8.4:IBM Cloud 激活邮件
  1. 当您点击邮件中的确认账户按钮时,我们将被带到 IBM Cloud 欢迎页面。点击登录按钮,使用注册 IBM Cloud 时使用的凭据登录,如下图所示:
图 8.5:IBM Cloud 欢迎页面
  1. 通过点击继续按钮来确认隐私数据,如下图所示:图 8.6:IBM Cloud 隐私政策
图 8.6:IBM Cloud 隐私政策
  1. 您可以跳过介绍视频,然后转到首页。现在,您可以点击屏幕左上角的汉堡包图标(),然后从菜单中选择函数,如下图所示:
图 8.7:IBM Cloud 首页
  1. 这将带您到IBM Cloud函数页面(https://cloud.ibm.com/functions/),如下图所示:图 8.8:IBM Cloud 函数页面
图 8.8:IBM Cloud 函数页面

OpenWhisk 提供了一个名为wsk的 CLI 来创建和管理 OpenWhisk 实体。接下来,我们将安装OpenWhisk CLI,它将用于与 OpenWhisk 平台交互。

练习 25:安装 IBM Cloud CLI

在这个练习中,我们将安装带有 Cloud Functions 插件的 IBM Cloud CLI,该插件支持 OpenWhisk:

  1. 首先,我们需要下载压缩的 IBM Cloud CLI 文件。使用curl命令和-Lo标志下载 CLI,如下所示:
$ curl -Lo ibm-cli.tar.gz  https://clis.cloud.ibm.com/download/bluemix-cli/0.18.0/linux64 

输出应如下所示:

图 8.9:下载 IBM Cloud CLI

](image/C12607_08_09.jpg)

图 8.9:下载 IBM Cloud CLI
  1. 接下来,我们将使用tar命令提取tar.gz文件,如下所示:
$ tar zxvf ibm-cli.tar.gz

输出应如下所示:

图 8.10:提取 IBM Cloud CLI

](image/C12607_08_10.jpg)

图 8.10:提取 IBM Cloud CLI
  1. 然后将ibmcloud可执行文件移动到/usr/local/bin/路径,如下命令所示:
$ sudo mv Bluemix_CLI/bin/ibmcloud /usr/local/bin/ibmcloud

输出应如下所示:

图 8.11:将 ibmcloud 移动到/usr/local/bin

](image/C12607_08_11.jpg)

图 8.11:将 ibmcloud 移动到/usr/local/bin
  1. 现在我们将使用 IBM Cloud CLI 登录 IBM Cloud。执行以下命令,将<YOUR_EMAIL>替换为注册 IBM Cloud 时使用的电子邮件地址。在提示时提供注册阶段使用的电子邮件和密码,并将区域号设置为5us-south),如下命令所示:
$ ibmcloud login -a cloud.ibm.com -o "<YOUR_EMAIL>" -s "dev"

输出应如下所示:

图 8.12:登录 IBM Cloud

](image/C12607_08_12.jpg)

图 8.12:登录 IBM Cloud
  1. 现在我们将使用ibmcloud CLI 安装 Cloud Functions 插件,如下命令所示。在处理 OpenWhisk 实体时,将使用此插件:
$ ibmcloud plugin install cloud-functions

输出应如下所示:

图 8.13:安装 Cloud Functions

](image/C12607_08_13.jpg)

图 8.13:安装 Cloud Functions
  1. 接下来,我们将使用以下命令提供目标组织(组织名称为您的电子邮件地址)和空间(默认为dev):
$ ibmcloud target -o <YOUR_EMAIL> -s dev

输出应如下所示:

图 8.14:设置目标组织和空间

](image/C12607_08_14.jpg)

图 8.14:设置目标组织和空间
  1. 现在配置完成了。我们可以使用ibmcloud wsk与 OpenWhisk 实体交互,如下命令所示:
$ ibmcloud wsk action list

输出应如下所示:

图 8.15:列出 OpenWhisk 操作

图 8.15:列出 OpenWhisk 操作

注意

在本书中,我们将使用wsk命令来管理 OpenWhisk 实体,而不是 IBM Cloud Functions 提供的ibmcloud wsk命令。它们两者提供相同的功能。唯一的区别是wsk是 OpenWhisk 的标准 CLI,而ibmcloud fn来自 IBM Cloud Functions 插件。

  1. 让我们创建一个 Linux 别名,wsk="ibmcloud wsk"。首先,用您喜欢的文本编辑器打开~/.bashrc文件。在下面的命令中,我们将使用vim文本编辑器打开文件:
vim ~/.bashrc

在文件末尾添加以下行:

alias wsk="ibmcloud wsk"
  1. ~/.bashrc文件以应用更改,如下命令所示:
$ source ~/.bashrc

输出应该如下:

图 8.16:获取 bashrc 文件

图 8.16:获取 bashrc 文件
  1. 现在我们应该能够使用wsk命令调用 OpenWhisk。执行以下命令以验证安装:
$ wsk --help

这将打印wsk命令的帮助页面,如下图所示:

图 8.17:wsk 命令的输出

图 8.17:wsk 命令的输出

现在,让我们继续进行下一节关于 OpenWhisk 动作的部分。

OpenWhisk 动作

在 OpenWhisk 中,动作是由开发人员编写的代码片段,将在响应事件时执行。这些动作可以用 OpenWhisk 支持的任何编程语言编写:

  • Ballerina

  • Go

  • Java

  • JavaScript

  • PHP

  • Python

  • Ruby

  • Swift

  • .NET Core

此外,如果我们喜欢的语言运行时尚未得到 OpenWhisk 的支持,我们可以使用自定义的 Docker 镜像。这些动作将接收一个 JSON 对象作为输入,然后在动作内执行必要的处理,最后返回一个包含处理结果的 JSON 对象。在接下来的章节中,我们将重点介续如何使用wsk CLI 编写、创建、列出、调用、更新和删除 OpenWhisk 动作。

为 OpenWhisk 编写动作

在使用您喜欢的语言编写 OpenWhisk 动作时,有一些标准必须遵循。它们如下:

  • 每个动作应该有一个名为main的函数,这是动作的入口点。源代码可以有额外的函数,但一旦触发动作,main函数将被执行。

  • 函数必须返回一个 JSON 对象作为响应。

注意

在本章中,我们将主要使用 JavaScript 来创建函数代码。

让我们看一个例子,我们创建一个符合我们刚才提到的规则的 JavaScript 代码(random-number.js)。这是一个简单的函数,它生成 0 到 1 之间的随机数,并将生成的数字作为函数的响应返回:

function main() {
    var randomNumber = Math.random();
    return { number: randomNumber };
} 

这是一个符合规则的 PHP 函数:

<?php
function main()
{
    $randomNumber = rand();
    return ["number" => $randomNumber];
}

在 OpenWhisk 框架上创建操作

现在是时候使用在上一节中编写的操作代码在 OpenWhisk 框架上创建一个操作了。我们将使用wsk action create命令,其格式如下:

$ wsk action create <action-name> <action-file-name>

<action-name>是操作的标识符。它应该是唯一的,以防止命名冲突。<action-file-name>是包含操作源代码的文件。让我们执行以下命令,使用random-number.js文件中的操作源代码创建一个名为randomNumber的 OpenWhisk 操作:

$ wsk action create randomNumber random-number.js

我们从这个命令接收到的输出如下所示:

图 8.18:创建一个 randomNumber 操作

图 8.18:创建一个 randomNumber 操作

正如我们在输出中看到的,每当成功创建一个操作时,CLI 提示都会适当地通知读者操作的状态。

OpenWhisk 框架将根据源代码文件的扩展名确定执行操作的运行时。在前面的场景中,将为提供的.js文件选择 Node.js 10运行时。如果要覆盖 OpenWhisk 框架选择的默认运行时,可以在wsk action create命令中使用--kind标志:

 $ wsk action create secondRandomNumber random-number.js --kind nodejs:8

输出应该如下所示:

图 8.19:使用 nodejs:8 运行时创建一个 randomNumber 操作

图 8.19:使用 nodejs:8 运行时创建一个 randomNumber 操作

前面的输出表明secondRandomNumber已成功创建。在本节结束时,我们已部署了两个 OpenWhisk 操作。

学会了如何在 OpenWhisk 框架上创建操作后,接下来我们将继续列出 OpenWhisk 操作。

列出 OpenWhisk 操作

在本节中,我们将使用以下命令在我们的环境中使用wsk CLI 列出 OpenWhisk 操作:

$ wsk action list

输出应该如下所示:

图 8.20:列出所有操作

图 8.20:列出所有操作

从上述输出中,我们可以看到我们之前创建的两个操作,名称分别为randomNumbersecondRandomNumberwsk action list命令列出操作及其运行时,如nodejs:8nodejs:10。默认情况下,操作列表将根据最后更新时间进行排序,因此最近更新的操作将位于列表顶部。如果我们希望列表按字母顺序排序,可以使用--name-sort(或-n)标志,如下所示:

$ wsk action list --name-sort

输出应该如下所示:

图 8.21:按名称升序列出所有操作

图 8.21:按名称升序列出所有操作

调用 OpenWhisk 操作

现在我们的操作已经准备好被调用了。OpenWhisk 操作可以通过wsk CLI 以两种方式被调用:

  • 请求-响应

  • 触发-忘记

请求-响应方法是同步的;操作调用将等待结果可用。另一方面,触发-忘记方法是异步的。这将返回一个称为激活 ID 的 ID,稍后可以用来获取结果。

以下是调用操作的wsk命令的标准格式:

$ wsk action invoke <action-name> 

请求-响应调用方法

请求-响应方法中,wsk action invoke命令与--blocking(或-b)标志一起使用,该标志要求wsk CLI 等待调用结果:

$ wsk action invoke randomNumber --blocking 

上述命令将在终端中返回以下输出,其中包含从方法返回的结果以及有关方法调用的其他元数据:

ok: invoked /_/randomNumber with id 002738b1acee4abba738b1aceedabb60
{
    "activationId": "002738b1acee4abba738b1aceedabb60",
    "annotations": [
        {
            "key": "path",
            "value": "your_email_address_dev/randomNumber"
        },
        {
            "key": "waitTime",
            "value": 79
        },
        {
            "key": "kind",
            "value": "nodejs:10"
        },
        {
            "key": "timeout",
            "value": false
        },
        {
            "key": "limits",
            "value": {
                "concurrency": 1,
                "logs": 10,
                "memory": 256,
                "timeout": 60000
            }
        },
        {
            "key": "initTime",
            "value": 39
        }
    ],
    "duration": 46,
    "end": 1564829766237,
    "logs": [],
    "name": "randomNumber",
    "namespace": "your_email_address_dev",
    "publish": false,
    "response": {
        "result": {
            "number": 0.6488215545330562
        },
        "status": "success",
        "success": true
    },
    "start": 1564829766191,
    "subject": "your_email_address",
    "version": "0.0.1"
}

我们可以在返回的 JSON 对象的response部分中看到输出("number": 0.6488215545330562),这是我们之前编写的 JavaScript 函数生成的随机数。返回的 JSON 对象包含一个激活 ID("activationId": "002738b1acee4abba738b1aceedabb60"),我们可以稍后用来获取结果。此输出包括其他重要值,如操作调用状态("status": "success"),开始时间("start": 156482976619),结束时间("end": 1564829766237)和执行持续时间("duration": 46)。

注意

我们将讨论如何在触发-忘记调用方法部分使用activationId获取激活结果。

如果我们需要获取动作的结果而不包括其他元数据,可以使用--result(或-r)标志,如下面的代码所示:

$ wsk action invoke randomNumber --result 

输出应该如下:

图 8.22:使用请求和响应方法调用 randomNumber 动作

图 8.22:使用请求和响应方法调用 randomNumber 动作

点火和忘记调用方法

使用点火和忘记方法的动作调用不会等待动作的结果。相反,它们返回一个激活 ID,我们可以使用它来获取动作的结果。这种调用方法使用类似的命令来请求响应方法,但没有--blocking(或-b)标志:

$ wsk action invoke randomNumber 

输出应该如下:

图 8.23:使用点火和忘记方法调用 randomNumber 动作

图 8.23:使用点火和忘记方法调用 randomNumber 动作

在上述结果中,我们可以看到返回的激活 ID 为2b90ade473e443bc90ade473e4b3bcff(请注意,您的激活 ID 将不同)。

现在我们可以使用wsk activation get命令获取给定激活 ID 的结果:

$ wsk activation get "<activation_id>"

您需要用wsk action invoke命令调用函数时返回的值替换<activation_id>

$ wsk activation get 2b90ade473e443bc90ade473e4b3bcff
ok: got activation 2b90ade473e443bc90ade473e4b3bcff
{
    "namespace": "sathsara89@gmail.com_dev",
    "name": "randomNumber",
    "version": "0.0.2",
    "subject": "sathsara89@gmail.com",
    "activationId": "2b90ade473e443bc90ade473e4b3bcff",
    "start": 1564832684116,
    "end": 1564832684171,
    "duration": 55,
    "statusCode": 0,
    "response": {
        "status": "success",
        "statusCode": 0,
        "success": true,
        "result": {
            "number": 0.05105974715780626
        }
    },
    "logs": [],
    "annotations": [
        {
            "key": "path",
            "value": "sathsara89@gmail.com_dev/randomNumber"
        },
        {
            "key": "waitTime",
            "value": 126
        },
        {
            "key": "kind",
            "value": "nodejs:10"
        },
        {
            "key": "timeout",
            "value": false
        },
        {
            "key": "limits",
            "value": {
                "concurrency": 1,
                "logs": 10,
                "memory": 256,
                "timeout": 60000
            }
        },
        {
            "key": "initTime",
            "value": 41
        }
    ],
    "publish": false
}

如果您希望仅检索激活的摘要,应该在wsk activation get命令中提供--summary(或-s)标志:

$ wsk activation get <activation-id> --summary

上述命令的输出将打印激活详细信息的摘要,如下面的截图所示:

图 8.24:激活摘要

图 8.24:激活摘要

wsk activation result命令仅返回动作的结果,省略任何元数据:

$ wsk activation result <activation-id> 

输出应该如下:

图 8.25:激活结果

图 8.25:激活结果

wsk activation list命令可用于列出所有激活:

$ wsk activation list 

输出应该如下:

图 8.26:列出激活

图 8.26:列出激活

上述命令返回按激活的日期时间排序的激活列表。以下表格描述了每列提供的信息:

图 8.27:列描述

图 8.27:列描述

更新 OpenWhisk 操作

在本节中,我们将学习如何在 OpenWhisk 平台上创建操作后更新操作的源代码。我们可能希望出于几个原因更新操作。代码中可能存在错误,或者我们可能只是想增强代码。可以使用wsk action update命令使用wsk CLI 来更新 OpenWhisk 操作:

$ wsk action update <action-name> <action-file-name>

我们已经有一个打印随机数的操作,它在random-number.js函数中定义。这个函数打印一个 0 到 1 之间的值,但是如果我们想要打印一个 1 到 100 之间的随机数呢?现在可以使用以下代码来实现:

function main() {
    var randomNumber = Math.floor((Math.random() * 100) + 1);
    return { number: randomNumber };
} 

然后,我们可以执行wsk action update命令来更新randomNumber操作:

$ wsk action update randomNumber random-number.js

输出应该如下:

图 8.28:更新 randomNumber 操作

现在我们可以通过执行以下命令来验证更新后操作的结果:

$ wsk action invoke randomNumber --result

图 8.29:调用 randomNumber 操作

图 8.29:调用 randomNumber 操作

正如我们所看到的,randomNumber操作返回了 1 到 100 之间的数字。我们可以多次调用randomNumber函数来验证它是否返回 1 到 100 之间的输出数字。

删除 OpenWhisk 操作

在本节中,我们将讨论如何删除 OpenWhisk 操作。使用wsk action delete命令来删除 OpenWhisk 操作:

$ wsk action delete <action-name> 

让我们执行wsk action delete命令来删除我们在前面部分创建的randomNumbersecondRandomNumber操作:

$ wsk action delete randomNumber 
$ wsk action delete secondRandomNumber 

输出应该如下:

图 8.30:删除 randomNumber 和 secondRandomNumber 操作

现在我们已经学会了如何编写、创建、列出、调用、更新和删除 OpenWhisk 操作。让我们继续进行一个练习,在这个练习中,您将创建您的第一个 OpenWhisk 操作。

练习 26:创建您的第一个 OpenWhisk 操作

在这个练习中,我们将首先创建一个 JavaScript 函数,该函数接收考试分数作为输入,并根据以下标准返回考试结果:

  • 如果分数等于或高于 60,则返回Pass

  • 如果分数低于 60,则返回Fail

接下来,我们将在 OpenWhisk 框架中创建一个名为examResults的操作,使用先前提到的 JavaScript 函数代码。然后,我们将调用该操作以验证它是否按预期返回结果。一旦验证了操作的响应,我们将根据以下标准更新操作以返回考试成绩与结果:

  • 如果分数等于或高于 80,则返回Pass with grade A

  • 如果分数等于或高于 70,则返回Pass with grade B

  • 如果分数等于或高于 60,则返回Pass with grade C

  • 如果分数低于 60,则返回Fail

再次,我们将调用该动作以验证结果,最后删除该动作。

注意

此练习的代码文件可以在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson08/Exercise26找到。

执行以下步骤完成练习:

  1. 首先,在exam-result.js文件中创建一个 JavaScript 函数,根据提供的考试分数返回考试结果:
function main(params) {
    var examResult = '';
    if (params.examMarks < 0 || params.examMarks > 100) {
        examResult = 'ERROR: invalid exam mark';
    } else if (params.examMarks >= 60) {
        examResult = 'Pass';
    } else {
      examResult = 'Fail';
    }

    return { result: examResult };
}
  1. 现在,让我们从步骤 1中创建的exam-result.js文件中创建名为examResult的 OpenWhisk 动作:
$ wsk action create examResult exam-result.js

输出应该如下:

图 8.31:创建 examResult 动作

图 8.31:创建 examResult 动作
  1. 一旦动作创建成功,我们可以通过将值发送到examMarks参数来调用examResult动作,值的范围在 0 到 100 之间:
$ wsk action invoke examResult --param examMarks 72 –result

输出应该如下:

图 8.32:调用 examResult 动作

图 8.32:调用 examResult 动作
  1. 在这一步,我们将在exam-result-02.js中创建一个新的 JavaScript 函数,以grade参数返回考试结果:
function main(params) {
    var examResult = '';
    if (params.examMarks < 0 || params.examMarks > 100) {
        examResult = 'ERROR: invalid exam mark';
    } else if (params.examMarks > 80) {
        examResult = 'Pass with grade A';
    } else if (params.examMarks > 70) {
        examResult = 'Pass with grade B';
    } else if (params.examMarks > 60) {
        examResult = 'Pass with grade C';
    } else {
        examResult = 'Fail';
    }

    return { result: examResult };
}
  1. 现在,让我们使用先前更新的exam-result-02.js文件更新 OpenWhisk 动作:
$ wsk action update examResult exam-result-02.js

输出应该如下:

图 8.33:更新 examResult 动作

图 8.33:更新 examResult 动作
  1. 一旦动作更新完成,我们可以多次调用该动作,并使用不同的考试分数作为参数来验证功能:
$ wsk action invoke examResult --param examMarks 150 --result
$ wsk action invoke examResult --param examMarks 75 --result
$ wsk action invoke examResult --param examMarks 42 –result

输出应该如下:

图 8.34:使用不同的参数值调用 examResult 动作

图 8.34:使用不同的参数值调用 examResult 动作
  1. 最后,我们将删除examResult动作:
$ wsk action delete examResult 

输出应该如下:

图 8.35:删除 examResult 动作

图 8.35:删除 examResult 动作

在这个练习中,我们学习了如何创建一个遵循 OpenWhisk 操作标准的 JavaScript 函数。然后我们创建了这个操作并使用wsk CLI 调用了它。之后,我们改变了函数代码的逻辑,并用最新的函数代码更新了操作。最后,我们通过删除操作来进行清理。

OpenWhisk 序列

在 OpenWhisk 中,以及一般的编程中,函数(在 OpenWhisk 中称为操作)被期望执行单一的专注任务。这将有助于通过重用相同的函数代码来减少代码重复。但创建复杂的应用程序需要连接多个操作以实现期望的结果。OpenWhisk 序列用于链接多个 OpenWhisk 操作(可以是不同编程语言运行时)并创建更复杂的处理流水线。

以下图表说明了如何通过链接多个操作来构建序列:

图 8.36:OpenWhisk 序列

图 8.36:OpenWhisk 序列

我们可以向序列传递参数(如果有的话),这些参数将被用作第一个操作的输入。然后,每个操作的输出将作为下一个操作的输入,序列的最终操作将返回其结果作为序列的输出。不同编程语言编写的操作也可以通过序列链接在一起。

可以使用wsk action create命令创建序列,并使用--sequence标志提供一个逗号分隔的操作列表来调用:

$ wsk action create <sequence-name> --sequence <action-01>,<action-02>

为了演示 OpenWhisk 序列的概念,我们将在下一节中创建一个名为login的序列,其中包括两个操作,名为authenticationauthorization。当用户尝试登录应用程序时,将调用login操作。如果用户在登录时提供了正确的凭据,他们可以查看系统上的所有内容。但如果用户未能提供正确的登录凭据,他们只能查看系统的公共内容。

注意

认证是验证用户的身份,授权是授予系统所需级别的访问权限。

首先,让我们创建 authentication.js 函数。这个函数将接收两个参数,名为 usernamepassword。如果用户名和密码与 admin(对于 username 参数)和 openwhisk(对于 password 参数)的硬编码值匹配,函数将返回 authenticationResulttrue。否则,authenticationResult 将为 false

function main(params) {
    var authenticationResult = '';
    if (params.username == 'admin' && params.password == 'openwhisk') {
        authenticationResult = 'true';
    } else {
        authenticationResult = 'false';
    }
    return { authenticationSuccess: authenticationResult };
}

接下来的函数是 authorization.js,它以 authenticationSuccess 值作为输入,并向用户显示适当的内容。如果用户成功通过认证(authenticationSuccess = true),将显示 'Authentication Success! You can view all content' 消息。如果认证失败(authenticationSuccess != true),将显示 'Authentication Failed! You can view only public content' 消息:

function main(params) {
    var contentMessage = '';
    if (params.authenticationSuccess == "true") {
        contentMessage = 'Authentication Success! You can view all content';
    } else {
        contentMessage = 'Authentication Failed! You can view only public content';
    }
    return { content: contentMessage };
}

现在,让我们使用 wsk action create 命令部署这两个操作:

$ wsk action create authentication authentication.js 
$ wsk action create authorization authorization.js 

输出应该如下:

图 8.37:创建认证和授权操作

图 8.37:创建认证和授权操作

现在认证和授权操作都准备好了。让我们通过组合 authenticationauthorization 操作来创建一个名为 login 的序列:

$ wsk action create login --sequence authentication,authorization

输出应该如下:

图 8.38:创建登录序列

现在是测试登录序列的时候了。首先,我们将通过发送正确的凭据(username = adminpassword = openwhisk)来调用登录序列:

$ wsk action invoke login --param username admin --param password openwhisk –result

输出应该如下:

图 8.39:使用有效凭据调用登录序列

成功登录的预期结果显示在上面的截图中。现在,让我们通过发送不正确的凭据(username = hackerpassword = hacker)来调用登录序列。这次我们期望收到一个认证失败的消息:

$ wsk action invoke login --param username hacker --param password hacker –result

输出应该如下:

图 8.40:使用无效凭据调用登录序列

图 8.40:使用无效凭据调用登录序列

在本节中,我们学习了关于 OpenWhisk 序列的知识。我们创建了多个操作,使用序列将它们链接在一起,并通过发送所需的参数来调用序列。

练习 27:创建 OpenWhisk 序列

在这个练习中,我们将创建一个包含两种不同语言编写的操作的序列。第一个操作用 Python 编写,接收两次考试的分数并返回平均分。第二个操作用 JavaScript 编写,接收平均分并返回通过或失败。

注意

此练习的代码文件可以在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson08/Exercise27找到。

以下步骤将帮助您完成练习:

  1. 编写第一个函数(calculate-average.py),计算平均分。此函数将接收两次考试的分数作为输入:
def main(params):
    examOneMarks = params.get("examOneMarks")
    examTwoMarks = params.get("examTwoMarks")

    fullMarks = examOneMarks + examTwoMarks
    averageMarks =  fullMarks / 2

    return {"averageMarks": averageMarks}
  1. calculate-average.py创建一个名为calculateAverage的 OpenWhisk 操作:
$ wsk action create calculateAverage calculate-average.py

输出应如下所示:

图 8.41:创建 calculateAverage 操作

图 8.41:创建 calculateAverage 操作
  1. 通过调用检查calculateAverage操作是否按预期工作:
$ wsk action invoke calculateAverage --param examOneMarks 82 --param examTwoMarks 68 –result
  1. 输出应如下所示:图 8.42:调用 calculateAverage 操作
图 8.42:调用 calculateAverage 操作
  1. 创建第二个函数(show-result.js),根据平均分返回考试结果(通过失败)。考试结果将基于逻辑,即分数小于 0 或大于 100 将返回错误;分数大于或等于 60 将返回通过;否则将返回失败

代码如下:

function main(params) {
    var examResult = '';

    if (params.averageMarks < 0 || params.averageMarks > 100) {
        examResult = 'ERROR: invalid average exam mark';
    } else if (params.averageMarks >= 60) {
        examResult = 'Pass';
    } else {
        examResult = 'Fail';
    }

    return { result: examResult };
}
  1. show-result.js创建一个名为showResult的 OpenWhisk 操作:
$ wsk action create showResult show-result.js

输出应如下所示:

图 8.43:创建 showResult 操作

图 8.43:创建 showResult 操作
  1. 检查showResult操作是否按预期工作,通过调用它来验证:
$ wsk action invoke showResult --param averageMarks 75 –result

输出应如下所示:

图 8.44:调用 showResult 操作

图 8.44:调用 showResult 操作
  1. 创建getExamResults序列,其中包括calculateAverageshowResult操作:
$ wsk action create getExamResults --sequence calculateAverage,showResult

输出应如下所示:

图 8.45:创建 getExamResults 序列

图 8.45:创建 getExamResults 序列
  1. 调用getExamResults序列并验证结果:
$ wsk action invoke getExamResults --param examOneMarks 82 --param examTwoMarks 68 –result

输出应如下所示:

图 8.46:调用 getExamResults 序列

图 8.46:调用 getExamResults 序列

OpenWhisk Web 动作

到目前为止,我们已经通过wsk CLI 和wsk action invoke命令调用了我们的 OpenWhisk 动作。尽管这种调用方法非常简单,并且在开发阶段非常适合我们,但wsk CLI 不能被外部方使用,比如外部应用程序或用户,来调用我们的动作。作为解决方案,我们可以使用 OpenWhisk web 动作,这将允许通过公开可用的 URL 通过 HTTP 请求调用动作。

当调用动作时,OpenWhisk 标准动作需要身份验证(这由wsk CLI 在内部处理),并且必须将 JSON 有效负载作为响应。相比之下,web 动作可以在没有身份验证的情况下被调用,并且可以返回额外的信息,比如 HTTP 头和非 JSON 有效负载,比如 HTML 和二进制数据。

通过在创建(wsk action create)或更新(wsk action update)动作时发送--web true(或--web yes)标志,可以将 OpenWhisk 标准动作转换为 web 动作。

让我们创建一个 JavaScript 函数(web-action.js)作为 web 动作被调用。如果我们没有为名称参数传递值,这个函数将返回Hello, Stranger,当我们通过 web 动作 URL 为name参数传递值时,它将返回Hello与名称:

function main(params) {
    var helloMessage = ''
    if (params.name) {
        helloMessage = 'Hello, ' + params.name;
    } else {
        helloMessage = 'Hello, Stranger';
    }

    return { result: helloMessage };
}

现在我们可以通过发送--web true标志和wsk action create命令来创建一个 web 动作:

$ wsk action create myWebAction web-action.js --web true

输出应该如下:

图 8.47:将 myWebAction 创建为 web 动作

图 8.47:将 myWebAction 创建为 web 动作

然后,我们可以使用 web 动作 URL 来调用创建的 web 动作。web 动作 URL 的一般格式如下:

https://{APIHOST}/api/v1/web/{QUALIFIED_ACTION_NAME}.{EXT}

让我们讨论一下这个 URL 的每个组件:

  • APIHOST:IBM Cloud Functions 的APIHOST值为openwhisk.ng.bluemix.net

  • QUALIFIED_ACTION_NAME:web 动作的完全限定名称,格式为<namespace>/<package-name>/<action-name>。如果动作不在命名的package中,请使用default作为<package-name>的值。

  • EXT:代表 web 动作预期响应类型的扩展。

我们可以使用wsk action get命令的--url标志来检索 web 动作的 URL:

$ wsk action get myWebAction –url

输出应该如下:

图 8.48:检索 myWebAction 的公共 URL

图 8.48:检索 myWebAction 的公共 URL

由于我们的 web action 正在响应 JSON 有效负载,我们需要在前面的 URL 中附加.json作为扩展名。现在我们可以在网页浏览器中打开此 URL,或者使用curl命令检索输出。

让我们在前面的 URL 中使用网页浏览器调用:

图 8.49:从网页浏览器中调用 myWebAction 而没有 name 参数

图 8.49:从网页浏览器中调用 myWebAction 而没有 name 参数

你好,陌生人是预期的响应,因为我们没有在查询中为name参数传递值。

现在,让我们通过在 URL 末尾附加?name=OpenWhisk来调用相同的 URL:

us-south.functions.cloud.ibm.com/api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.json?name=OpenWhisk

输出应该如下:

图 8.50:从网页浏览器中调用 myWebAction 而有 name 参数

图 8.50:从网页浏览器中调用 myWebAction 而有 name 参数

我们可以使用以下命令将相同的 URL 作为curl请求调用:

$ curl https://us-south.functions.cloud.ibm.com/api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.json?name=OpenWhisk

输出应该如下:

图 8.51:使用 name 参数作为 curl 命令调用 myWebAction

图 8.51:使用 name 参数作为 curl 命令调用 myWebAction

这个命令将产生与我们在网页浏览器中看到的相同的输出。

正如我们之前讨论的,OpenWhisk web actions 可以配置为返回附加信息,包括 HTTP 标头、HTTP 状态码和使用 JSON 响应中的一个或多个字段的不同类型的主体内容:

  • headers:此字段用于在响应中发送 HTTP 标头。一个示例是将Content-Type发送为text/html

  • statusCode:这将发送一个有效的 HTTP 响应代码。除非明确指定,否则将发送200 OK的状态代码。

  • body:这包含响应内容,可以是纯文本、JSON 对象或数组,或者是用于二进制数据的 base64 编码字符串。

现在我们将更新web-action.js函数以按照我们之前讨论的格式发送响应:

function main(params) {
    var helloMessage = ''

    if (params.name) {
        username = params.name;
        httpResponseCode = 200;
    } else {
        username = 'Stranger';
        httpResponseCode = 400;
    }

    var htmlMessage = '<html><body><h3>' + 'Hello, ' + username + '</h3></body></html>';

    return {
        headers: {
            'Set-Cookie': 'Username=' + username + '; Max-Age=3600',
            'Content-Type': 'text/html'
        },
        statusCode: httpResponseCode,
        body: htmlMessage 
    };
}

然后,我们将使用最新的函数代码更新myWebAction动作:

$ wsk action update myWebAction web-action.js

输出应该如下:

图 8.52:更新 myWebAction

图 8.52:更新 myWebAction

让我们使用以下curl命令调用更新后的动作。我们将在 URL 中提供name=OpenWhisk作为查询参数。此外,使用-v选项打印详细输出,这将帮助我们验证我们添加到响应中的字段:

$ curl https://us-south.functions.cloud.ibm.com/api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.http?name=OpenWhisk -v

这是在前述curl命令之后收到的响应:

     *   Trying 104.17.9.194...
* Connected to us-south.functions.cloud.ibm.com (104.17.9.194) port 443 (#0)
* * * 
* * * 
> GET /api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.http?name=OpenWhisk HTTP/1.1
> Host: us-south.functions.cloud.ibm.com
> User-Agent: curl/7.47.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Sun, 04 Aug 2019 16:32:56 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Set-Cookie: __cfduid=d1cb4dec494fb11bd8b60a225c218b3101564936375; expires=Mon, 03-Aug-20 16:32:55 GMT; path=/; domain=.functions.cloud.ibm.com; HttpOnly
< X-Request-ID: 7dbce6e92b0a90e313d47e0c2afe203b
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: OPTIONS, GET, DELETE, POST, PUT, HEAD, PATCH
< Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept, User-Agent
< x-openwhisk-activation-id: f86aad67a9674aa1aaad67a9674aa12b
< Set-Cookie: Username=OpenWhisk; Max-Age=3600
< IBM_Cloud_Functions: OpenWhisk
< Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< Server: cloudflare
< CF-RAY: 5011ee17db5d7f2f-CMB
< 
* Connection #0 to host us-south.functions.cloud.ibm.com left intact
<html><body><h3>Hello, OpenWhisk</h3></body></html>

如预期的那样,我们收到了HTTP/1.1 200 OK作为 HTTP 响应代码,Content-Type: text/html作为头部,一个 cookie,以及<html><body><h3>Hello, OpenWhisk</h3></body></html>作为响应的主体。

现在,让我们在不带name=OpenWhisk查询参数的情况下调用相同的curl请求。这次,预期的响应代码是HTTP/1.1 400 Bad Request,因为我们没有为查询参数传递值。此外,curl命令将以<html><body><h3>Hello, Stranger</h3></body></html>作为 HTTP 响应体代码做出响应:

$ curl https://us-south.functions.cloud.ibm.com/api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.http -v

这是在前述curl命令之后收到的响应:

*   Trying 104.17.9.194...
* Connected to us-south.functions.cloud.ibm.com (104.17.9.194) port 443 (#0)
* * * 
* * * 
* ALPN, server accepted to use http/1.1
> GET /api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.http HTTP/1.1
> Host: us-south.functions.cloud.ibm.com
> User-Agent: curl/7.47.0
> Accept: */*
> 
< HTTP/1.1 400 Bad Request
< Date: Sun, 04 Aug 2019 16:35:09 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Set-Cookie: __cfduid=dedba31160ddcdb6791a04ff4359764611564936508; expires=Mon, 03-Aug-20 16:35:08 GMT; path=/; domain=.functions.cloud.ibm.com; HttpOnly
< X-Request-ID: 8c2091fae68ab4b678d835a000a21cc2
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: OPTIONS, GET, DELETE, POST, PUT, HEAD, PATCH
< Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept, User-Agent
< x-openwhisk-activation-id: 700916ace1d843e78916ace1d813e7c3
< Set-Cookie: Username=Stranger; Max-Age=3600
< IBM_Cloud_Functions: OpenWhisk
< Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< Server: cloudflare
< CF-RAY: 5011f1577b7a7f35-CMB
< 
* Connection #0 to host us-south.functions.cloud.ibm.com left intact
<html><body><h3>Hello, Stranger</h3></body></html>

在本节中,我们介绍了 OpenWhisk web 动作,并讨论了标准动作和 web 动作之间的区别。然后,我们使用wsk CLI 创建了一个 web 动作。接下来,我们了解了 web 动作暴露的 URL 的格式。我们使用 web 浏览器和curl命令调用了 web 动作。然后,我们讨论了 web 动作可以返回的附加信息。最后,我们更新了我们的 web 动作以包括响应中的头部、statusCode和主体,并使用curl命令调用了 web 动作以验证响应。

OpenWhisk Feeds、触发器和规则

在前几节中,我们学习了如何使用wsk CLI 或使用 web 动作的 HTTP 请求来调用动作。在本节中,我们将学习如何使用 OpenWhisk feeds、触发器和规则自动调用动作。以下图解说明了如何使用 feeds、触发器和规则从外部事件源的事件调用动作:

图 8.53:OpenWhisk Feeds、触发器和规则

图 8.53:OpenWhisk Feeds、触发器和规则

触发器是从事件源发送的不同类型的事件。这些触发器可以通过wsk CLI 手动触发,也可以自动从外部事件源发生的事件中触发。事件源的一些例子包括 Git 存储库、电子邮件帐户或 Slack 频道。如前图所示,feeds 用于将触发器连接到外部事件源。feeds 的示例如下:

  • 向 Git 存储库提交。

  • 发送到特定帐户的传入电子邮件消息。

  • 通过 Slack 频道收到的消息。

如图所示,规则是连接触发器和动作的组件。规则将一个触发器连接到一个动作。创建此链接后,触发器的每次调用都将执行关联的动作。通过创建适当的规则集,还可以实现以下情景:

  • 执行多个动作的单个触发器

  • 单个动作以响应多个触发器

让我们从创建一个简单的动作开始,以便与触发器和规则一起调用。创建一个名为triggers-rules.js的文件,并添加以下 JavaScript 函数:

function main(params) {
    var helloMessage = 'Invoked with triggers and rules';
    return { result: helloMessage };
}

然后我们将创建动作:

$ wsk action create triggersAndRules triggers-rules.js

现在是创建我们的第一个触发器的时候了。我们将使用wsk trigger create命令使用wsk CLI 创建触发器:

$ wsk trigger create <trigger-name>

让我们创建一个名为myTrigger的触发器:

$ wsk trigger create myTrigger

输出应如下所示:

图 8.54:创建 myTrigger

图 8.54:创建 myTrigger

我们可以列出可用的触发器,以确保myTrigger已成功创建:

$ wsk trigger list

输出应如下所示:

图 8.55:列出所有触发器

图 8.55:列出所有触发器

触发器在通过规则连接到动作之前是无用的。现在我们将使用wsk rule create命令创建一个 OpenWhisk 规则,其格式如下:

$ wsk rule create <rule-name> <trigger-name> <action-name>

让我们创建一个名为myRule的规则,将myTriggertriggerAndRules动作连接在一起:

$ wsk rule create myRule myTrigger triggersAndRules

输出应如下所示:

图 8.56:创建 myRule 以连接 myTrigger 与 triggersAndRules 动作

图 8.56:创建 myRule 以连接 myTrigger 与 triggersAndRules 动作

我们可以获取有关myRule的详细信息,其中显示了与规则关联的触发器和动作:

$ wsk rule get myRule

此命令将打印有关myRule的详细输出,如下面的屏幕截图所示,其中包括命名空间版本状态以及规则的关联触发器动作

输出应如下所示:

图 8.57:获取 myRule 的详细信息

图 8.57:获取 myRule 的详细信息

一旦动作触发器规则准备就绪,就是时候看到触发器的作用了。使用wsk trigger fire命令触发触发器:

$ wsk trigger fire myTrigger

输出应如下所示:

图 8.58:触发 myTrigger

图 8.58:触发 myTrigger

这将打印触发器的激活 ID。

让我们执行以下命令来列出最后两次激活:

$ wsk activation list --limit 2

输出应该如下:

图 8.59:列出最后两次激活

图 8.59:列出最后两次激活

在上述截图中,我们可以看到myTrigger触发器激活被记录,然后是triggersAndRules动作激活。

我们可以打印triggersAndRules动作激活的结果,以确保触发器正确调用了该动作:

$ wsk activation get 85d9d7e50891468299d7e50891d68224 –summary

输出应该如下:

图 8.60:打印激活的结果

图 8.60:打印激活的结果

在本节中,我们讨论了如何使用提要、触发器和规则自动调用动作。我们创建了一个动作,一个触发器,然后创建了一个规则来连接它们。最后,我们通过触发器来调用动作。

OpenWhisk CronJob 触发器

在前面的部分中,我们讨论了如何使用wsk trigger fire命令触发触发器。然而,有些情况下我们需要自动触发触发器。一个例子是执行定期任务,比如运行系统备份、日志归档或数据库清理。OpenWhisk 提供了基于 cron 的触发器,可以在固定间隔调用无服务器函数。OpenWhisk 提供的/whisk.system/alarms软件包可以用来在预定的间隔触发触发器。

此软件包包括以下提要:

图 8.61:警报软件包中可用的提要

图 8.61:警报软件包中可用的提要

在接下来的练习中,让我们学习如何创建基于 cron 作业的触发器。

练习 28:创建 CronJob 触发器

在本练习中,我们将创建一个 OpenWhisk 动作,该动作将使用提要、触发器和规则每分钟被调用。函数代码将打印当前日期和时间作为输出,以便我们可以验证 cron 作业触发器是否正确调用了该动作。

注意

此练习的代码文件可以在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson08/Exercise28找到。

以下步骤将帮助您完成本练习:

  1. 让我们从创建函数代码开始。此函数将返回当前日期和时间。创建一个名为date-time.js的文件,其中包含以下代码,并创建一个名为dateTimeAction的动作:
function main() {
    var currentDateTime = new Date();
    return { currentDateTime: currentDateTime };
}
$ wsk action create dateTimeAction date-time.js

输出应该如下所示:

图 8.62:创建 dateTimeAction

图 8.62:创建 dateTimeAction
  1. 下一步是使用/whisk.system/alarms/alarm feed 创建一个触发器。 cron 值为"* * * * *",旨在每分钟触发此动作:
$ wsk trigger create dateTimeCronTrigger \
                            --feed /whisk.system/alarms/alarm \
                            --param cron "* * * * *" 

以下是wsk trigger create命令的响应。确保输出的末尾有ok: created trigger dateTimeCronTrigger,这表示成功创建了dateTimeCronTrigger

ok: invoked /whisk.system/alarms/alarm with id 06f8535f9d364882b8535f9d368882cd
{
    "activationId": "06f8535f9d364882b8535f9d368882cd",
    "annotations": [
        {
            "key": "path",
            "value": "whisk.system/alarms/alarm"
        },
        {
            "key": "waitTime",
            "value": 85
        },
        {
            "key": "kind",
            "value": "nodejs:10"
        },
        {
            "key": "timeout",
            "value": false
        },
        {
            "key": "limits",
            "value": {
                "concurrency": 1,
                "logs": 10,
                "memory": 256,
                "timeout": 60000
            }
        },
        {
            "key": "initTime",
            "value": 338
        }
    ],
    "duration": 594,
    "end": 1565083299218,
    "logs": [],
    "name": "alarm",
    "namespace": "sathsara89@gmail.com_dev",
    "publish": false,
    "response": {
        "result": {
            "status": "success"
        },
        "status": "success",
        "success": true
    },
    "start": 1565083298624,
    "subject": "sathsara89@gmail.com",
    "version": "0.0.152"
}
ok: created trigger dateTimeCronTrigger
  1. 创建规则(dateTimeRule)以将动作(dateTimeAction)与触发器(dateTimeCronTrigger)连接起来:
$ wsk rule create dateTimeRule dateTimeCronTrigger dateTimeAction

输出应该如下所示:

图 8.63:创建 dateTimeRule 以连接 dateTimeCronTrigger 和 dateTimeAction

图 8.63:创建 dateTimeRule 以连接 dateTimeCronTrigger 和 dateTimeAction
  1. 该动作现在将每分钟被触发。让 cron 作业触发器运行大约 5 分钟。我们可以使用以下命令列出最近 6 次激活:
$ wsk activation list --limit 6

输出应该如下所示:

图 8.64:列出最近六次激活

图 8.64:列出最近六次激活
  1. 列出dateTimeAction的激活摘要,以确保它已经每分钟打印了当前日期时间:
$ wsk activation get 04012f4f3e6044ed812f4f3e6054edc4 --summary
$ wsk activation get c4758e5fa4464d0cb58e5fa446cd0cf7 --summary
$ wsk activation get cf78acfd78d044e8b8acfd78d044e89c –summary

输出应该如下所示:

图 8.65:打印 dateTimeAction 激活的摘要

图 8.65:打印 dateTimeAction 激活的摘要

检查currentDateTime字段的值,打印每次调用以验证该动作是否按计划每分钟被调用。在前面的截图中,我们可以看到该动作在09:37:02被调用,然后在09:38:03再次被调用,最后在09:39:03被调用。

在这个活动中,我们创建了一个简单的函数,用于打印当前日期和时间。然后,我们创建了一个 cron 作业触发器,以便每分钟调用此动作。

OpenWhisk Packages

OpenWhisk 包允许我们通过将相关动作捆绑在一起来组织我们的动作。举个例子,假设我们有多个动作,比如createOrderprocessOrderdispatchOrderrefundOrder。当应用用户创建订单、处理订单、派送订单和退款订单时,这些动作将执行相关的应用逻辑。在这种情况下,我们可以创建一个名为order的包来将所有与订单相关的动作分组在一起。

正如我们之前学到的,动作名称应该是唯一的。包有助于防止命名冲突,因为我们可以通过将它们放入不同的包中来创建具有相同名称的多个动作。举个例子,来自order包的retrieveInfo动作可能会检索有关订单的信息,但来自 customer 包的retrieveInfo动作可以检索有关客户的信息。

到目前为止,我们已经创建了许多动作,而不用担心包。这是如何可能的?这是因为如果我们在创建动作时没有提及任何特定的包,OpenWhisk 会将动作放入默认包中。

OpenWhisk 有两种类型的包:

  • 内置包(OpenWhisk 自带的包)

  • 用户定义的包(用户创建的其他包)

可以使用wsk package list <namespace>命令检索命名空间中的所有包。

输出应如下所示:

图 8.66:列出/whisk.system 命名空间中的包

图 8.66:列出/whisk.system 命名空间中的包

使用wsk package create命令可以创建包:

$ wsk package create <package-name>

在本节中,我们介绍了包的概念,并讨论了 OpenWhisk 的内置包和用户定义的包。在下一个练习中,我们将创建一个包并将一个动作添加到新创建的包中。

练习 29:创建 OpenWhisk 包

在本练习中,我们将创建一个名为arithmetic的包,其中包含所有与算术相关的动作,如加法、减法、乘法和除法。我们将创建一个接收两个数字作为输入并通过将数字相加返回结果的函数。然后,我们将在arithmetic包中创建此动作:

  1. 让我们从创建一个名为arithmetic的包开始:
$ wsk package create arithmetic

输出应如下所示:

图 8.67:创建算术包

图 8.67:创建算术包
  1. 现在我们将创建一个操作,将其添加到我们的arithmetic包中。创建一个名为add.js的文件,内容如下:
function main(params) {
    var result = params.firstNumber + params.secondNumber;
    return { result: result };
}
  1. 我们可以使用wsk action create命令同时创建操作并将其添加到arithmetic包中。这只需要我们在操作名称前加上包名称。执行以下命令:
$ wsk action create arithmetic/add add.js

在输出中,我们可以看到操作已成功创建在arithmetic包中。

输出应该如下所示:

图 8.68:向算术包添加 add 操作

图 8.68:向算术包添加 add 操作
  1. 现在我们可以使用wsk action list命令来验证我们的add操作是否已放置在算术包中。
$ wsk action list --limit 2

输出应该如下所示:

图 8.69:列出操作

图 8.69:列出操作
  1. wsk package get命令将返回描述包的 JSON 输出:
$ wsk package get arithmetic

输出应该如下所示:

图 8.70:获取算术包的详细描述

图 8.70:获取算术包的详细描述
  1. 如果我们想要查看包描述的摘要,列出包内的操作,我们可以使用--summary标志:
$ wsk package get arithmetic –summary

输出应该如下所示:

图 8.71:获取算术包的摘要描述

图 8.71:获取算术包的摘要描述

活动 8:通过电子邮件接收每日天气更新

想象一下,你正在为灾害管理中心工作,需要及时了解天气信息。你决定创建一个应用程序,可以在指定的时间间隔通过电子邮件向你发送天气更新。为了实现这一目标,你决定部署一个应用程序,可以获取特定城市的当前天气,并在每天上午 8 点向指定的电子邮件地址发送当前天气信息。在这个活动中,我们将使用外部服务来检索天气信息(OpenWeather)并发送电子邮件(SendGrid)。

在开始这个活动之前,我们需要准备以下内容:

  • 一个 OpenWeather 账户(用于检索当前天气信息)

  • 一个 SendGrid 账户(用于发送电子邮件)

  • npm已安装

  • zip已安装

执行以下步骤来创建一个 OpenWeather 账户和一个 SendGrid 账户:

  1. 创建一个OpenWeatheropenweathermap.org/)账户以检索当前天气信息并保存 API 密钥。在home.openweathermap.org/users/sign_up创建一个OpenWeather账户。

转到API 密钥选项卡(home.openweathermap.org/api_keys)并保存 API 密钥,因为这个 API 密钥是从 OpenWeather API 获取数据所必需的。

在 Web 浏览器中使用https://api.openweathermap.org/data/2.5/weather?q=London&appid=<YOUR-API-KEY>来测试 OpenWeather API。请注意,您需要用步骤 1中的 API 密钥替换

  1. 创建一个 SendGrid(sendgrid.com)账户并保存 API 密钥。这用于发送电子邮件。在signup.sendgrid.com/创建一个 SendGrid 账户。

转到设置 > API 密钥,然后单击创建 API 密钥按钮。

API 密钥名称字段中提供一个名称,选择完全访问单选按钮,然后单击创建和查看按钮以创建具有完全访问权限的 API 密钥。

生成密钥后,复制 API 密钥并将其保存在安全的地方,因为您只能看到这个密钥一次。

注意

有关创建OpenWeather账户和 SendGrid 账户的详细步骤,请参阅第 432 页的附录部分。

现在我们准备开始这项活动。执行以下步骤以完成这项活动:

  1. 在您熟悉的任何语言中创建一个函数(并且受 OpenWhisk 框架支持),该函数将以城市名称作为参数,并从 OpenWeather API 中检索的天气信息返回一个 JSON 对象。

注意

对于这个解决方案,我们将使用 JavaScript 编写的函数。但是,您可以使用任何您熟悉的语言来编写这些函数。

以下是一个用 JavaScript 编写的示例函数:

const request = require('request');
function main(params) {
    const cityName = params.cityName
    const openWeatherApiKey = '<OPEN_WEATHER_API_KEY>';
    const openWeatherUrl = 'https://api.openweathermap.org/data/2.5/weather?q=' + cityName + '&mode=json&units=metric&appid=' + openWeatherApiKey ;
    return new Promise(function(resolve, reject) {
        request(openWeatherUrl, function(error, response, body) {   
            if (error) {
                reject('Requesting weather data from provider failed ' 
                        + 'with status code ' 
                        + response.statusCode + '.\n' 
                        + 'Please check the provided cityName argument.');
            } else {
                try {
                    var weatherData = JSON.parse(body);
                    resolve({weatherData:weatherData});
                } catch (ex) {
                    reject('Error occurred while parsing weather data.');
                }
            }
        });
    });
}
  1. 创建第二个函数(在您熟悉的任何语言中,并且受 OpenWhisk 框架支持),该函数将以消息作为输入,并使用 SendGrid 服务将输入消息发送到指定的电子邮件地址。

以下是一个用 JavaScript 编写的示例函数:

const sendGridMailer = require('@sendgrid/mail');
function main(params) {
    const sendGridApiKey = '<SEND_GRID_API_KEY>';
    const toMail = '<TO_EMAIL>';
    const fromMail = '<FROM_EMAIL>';
    const mailSubject = 'Weather Information for Today';
    const mailContent = params.message;
    return new Promise(function(resolve, reject) {
        sendGridMailer.setApiKey(sendGridApiKey);
        const msg = {
            to: toMail,
            from: fromMail,
            subject: mailSubject,
            text: mailContent,
        };
        sendGridMailer.send(msg, (error, result) => {
            if (error) {
                reject({msg: "Message sending failed."});
            } else {
                resolve({msg: "Message sent!"});
            }
        });
    });
}
exports.main = main;
  1. 创建第三个函数(在您熟悉的任何语言中,并且受 OpenWhisk 框架支持),该函数将以天气数据的 JSON 对象,并将其格式化为字符串消息,以便作为电子邮件正文发送。

以下是一个用 JavaScript 编写的示例函数:

     function main(params) {
    return new Promise(function(resolve, reject) {
        if (!params.weatherData) {
            reject("Weather data not provided");
        }
        const weatherData = params.weatherData;
        const cityName = weatherData.name;
        const currentTemperature = weatherData.main.temp;
        weatherMessage = "It's " + currentTemperature
                                 + " degrees celsius in " + cityName;
        resolve({message: weatherMessage});
   });
}
  1. 接下来,创建一个连接所有三个动作的序列。

  2. 最后,创建触发器和规则,在每天上午 8 点调用序列。

注意

活动的解决方案可以在第 432 页找到。

摘要

在本章中,我们首先了解了 Apache OpenWhisk 的历史和核心概念。然后,我们学习了如何使用 CLI 设置 IBM Cloud Functions 来运行我们的无服务器函数。之后,介绍了 OpenWhisk 动作,这些是用 OpenWhisk 支持的语言之一编写的代码片段。我们讨论了如何使用wsk CLI 编写、创建、列出、调用、更新和删除 OpenWhisk 动作。接下来,我们介绍了 OpenWhisk 序列,用于将多个动作组合在一起,创建更复杂的处理流水线。接下来,我们学习了如何使用 Web 动作公开动作,通过 URL 返回动作的附加信息,如 HTTP 标头和非 JSON 有效载荷,包括 HTML 和二进制数据。下一部分是关于饲料、触发器和规则,它们使用来自外部事件源的事件自动化动作调用。最后,我们讨论了 OpenWhisk 包,它们用于通过捆绑它们来组织相关动作。

在下一章中,我们将学习关于 OpenFaaS,并使用 OpenFaaS 函数。

第九章: 使用 OpenFaaS 进行无服务器操作

学习目标

在本章结束时,您将能够:

  • 在 Minikube 集群上设置 OpenFaaS 框架

  • 使用 OpenFaaS CLI 创建、构建、部署、列出、调用和删除函数

  • 从 OpenFaaS 门户部署和调用 OpenFaaS 函数

  • 从 OpenFaaS 函数返回 HTML 网页

  • 设置 Prometheus 和 Grafana 仪表板以监视 OpenFaaS 函数

  • 配置函数自动缩放以根据需求调整函数计数

在本章中,我们的目标是在 Minikube 集群上设置 OpenFaaS 框架,并学习如何使用 OpenFaaS 函数,同时使用 OpenFaaS CLI 和 OpenFaaS 门户。我们还将研究 OpenFaaS 的可观察性和自动缩放等功能。

OpenFaaS 简介

在上一章中,我们了解了 OpenWhisk,这是一个开源的无服务器框架,它是 Apache 软件基金会的一部分。我们学习了如何创建、列出、调用、更新和删除 OpenWhisk 动作。我们还讨论了如何使用 feeds、triggers 和 rules 自动化动作调用。

在本章中,我们将学习 OpenFaas,这是另一个用于在容器之上构建和部署无服务器函数的开源框架。这是由 Alex Ellis 于 2016 年 10 月作为概念验证项目开始的,框架的第一个版本是用 Golang 编写的,并于 2016 年 12 月提交到 GitHub。

OpenFaaS 最初设计用于与 Docker Swarm 一起工作,这是 Docker 容器的集群和调度工具。后来,OpenFaaS 框架被重新设计为支持 Kubernetes 框架。

OpenFaaS 带有一个名为OpenFaaS 门户的内置 UI,可以用于在 Web 浏览器中创建和调用函数。该门户还提供了一个名为faas-cli的 CLI,允许我们通过命令行管理函数。OpenFaaS 框架内置支持自动缩放。这将在需求增加时扩展函数,并在需求减少时缩小,甚至在函数空闲时缩小到零。

现在,让我们来看看 OpenFaaS 框架的组件:

图 9.1:OpenFaaS 组件

图 9.1:OpenFaaS 组件

OpenFaaS 由以下组件组成,这些组件正在底层的 Kubernetes 或 Docker Swarm 上运行:

  • API 网关

API 网关是 OpenFaaS 框架的入口点,可以在外部公开函数。它还负责收集函数指标,如函数调用次数、函数执行持续时间和函数副本数量。API 网关还通过根据需求增加或减少函数副本来处理函数自动扩展。

  • Prometheus

Prometheus 是一个开源的监控和警报工具,与 OpenFaaS 框架捆绑在一起。它用于存储 API 网关收集的函数指标信息。

  • 函数看门狗

函数看门狗是一个微小的 Golang Web 服务器,运行在每个函数容器旁边。这个组件位于 API 网关和您的函数之间,负责在 API 网关和函数之间转换消息格式。它将 API 网关发送的 HTTP 消息转换为函数可以理解的“标准输入”(stdin)消息。它还通过将函数发送的“标准输出”(stdout)响应转换为 HTTP 响应来处理响应路径。

以下是函数看门狗的示例:

图 9.2:OpenFaaS 函数看门狗

图 9.2:OpenFaaS 函数看门狗

Docker Swarm 或 Kubernetes 可以作为容器编排工具与 OpenFaaS 框架一起使用,用于管理底层 Docker 框架上运行的容器。

在本地 Minikube 集群上开始使用 OpenFaaS

在本节中,我们将在本地 Minikube 集群上设置 OpenFaaS 框架和 CLI。在开始安装之前,我们需要确保满足以下先决条件:

  • 已安装 Minikube

  • 安装 Docker(版本 17.05 或更高)

  • 已安装 Helm

  • 创建 Docker Hub 帐户

一旦这些先决条件准备就绪,我们就可以继续安装 OpenFaaS。OpenFaaS 的安装可以大致分为三个步骤,如下所示:

  1. 安装 OpenFaaS CLI

  2. 安装 OpenFaaS 框架(在 Minikube 集群上)

  3. 设置环境变量

让我们更深入地看看这些步骤:

安装 OpenFaaS CLI

faas-cli是 OpenFaaS 框架的命令行实用程序,可用于从终端创建和调用 OpenFaaS 函数。我们可以使用以下命令安装最新版本的faas-cli

$ curl -sL https://cli.openfaas.com | sudo sh

输出应如下所示:

图 9.3:安装 faas-cli

安装完成后,我们可以使用faas-cli version命令验证安装:

$ faas-cli version

输出应如下所示:

图 9.4:faas-cli 版本

图 9.4:faas-cli 版本

如您所见,我们已在集群上安装了faas-cli实用程序,并且还可以检查版本号。

安装 OpenFaaS 框架

接下来,我们需要使用 OpenFaaS helm存储库安装 OpenFaaS 框架。首先,我们需要添加openfaas helm存储库并更新以拉取任何新版本。使用以下命令:

$ helm repo add openfaas https://openfaas.github.io/faas-netes/
$ helm repo update

输出应如下所示:

图 9.5:添加和更新 helm 图表

图 9.5:添加和更新 helm 图表

安装 OpenFaaS 需要两个 Kubernetes 命名空间。openfaas命名空间用于 OpenFaaS 框架的核心服务,openfaas-fn命名空间用于 OpenFaaS 函数。运行以下命令创建命名空间:

$ kubectl create namespace openfaas
$ kubectl create namespace openfaas-fn

输出将如下所示:

图 9.6:创建命名空间

图 9.6:创建命名空间

现在我们将创建 Kubernetes 密钥,这是启用 OpenFaaS 网关的基本身份验证所需的。首先,我们将创建一个随机字符串,将用作密码。生成密码后,我们将echo生成的密码并将其保存在安全的位置,因为我们稍后需要用它登录到 API 网关。运行以下命令生成密码:

$ PASSWORD=$(head -c 12 /dev/urandom | shasum| cut -d' ' -f1)
$ echo $PASSWORD

输出将如下所示:

图 9.7:生成密码

图 9.7:生成密码

生成密码后,我们将创建一个 Kubernetes secret对象来存储密码。

注意:

Kubernetes secret对象用于存储诸如密码之类的敏感数据。

执行以下命令创建名为basic-auth的 Kubernetes 密钥:

$ kubectl -n openfaas create secret generic basic-auth \
    --from-literal=basic-auth-user=admin \
    --from-literal=basic-auth-password="$PASSWORD"

输出将如下所示:

图 9.8:创建 basic-auth 密钥

图 9.8:创建 basic-auth 密钥

现在我们可以从helm图表部署 OpenFaaS 框架。helm upgrade openfaas命令开始部署 OpenFaaS,并将在本地 Minikube 集群上开始部署 OpenFaaS 框架。这将根据网络速度需要 5 到 15 分钟。运行以下命令安装OpenFaaS

$ helm upgrade openfaas \
    --install openfaas/openfaas \
    --namespace openfaas \
    --set functionNamespace=openfaas-fn \
    --set basic_auth=true

上述命令会打印出一长串的输出,在底部提供了一个命令来验证安装,如下截图所示:

图 9.9:OpenFaaS 安装

图 9.9:OpenFaaS 安装

您可以使用以下命令验证部署状态:

$ kubectl --namespace=openfaas get deployments -l "release=openfaas, app=openfaas"

输出将显示如下:

图 9.10:验证 OpenFaaS 安装

图 9.10:验证 OpenFaaS 安装

安装成功完成并且所有服务正在运行后,我们需要使用在前面步骤中创建的凭据登录到 OpenFaaS 网关。运行以下命令登录到 OpenFaas 网关:

$ faas-cli login --username admin --password $PASSWORD

输出应如下所示:

图 9.11:登录到 OpenFaaS 网关

图 9.11:登录到 OpenFaaS 网关

设置环境变量

本节中有几个与 OpenFaaS 相关的环境变量,我们将设置两个环境变量。如果需要,这些环境变量可以使用faas-cli的命令行标志进行覆盖。

  • OPENFAAS_URL:这应该指向 API 网关组件。

  • OPENFAAS_PREFIX:这是您的 Docker Hub 帐户的 Docker ID。

用您喜欢的文本编辑器打开~/.bashrc文件,并在文件末尾添加以下两行。在以下命令中用您的 Docker ID 替换<your-docker-id>

export OPENFAAS_URL=$(minikube ip):31112
export OPENFAAS_PREFIX=<your-docker-id>

然后,您需要源~/.bashrc文件以重新加载新配置的环境变量,如下命令所示:

$ source ~/.bashrc

命令应如下所示:

图 9.12:源 bashrc 文件

图 9.12:源 bashrc 文件

OpenFaaS 函数

OpenFaaS 函数可以用 Linux 或 Windows 支持的任何语言编写,然后可以使用 Docker 容器将其转换为无服务器函数。这是 OpenFaaS 框架与其他仅支持预定义语言和运行时的无服务器框架相比的主要优势。

OpenFaaS 函数可以使用faas-cli或 OpenFaaS 门户部署。在接下来的几节中,我们首先将讨论如何使用faas-cli命令行工具构建、部署、列出、调用和删除 OpenFaaS 函数。然后,我们将讨论如何使用 OpenFaaS 门户部署和调用函数。

创建 OpenFaaS 函数

正如我们之前讨论的,OpenFaaS 函数可以用 Linux 和 Windows 支持的任何语言编写。这需要我们创建函数代码,添加任何依赖项,并创建一个Dockerfile来构建 Docker 镜像。这需要一定程度上对 OpenFaaS 平台的理解,以便能够执行前面提到的任务。作为解决方案,OpenFaaS 有一个模板存储库,其中包括一组支持的语言的预构建模板。这意味着你可以从模板存储库下载这些模板,更新函数代码,然后 CLI 会完成其余的工作来构建 Docker 镜像。

首先,我们需要使用faas-cli template pull命令拉取 OpenFaaS 模板。这将从官方 OpenFaaS 模板存储库github.com/openfaas/templates.git获取模板。

现在,让我们使用以下命令创建一个新文件夹,并将模板拉到新创建的文件夹中:

$ mkdir chapter-09
$ cd chapter-09/
$ faas-cli template pull

输出将如下所示:

图 9.13:创建目录

让我们使用tree -L 2命令来检查文件夹结构,该命令将打印出两层深度的文件夹,如下截图所示:

图 9.14:文件夹的树形视图

](image/C12607_09_14.jpg)

图 9.14:文件夹的树形视图

在模板文件夹中,我们可以看到 17 个文件夹,每个文件夹都是针对特定语言模板的。

现在,我们可以使用faas-cli new命令使用下载的模板创建新函数的结构和文件,如下所示:

$ faas-cli new <function-name> --lang=<function-language>

<function-language>可以被 OpenFaaS 模板支持的任何编程语言替换。faas-cli new --list可以用来获取支持的编程语言的列表,如下图所示:

图 9.15:列出支持的编程语言模板

](image/C12607_09_15.jpg)

图 9.15:列出支持的编程语言模板

让我们使用以下命令创建我们的第一个 OpenFaaS 函数,名为hello,并使用go语言模板:

$ faas-cli new hello --lang=go

输出将如下所示:

图 9.16:创建 hello 函数模板

图 9.16:创建 hello 函数模板

根据输出,上述命令将在当前文件夹内创建多个文件和目录。让我们再次执行tree -L 2命令,以识别新创建的文件和目录:

图 9.17:文件夹的树状视图

图 9.17:文件夹的树状视图

我们可以看到一个名为hello.yml的文件,一个名为hello的文件夹,以及hello文件夹内的handler.go文件。

首先,我们将查看hello.yml文件,它被称为函数 定义文件:

version: 1.0
provider:
  name: openfaas
  gateway: http://192.168.99.100:31112
functions:
  hello:
    lang: go
    handler: ./hello
    image: sathsarasa/hello:latest

这个文件有三个顶层,分别是versionproviderfunctions

provider部分内,有一个name: faas标签,它将提供者名称定义为faas。这是name标签的默认且唯一有效的值。接下来是gateway标签,它指向 API 网关运行的 URL。这个值可以在部署时用--gateway标志或OPENFAAS_URL环境变量进行覆盖。

接下来是functions部分,用于定义一个或多个要使用 OpenFaaS CLI 部署的函数。在前面的代码中,hello.yml文件有一个名为hello的函数,用 Go 语言(lang: go)编写。函数的处理程序是用handler: ./hello部分定义的,它指向hello函数的源代码(hello/handler.go)所在的文件夹。最后,有一个image标签,指定了输出 Docker 镜像的名称。Docker 镜像名称以您使用OPENFAAS_PREFIX环境变量配置的 Docker 镜像 ID 为前缀。

接下来,我们将讨论在hello文件夹内创建的handler.go文件。这个文件包含用 Go 语言编写的函数的源代码。这个函数接受一个字符串参数,并通过在其前面加上Hello, Go. You said:来返回这个字符串,就像下面的代码片段中显示的那样:

package function
import (
	"fmt"
)
// Handle a serverless request
func Handle(req []byte) string {
	return fmt.Sprintf("Hello, Go. You said: %s", string(req))
}

这只是一个由模板生成的示例函数。我们可以用我们的函数逻辑来更新它。

构建 OpenFaaS 函数

一旦函数定义文件(hello.yml)和函数源代码(hello/handler.go)准备好,下一步就是将函数构建为 Docker 镜像。使用faas-cli build CLI 命令来构建 Docker 镜像,其格式如下:

$ faas-cli build -f <function-definition-file>

这将启动构建 Docker 镜像的过程,并将在内部调用docker build命令。在这一步中将创建一个名为build的新文件夹,其中包含构建过程所需的所有文件。

现在,让我们构建在上一节中创建的hello函数:

$ faas-cli build -f hello.yml

我们将收到类似以下的输出:

     [0] > Building hello.
Clearing temporary build folder: ./build/hello/
Preparing ./hello/ ./build/hello/function
Building: sathsarasa/hello with go template. Please wait..
Sending build context to Docker daemon  6.656kB
Step 1/24 : FROM openfaas/classic-watchdog:0.15.4 as watchdog
 ---> a775beb8ba9f
...
...
Successfully built 72c9089a7dd4
Successfully tagged sathsarasa/hello:latest
Image: sathsarasa/hello built.
[0] < Building hello done.
[0] worker done.

一旦我们收到构建成功的消息,我们可以使用docker images命令列出 Docker 镜像,如下所示:

$ docker images | grep hello

输出如下:

图 9.18:验证 Docker 镜像

图 9.18:验证 Docker 镜像

推送 OpenFaaS 函数镜像

该过程的下一步是将函数的 Docker 镜像推送到 Docker 注册表或 Docker Hub。我们可以使用faas-cli pushdocker push命令来推送镜像。

注意

Docker Hub 是一个免费的用于存储和共享 Docker 镜像的服务。

让我们使用faas-cli push命令推送镜像:

$ faas-cli push -f hello.yml

输出将如下所示:

图 9.19:推送 Docker 镜像

图 9.19:推送 Docker 镜像

我们可以通过访问 Docker Hub 页面hub.docker.com/来验证镜像是否成功推送。

输出应如下所示:

图 9.20:从 Docker Hub 验证

图 9.20:从 Docker Hub 验证

因此,我们已成功将 Docker 镜像功能推送到 Docker Hub。

部署 OpenFaaS 函数

现在,我们准备使用faas-cli deploy命令将hello函数部署到 OpenFaaS 框架中。该命令还需要使用-f标志的函数规范文件,类似于我们之前执行的其他faas-cli命令:

$ faas-cli deploy -f hello.yml

输出应如下所示:

图 9.21:部署 hello 函数

图 9.21:部署 hello 函数

我们将收到一个202 Accepted的输出,以及函数 URL,我们可以用它来调用函数。

在这一步,将在openfaas-fn命名空间中创建一些 Kubernetes 对象,包括 pod、service、deployment 和 replica set。我们可以使用以下命令查看所有这些 Kubernetes 对象:

$ kubectl get all -n openfaas-fn

输出应如下所示:

图 9.22:验证 Kubernetes 对象

图 9.22:验证 Kubernetes 对象

因此,我们已成功将hello函数部署到 OpenFaaS 框架中。

列出 OpenFaaS 函数

faas-cli list命令用于列出部署在 OpenFaaS 框架上的所有函数:

$ faas-cli list

输出应如下所示:

图 9.23:列出 OpenFaaS 函数

图 9.23:列出 OpenFaaS 函数

faas-cli list命令的输出将包括以下列:

  • 函数 - 函数的名称

  • 调用 - 函数被调用的次数

  • 副本 - 函数的 Kubernetes pod 副本数

调用列的值每次调用函数时都会增加。如果调用率增加,副本列的值将自动增加。

如果您想要获取额外的列名为Image的附加列,可以在faas-cli list命令中使用--verbose标志,该列会列出用于部署函数的 Docker 镜像,如下命令所示:

$ faas-cli list --verbose

输出应该如下所示:

图 9.24:列出 OpenFaaS 函数并显示详细输出

图 9.24:列出 OpenFaaS 函数并显示详细输出

如果我们想要获取关于特定函数的详细信息,可以使用faas-cli describe CLI 命令:

$ faas-cli describe hello

输出应该如下所示:

图 9.25:描述一个 OpenFaaS 函数

图 9.25:描述一个 OpenFaaS 函数

调用 OpenFaaS 函数

现在,函数已部署并准备好被调用。函数可以通过faas-cli invoke命令来调用,格式如下:

$ faas-cli invoke <function-name>

现在,让我们调用在上一步中部署的hello函数。

运行以下命令来调用hello函数:

$ faas-cli invoke hello

一旦函数被调用,它将要求您输入输入参数并按Ctrl + D停止从标准输入读取。输出应该如下所示:

图 9.26:调用 hello 函数

图 9.26:调用 hello 函数

我们也可以将输入数据发送到函数中,如下命令所示:

$ echo "Hello with echo" | faas-cli invoke hello

输出应该如下所示:

图 9.27:使用管道输入调用 hello 函数

图 9.27:使用管道输入调用 hello 函数

curl命令也可以用来调用函数,如下所示:

$ curl http://192.168.99.100:31112/function/hello -d "Hello from curl"

输出应该如下所示:

图 9.28:使用 curl 调用 hello 函数

图 9.28:使用 curl 调用 hello 函数

因此,我们已成功使用faas-cli invoke命令和curl命令调用了hello函数。

删除 OpenFaaS 函数

faas-cli remove命令用于从 OpenFaaS 集群中删除函数,可以通过使用-f标志指定函数定义文件,或者显式指定函数名称,如下命令所示:

$ faas-cli remove <function-name>

或者,使用以下命令:

$ faas-cli remove -f <function-definition-file>

我们可以使用以下命令删除我们之前创建的hello函数:

$ faas-cli remove hello

输出应如下所示:

图 9.29:删除 hello 函数

图 9.29:删除 hello 函数

在这些部分中,我们学习了如何使用faas-cli命令行创建、部署、列出、调用和删除 OpenFaaS 函数。现在,让我们继续进行一个练习,我们将创建我们的第一个 OpenFaaS 函数。

练习 30:创建具有依赖关系的 OpenFaaS 函数

在这个练习中,我们将创建一个 Python 函数,通过调用外部 API 来打印源 IP 地址。我们将使用requests Python 模块来调用这个 API:

  1. 使用Python3模板创建一个名为ip-info的新函数:
$ faas-cli new ip-info --lang=python3 

输出应如下所示:

图 9.30:创建 ip-info 函数模板

](image/C12607_09_30.jpg)

图 9.30:创建 ip-info 函数模板
  1. 更新ip-info/requirements.txt文件,添加我们需要从函数中调用 HTTP 请求的requests pip模块:
requests
  1. 更新ip-info/handler.py文件以调用httpbin.org/ip端点。这个端点是一个简单的服务,将返回发起请求的 IP。以下代码将向httpbin.org/ip端点发送 HTTP GET 请求,并返回原始 IP 地址:
import requests
import json
def handle(req):
    api_response = requests.get('https://httpbin.org/ip')
    json_object = api_response.json()
    origin_ip = json_object["origin"]
    return "Origin IP is " + origin_ip
  1. 使用faas-cli up命令构建、推送和部署ip-info函数。faas-cli up命令将在后台执行faas-cli buildfaas-cli pushfaas-cli deploy命令,构建函数,将 Docker 镜像推送到 Docker 注册表,并在 OpenFaaS 框架上部署函数:
$ faas-cli up -f ip-info.yml

faas-cli up命令将打印以下输出,列出构建、推送和部署ip-info函数的步骤:

[0] > Building ip-info.
Clearing temporary build folder: ./build/ip-info/
Preparing ./ip-info/ ./build/ip-info//function
Building: sathsarasa/ip-info:latest with python3 template. Please wait..
Sending build context to Docker daemon  9.728kB
...
Successfully built 1b86554ad3a2
Successfully tagged sathsarasa/ip-info:latest
Image: sathsarasa/ip-info:latest built.
[0] < Building ip-info done.
[0] worker done.
[0] > Pushing ip-info [sathsarasa/ip-info:latest].
The push refers to repository [docker.io/sathsarasa/ip-info]
... 
latest: digest: sha256:44e0b0e1eeca37f521d4e9daa1c788192cbc0ce6ab898c5e71cb840c6d3b4839 size: 4288
[0] < Pushing ip-info [sathsarasa/ip-info:latest] done.
[0] worker done.
Deploying: ip-info.
WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.
Deployed. 202 Accepted.
URL: http://192.168.99.100:31112/function/ip-info
  1. 使用以下curl命令调用ip-info函数:
$ curl http://192.168.99.100:31112/function/ip-info

输出应如下所示:

图 9.31:调用 ip-info 函数模板

图 9.31:调用 ip-info 函数模板
  1. 最后,删除ip-info函数:
$ faas-cli remove ip-info

因此,我们已经创建、部署和调用了一个名为ip-info的 OpenFaaS 函数,它将打印函数调用者的源 IP 地址。

使用 OpenFaaS Portal 部署和调用函数

OpenFaaS 框架配备了一个内置的 UI,允许我们从 Web 浏览器部署和调用功能。它可以用于部署自定义功能或功能存储库中的功能。OpenFaaS 功能存储库是一组免费提供的预构建功能。这些功能可以轻松部署到我们现有的 OpenFaaS 集群上。

OpenFaaS 门户网址的格式是http://<openfaas-gateway-endpoint>/ui。让我们使用以下命令从我们之前设置的$OPENFAAS_URL环境变量中获取 OpenFaaS 门户网址:

echo $OPENFAAS_URL/ui/

输出应该如下所示:

图 9.32:生成 OpenFaaS 门户网址

图 9.32:生成 OpenFaaS 门户网址

让我们导航到http://192.168.99.100:31112/ui/的输出网址。

您应该能够看到类似以下的门户网址,我们将在接下来的步骤中使用它来部署和调用 OpenFaaS 功能:

图 9.33:导航到 OpenFaaS 门户网址

部署功能从功能存储

在本节中,我们将学习如何从功能存储库部署功能。首先,在 OpenFaaS 门户网站中点击部署新功能按钮。这将提示您显示一个对话框,其中列出了功能存储库中所有可用的功能。在本节中,我们将部署Figlet功能,它可以从提供的字符串输入生成 ASCII 标志。从功能列表中选择Figlet,然后点击部署按钮,如下图所示:

图 9.34:部署 figlet 功能

这就是您需要做的全部!这将在我们现有的 OpenFaaS 集群中部署Figlet功能。现在,您将能够在 OpenFaaS 门户网站的左侧边栏中看到一个名为figlet的新功能,如下图所示:

图 9.35:验证 figlet 功能

图 9.35:验证 figlet 功能

让我们从 OpenFaaS 门户网站调用功能。您需要点击功能名称,然后屏幕的右侧面板将显示有关功能的信息,包括功能状态、调用计数、副本计数、功能图像和功能网址:

图 9.36:Figlet 功能描述

图 9.36:Figlet 功能描述

我们可以通过单击调用按钮来调用此函数,该按钮位于调用函数部分下方。如果函数需要输入值,您可以在调用函数之前在请求体部分提供输入值。

让我们通过提供OpenFaaS字符串作为请求体来调用figlet函数,如下图所示:

图 9.37:调用 figlet 函数

图 9.37:调用 figlet 函数

现在,您可以看到函数的预期输出。这将是我们在调用函数时提供的输入值的 ASCII 标志。此外,UI 将为您提供函数调用的响应状态代码和执行持续时间。

部署自定义函数

现在,让我们使用我们之前构建的 Docker 镜像部署一个名为hello的自定义函数。在从 OpenFaaS 门户部署函数之前,我们应该编写我们的函数,并使用faas-cli命令构建和推送 Docker 镜像。

再次单击部署新函数按钮,然后从对话框中选择CUSTOM选项卡。现在,我们需要提供 Docker 镜像名称和函数名称作为必填字段。让我们提供我们之前构建的hello Docker 镜像(/hello)并提供hello-portal作为函数名称,然后单击DEPLOY按钮:

图 9.38:部署 hello-portal 函数

图 9.38:部署 hello-portal 函数

然后,您将看到hello-portal函数添加到 OpenFaaS 门户的左侧菜单中:

图 9.39:验证 hello-portal 函数

图 9.39:验证 hello-portal 函数

现在,您可以按照我们之前讨论的类似步骤来调用hello-portal函数。

具有 HTML 输出的 OpenFaaS 函数

在本节中,我们将设置一个 OpenFaaS 函数来返回 HTML 内容。这使我们能够使用 OpenFaaS 框架创建静态和动态网站。

首先,我们将使用php7模板创建html-output函数,如下命令所示:

$ faas-cli new html-output --lang=php7

输出应该如下所示:

图 9.40:创建 html-output 函数

图 9.40:创建 html-output 函数

然后,我们将更新生成的Handler.php文件,以使用以下命令返回硬编码的 HTML 字符串:

使用您喜欢的文本编辑器打开html-output/src/Handler.php文件。以下命令将使用vi编辑器打开此文件:

$ vi html-output/src/Handler.php

将以下内容添加到文件中。这是一个简单的 PHP 代码,将返回文本OpenFaaS HTML Output,格式化为 HTML 标题文本:

<?php
namespace App;
/**
 * Class Handler
 * @package App
 */
class Handler
{
    /**
     * @param $data
     * @return
     */
    public function handle($data) {
        $htmlOutput = "<html><h1>OpenFaaS HTML Output</h1></html>";
        return $htmlOutput;
    }
}

现在,PHP 函数已经准备好输出 HTML。下一步是将函数的Content-Type配置为text/html。这可以通过更新函数定义文件的environment部分来完成。让我们在html-output.yml文件中更新environment部分,添加content_type: text/html,如下所示:

$ vi html-output.yml
provider:
  name: faas
  gateway: http://192.168.99.100:31112
functions:
  html-output:
    lang: php7
    handler: ./html-output
    image: sathsarasa/html-output:latest
    environment:
      content_type: text/html

现在,让我们使用faas-cli up命令构建、推送和部署html-output函数:

$ faas-cli up -f html-output.yml

执行上述命令后,我们将收到类似以下的输出:

[0] > Building html-output.
Clearing temporary build folder: ./build/html-output/
Preparing ./html-output/ ./build/html-output//function
Building: sathsarasa/html-output:latest with php7 template. Please wait..
Sending build context to Docker daemon  13.31kB
...
Successfully built db79bcf55f33
Successfully tagged sathsarasa/html-output:latest
Image: sathsarasa/html-output:latest built.
[0] < Building html-output done.
[0] worker done.
[0] > Pushing html-output [sathsarasa/html-output:latest].
The push refers to repository [docker.io/sathsarasa/html-output]
b7fb7b7178f2: Pushed 
06f1d60fbeaf: Pushed 
b2f016541c01: Pushed 
1eb73bc41394: Pushed 
dc6f559fd649: Mounted from sathsarasa/php7 
e50d92207970: Mounted from sathsarasa/php7 
9bd686c066e4: Mounted from sathsarasa/php7 
35b76def1bb4: Mounted from sathsarasa/php7 
34986ef73af3: Mounted from sathsarasa/php7 
334b08a7c2ef: Mounted from sathsarasa/php7 
5833c19f1f2c: Mounted from sathsarasa/php7 
98d2cfd0a4c9: Mounted from sathsarasa/php7 
24291ffdb574: Mounted from sathsarasa/php7 
eb2c5ec03df0: Pushed 
3b051c6cbb79: Pushed 
99abb9ea3d15: Mounted from sathsarasa/php7 
be22007b8d1b: Mounted from sathsarasa/php7 
83a68ffd9f11: Mounted from sathsarasa/php7 
1bfeebd65323: Mounted from sathsarasa/php7 
latest: digest: sha256:ec5721288a325900252ce928f8c5f8726c6ab0186449d9414baa04e4fac4dfd0 size: 4296
[0] < Pushing html-output [sathsarasa/html-output:latest] done.
[0] worker done.
Deploying: html-output.
WARNING! Communication is not secure, please consider using HTTPS. 
Letsencrypt.org offers free SSL/TLS certificates.
Deployed. 202 Accepted.
URL: http://192.168.99.100:31112/function/html-output

函数现在已经成功部署。现在,我们可以从 Web 浏览器访问函数 URL,网址为 http://192.168.99.100:31112/function/html-output,以查看输出,如下图所示:

图 9.41:调用 html-output 函数

图 9.41:调用 html-output 函数

练习 31:基于路径参数返回 HTML

在这个练习中,我们将创建一个函数,根据函数 URL 的路径参数之一返回两个静态 HTML 文件:

  1. 创建一个名为serverless-website的新函数,基于php7模板:
$ faas-cli new serverless-website --lang=php7

输出应该如下所示:

图 9.42:创建 serverless-website 函数

图 9.42:创建 serverless-website 函数
  1. serverless-website内创建 HTML 文件夹,用于存储所有 HTML 文件:
$ mkdir serverless-website/src/html
  1. 创建主页的第一个 HTML 文件(serverless-website/src/html/home.html),包含以下代码。这个 HTML 页面将输出文本Welcome to OpenFaaS Home Page作为页面标题,以及OpenFaaS Home作为页面标题:
<!DOCTYPE html>
<html>
  <head>
    <title>OpenFaaS Home</title>
 </head>
 <body>
    <h1>Welcome to OpenFaaS Home Page</h1>
 </body>
</html> 
  1. 为登录页面创建第二个 HTML 文件(serverless-website/src/html/login.html)。这个 HTML 页面将输出一个简单的登录表单,包括用户名密码两个字段,以及一个登录按钮用于提交表单:
<!DOCTYPE html>
<html>
 <head>
    <title>OpenFaaS Login</title>
 </head>
 <body>
    <h1>OpenFaaS Login Page</h1>
    <form id="contact_us_form">
       <label for="username">Username:</label>
       <input type="text" name="username" required>
       <label for="password">Password:</label>
       <input type="text" name="password" required>
       <input type="submit" value="Login">
    </form>
 </body>
</html> 
  1. 更新处理程序文件(serverless-website/src/Handler.php),根据函数 URL 的路径参数返回相应的 HTML 文件,使用以下代码。该函数将接收homelogin作为路径参数进行调用。然后读取路径参数,并根据提供的路径参数相应地设置 HTML 页面名称。下一步是打开 HTML 文件,读取文件内容,最后将文件内容作为函数响应返回:
<?php
namespace App;
class Handler
{
    public function handle($data) {
	     // Retrieve page name from path params
		$path_params = getenv('Http_Path');
		$path_params_array = explode('/',$path_params);
		$last_index = count($path_params_array);
		$page_name = $path_params_array[$last_index-1];

		// Set the page name
		$current_dir = __DIR__;
		$html_file_path = $current_dir . "/html/" . $page_name . ".html";

		// Read the file
		$html_file = fopen($html_file_path, "r") or die("Unable to open HTML file!");
		$html_output = fread($html_file,filesize($html_file_path));
		fclose($html_file);

		// Return file content
		return $html_output;	
    }
}
  1. serverless-website.yml中将content_type设置为text/html
version: 1.0
provider:
  name: openfaas
  gateway: http://192.168.99.100:31112
functions:
  serverless-website:
    lang: php7
    handler: ./serverless-website
    image: sathsarasa/serverless-website:latest
    environment:
      content_type: text/html
  1. 使用以下命令构建、推送和部署serverless-website函数:
$ faas-cli up -f serverless-website.yml

以下是前述命令的输出:

[0] > Building serverless-website.
Clearing temporary build folder: ./build/serverless-website/
Preparing ./serverless-website/ ./build/serverless-website//function
Building: sathsarasa/serverless-website:latest with php7 template. Please wait..
Sending build context to Docker daemon  16.38kB
...
Successfully built 24fd037ce0d0
Successfully tagged sathsarasa/serverless-website:latest
Image: sathsarasa/serverless-website:latest built.
[0] < Building serverless-website done.
[0] worker done.
[0] > Pushing serverless-website [sathsarasa/serverless-website:latest].
The push refers to repository [docker.io/sathsarasa/serverless-website]
...
latest: digest: sha256:991c02fa7336113915acc60449dc1a7559585ca2fea3ca1326ecdb5fae96f2fc size: 4298
[0] < Pushing serverless-website [sathsarasa/serverless-website:latest] done.
[0] worker done.
Deploying: serverless-website.
WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.
Deployed. 202 Accepted.
URL: http://192.168.99.100:31112/function/serverless-website
  1. 通过以下 URL 验证调用主页和登录页面:

http://192.168.99.100:31112/function/serverless-website/home

主页应如下所示:

图 9.43:调用无服务器网站功能的主页

图 9.43:调用无服务器网站功能的主页

接下来,运行以下 URL:http://192.168.99.100:31112/function/serverless-website/login

登录页面应如下所示:

图 9.44:调用无服务器网站功能的登录页面

图 9.44:调用无服务器网站功能的登录页面

因此,我们已成功根据路径参数解析了 HTML。

OpenFaaS 函数可观测性

可观测性是每个生产系统的关键特性。这使我们能够观察系统的健康状况和执行的活动。一旦我们的应用程序部署并在生产环境中运行,我们需要确保它们在功能和性能方面按预期运行。任何服务停机都可能对组织产生负面影响。因此,非常重要观察重要的应用程序指标,如 CPU 使用率、内存使用率、请求计数、响应持续时间等,然后分析是否存在异常。

OpenFaaS 内置了Prometheus,可用于收集函数指标。Prometheus 包含时间序列数据库,可用于随时间存储各种指标。OpenFaaS API 网关收集与函数调用相关的指标,并将其存储在 Prometheus 中。以下表显示了 OpenFaaS API 网关公开的指标,并存储在 Prometheus 中:

图 9.45:带有描述的函数指标

图 9.45:带有描述的函数指标

我们可以使用Prometheus仪表板来可视化这些指标。

首先,我们需要暴露安装过程中创建的Prometheus部署。执行以下命令将 Prometheus 暴露为NodePort服务:

$ kubectl expose deployment prometheus -n openfaas --type=NodePort --name=prometheus-ui

这将在30,000以上的随机端口上暴露 Prometheus 部署。执行以下命令以获取Prometheus UI 的 URL:

$ MINIKUBE_IP=$(minikube ip)
$ PROMETHEUS_PORT=$(kubectl get svc prometheus-ui -n openfaas -o jsonpath="{.spec.ports[0].nodePort}")
$ PROMETHEUS_URL=http://$MINIKUBE_IP:$PROMETHEUS_PORT/graph
$ echo $PROMETHEUS_URL

输出应如下所示:

图 9.46:生成 Prometheus URL

图 9.46:生成 Prometheus URL

对我来说,PROMETHEUS_URL输出值为 http://192.168.99.100:30479/graph。但是<minikube-ip><node-port>的值可能不同。

我们可以使用 UI 查看 Prometheus 公开的指标,如下图所示:

图 9.47:Prometheus UI

图 9.47:Prometheus UI

Expression区域中键入gateway_function_invocation_total,然后单击Execute按钮。这将在Console选项卡下列出结果。如果需要在线图中查看函数调用次数,请单击Graph选项卡。如果要将此图永久添加到 Prometheus 仪表板中,请单击左下角的Add Graph按钮,如下图所示:

图 9.48:gateway_function_invocation_total 指标的 Prometheus 图

注意

多次调用可用函数,以便我们可以从 Prometheus 仪表板中查看这些调用的统计信息。

除了我们讨论过的 Prometheus 仪表板之外,我们还可以使用Grafana来可视化存储在 Prometheus 中的指标。Grafana是一个开源工具,用于分析和可视化一段时间内的指标。它可以与多个数据源集成,如PrometheusElasticSearchInflux DBMySQL。在下一个练习中,我们将学习如何使用 OpenFaaS 设置 Grafana 并创建仪表板,以监视存储在 Prometheus 数据源中的指标。

练习 32:安装 OpenFaaS Grafana 仪表板

在这个练习中,我们将安装一个 Grafana 仪表板,以查看来自Prometheus数据源的指标。然后,我们将在 Grafana 中导入另一个 OpenFaaS 仪表板:

  1. 使用stefanprodan/faas-grafana:4.6.3 Docker 镜像在openfaas命名空间中创建grafana部署:
kubectl run grafana -n openfaas \
    --image=stefanprodan/faas-grafana:4.6.3 \
    --port=3000

输出应如下所示:

图 9.49:创建 Grafana 部署

图 9.49:创建 Grafana 部署
  1. 使用NodePort服务暴露grafana部署:
kubectl expose deployment grafana -n openfaas  \
    --type=NodePort \
    --name=grafana

输出应如下所示:

图 9.50:暴露 grafana 端口

图 9.50:暴露 grafana 端口
  1. 使用以下命令找到grafana仪表板的 URL:
$ MINIKUBE_IP=$(minikube ip)
$ GRAFANA_PORT=$(kubectl get svc grafana -n openfaas -o jsonpath="{.spec.ports[0].nodePort}")
$ GRAFANA_URL=http://$MINIKUBE_IP:$GRAFANA_PORT/dashboard/db/openfaas
$ echo $GRAFANA_URL

输出应如下所示:

图 9.51:生成 grafana URL
  1. 使用上一步中打印的 URL 导航到grafana URL:
图 9.52:Grafana UI
  1. 使用默认凭据(用户名为admin,密码为admin)登录Grafana

  2. 密码是admin)。输出应如下所示:图 9.53:Grafana 仪表板

图 9.53:Grafana 仪表板

从左上角的Grafana 菜单()中,如图 9.53中所示,选择仪表板 > 导入。在Grafana.com 仪表板输入框中提供 ID 3434,然后等待几秒钟以加载仪表板数据:

图 9.54:导入新仪表板

图 9.54:导入新仪表板
  1. 从此屏幕中,选择faas作为 Prometheus 数据源,然后单击导入,如下图所示:图 9.55:导入新仪表板
图 9.55:导入新仪表板
  1. 现在您可以在新仪表板中看到指标:

图 9.56:OpenFaaS 无服务器 Grafana 仪表板

图 9.56:OpenFaaS 无服务器 Grafana 仪表板

因此,我们已成功设置了 Grafana 仪表板,以可视化存储在 Prometheus 中的指标。

OpenFaaS 函数自动缩放

自动缩放是 OpenFaaS 中可用的一个功能,它根据需求扩展或缩小函数副本。该功能是使用 OpenFaaS 框架提供的PrometheusAlert Manager组件构建的。当函数调用频率超过定义的阈值时,Alert Manager 将触发警报。

在部署函数时,使用以下标签来控制函数的最小副本数、最大副本数以及增加/减少函数的因子:

  • com.openfaas.scale.min – 这定义了初始副本数,默认为 1。

  • com.openfaas.scale.max – 这定义了最大副本数。

  • com.openfaas.scale.factor - 这定义了当警报管理器触发警报时,pod 副本增加(或减少)的百分比。默认情况下,这设置为20%,应该在0100之间。

当 OpenFaaS 部署在 Kubernetes 上时,可以使用 Kubernetes 框架的水平 Pod 自动缩放功能来根据需求自动缩放函数,作为 OpenFaaS 框架提供的内置自动缩放功能的替代方案。

现在让我们部署 OpenFaaS 函数商店中的figlet函数,以检查自动缩放功能的运行情况:

faas-cli store deploy figlet \
    --label com.openfaas.scale.min=1 \
    --label com.openfaas.scale.max=5

输出应该如下所示:

图 9.57:部署 figlet 函数

图 9.57:部署 figlet 函数

现在我们可以通过调用它 1,000 次来对figlet函数施加负载,如下面的代码所示。以下脚本将通过为函数提供OpenFaaS字符串作为输入,并在每次调用之间休眠 0.1 秒,来调用figlet函数 1,000 次:

for i in {1..1000}
do
   echo "Invocation $i"
   echo OpenFaaS | faas-cli invoke figlet
   sleep 0.1
done

转到Grafana门户,并观察figlet函数的副本数量增加。一旦负载完成,副本计数将开始缩减,并返回到com.openfaas.scale.min计数为 1 个函数副本。

输出应该如下所示:

图 9.58:验证自动缩放功能

图 9.58:验证自动缩放功能

在本节中,我们介绍了函数自动缩放,讨论了函数自动缩放是什么,以及我们可以使用的配置来设置最小副本计数、最大副本计数和缩放因子。最后,我们部署了一个示例函数,对函数进行了负载,并在 Grafana 仪表板上观察了自动缩放功能。

活动 9:OpenFaaS 表单处理器

在这个活动中,我们将为一个品牌创建一个网站,该网站将有一个联系表单,供潜在客户联系品牌人员。我们将广泛使用OpenFaas来完成这个网站。

假设你是一名自由职业者,你想创建一个网站来增加你的品牌知名度。这个网站需要有一个“联系我们”表单,让潜在客户联系你。你决定使用无服务器技术创建这个网站,选择 OpenFaaS 作为这项任务的框架。

执行以下步骤完成此活动:

  1. 创建一个 SendGrid (sendgrid.com) 账户来发送电子邮件并保存 API 密钥。

  2. 使用 HTML 创建“联系我们”表格,并使用 OpenFaaS 函数返回 HTML。以下是实现具有nameemailmessage输入字段以及submit按钮功能的 HTML 表单的示例代码;CSS 用于为 HTML 表单添加样式;以及一个 JavaScript 函数,当用户单击Submit按钮时将触发该函数,并将表单数据作为POST请求发送到form-processor函数:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>OpenFaaS Contact Us  Form</title>         
    <style>
      /** Page  background colour */
      body  {
        background-color: #f2f2f2;
      }  
      /** Style the h1  headers */
      h1 {  
        text-align: center;
        font-family: Arial;

      /** CSS for the input box and textarea */
      input[type=text], input[type=email], textarea {
        width: 100%; 
        margin-top: 10px;   
        margin-bottom: 20px; 
        padding: 12px; 
        box-sizing: border-box; 
        resize: vertical 
      } 
      /** Style the submit  button */
      input[type=submit] {
        color: white;
        background-color: #5a91e8;
        padding: 10px 20px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
      }
      /** Change submit button  color for mouse hover */
      input[type=submit]:hover  {
        background-color: #2662bf;
      } 
      /** Add padding around the form */
       container {
        padding: 20px;
        border-radius: 5px;
      }
     /** Bold font for response and add margin */
  #response { 
    font-weight: bold;
margin-bottom: 20px; 
  }
      </style>
    </head>
    <body>
      <h1>OpenFaaS Contact Form</h1>
      <div class="container">
		<!-- Placeholder for the response -->
        <div id='response'></div>  
        <form id="contact_us_form">
          <label for="name">Name:</label>
          <input type="text" id="name" name="name" required>
          <label for="email">Email:</label>
          <input type="email" id="email" name="email" required>
          <label for="message">Message:</label>
          <textarea id="message" name="message" required></textarea>
          <input type="submit" value="Send Message">
          </form>
      </div>
      <script src="http://code.jquery.com/jquery-3.4.1.min.js"></script>
      <script>     
        $(document).ready(function(){
        $('#contact_us_form').on('submit', function(e){
          // prevent form from submitting.
            e.preventDefault(); 
$('#response').html('Sending message...');
            // retrieve values from the form field
            var name = $('#name').val();
            email = $('#email').val();
            var message = $('#message').val();
            var formData = {
              name: name,
              email: email,
              message: message
            };
            // send the ajax POST request         
            $.ajax({
              type: "POST",
              url: './form-processor',
              data: JSON.stringify(formData)
            })
             done(function(data) {
              $('#response').html(data);
            })
             fail(function(data) {
              $('#response').html(data);
            });
          });
        });
        </script>  
    </body>
</html>
  1. 创建form-processor函数,该函数从联系我们表格中获取表单数值,并将信息发送到指定的电子邮件地址。

  2. 使用 Web 浏览器调用联系我们表格函数,并验证电子邮件发送。

联系表格应如下图所示:

图 9.59:联系我们表格

图 9.59:联系我们表格

从联系表格收到的电子邮件应如下截图所示:

图 9.60:从联系我们表格收到的电子邮件

图 9.60:从联系我们表格收到的电子邮件

注意

活动的解决方案可在第 444 页找到。

总结

我们从介绍 OpenFaaS 框架开始本章,并继续概述了 OpenFaaS 框架提供的组件。接下来,我们看了如何在本地的Minikube集群上安装faas-cliOpenFaaS框架。

然后,我们开始研究OpenFaaS函数。我们讨论了如何使用faas-cli创建函数模板,构建和推送函数 Docker 镜像,并将函数部署到OpenFaaS框架。然后,我们学习了如何使用faas-cli命令和curl命令调用已部署的函数。接下来,我们介绍了OpenFaaS门户,这是 OpenFaaS 框架的内置 UI。

我们还学习了如何设置OpenFaaS函数来返回 HTML 内容,并根据提供的参数返回不同的内容。我们配置了PrometheusGrafana仪表板来可视化函数指标,包括调用次数、调用持续时间和副本计数。然后,我们讨论了函数自动缩放功能,根据需求扩展或缩小函数副本。我们对函数进行了负载测试,并观察了 Grafana 仪表板上的自动缩放功能。

最后,在这个活动中,我们使用OpenFaaS框架构建了一个网站的联系我们表单的前端和后端。

通过本书中介绍的概念、各种练习和活动,我们已经为您提供了使用无服务器架构和最先进的容器管理系统 Kubernetes 所需的所有技能。

我们相信您将能够运用这些知识来构建更健壮、更有效的系统,并将它们托管在AWS LambdaGoogle Cloud Function等云提供商上。您还将能够使用OpenFaaSOpenWhiskKubeless等一流框架的高效功能。

附录

关于

这一部分是为了帮助学生在书中执行活动。

其中包括学生执行的详细步骤,以实现活动的目标。

1. 介绍无服务器

活动 1:伦敦自行车积分的 Twitter 机器人后端

解决方案:

执行以下步骤以完成此活动:

  1. 创建一个main.go文件来注册函数处理程序,就像练习 1中一样。

这段代码是应用程序的入口,其中函数被注册,并启动主应用程序:

package main
import (
   "fmt"
   "net/http"
)
func main() {
   fmt.Println("Starting the 🚲 finder..")
   http.HandleFunc("/", FindBikes)
   fmt.Println("Function handlers are registered.")
   http.ListenAndServe(":8080", nil)
}
  1. FindBikes函数创建一个function.go文件:
...
func FindBikes(w http.ResponseWriter, r *http.Request) {
   ...

   // Get bike points for the query
   bikePoints, err := httpClient.Get(fmt.Sprintf(TFL_API_URL + "BikePoint/Search?query=" + url2.QueryEscape(query)))
   ...
   // Get available number of bikes
   availableBikeResponse, err := httpClient.Get(TFL_API_URL + "BikePoint/" + bikePoint.ID)

...
         if bikeAmount == 0 {
            w.Write([]byte(fmt.Sprintf(RESPONSE_NO_AVAILABLE_BIKE, bikePoint.CommonName, url)))
            return
         } else {
            w.Write([]byte(fmt.Sprintf(DEFAULT_RESPONSE, bikePoint.CommonName, bikeAmount, url)))
            return
         }
...

注意

活动所需的文件可以在以下链接找到:github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson01/Activity1

在这个文件中,应该实现实际的函数及其辅助函数。FindBikes负责从TFL 统一 API获取自行车积分位置的数据,然后获取可用自行车的数量。根据收集到的信息,该函数返回完整的句子,用作 Twitter 的响应。

  1. 创建一个Dockerfile来构建和打包函数,就像练习 2中一样:
FROM golang:1.12.5-alpine3.9 AS builder
ADD . .
RUN go build *.go
FROM alpine:3.9
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
RUN update-ca-certificates
COPY --from=builder /go/function ./bikes
RUN chmod +x ./bikes
ENTRYPOINT ["./bikes"]

在这个Dockerfile中,应用程序在第一个容器中构建,并在第二个容器中打包以进行交付。

  1. 使用 Docker 命令构建容器镜像:docker build . -t find-bikes

它应该看起来像这样:

图 1.27:构建 Docker 镜像

图 1.27:构建 Docker 镜像
  1. 运行容器镜像作为 Docker 容器,并使端口在主机系统上可用:docker run -it --rm -p 8080:8080 find-bikes

事情应该看起来如下截图所示:

图 1.28:运行 Docker 容器

图 1.28:运行 Docker 容器
  1. 使用不同查询测试函数的 HTTP 端点,例如牛津修道院对角巷

我们期望得到伦敦街道的真实响应,以及来自文学作品的虚构街道的失败响应。

图 1.29:不同街道的函数响应

图 1.29:不同街道的函数响应
  1. 按下Ctrl + C退出容器:图 1.30:退出容器
图 1.30:退出容器

2. 云中无服务器的介绍

活动 2:Slack 的每日站立会议提醒功能

解决方案Slack 设置:

  1. Slack工作区中,单击您的用户名,然后选择自定义 Slack,如下截图所示:
图 2.49:Slack 菜单
  1. 在打开的窗口中单击配置应用程序,如下截图所示:
图 2.50:Slack 配置菜单
  1. 单击浏览应用程序目录以从目录中添加新应用程序,如下截图所示:图 2.51:Slack 管理
图 2.51:Slack 管理
  1. 应用程序目录的搜索框中找到传入 WebHooks,如下截图所示:图 2.52:应用程序目录
图 2.52:应用程序目录
  1. 单击添加配置以为传入 WebHooks应用程序添加配置,如下截图所示:图 2.53:传入 Webhooks 页面
图 2.53:传入 Webhooks 页面
  1. 填写传入 webhook 的配置,指定您的特定频道名称和图标,如下截图所示:图 2.54:传入 webhook 配置
图 2.54:传入 webhook 配置

复制Webhook URL,然后单击保存设置,如上图所示。

  1. 打开 Slack 工作区和我们在步骤 6 中提到的频道。您将看到一个集成消息:

图 2.55:Slack 中的集成消息

图 2.55:Slack 中的集成消息

活动解决方案

执行以下步骤完成此活动:

  1. 创建一个新函数,在调用函数时调用 Slack webhook。

在 GCF 中,可以使用名称StandupReminder,128 MB 内存和 HTTP 触发器来定义。

此功能可以在任何支持的语言中实现,例如Go 1.11,如下截图所示:

图 2.56:Google Cloud 平台中的云函数

图 2.56:Google Cloud 平台中的云函数

要添加的代码如下:

package p
import (
    "bytes"
    "net/http"
)
func Reminder(http.ResponseWriter, *http.Request) {
    url := "https://hooks.slack.com/services/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"

    var jsonStr = []byte(`{"text": "Time for a stand-up meeting!"}`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))

    client := &http.Client{}
    _, err = client.Do(req)
    if err != nil {
        panic(err)
    }
}

注意

不要忘记使用步骤 6 中的 Slack URL 更改url值。

您可以在本书 GitHub 存储库的活动解决方案中找到完整的function.go文件:github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson02/Activity2/function.go.

  1. 创建一个调度程序作业,使用函数的触发 URL,并根据你的站立会议时间指定计划。

可以在 Google Cloud Scheduler 中定义调度程序的名称为StartupReminder,并且函数的 URL,如下面的截图所示:

图 2.57:Google 云平台中的云调度程序

图 2.57:Google 云平台中的云调度程序

使用0 9 * * 1-5的计划,提醒将在每周一至周五的 09:00 调用函数。

  1. 在提醒消息的计划时间到达时,检查 Slack 频道。

对于0 9 * * 1-5的计划,您将在工作日的 09:00 在您选择的 Slack 频道上看到一条消息,如下面的截图所示:

图 2.58:Slack 提醒消息

图 2.58:Slack 提醒消息
  1. 从云提供商中删除调度作业和函数,如下面的截图所示:图 2.59:删除调度程序
图 2.59:调度程序的删除

可以这样删除函数:

图 2.60:删除函数

图 2.60:删除函数

在这个活动中,我们使用函数构建了 Slack 应用程序的后端。我们首先为传入的 webhook 配置了 Slack,然后创建了一个发送数据到 webhook 的函数。由于我们的函数应该在预定义的时间被调用,我们使用了云调度程序服务来调用函数。通过在 Slack 中成功发送提醒消息,展示了将函数集成到其他云服务和外部服务中。

3. 无服务器框架简介

活动 3:Slack 的每日天气状态功能

解决方案- Slack 设置

  1. 执行以下步骤来配置 Slack:

  2. 在您的 Slack 工作区中,点击您的用户名,然后选择自定义 Slack:图 3.44:Slack 菜单

图 3.44:Slack 菜单
  1. 点击打开窗口中的配置应用程序:图 3.45:Slack 配置菜单
图 3.45:Slack 配置菜单
  1. 单击“浏览应用程序目录”以从目录中添加新应用程序:图 3.46:Slack 管理
图 3.46:Slack 管理
  1. 在应用目录的搜索框中找到传入的 WebHooks:图 3.47:应用目录
图 3.47:应用目录
  1. 单击“设置”传入的 WebHooks 应用程序:图 3.48:传入 WebHooks 页面
图 3.48:传入 WebHooks 页面
  1. 选择一个频道发布笑话消息,并单击“添加传入 WebHooks 集成”:图 3.49:频道选择
图 3.49:频道选择
  1. 使用您特定的频道名称和图标填写传入 WebHook 的配置:图 3.50:传入 WebHook 配置
图 3.50:传入 WebHook 配置

复制 Webhook URL 并单击保存设置。

  1. 打开您的 Slack 工作区和您在第 6 步中配置的频道,以检查集成消息:图 3.51:Slack 中的集成消息
图 3.51:Slack 中的集成消息

活动解决方案

  1. 执行以下步骤完成此活动:

  2. 在您的终端中,启动 Serverless Framework 开发环境:

docker run -it --entrypoint=bash onuryilmaz/serverless

此命令将以交互模式启动 Docker 容器。在接下来的步骤中,将在此 Docker 容器内执行操作:

图 3.52:启动服务器无容器的 Docker 容器

图 3.52:启动服务器无容器的 Docker 容器
  1. 在您的终端中,在名为 daily-weather 的文件夹中创建一个 Serverless Framework 应用程序结构。

创建一个名为 daily-joker 的文件夹,并将其更改为以下目录:

mkdir daily-weather
cd daily-weather

注意

nano 和 vim 作为文本编辑器安装在 Serverless Framework 开发环境 Docker 容器中。

  1. 创建一个 serverless.yaml 文件,并用以下内容替换 SLACK_WEBHOOK_URL 的值,该值是从 Slack 设置的第 6 步中复制的 URL。此外,更新 CITY 环境变量为当前办公地点,以获取正确的天气信息。此外,您可以更改计划部分,该部分当前在工作日的 08:00 触发函数:
service: daily-weather
provider:
  name: aws
  runtime: nodejs8.10
functions:
  weather:
    handler: handler.weather
    events:
      - schedule: cron(0 8 ? * 1-5 *)
    environment:
      CITY: Berlin
      SLACK_WEBHOOK_URL: https://hooks.slack.com/services/.../.../...

注意

serverless.yaml 可在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson03/Activity3/serverless.yaml找到。

  1. 在 daily-weather 文件夹中创建一个 package.json 文件来定义 Node.js 环境。

package.json 定义了函数及其依赖关系:

{
  "name": "daily-weather",
  "description": "",
  "main": "handler.js",
    "dependencies": {
    "node-fetch": "².2.1",
    "slack-node": "0.1.8"
  }
}

注意

package.json 可在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson03/Activity3/package.json找到。

  1. 在 daily-weather 文件夹中创建一个 handler.js 文件来实现实际功能。

handler.js 包括实际的 Node.js 函数:

const fetch = require('node-fetch');
const Slack = require('slack-node');
module.exports.weather = (event, context, callback) => {
    const webhookUri = process.env.SLACK_WEBHOOK_URL;
    const location = process.env.CITY;
    const slack = new Slack();
    slack.setWebhook(webhookUri);
    weatherURL = "http://wttr.in/" + encodeURIComponent(location) + "?m&&format=1"
    console.log(weatherURL)
    fetch(weatherURL)
        .then(response => response.text())
        .then(data => {
            console.log("======== WEATHER TEXT ========")
            console.error(data);
            console.log("======== WEATHER TEXT ========")
            slack.webhook({
                text: "Current weather status is " + data
            }, function(err, response) {
                console.log("======== SLACK SEND STATUS ========")
                console.error(response.status);
                return callback(null, {statusCode: 200, body: "ok" });
                console.log("======== SLACK SEND STATUS ========")
                if (err) {
                    console.log("======== ERROR ========")
                    console.error(error);
                    console.log("======== ERROR ========")
                    return callback(null, {statusCode: 500, body: JSON.stringify({ error}) });
                }
            });
        }).catch((error) => {
            console.log("======== ERROR ========")
            console.error(error);
            console.log("======== ERROR ========")
             return callback(null, {statusCode: 500, body: JSON.stringify({ error}) });
        });
};

注意

handler.js 可在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson03/Activity3/handler.js找到。

  1. 在文件创建结束时,您将看到以下文件结构,包括三个文件:
ls -l

输出应该如下所示:

图 3.53:文件夹结构

图 3.53:文件夹结构
  1. 安装 serverless 应用程序所需的 Node.js 依赖项。运行以下命令来安装依赖项:
npm install -i

输出应该如下所示:

图 3.54:依赖安装

图 3.54:依赖安装
  1. 将 AWS 凭据导出为环境变量。从练*** 中导出以下环境变量和 AWS 凭据:
export AWS_ACCESS_KEY_ID=AKIASVTPHRZR33BS256U
export AWS_SECRET_ACCESS_KEY=B***************************R

输出应该如下所示:

图 3.55:AWS 凭证

图 3.55:AWS 凭证
  1. 使用 Serverless Framework 将 serverless 应用程序部署到 AWS。运行以下命令来部署函数:
serverless deploy 

这些命令将使 Serverless Framework 将函数部署到 AWS。输出日志从打包服务和为源代码、工件和函数创建 AWS 资源开始。创建完所有资源后,服务信息部分提供了完整堆栈的摘要,如下图所示:

图 3.56:Serverless Framework 部署输出

图 3.56:Serverless Framework 部署输出
  1. 在 AWS 控制台中检查已部署函数的 AWS Lambda,如下图所示:图 3.57:AWS 控制台中的 AWS Lambda
图 3.57:AWS 控制台中的 AWS Lambda
  1. 使用 Serverless Framework 的客户端工具调用该函数。在您的终端中运行以下命令:
serverless invoke --function weather

此命令调用部署的函数并打印出响应,如下图所示:

图 3.58:函数输出

图 3.58:函数输出

正如我们所看到的,statusCode 为 200,响应的主体也表明函数已成功响应。

  1. 检查 Slack 频道发布的天气状态:图 3.59:带有天气状态的 Slack 消息
图 3.59:带有天气状态的 Slack 消息
  1. 返回到您的终端,并使用 Serverless Framework 删除该函数。在您的终端中运行以下命令:
serverless remove

此命令将删除部署的函数及其所有依赖项:

图 3.60:移除函数

图 3.60:移除函数
  1. 退出 Serverless Framework 开发环境容器。在您的终端中运行 exit:

](image/C12607_03_61.jpg)

图 3.61:退出容器

在这个活动中,我们使用了一个无服务器框架构建了 Slack 应用的后端。我们首先配置了 Slack 以接收传入的 webhooks,然后创建了一个无服务器应用程序来向 webhook 发送数据。为了在预定的时间调用函数,利用了无服务器框架的配置,而不是特定于云的调度程序。由于无服务器框架为云提供商创建了一个抽象,我们在这个活动中开发的无服务器应用程序适用于多云部署。

4. Kubernetes 深入探讨

活动 4:在 Kubernetes 中将金价收集到 MySQL 数据库中

解决方案:

执行以下步骤以完成此活动:

  1. 创建一个应用程序,从CurrencyLayer中检索金价并将其插入到 MySQL 数据库中。

可以使用以下 main.go 文件在 Go 中实现此函数:

...
func main() {
    db, err := sql.Open("mysql",  ...
    ...
    r, err := http.Get(fmt.Sprintf(„http://apilayer.net/api/...
   ...
    stmt, err := db.Prepare("INSERT INTO GoldPrices(price) VALUES(?)")
    ...
    _, err = stmt.Exec(target.Quotes.USDXAU)
    ...
    log.Printf("Successfully inserted the price: %v", target.Quotes.USDXAU)
    ...
}

主要函数从数据库连接开始,然后从CurrencyLayer中检索价格。然后继续创建 SQL 语句并在数据库连接上执行。

注意

main.go 可在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson04/Activity4/main.go找到。

  1. 将应用程序构建为 Docker 容器。可以使用以下 Dockerfile 从第 1 步构建应用程序:
FROM golang:1.12.5-alpine3.9 AS builder
RUN apk add --no-cache git
ADD main.go /go/src/gold-price-to-mysql/main.go
WORKDIR /go/src/gold-price-to-mysql/
RUN go get -v
RUN go build .
FROM alpine:3.9
COPY --from=builder /go/src/gold-price-to-mysql/gold-price-to-mysql ./gold-price-to-mysql
RUN chmod +x ./gold-price-to-mysql
ENTRYPOINT ["./gold-price-to-mysql"]

注意

Dockerfile 可在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson04/Activity4/Dockerfile找到。

  1. 使用以下命令在终端中运行:
docker build -t <USERNAME>/gold-price-to-mysql .

该命令将应用程序构建为 Docker 容器,如下图所示:

图 4.26:Docker 构建

图 4.26:Docker 构建

注意

不要忘记将<USERNAME>更改为您的 Docker Hub 用户名。

  1. 将 Docker 容器推送到 Docker 注册表。在终端中运行以下命令:
docker push <USERNAME>/gold-price-to-mysql

该命令将上传容器映像到 Docker Hub,如下图所示:

图 4.27:Docker 推送

图 4.27:Docker 推送

注意

不要忘记将<USERNAME>更改为您的 Docker Hub 用户名。

  1. 将 MySQL 数据库部署到 Kubernetes 集群。创建一个 mysql.yaml 文件,其中包含 MySQL StatefulSet 的定义:
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "root"
        - name: MYSQL_DATABASE
          value: "db"
        - name: MYSQL_USER
          value: "user"
        - name: MYSQL_PASSWORD
          value: "password"
        ports:
        - name: mysql
          containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

注意

mysql.yaml 可在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson04/Activity4/mysql.yaml找到。

  1. 在终端中使用以下命令部署 StatefulSet:
kubectl apply -f mysql.yaml

该命令提交文件到 Kubernetes 并创建 mysql StatefulSet,如下图所示:

图 4.28:StatefulSet 创建

图 4.28:StatefulSet 创建
  1. 部署 Kubernetes 服务以公开 MySQL 数据库。创建一个 service.yaml 文件,其中包含以下 Kubernetes 服务定义:
apiVersion: v1
kind: Service
metadata:
  name: gold-price-db
spec:
  selector:
    app: mysql
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306

注意

service.yaml 可在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson04/Activity4/service.yaml找到。

  1. 使用以下命令在终端中部署服务:
kubectl apply -f service.yaml

此命令将文件提交到 Kubernetes 并创建 gold-price-db 服务,如下图所示:

图 4.29:服务创建

图 4.29:服务创建
  1. 部署一个每分钟运行一次的 CronJob。创建一个名为 insert-gold-price.yaml 的文件,其中包含以下 Kubernetes CronJob 定义:
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: gold-price-to-mysql
spec:
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: insert
            image: <USERNAME>/gold-price-to-mysql
            env:
            - name: MYSQL_ADDRESS
              value: "gold-price-db:3306"
            - name: MYSQL_DATABASE
              value: "db"
            - name: MYSQL_USER
              value: "user"
            - name: MYSQL_PASSWORD
              value: "password"
            - name: API_KEY
              value: "<API-KEY>"

注意

insert-gold-price.yaml 可在github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson04/Activity4/insert-gold-price.yaml找到。

不要忘记将<USERNAME>更改为您的 Docker Hub 用户名,将<API-KEY>更改为您的 CurrencyLayer API 密钥。

  1. 在您的终端中使用以下命令部署 CronJob:
kubectl apply -f insert-gold-price.yaml

此命令将文件提交到 Kubernetes 并创建 gold-price-to-mysql CronJob,如下图所示:

图 4.30:CronJob 创建

图 4.30:CronJob 创建
  1. 等待几分钟并检查 CronJob 的实例。在您的终端中使用以下命令检查运行中的 pod:
kubectl get pods

此命令列出了 pod,并且您应该看到一些实例,其名称以 gold-price-to-mysql 开头,并且状态为已完成,如下图所示:

图 4.31:Pod 列表

图 4.31:Pod 列表
  1. 连接到数据库并检查条目:
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never \
-- mysql -h gold-price-db -u user -ppassword  db -e "SELECT * FROM GoldPrices;"

此命令运行 mysql:5.7 镜像的临时实例,并运行 SELECT * FROM GoldPrices 命令,如下图所示:

图 4.32:表列表

图 4.32:表列表

在 GoldPrices MySQL 表中,每分钟收集一次价格数据。它显示 MySQL StatefulSet 正在成功运行数据库。此外,CronJob 每分钟创建一次 pod 并成功运行。

  1. 从 Kubernetes 中清除数据库和自动化任务。在您的终端中使用以下命令清除资源:
kubectl delete -f insert-gold-price.yaml,service.yaml,mysql.yaml

您应该看到以下图中显示的输出:

图 4.33:资源删除

图 4.33:资源删除

在这个活动中,我们在 Kubernetes 中创建了一个 MySQL 数据库作为 StatefulSet。Kubernetes 已经创建了所需的卷资源并附加到 MySQL 容器上。接着,我们创建并打包了我们的无服务器函数。该函数被部署到 Kubernetes 集群作为 CronJob。Kubernetes 确保该函数每分钟都被调度和运行。在 Kubernetes 中运行函数提供了两个重要的优势。第一个是重用 Kubernetes 集群和资源。换句话说,我们不需要额外的云资源来运行我们的无服务器工作负载。第二个优势是与数据的接近。由于我们的微服务已经在 Kubernetes 上运行,建议将我们的数据库放在 Kubernetes 中。当无服务器应用程序也在同一集群中运行时,更容易操作、管理和排除故障应用程序。

5.生产就绪的 Kubernetes 集群

活动 5:在 GKE 集群中最小化无服务器函数的成本

解决方案

  1. 创建一个具有可抢占服务器的新节点池。

在 GCP 云 shell 中运行以下和即将到来的函数:

gcloud beta container node-pools create preemptible --preemptible \
--min-nodes 1 --max-nodes 10  --enable-autoscaling  \
--cluster serverless --zone us-central1-a 

注意

如果您的集群在另一个区域运行,请更改zone参数。

此函数创建一个名为preemptible的新节点池,自动缩放的最小节点数为 1 个,最大节点数为 10 个,如下图所示:

图 5.29:节点池创建

图 5.29:节点池创建
  1. 给可抢占服务器施加污点,只能运行无服务器函数:
kubectl taint node -l cloud.google.com/gke-nodepool=preemptible   \
preemptible="true":NoSchedule

此命令将对所有具有标签cloud.google.com/node-pool = preemptible的节点应用污点。污点键将是preemptible,值为true。此限制的操作是NoSchedule,这意味着只有具有匹配容忍性的 pod 才会被调度到这些节点上,如下图所示:

图 5.30:给节点施加污点

图 5.30:给节点施加污点
  1. 创建一个 Kubernetes 服务以访问后端 pod:
kubectl expose deployment backend --port 80 --target-port=80

此命令在端口80上为部署后端创建了一个服务,如下图所示:

图 5.31:暴露部署

图 5.31:暴露部署
  1. 创建一个CronJob,每分钟连接到后端服务。CronJob 定义应该具有容忍性,以在可抢占服务器上运行。

在名为cronjob.yaml的文件中创建一个包含以下内容的CronJob定义:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: backend-checker
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: checker
            image: appropriate/curl
            args:
            - curl
            - -I
            - backend
          nodeSelector:
            cloud.google.com/gke-nodepool: "preemptible"
          tolerations:
          - key: preemptible
            operator: Equal
            value: "true"
            effect: NoSchedule
          restartPolicy: OnFailure

该文件包含了每分钟运行 curl -I backend 函数的 CronJob 定义。nodeSelector 表示调度器将选择在具有标签键 cloud.google.com/gke-nodepool 和值 preemptible 的节点上运行。然而,由于可抢占节点上有污点,因此还添加了容忍。

注意

cronjob.yaml 可在 GitHub 上找到:github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/blob/master/Lesson05/Activity5/cronjob.yaml

  1. 使用以下命令部署 CronJob:
kubectl apply -f cronjob.yaml

输出应如下所示:

图 5.32:CronJob 创建

图 5.32:CronJob 创建
  1. 检查 CronJob 函数的节点分配:
kubectl get pods -o wide

此命令列出了带有相应节点的 pod。如预期的那样,在 high-memory 节点上运行了确切的 10 个后端实例。此外,如下图所示,在 preemptible 节点上运行了 3 个 CronJob 函数实例:

图 5.33:Pod 列表

图 5.33:Pod 列表
  1. 检查 CronJob 函数实例的日志:
kubectl logs brand-checker-<ID> 

注意

步骤 5 中的 pod 名称替换 <ID>

函数的输出显示了 curl 连接到 nginx 实例的轨迹,如下图所示:

图 5.34:curl 输出

图 5.34:curl 输出
  1. 清理后端部署和无服务器函数:
kubectl delete deployment/backend cronjob/backend-checker

此命令将删除 backend 部署和 backend-checker CronJob,如下图所示:

图 5.35:清理

图 5.35:清理
  1. 如果不再需要,删除 Kubernetes 集群:
gcloud container clusters delete serverless --zone us-central1-a 

注意

如果您的集群在另一个区域运行,请在命令中更改 zone 参数。

此命令将从 GKE 中删除集群,如下图所示:

图 5.36:集群移除

图 5.36:集群移除

在此活动中,我们对生产集群进行了管理任务。在 Kubernetes 集群中创建不同类型的节点并运行异构节点集有助于降低整个集群的成本。此外,启用了自动缩放以满足用户需求,无需人工干预。

自动扩展和应用程序迁移是生产集群上最常见的运维任务。这些任务可以在最小的停机时间和成本下实现更好的性能。然而,用于生产环境的 Kubernetes 平台还应满足您日常运营的要求。Kubernetes 和云提供商的能力对于安装、监视和操作在云中运行的应用程序至关重要。

6. Kubernetes 中即将推出的无服务器功能

活动 6:在无服务器环境中部署容器化应用

解决方案

  1. 首先,创建一个新目录来存储此活动的文件,并切换到新创建的目录:
$ mkdir chapter-06-activity
$ cd chapter-06-activity
  1. 创建一个可以返回给定时区的当前日期和时间的应用程序。我们将使用 PHP 来编写这个函数,但您可以选择任何您熟悉的语言。创建一个名为 index.php 的文件,其中包含第 1 步中给出的内容。

现在我们需要根据 Google Cloud Run 的容器运行时合同(cloud.google.com/run/docs/reference/container-contract)创建 Docker 镜像。创建一个名为 Dockerfile 的新文件,其中包含第 2 步中的内容。

  1. 一旦 Dockerfile 准备好,我们就可以构建 Docker 镜像。用你的 GCP 项目的 ID 替换<your-gcp-project-name>。接下来,使用 docker build 命令构建 Docker 镜像。--tag标志用于按照[HOSTNAME]/[GCP-PROJECT-ID]/[IMAGE-NAME]:[TAG]格式标记 Docker 镜像,因为我们将在下一步将其推送到Google 容器注册表(GCR)
$ export GCP_PROJECT=<your-gcp-project-name>
$ docker build . --tag gcr.io/${GCP_PROJECT}/clock:v1.0

输出应该如下所示:

图 6.57:构建 Docker 镜像

图 6.57:构建 Docker 镜像
  1. 接下来,我们可以将 docker 镜像推送到 GCR:
$ docker push gcr.io/${GCP_PROJECT}/clock:v1.0

输出应该如下所示:

图 6.58:推送 Docker 镜像

图 6.58:推送 Docker 镜像
  1. 现在我们已经创建并推送了一个 Docker 镜像到注册表。现在转到 GCP 控制台,打开 Cloud Run 页面。单击创建服务按钮,使用以下信息创建一个新服务:

容器镜像 URL:gcr.io/<your-gcp-project-id>/clock:v1.0

部署平台:Cloud Run(完全托管)

位置:从可用选项中选择任何您喜欢的区域

服务名称:clock

认证:允许未经身份验证的调用

页面将如下所示:

图 6.59:创建服务

图 6.59:创建服务
  1. 单击创建按钮,您将被导航到服务详细信息页面:图 6.60:服务详细信息
图 6.60:服务详细信息
  1. 从服务详细信息页面打开提供的 URL。对我来说,这个 URL 是https://clock-awsve2jaoa-uc.a.run.app/,但您的 URL 将会不同:图 6.61:时区错误
图 6.61:时区错误
  1. 由于我们没有提供时区参数,所以我们收到了这个错误。

  2. 让我们再次使用带有时区参数的 URL 进行调用,https://clock-awsve2jaoa-uc.a.run.app/?timezone=Europe/London图 6.62:带时区的输出

图 6.62:带时区的输出

在这个活动中,我们已成功在 Google Cloud Run 上部署了一个容器化应用程序,可以根据提供的时区值输出当前日期和时间。

7. 使用 Kubeless 的 Kubernetes 无服务器

活动 7:使用 Kubeless 将消息发布到 Slack

解决方案- Slack 设置

  1. 访问 https://slack.com/create 以创建一个工作区。输入您的电子邮件地址,然后单击“创建”:图 7.77:创建新工作区
图 7.77:创建新工作区
  1. 现在,您将收到一个六位数的确认码,发送到您在上一页输入的电子邮件中。在下一页上输入收到的代码:图 7.78:检查您的电子邮件
图 7.78:检查您的电子邮件
  1. 在这里添加一个合适的名称。这将是您的工作区名称:图 7.79:添加工作区名称
图 7.79:添加工作区名称
  1. 在这里添加一个合适的名称。这将是您的 Slack 频道名称:图 7.80:添加 Slack 频道名称
图 7.80:添加 Slack 频道名称

如果您愿意,可以跳过以下部分:

图 7.81:填写更多细节或选择跳过

图 7.81:填写更多细节或选择跳过
  1. 现在您的 Slack 频道已准备就绪。单击在 Slack 中查看您的频道,如下面的屏幕截图所示:图 7.82:查看新的 Slack 频道
图 7.82:查看新的 Slack 频道

一旦点击,我们应该看到我们的频道如下:

图 7.83:您的新 Slack 频道

图 7.83:您的新 Slack 频道
  1. 现在我们要向 Slack 添加一个传入的 Webhook 应用程序。从左侧菜单中,在应用程序部分下选择添加应用程序:图 7.84:在应用程序部分下添加应用程序
图 7.84:在应用程序部分下添加应用程序
  1. 在搜索栏中输入传入的 Webhooks,然后点击安装传入的 Webhook 应用程序:图 7.85:浏览应用程序
图 7.85:浏览应用程序
  1. 点击添加配置图 7.86:添加配置
图 7.86:添加配置
  1. 点击添加传入的 WebHooks 集成图 7.87:添加传入的 webhooks
图 7.87:添加传入的 webhooks
  1. 保存 webhook URL。在编写 Kubeless 函数时,我们将需要这个。

  2. 现在,让我们创建函数并部署它。首先,我们需要创建 requirements.txt 文件,该文件指定了我们需要为函数的运行时安装的依赖项。这些是我们需要成功运行函数的额外模块。我们将使用 requests 包向 Slack webhook 端点发送 HTTP POST 请求:

Requests==2.22.0

活动解决方案

  1. 按照以下步骤创建函数。
import json
import requests
def main(event, context):
    webhook_url = 'YOUR_INCOMMING_WEBHOOK_URL'
    response = requests.post(
        webhook_url, data=json.dumps(event['data']),
        headers={'Content-Type': 'application/json'}
    )
    if response.status_code == 200:
        return "Your message successfully sent to Slack"
    else:
        return "Error while sending your message to Slack: " + response.get('error')
  1. 部署函数:
$ kubeless function deploy slack --runtime python3.6 \
                                      --from-file slack.py \
                                      --handler slack.main \
                                      --dependencies requirements.txt

部署函数将产生以下输出:

图 7.88:部署函数

图 7.88:部署函数

在部署 slack 函数时,我们将传递我们在上一步中创建的 requirements.txt 文件作为依赖项。这将确保 Kubeless 运行时包含函数执行所需的 Python 包。

  1. 调用 kubeless 函数:
$ kubeless function call slack --data '{"username": "kubeless-bot", "text": "Welcome to Serverless Architectures with Kubeless !!!"}'

这将产生以下输出:

图 7.89:调用函数

图 7.89:调用函数
  1. 转到您的 Slack 工作区,并验证消息是否成功发布到 Slack 频道:图 7.90:验证消息是否成功发布
图 7.90:验证消息是否成功发布

在这个活动中,我们创建了一个 Slack 空间并创建了一个传入的 webhook。接下来,我们创建并部署了一个 Kubeless 函数,可以向 Slack 频道发布消息。

8. Apache OpenWhisk 简介

活动 8:通过电子邮件接收每日天气更新

创建 OpenWeather 和 SendGrid 帐户的步骤:

  1. home.openweathermap.org/users/sign_up创建一个OpenWeather账户:图 8.72:创建 OpenWeather 账户
图 8.72:创建 OpenWeather 账户
  1. 一旦您注册到OpenWeather,API 密钥将自动生成。转到API 密钥选项卡(home.openweathermap.org/api_keys)并保存 API 密钥,因为这个密钥是从 OpenWeather API 获取数据所需的。图 8.73:OpenWeather API 密钥
图 8.73:OpenWeather API 密钥
  1. 在 Web 浏览器中使用https://api.openweathermap.org/data/2.5/weather?q=London&appid=<YOUR-API-KEY>来测试OpenWeather API。请注意,您需要用步骤 2 中的 API 密钥替换<YOUR-API-KEY>

注意

可能需要几分钟来激活您的 API 密钥。如果收到无效的 API 密钥,请等待几分钟后重试。请参阅openweathermap.org/faq#error401获取更多信息。在调用 URL 时出现错误。

图 8.74:调用 OpenWeather API

图 8.74:调用 OpenWeather API
  1. signup.sendgrid.com/创建一个SendGrid账户。

应该如下所示:

图 8.75:创建 SendGrid 账户

图 8.75:创建 SendGrid 账户
  1. 转到设置 > API 密钥,然后点击创建 API 密钥按钮:图 8.76:SendGrid 中的 API 密钥页面
图 8.76:SendGrid 中的 API 密钥页面
  1. API 密钥名称字段中提供一个名称,选择完全访问单选按钮,然后点击创建和查看按钮以创建具有完全访问权限的 API 密钥:图 8.77:在 SendGrid 中生成 API 密钥
图 8.77:在 SendGrid 中生成 API 密钥
  1. 一旦生成密钥,请复制 API 密钥并将其保存在安全的地方,因为您只能看到这个密钥一次:图 8.78:在 SendGrid 中生成的 API 密钥
图 8.78:在 SendGrid 中生成的 API 密钥

活动解决方案

  1. 使用步骤 3中提供的函数代码创建get-weather.js函数。将<OPEN_WEATHER_API_KEY>替换为步骤 1中保存的 API 密钥。

  2. 创建名为getWeather的操作,其中包含在前一步中创建的get-weather.js函数,并将cityName参数的默认值设置为London

$ wsk action create getWeather get-weather.js --param cityName London

输出应如下所示:

图 8.79:创建 getWeather 操作

图 8.79:创建 getWeather 操作
  1. 通过调用操作验证操作是否按预期工作:
$ wsk action invoke getWeather --result

图 8.80:调用 getWeather 操作

图 8.80:调用 getWeather 操作
  1. 现在我们可以创建发送电子邮件的操作(我们将使用与 SendGrid 生成的 API 密钥)。我们将为此函数使用sendgrid模块。首先,我们需要创建一个目录来存储函数代码和依赖项:
$ mkdir send-email
$ cd send-email

输出应如下所示:

图 8.81:创建 send-mail 目录

图 8.81:创建 send-mail 目录
  1. 运行npm init命令,接受默认参数:
$ npm init

输出应如下所示:

图 8.82:npm init

图 8.82:npm init
  1. 安装sendgrid npm包,这是函数所需的:
$ npm install sendgrid -save

输出应如下所示:

图 8.83:添加 sendgrid 依赖包

图 8.83:添加 sendgrid 依赖包
  1. 使用步骤 4中提供的函数代码创建index.js文件。将<SEND_GRID_API_KEY>替换为创建 SendGrid 帐户时保存的密钥。类似地,将<TO_EMAIL>替换为接收天气数据的电子邮件地址,将<FROM_EMAIL>替换为发送天气数据的电子邮件地址。

  2. 压缩所有依赖项的代码:

$ zip -r send-email.zip *
  1. 现在我们可以使用send-email.zip创建名为sendEmail的操作:
$ wsk action create sendEmail send-email.zip --kind nodejs:default

输出应如下所示:

图 8.84:创建 sendEmail 操作

图 8.84:创建 sendEmail 操作
  1. 验证sendEmail操作是否按预期工作:

注意

请确保检查您的垃圾邮件文件夹,因为电子邮件客户端可能已将其归类为垃圾邮件。

$ wsk action invoke sendEmail --param message "Test Message" –result

输出应如下所示:

图 8.85:调用 sendEmail 操作

图 8.85:调用 sendEmail 操作
  1. 使用步骤 5中提供的函数代码创建format-weather-data.js函数。

  2. 创建名为formatWeatherData的操作,其中包含在前一步中创建的format-weather-data.js函数:

$ wsk action create formatWeatherData format-weather-data.js

输出应如下所示:

图 8.86:创建 formatWeatherData 操作

图 8.86:创建 formatWeatherData 动作
  1. 通过组合getWeatherformatWeatherDatasendEmail动作创建一个名为weatherMailSender的序列:
$ wsk action create weatherMailSender --sequence getWeather,formatWeatherData,sendEmail

输出应如下所示:

图 8.87:创建 weatherMailSender 动作序列

图 8.87:创建 weatherMailSender 动作序列
  1. 调用weatherMailSender序列:
$ wsk action invoke weatherMailSender --result

输出应如下所示:

图 8.88:调用 weatherMailSender 动作序列

图 8.88:调用 weatherMailSender 动作序列
  1. 检查您添加为<TO_EMAIL>的邮件帐户(检查垃圾邮件文件夹)。在app.sendgrid.com/email_activity上检查电子邮件传递的状态。

输出应如下所示:

图 8.89:从 weatherMailSender 动作序列接收的电子邮件

图 8.89:从 weatherMailSender 动作序列接收的电子邮件
  1. 最后,我们需要创建触发器和规则,以便每天上午 8 点调用该序列。首先,我们将创建weatherMailSenderCronTrigger,它将在每天上午 8:00 触发:
$ wsk trigger create weatherMailSenderCronTrigger \
                             --feed /whisk.system/alarms/alarm \
                             --param cron "0 8 * * *" 
ok: invoked /whisk.system/alarms/alarm with id cf1af9989a7a46a29af9989a7ad6a28c
{
    "activationId": "cf1af9989a7a46a29af9989a7ad6a28c",
    "annotations": [
        {
            "key": "path",
            "value": "whisk.system/alarms/alarm"
        },
        {
            "key": "waitTime",
            "value": 66
        },
        {
            "key": "kind",
            "value": "nodejs:10"
        },
        {
            "key": "timeout",
            "value": false
        },
        {
            "key": "limits",
            "value": {
                "concurrency": 1,
                "logs": 10,
                "memory": 256,
                "timeout": 60000
            }
        }
    ],
    "duration": 162,
    "end": 1565457634929,
    "logs": [],
    "name": "alarm",
    "namespace": "sathsara89@gmail.com_dev",
    "publish": false,
    "response": {
        "result": {
            "status": "success"
        },
        "status": "success",
        "success": true
    },
    "start": 1565457634767,
    "subject": "sathsara89@gmail.com",
    "version": "0.0.152"
}
ok: created trigger weatherMailSenderCronTrigger
  1. 然后,我们将创建一个名为weatherMailSenderCronRule的规则,以连接触发器(weatherMailSenderCronTrigger)和动作(weatherMailSender):
$ wsk rule create weatherMailSenderCronRule weatherMailSenderCronTrigger weatherMailSender

输出应如下所示:

图 8.90:创建 weatherMailSenderCronRule

图 8.90:创建 weatherMailSenderCronRule

完成上述步骤后,您应该每天在上午 8:00 收到发送到指定电子邮件地址的有关所请求城市天气数据的电子邮件。

9. 使用 OpenFaaS 进行无服务器化

活动 9:OpenFaaS 表单处理器

解决方案

  1. 首先,您需要创建一个 SendGrid 账户并生成一个 API 密钥。您可以使用在第八章,介绍 Apache OpenWhisk中创建的相同 API 密钥。请参考第八章,介绍 Apache OpenWhisk中关于如何创建 SendGrid 账户和生成 API 密钥的步骤 4-7 的活动。

  2. 使用 python3 模板创建一个名为 contact-form 的 OpenFaaS 函数。这将是联系表单的前端:

$ faas-cli new contact-form --lang=python3

输出应如下所示:

图 9.59:创建 contact-form 函数

图 9.59:创建 contact-form 函数
  1. 在 contact-form 目录内创建一个名为 html 的新目录以存储 HTML 文件:
$ mkdir contact-form/html

输出应如下所示:

图 9.60:创建 HTML 文件夹

图 9.60:创建 HTML 文件夹
  1. 在 contact-form/html 文件夹中创建 contact-us.html 文件,并使用步骤 2 中提供的代码填写表单。

  2. 更新 contact-form 文件夹中的handler.py Python 文件。这个 Python 函数将读取contact-us.html文件的内容,并将其作为函数响应返回:

import os

def handle(req):
    current_directory = os.path.dirname(__file__)
    html_file_path = os.path.join(current_directory, 'html', 'contact-us.html')
    with(open(html_file_path, 'r')) as html_file:
        html = html_file.read()

    return html
  1. 更新函数定义(contact-form.yml)文件,将 content_type 指定为text/html,如下面的代码所示:
version: 1.0
provider:
  name: openfaas
  gateway: http://192.168.99.100:31112
functions:
  contact-form:
    lang: python3
    handler: ./contact-form
    image: sathsarasa/contact-form:latest
    environment:
      content_type: text/html
  1. 构建、推送和部署 contact-form 函数:
$  faas-cli up -f contact-form.yml

命令的输出应该如下所示:

[0] > Building contact-form.
Clearing temporary build folder: ./build/contact-form/
Preparing ./contact-form/ ./build/contact-form//function
Building: sathsarasa/contact-form:latest with python3 template. Please wait..
Sending build context to Docker daemon  14.34kB
...
Successfully built 6c008c91f0bb
Successfully tagged sathsarasa/contact-form:latest
Image: sathsarasa/contact-form:latest built.
[0] < Building contact-form done.
[0] worker done.
[0] > Pushing contact-form [sathsarasa/contact-form:latest].
The push refers to repository [docker.io/sathsarasa/contact-form]
...
latest: digest: sha256:b4f0a4f474af0755b53acb6a1c0ce26e0f91a9a893bb8bfc78501cab267d823e size: 4282
[0] < Pushing contact-form [sathsarasa/contact-form:latest] done.
[0] worker done.
Deploying: contact-form.
WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.
Deployed. 202 Accepted.
URL: http://192.168.99.100:31112/function/contact-form
  1. 使用 python3 模板创建名为 form-processor 的第二个 OpenFaaS 函数。这将是联系表单的后端:
$ faas-cli new form-processor --lang=python3

输出应该如下所示:

图 9.61:创建 form-processor 函数

图 9.61:创建 form-processor 函数
  1. 更新 form-processor 文件夹中的handler.py Python 文件。这个 Python 函数执行接收输入到“联系我们”表单中的电子邮件、姓名和消息参数,格式化要发送的电子邮件正文,使用 SendGrid 发送电子邮件,并将电子邮件发送状态作为函数响应返回。

  2. 用步骤 1 中保存的 SendGrid API 密钥替换<SEND_GRID_API_KEY>,用电子邮件地址替换<TO_EMAIL>以接收“联系我们”表单数据:

     import json
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
def handle(req):

    SENDGRID_API_KEY = '<SEND_GRID_API_KEY>'
    TO_EMAIL = '<TO_EMAIL>'
    EMAIL_SUBJECT = 'New Message from OpenFaaS Contact Form'

    json_req = json.loads(req)
    email = json_req["email"]
    name = json_req["name"]
    message = json_req["message"]
    email_body = '<strong>Name: </strong>' + name + '<br><br> <strong>Email: </strong>' + email + '<br><br> <strong>Message: </strong>' + message

    email_object = Mail(
        from_email= email,
        to_emails=TO_EMAIL,
        subject=EMAIL_SUBJECT,
        html_content=email_body)

    try:
        sg = SendGridAPIClient(SENDGRID_API_KEY)
        response = sg.send(email_object)
        sendingStatus = "Message sent successfully"
    except Exception as e:
        sendingStatus = "Message sending failed"

    return sendingStatus
  1. 在 form-processor/requirements.txt 中将 sendgrid 模块添加为 form-processor 函数的依赖项:
sendgrid
  1. 增加 form-processor.yml 中的超时(read_timeout、write_timeout 和 exec_timeout)值,如下面的代码所示:
version: 1.0
provider:
  name: openfaas
  gateway: http://192.168.99.100:31112
functions:
  form-processor:
    lang: python3
    handler: ./form-processor
    image: sathsarasa/form-processor:latest
    environment:
      read_timeout: 20
      write_timeout: 20
      exec_timeout: 20
  1. 构建、部署和推送 form-processor 函数:
$  faas-cli up -f form-processor.yml

命令的输出应该如下所示:

[0] > Building form-processor.
Clearing temporary build folder: ./build/form-processor/
Preparing ./form-processor/ ./build/form-processor//function
Building: sathsarasa/form-processor:latest with python3 template. Please wait..
Sending build context to Docker daemon  10.24kB
...
Successfully built 128245656019
Successfully tagged sathsarasa/form-processor:latest
Image: sathsarasa/form-processor:latest built.
[0] < Building form-processor done.
[0] worker done.
[0] > Pushing form-processor [sathsarasa/form-processor:latest].
The push refers to repository [docker.io/sathsarasa/form-processor]
...
latest: digest: sha256:c700592a3a7f16875c2895dbfa41bd269631780d9195290141c245bec93a2257 size: 4286
[0] < Pushing form-processor [sathsarasa/form-processor:latest] done.
[0] worker done.
Deploying: form-processor.
WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.
Deployed. 202 Accepted.
URL: http://192.168.99.100:31112/function/form-processor
  1. 通过在 Web 浏览器中打开 URL 来打开联系我们表单:

http://192.168.99.100:31112/function/contact-form

联系表单应该如下所示:

图 9.62:调用“联系我们”表单

图 9.62:调用“联系我们”表单
  1. 填写表单,然后提交表单,如下图所示:图 9.63:提交联系我们表单
图 9.63:提交联系我们表单
  1. 检查步骤 9 中提供的电子邮件帐户<TO_EMAIL>以验证电子邮件发送:

图 9.64:验证电子邮件发送

图 9.64:验证电子邮件发送
posted @ 2024-05-20 12:03  绝不原创的飞龙  阅读(13)  评论(0编辑  收藏  举报