Oracle Cloud Infrastructure Functions in Go 和使用 OCI Go SDK 从 Go 访问 OCI 服务

这是关于 Go 和 Oracle Cloud Infrastructure 的五部分系列中的第三部分。本系列讨论如何在计算实例 (VM)、在 Kubernetes 上容器化或作为无服务器函数的 Oracle Cloud Infrastructure (OCI) 上创建和运行 Go 应用。这些文章展示了如何使用 OCI DevOps 自动构建和部署这些 Go 应用。一个重要主题是如何使用来自 Go 应用的 OCI 服务,包括基于 OCI 运行的服务以及在其他位置运行的 Go 代码。讨论的一些 OCI 服务包括 Object Storage、Streaming、Key Vault 和 Autonomous Database。

为了跟上这些文章,读者至少应该对如何创建 Go 应用程序有基本的了解。假设读者可以访问自己的 Go 开发环境。一些示例和截图将特别提到 VS Code 作为开发工具。但是,也可以使用其他编辑器和 IDE。这些文章中介绍的 Go 代码以最简单的形式展示了一些机制,以实现最大清晰度和最小依赖性。读者不应该期望有意义的功能或生产就绪的代码。

这些文章介绍了如何在 OCI 上运行。要试用这些示例,读者需要有权访问 OCI 租户,并有权创建这些文章中讨论的 OCI 资源。所使用的大多数资源都位于始终免费层(计算实例、VCN、Autonomous Database、Object Storage、Logging、Resource Manager)中,或者具有用于每月有限使用的免费配额层(函数、API 网关、流处理、Vault、DevOps)。

本系列的第一部分介绍基于 Oracle Linux Cloud Developer 映像预配计算实例,为入站和出站网络活动打开计算实例,创建和运行提供 HTTP 请求的 Go 应用程序,以及将应用程序生成的日志记录连接到 OCI 日志记录。第二部分涉及软件工程、构建自动化以及使用 OCI DevOps 服务部署应用。此服务用于存储 Go 源代码、构建应用程序可执行文件、将其存储为可部署构件以及将该构件部署到计算实例。第二篇文章还介绍了如何通过 OCI API 网关公开应用程序的 HTTP 端点。

第三部分介绍了如何在 Go 中创建无服务器函数并将其部署到 OCI。引入了适用于 OCI 的 Go SDK —首先用于本地独立 Go 应用,随后用于函数—利用资源主用户验证。此 SDK 用于与 OCI 对象存储服务交互,以创建存储桶以及写入和读取文件。

最初,该函数是手动构建和部署的。将路由添加到 API 网关中的部署,用于从 OCI 外部的客户端调用函数。然后,创建 OCI DevOps 部署管道,以从容器映像注册表中的映像部署函数。最后,构建管道设置为获取代码资料档案库中的源,构建和发布容器映像,然后触发部署管道以进行端到端构建和部署。

迁移中的 OCI 函数

OCI 中的无服务器功能基于开源项目 Fn 的技术。函数的业务逻辑是用您最喜欢的语言(在本例中为 Go)编写的,并且嵌入在处理函数生命周期以及与函数交互的 Fn 框架中。Fn 框架可以在任何位置运行:在本地计算机上、任何云上的 VM 或内部部署中。Oracle Cloud Infrastructure 为基于相同技术的无服务器函数提供完全托管的 PaaS 服务 OCI Functions。

函数内置在容器映像中。此映像被推送到容器映像注册表。要发布函数,此映像将传输到 Fn 服务器。每当调用函数时,都会从映像启动容器并处理请求。容器在处理调用后会持续运行一段时间,处于可处理其他请求的热状态。当并发请求数量增加时,Fn 服务器将启动同一函数的其他容器,以确保可以处理所有请求。

开发人员和应用运营商的功能的吸引力在于,无需投入任何精力来设计、创建和管理运行业务逻辑的平台。所有的注意力都集中在写这种逻辑上。

现在,我们将研究在 Go 中创建函数,将其构建成容器映像,在本地部署和运行它。然后,我们将此函数转到 OCI Functions 服务并使其运行云端。

Fn 开发环境

要开发函数,您需要一个支持 Project Fn 的环境。Fn 是一个基于 Docker 的轻量级无服务器函数平台,您可以在笔记本电脑、服务器或云上运行。您可以按照 Fn Project Tutorials – Install Fn 中的说明在 Linux 或 MacOS 上轻松安装 Fn。

您可以选择使用我们在第一部分中创建的并也在本系列的第二部分中使用的 go-app-vm 计算实例。此 Oracle Linux 环境不附带 Fn 设置,但安装过程非常简单。

您也可以使用 OCI Cloud Shell。此浏览器可访问的环境是使用 Fn 设置的。有关在 OCI Cloud Shell 中使用 Fn 的信息,请参阅 OCI 文档函数:使用 Cloud Shell 入门

开发 Fn 函数

在安装了 Fn CLI 的开发环境中,导航到要创建函数子目录的目录。在命令行上,输入以下命令并执行该命令:

 fn init --runtime go --trigger http greeter 

将创建名为 greeter 的子目录。导航到它并检查其内容:

 cd greeter ls -l 

文件 func.yaml 包含有关函数的元数据,在构建时由 Fn 框架解释,在运行函数时稍后由 Fn 框架解释。文件 go.mod 包含函数对 fdk-go 软件包的依赖性。实际函数本身位于 func.go 中。此处可以看到函数的结构及其与 Fn 运行时的交互:函数 main 将函数 myHandler 与 Fn 运行时注册,Fn 运行时将指示并允许运行时针对收到的任何 HTTP 请求调用此函数。该函数在 io.Reader 参数中接收请求的正文。它还接收 io.Writer 的输出,响应正文可以写入该输出。context.Context 参数 ctx 包含原始请求的元数据,包括 HTTP 标头、完整 URL、请求方法和函数本身,包括为其定义的所有配置变量。

目前,myHandler 函数对请求正文进行解码,要求它包含一个名为 name 的字段的 JSON 有效负载。它将创建其名称设置为此字段值的人员,或在缺勤时默认为 World。然后,它创建预期的响应:一个 JSON 对象,其中包含一个名为 message 的字段,其中包含由 Hello 组成的字符串和 name 值。

虽然它没有做任何真正壮观的事情,但功能是健全和完整的,我们可以在本地部署和调用它。为此,我们需要本地上下文,并且本地 Fn 服务器已启动且正在运行。使用以下项检查上下文:

 fn list contexts 

这将显示至少一个上下文的列表 - 可能有多个上下文。要使用本地 Fn 服务器,请确保缺省上下文为活动上下文。如果需要将当前上下文设置为默认值,请使用:

 fn use context default 

现在创建一个应用程序作为函数的主机:

 fn create app go-oci-app fn list apps 

如果这些语句中的第一个语句失败并拒绝连接,则服务器可能尚未运行。使用下一个命令启动服务器,然后重试创建应用程序。

 fn start 

成功创建应用程序后,该函数现在可以部署到其中。下一个命令负责此部署;它前面有容器映像构建过程。

 fn --verbose deploy --app go-oci-app --local 

指定 --local 将部署到本地服务器,但不会将函数映像推送到 Docker 注册表,如果要部署到远程 Fn 服务器,这是必需的。

由于它释放了大量要生成的日志消息,因此 --verbose 标志不是您一直使用的标志。然而,它让你对正在发生的事情有了很好的了解。将提取多个容器映像,然后执行两阶段的容器构建过程来为 greeter 函数创建容器映像。预定义的 Fn 项目映像用于构建阶段(在编写时为 fnproject/go:1.15-dev)以及作为运行时映像的基础 (fnproject/go:1.15)。

最终输出如下所示:

 Updating function greeter using image greeter:0.0.2... Successfully created function: greeter with greeter:0.0.2 Successfully created trigger: greeter Trigger Endpoint: http://localhost:8080/t/go-oci-app/greeter 

函数图像称为 greeter:0.0.2。您可以在本地容器注册表中找到此映像,其中包含:

 docker images | grep greeter 

可以使用函数的名称和应用程序通过 Fn CLI 调用该函数,如下所示:

 fn invoke go-oci-app greeter 

该函数需要包含名称字段的 JSON 有效负载,因此让我们提供如下内容:

 echo -n '{"name":"Clark Kent"}' | fn invoke go-oci-app greeter --content-type application/json 

函数部署的输出还为我们提供了函数的触发器端点。这是一个 HTTP 端点,我们可以向其发送 HTTP 请求,并让它触发函数。我们与 Fn 没有(可见)交互,尽管我们调用的端点实际上是 Fn 服务器端点。URL 路径告诉 Fn 要触发的应用程序和特定函数。

 curl -X "POST" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse"}' http://localhost:8080/t/go-oci-app/greeter 

创建 OCI 函数

现在,让我们在 OCI 上创建此函数,而不仅仅是在我们的开发环境中。这些步骤与我们用于在本地创建函数的步骤非常相似;我们只需要使用不同的上下文。不是本地上下文,而是 OCI 的上下文。

创建申请

首先,我们通过 OCI 控制台创建应用。在搜索框中键入应用程序,然后单击服务区域中的应用程序函数。

单击“Create Application(创建应用程序)”按钮。键入应用程序的名称:go-on-oci-app。选择在文章系列第一部分创建的 VCN 及其一个公共子网。然后单击“Create(创建)”创建应用程序。

逐条处理

为 OCI 交互和功能映像推送准备本地环境

创建应用程序后,将显示该应用程序的一般信息。该页面还包含有关在 OCI Cloud Shell 中或本地设置(当然也可以是 go-app-vm 计算实例)中创建第一个函数的说明。

逐条处理

如果您使用的是 OCI Cloud Shell,创建此上下文的步骤与在常规开发环境中工作时略有不同(且更简单)。随时按照 OCI Shell 设置操作。在本文中,我们将采取其他路径,用于任何本地开发环境。

在本地环境中(之前安装了 Fn CLI 的环境)需要执行许多步骤:

  1. 设置 API 签名密钥并将私钥存储在本地 HOME/.OCI 目录中的 .pem 文件中,并将公钥上载到 OCI 控制台—请参阅 OCI 文档—必需密钥中的说明。
  2. 在本地环境的 .oci 目录中创建文件配置;将配置文件预览片段复制到配置文件。更新文件中的 key_file 条目:使其引用私钥的 pem 文件。OCI 文档— SDK 和 CLI 配置文件
  3. 要将容器映像推送到 OCI 容器映像注册表,您需要验证令牌。在本系列文章的第一部分,您创建了一个令牌,用于从 git 客户机登录到 DevOps 代码资料档案库。您可以重用该令牌将 Docker 客户端记录到容器映像注册表中,也可以创建新的验证令牌。在后一种情况下,请参见 OCI Documentation – Getting an Authentication Token
  4. 您需要 OCI CLI。有关安装此工具的说明,请参阅 OCI 文档:使用 OCI 命令行界面—快速入门。OCI CLI 将使用 HOME/.oci/config 文件和引用的私有密钥建立与 OCI API 的安全连接。

完成这些步骤后,可以使用以下命令尝试步骤 1、2 和 4 的成功结果,该命令应返回租户中的区间列表:

 oci iam compartment list 

可选:创建容器映像注册表资料档案库

如果用于部署函数的用户账户具有必要的 IAM 权限,则部署将为容器映像注册表中的函数映像创建资料档案库。如果这些特权不可用或者您希望准备系统信息库,可以执行如下操作。

  1. 在搜索栏中键入 regi。单击“Container Registry(容器注册表)”>“Containers & Artifacts(容器和构件)”。
  2. 单击“Create repository(创建资源库)”。键入资料档案库的名称:go-on-oci/greeter。这由系统信息库前缀和函数名称组成,其中系统信息库将包含映像。设置对公共的访问权限。
  3. 逐条处理
  4. 单击“创建资料档案库”按钮。几秒钟后,将创建一个新的空容器映像系统信息库,以便接收我们使用 Fn CLI 推送的功能(容器)映像。

在 Fn CLI 中为 OCI 创建上下文

移回本地环境的命令行,我们需要为 OCI 上的当前区间创建 Fn 上下文,然后选择该上下文以用于 Fn 操作。执行这些命令(您可以从 go-on-oci-app 页面上的“Getting Started(使用入门)”选项卡复制这些命令):

 fn create context go-on-oci --provider oracle fn use context go-on-oci 
逐条处理

复制步骤 4 下的命令,以使用区间 OCID 和 Oracle Functions API URL 更新上下文。就我而言:

 fn update context oracle.compartment-id ocid1.compartment.oc1..aaaaaaaaqb4vxvxuho5h7eewd3fl6dmlh4xg5qaqmtlcmzjtpxszfc7nzbyq fn update context api-url https://functions.us-ashburn-1.oraclecloud.com 

这个命令对你来说是相似的,但不同。

提供唯一的资料档案库名称前缀。使用 go-on-oci 并指定包含函数映像必须发布到的映像注册表资料档案库的区间:

 fn update context registry iad.ocir.io/idtwlqf2hanz/go-on-oci fn update context oracle.image-compartment-id  

使用验证令牌作为密码登录注册表:

 docker login iad.ocir.io 

就我而言,我所在的区域是阿什本,由区域关键字 iad.ocir.io 标识。我被提示输入用户名。这是包含容器映像注册表名称和每个资料档案库中包括的名称空间前缀的字符串。随后请求口令。在这里,您为用户设置了一个身份验证令牌,我们在前一篇文章中之前在代码存储库中执行登录时使用该令牌。

下一个命令显示当前 Fn 上下文中应用程序的列表:

 fn list apps 

该列表包含一个名为 go-on-oci-app 的应用程序。

创建、本地部署和之前调用的函数 greeter 现在也可以部署到 OCI 应用程序中,成为云原生无服务器函数。我们用于部署的命令与我们之前使用的命令相同。其效果因环境变化而大不相同。现在,基于 OCI 提供程序的上下文已链接到 OCI 租户和区间,而不是本地上下文。容器映像将推送到 OCI 容器映像注册表,并在 OCI 函数服务中创建函数。

 fn -v deploy --app go-on-oci-app 

输出与之前生成的输出类似,但构建过程完全相同。函数容器映像准备就绪后,事情开始发生偏差。将映像推送到 OCI 容器映像注册表,并将该功能部署到云中。输出中的相关行:

 => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:008dc3b990f1e69d67a7dd8649fbd63649d72f0bf1a161b2c2e073064f16c918 0.0s => => naming to iad.ocir.io/idtwlqf2hanz/go-on-oci/greeter:0.0.3 0.0s Parts: [iad.ocir.io idtwlqf2hanz go-on-oci greeter:0.0.3] Using Container engine docker to push Pushing iad.ocir.io/idtwlqf2hanz/go-on-oci/greeter:0.0.3 to docker registry...The push refers to repository [iad.ocir.io/idtwlqf2hanz/go-on-oci/greeter] ... e57f007acf74: Pushed 0.0.3: digest: sha256:bb4f2abde44d97517520571a21c407e349ddfc6572583a8ba53717436fd0b7f5 size: 1155 Updating function greeter using image iad.ocir.io/idtwlqf2hanz/go-on-oci/greeter:0.0.3... Successfully created function: greeter with iad.ocir.io/idtwlqf2hanz/go-on-oci/greeter:0.0.3 Fn: HTTP Triggers are not supported on Oracle Functions 

此时,该函数位于云中,并且可以调用(仍然使用 Fn CLI):

 fn invoke go-on-oci-app greeter 

第一次调用需要相当长的时间,因为函数开始时很冷,需要将底层容器映像实例化到正在运行的容器中。每次函数的后续调用都会更快。请注意,如果等待十分钟而函数变冷,则容器将停止。

此图描述了我们到达的情况:

逐条处理

您可以在 OCI 控制台中查看刚刚发生的证据。在控制台的搜索框中键入 greeter。在“资源”下将有一个条目 >greeter > Functions>。单击链接可转到显示函数详细信息的页面。您将找到对函数映像、内存设置和用于调用函数的端点的引用。在度量下,应找到使用 Fn CLI 调用函数的证据。

在 greeter 的搜索结果中,您还可以找到 Container Repository go-on-oci/greeter。导航到资料档案库时,您将找到发布到该资料档案库的映像的详细信息。

为函数创建 API 网关路由

无法仅调用 OCI 函数。尽管他们有一个 HTTP 端点,似乎建议你可以从浏览器或命令行上的 curl 调用它们,但实际上并不那么简单。对函数的 HTTP 调用需要签名,这个签名过程并不简单和简单。

使用者通过 API 网关调用函数的更好方法。我们在前一篇文章中使用了 API 网关,以打开到在(潜在)专用计算实例上运行的 myserver 应用的公共路由。现在,我们将使用 API Gateway the-API-gateway 中的附加路由和上一篇文章中创建的部署 myserver-API 对 greeter 函数执行相同的操作。

逐条处理

为 API 网关设置 IAM 访问

需要允许 API 网关使用为 API 网关提供调用函数权限的策略来调用函数。

为 API 网关创建用于调用函数的策略。要在控制台中创建策略:请在搜索栏中键入 poli,然后在搜索结果弹出窗口的“服务”区域中单击 >Policies > Identity>。这将转到当前区间的“策略概览”页。

该策略定义 API 网关对区间中资源的访问权限。创建新策略、键入名称 (invoke-function-for-api-gateway)、说明和以下语句:

ALLOW any-user to use functions-family in compartment  where ALL {request.principal.type= 'ApiGateway', request.resource.compartment.id = ''}

替换为区间的名称,该名称可能与区间关联。将 替换为您正在使用的区间的标识符。

在 API 网关上的部署中为函数定义路由

借助这些权限,我们现在可以在 API 网关上定义路由。在控制台的搜索栏中键入 gat。单击“Gateways(网关)”>“API Management(API 管理)”。单击 *the-api-gateway 的链接。单击“Deployments(部署)”。在部署列表中(包含单个部署),单击 myserver-api 链接。

单击“编辑”按钮以打开部署规范。单击第二步的链接:路由。向下滚动并单击按钮 + 其他路由。

键入 /greeting 作为此新路由的路径。选择 GET 作为方法,选择 Oracle Functions 作为后端的类型。选择应用程序 go-on-oci-app,然后将函数名称设置为 greeter。

逐条处理

按“下一步”。然后按“保存更改”以应用更改并使新路由变为实际。

通过 API 网关调用函数

通过在 API 网关上设置新路由并刷新部署,我们现在可以向 API 网关的公共端点发出简单、直接的 HTTP 请求,间接触发函数 greeter 并接收其响应。

在任何浏览器中使用此 URL,您都应该能够获得函数的响应:

https:///my-api/greeting

反应有点令人沮丧,但这是预期的如此简单的功能。

使用 curl,您可以向函数发送 JSON 有效负载,并接收稍微更有趣的响应。

curl -X "GET" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse"}' https:///my-api/greeting

响应读取 {"message":"Hello Mickey Mouse"。

因此,我们现在已经建立了从 API 网关到无服务器功能的端到端流。我们还有一种方法可以基于本地开发环境中的源手动部署函数。为了利用我们的工作,您可以在 func.go 中对源代码进行一些更改,然后再次部署该函数(带有 Fn CLI 的单个命令),并在 API 网关上调用问候路由,以查看我们的更改是否已生效。

例如:将设置消息值的行更改为

Msg: fmt.Sprintf("Warmest greetings from your function dear %s", p.Name)

保存更新的 func.go 源。然后执行以下命令来部署更新的函数,然后调用它:



fn -v deploy --app go-on-oci-app
curl -X "GET" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse"}' https:///my-api/greeting
	  

这应导致作出更好的反应。在准备好的环境中,构建和部署过程会压缩为单个手动命令。接下来,我们将了解使用 OCI DevOps 的函数的自动化部署过程,然后基于代码存储库中的源执行之前的自动化构建过程。然后,我们将转向比返回简单问候稍微多一些的函数。

自动部署功能

在本系列前面的部分中,我们看到了使用 OCI DevOps 部署管道将应用程序部署到计算实例。现在,我们将使用管道来自动部署函数。整体方法和成分是相似的。我们需要一个构件、(目标)环境以及具有函数部署阶段的部署管道,以及管道的 IAM 权限来读取构件和部署函数。

逐条处理

这些成分更详细:

  1. 对象:在这种情况下,引用 OCI 容器映像注册表中的特定容器映像,使用资料档案库的全限定路径以及特定映像和版本。
  2. 环境:对要(重新)部署的函数的引用。在函数部署的情况下,环境不是区间或区间中的应用程序(人们可能会猜测),而是功能本身,因此在通过 OCI DevOps 部署管道部署之前,它已经需要存在。(请注意,该函数不必有用,它可以基于 Scratch 容器映像。)
  3. 具有“函数部署”类型的部署管道阶段的部署管道,用于连接对象和环境。
  4. 包含部署管道和 IAM 策略的动态组,允许动态组读取对象(例如函数容器映像)和部署函数(一般来说,管理 OCI 资源)。

为函数容器映像创建 DevOps 对象

在 OCI 控制台中,导航到 DevOps Project 联机的主页。打开“构件”选项卡。单击“添加构件”按钮。请注意,此处定义的是从 DevOps 项目到实际构件的链接或代理,而不是构件本身。

输入 greeter-function 作为 DevOps 对象的名称。该类型应设置为容器映像资料档案库。映像的全限定路径包括区域键、资料档案库名称空间和前缀、函数名称和函数版本标记。在这种情况下,请为版本标记使用占位符。路径现在定义如下:

///greeter:${imageVersion}

将此构件中使用的下拉字段“替换”参数设置为“是”,以替换占位符。

逐条处理

单击“添加”按钮以完成并保存构件的定义。

为函数定义 DevOps 环境

在 DevOps 项目中打开“Environments(环境)”选项卡。它包含为将 myserver 部署到计算实例而创建的 go-on-oci-vm 环境(在上一篇文章中)。单击“Create environment(创建环境)”按钮。

在第一步“基本信息”中,单击磁贴“函数 - 为函数创建环境”。输入 greeter-function-in-app-go-on-oci-app 作为环境的名称。按“下一步”以转到包含环境详细信息的第二步。确认“Region(区域)”、“Compartment(区间)”、“Application(应用程序)”和“Function(函数)”- 您可能不需要更改其中的任何设置。如果您这样做,请确保在应用程序 go-on-oci-app 中选择函数 greeter。

单击“Create environment(创建环境)”保存定义。

逐条处理

创建用于部署函数的部署管道

在 DevOps 项目的概览页面上,单击“Create pipeline(创建管道)”。此时将显示“创建管道”表单。键入名称 (deploy-greeter-function-to-go-on-oci-app) 和可选的说明。然后单击“Create Pipeline(创建管道)”。部署管道是创建的,虽然它是相当空的:不是它应该部署到的环境,没有要部署的构件,也没有配置文件来定义要执行的步骤。

在显示的管道编辑器中,单击“添加阶段”磁贴(或加号图标)。下一页显示阶段类型的列表。单击标记为“使用内置函数更新策略”的磁贴。

按“下一步”按钮。

键入阶段名称,例如 update-function-greeter。选择先前为函数定义的环境:greeter-function-in-app-go-on-oci-app。

在“Artifact(对象)”标题下,单击“Select Artifact(选择对象)”。此时将显示 DevOps 项目中类型为 Docker Image 的所有对象的列表。选择唯一的条目,即先前为函数容器映像创建的条目。

请注意,“选择构件”按钮不再启用:只能将单个容器映像与此阶段关联。

逐条处理

单击“添加”。在管道中创建管道阶段。现在,管道已准备好执行,其定义已完成。是吗?此管道使用的构件未明确定义:容器映像路径中的版本标签包含占位符 ${imageVersion}。为了确保使用正确的版本进行部署,需要将此占位符替换为正确的值。通过在管道中定义名为 imageVersion 的参数并将其设置为现有版本标签来排列。

单击管道的“参数”选项卡。定义名为 imageVersion 的新参数。它的默认值可以是任何值,但它也可能对应于 greeter 函数容器映像的现有版本标签。保存参数定义。

似乎管道已经准备好执行,但我们仍然必须确保它被允许完成其工作。在尝试任何皮疹之前,请阅读下一节。

动态组和策略

在前一篇文章中,为区间中的所有部署管道定义了一个动态组。新管道自动成为该组的成员。我们还定义了一个策略,该策略为动态组授予了读取所有构件的权限,其中包括区间的容器映像注册表资料档案库中的(函数)容器映像。此外,已创建的另一个策略向动态组授予管理区间中所有资源的广泛权限。我们可以从该政策的广泛范围中获益,因为它还涉及职能的创建和更新。

运行部署管道

通过按“运行”管道运行部署管道。

部署完成后,您将看到宣布成功的绿色标记。但是,没有其他明显的迹象表明这一成功,因为最终结果正是我们从 Fn CLI 命令行手动部署函数所实现的情况。

逐条处理

为了让事情变得更有趣,我们将对函数的代码进行更改。然后,构建函数的容器映像(本地),并将新函数映像推送到容器映像注册表。然后,我们将再次启动部署管道;这一次,当成功时,它将通过调用 API 网关上的 my-API/greeting 路由来呈现一种我们可以体验的新情况。

更改功能实施

在本地环境中对 func.go 进行微小但可见的更改:确保来自新版本函数的响应与当前版本明显不同。保存更改。

在接下来的部分中,我们将从更改的源构建函数容器映像的新版本,并最终使其在 OCI Functions 上运行。

构建新的函数容器映像(本地)

下面的这些命令将首先修改用于标记函数的版本标签,第三位数增加(bm 是 bump 的缩写)。接下来,函数容器映像是使用更改的源构建的。第三个命令列出本地容器映像,对名称较宽的映像进行过滤。现在请执行命令。




fn bm
fn build 
docker images | grep greeter
	  

您应该能够找到新构建的映像及其全限定名称,包括 OCI 区域密钥、名称空间、资料档案库前缀以及附加了版本标签的函数名称 greeter。

使用新版本标签和推送到注册表的标记容器映像

让我们使用以下命令为映像定义一个新标识符,将版本标签设置为 0.1.0:




docker tag :  :0.1.0

	  

然后,使用以下命令将新的函数容器映像推送到 OCI 容器映像注册表资料档案库:




docker push :0.1.0

	  

请注意,此时我们尚未根据容器映像的此新版本重新部署该函数。我们所做的只是构建映像并将其推送到 OCI 上的注册表。调用 OCI 函数不会显示任何差异。

运行部署管道(对于新函数映像)

再运行一次部署管道。将参数 imageVersion 的值设置为 0.1.0。

当管道成功完成时,新版本的函数及其应用的所有令人兴奋的更改将生效。

调用新部署的函数

通过使用 Fn CLI 在命令行上调用新函数版本,您可以看到它正在运行:




fn invoke go-on-oci-app greeter

	  

(由于 Fn CLI 的上下文仍与 Oracle 提供程序联机,并且针对包含 greeter 函数的联机区间进行了配置,因此此调用将定向到 OCI 函数,此时该函数基于容器映像的新版本。)

或者,您可以弯曲到 API Gateway 上调用该函数的路由:




curl -X "GET" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse"}' https:///my-api/greeting

	  

自动构建功能

到目前为止,我们已经使用 Fn CLI 在本地开发环境中手动构建了函数容器映像。但是,正如我们在前一篇关于 Go 应用程序的文章中所做的那样,该应用程序是由 Build Pipeline 作为可执行文件生成,我们现在将把函数的构建变成一个自动化过程。创建 OCI DevOps 构建管道以从代码资料档案库获取源,运行生成本地容器映像的托管构建阶段,然后将此映像作为构件发布到容器映像注册表资料档案库。作为最后一步,构建管道会触发部署管道将函数的最新定义发布到运行时 OCI 函数环境中。

当所有元素都到位时,互联的 OCI 组件总数将在下图中可视化。

逐条处理

此图中显示的构件和部署管道已在 DevOps 项目中定义,函数映像的应用程序、函数和容器映像注册表存储库也是如此。我们将使用前面文章中设置的代码资料档案库。我们需要创建的是具有三个阶段的构建管道构建更环保的功能。

创建构建管道

在 DevOps 项目联机的概览页上,单击“创建构建管道”按钮。此时将显示用于指定名称(例如 build-greeter-function)和说明的页面。按“创建”将构建管道添加到 DevOps 项目。

单击列表中的链接 build-greeter-function 以导航到详细信息页。

第一阶段 - 托管构建

任何构建管道的第一阶段都是托管构建阶段。此阶段为管道提供了获取构建服务器、将指定的源代码从代码资料档案库复制到服务器以及通过该服务器上的许多操作运行的说明。在编写本文时,我们可以为构建服务器使用单个映像。它是一个 Oracle Linux 映像(8 GB 内存、1 个 OCPU),具有许多预安装的工具和语言运行时间。对于构建函数容器映像,构建服务器同时配备了 Docker 和 Fn CLI,这一点非常重要。

单击加号图标或“添加阶段”卡。此时将显示“添加阶段”向导的两个步骤。在向导的步骤 1 中,确保为阶段类型选择了“托管构建”卡。按“下一步”。

此时将显示第二页。定义构建阶段的名称:build-go-source-to-function-container-image。(可选)添加说明。

目前,我们无法选择不同的构建映像,因此我们满足于可用的构建映像,这对于我们的目的来说很好。

将构建规范文件路径设置为 /functions/greeter/go-function-build-spec.yaml。此文件包含有关在 greeter 函数(或任何其他 Go 函数)中构建 Go 源以及最终构建函数容器映像的说明。

单击“Primary code repository(主代码资料档案库)”下的“Select(选择)”按钮。现在,我们可以指定构建将从哪个代码资料档案库获取其源代码。选择 OCI 代码资料档案库作为源连接类型。然后选择 go-on-oci-repo 系统信息库。我们将使用主分支上的源,因此请勿更改该默认值。键入 go-on-oci-sources 作为构建源名称的值。托管构建阶段可以使用来自多个资料档案库的源。在构建规范中,我们可以使用定义为构建源名称的标签来引用每个源的根位置。单击“保存”。

逐条处理

按“添加”按钮。这将完成托管构建阶段的定义。这是获取源并将其处理为构件所需的一切。此托管构建阶段和构建服务器上执行的详细说明在 go-function-build-spec.yaml 文件中定义。正是此文件包含有关在构建服务器上执行的实际详细步骤的说明。




version: 0.1
component: build
timeoutInSeconds: 6000
runAs: root
shell: bash
env:
# these are local variables to the build config
variables:
SOURCE_DIRECTORY: "go-on-oci-sources/functions/greeter"
FUNCTION_NAME: "greeter"

# # the value of a vaultVariable is the secret-id (in OCI ID format) stored in the OCI Vault service
# you can then access the value of that secret in your build_spec.yaml commands
vaultVariables:

# exportedVariables are made available to use in sucessor stages in this Build Pipeline
# For this Build to run, the Build Pipeline needs to have a BUILDRUN_HASH parameter set
exportedVariables:
- BUILDRUN_HASH


steps:
- type: Command
name: "Export variables"
timeoutInSeconds: 40
command: |
export BUILDRUN_HASH=`echo ${OCI_BUILD_RUN_ID} | rev | cut -c 1-7`
echo "BUILDRUN_HASH: " $BUILDRUN_HASH
echo "fully qual sources" ${OCI_WORKSPACE_DIR}/${SOURCE_DIRECTORY}
echo "container image version from build pipeline parameter" ${imageVersion}      
go version

- type: Command
timeoutInSeconds: 600
name: "Install golangci-lint"
command: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.37.1

- type: Command
timeoutInSeconds: 600
name: "Verify golangci-lint version"
command: |
/root/go/bin/golangci-lint version

- type: Command
timeoutInSeconds: 600
name: "Run go mod tidy for Go Application"
command: |
cd ${OCI_WORKSPACE_DIR}/${SOURCE_DIRECTORY}
go mod tidy

- type: Command
timeoutInSeconds: 600
name: "Run go vet for Go Application"
command: |
cd ${OCI_WORKSPACE_DIR}/${SOURCE_DIRECTORY}
go vet .

- type: Command
timeoutInSeconds: 600
name: "Run gofmt for Go Application"
command: |
gofmt -w ${OCI_WORKSPACE_DIR}/${SOURCE_DIRECTORY}

- type: Command
timeoutInSeconds: 600
name: "Run Lint for Go Application"
command: |
cd ${OCI_WORKSPACE_DIR}/${SOURCE_DIRECTORY}
/root/go/bin/golangci-lint run .

- type: Command
timeoutInSeconds: 600
name: "Run Unit Tests for Go Application (with verbose output)"
command: |
cd ${OCI_WORKSPACE_DIR}/${SOURCE_DIRECTORY}
go test -v 

- type: Command
timeoutInSeconds: 600
name: "Build Go Function into Function Container Image"
command: |
cd ${OCI_WORKSPACE_DIR}/${SOURCE_DIRECTORY}
pwd
fn build --verbose
image=$(docker images | grep $FUNCTION_NAME  | awk -F ' ' '{print $3}') ; docker tag $image go-function-container-image    


outputArtifacts:
- name: go-function-container-image
type: DOCKER_IMAGE
# this location tag doesn't effect the tag used to deliver the container image
# to the Container Registry
location: go-function-container-image:latest

	  

构建规范由三个部分组成:

  1. 设置脚本的运行者、要使用的 shell 以及要使用的变量
  2. 构建步骤:要在构建服务器上执行的 Shell 命令
  3. 输出构件指明所有构建步骤末尾的哪些文件有意义并且可供管道中的其他步骤使用(例如作为构件发布)

构建步骤可概括为:

  1. 打印环境变量和当前安装的 Go 版本(在 vanilla 构建服务器上)
  2. 安装 golangci-lint
  3. 验证 golangci-lint 安装的成功和版本
  4. 运行 go mod tidy 以使用依赖项组织 go.mod 文件
  5. 运行去审查以对去源运行第一次检查
  6. 运行 go fmt 以根据通用格式设置规则设置源格式
  7. 根据各种 lint 规则运行 golangci-lint 以 lint(检查)源
  8. 运行单位测试
  9. 使用 Fn CLI 将函数源构建到函数容器映像中(存储在本地映像注册表中)
  10. 使用名称 go-function-container-image 标记函数容器映像。这是用于在下一阶段查找要发布的图像的名称

这些步骤在很大程度上等同于上一篇文章中为 Go 应用程序定义的托管构建,该应用程序最终变成了部署在 VM 上的二进制可执行文件。步骤 9 和 10 不同—这些步骤将 Go 应用程序转换为函数容器映像,这是构建的最终产品。

第二阶段 - 发布构件

在构建管道的概览页面中,单击当前托管构建阶段底部的加号图标。在弹出的上下文菜单中,单击“添加阶段”。此时将显示阶段向导。

单击“Deliver 构件”。然后单击“下一步”。

输入此阶段的名称:publish-greeter-function-container-image。我们需要在要发布的 DevOps 项目中选择构件。此构件是容器映像 go-on-oci/greeter:${imageVersion}。单击“Select 构件(选择)”,然后选择容器映像。

在“将构件与构建结果关联”区域中,我们必须为每个所选构件指明托管构建阶段的哪些结果是发布构件的源。构建规范定义了一个标记为 go-function-container-image 的输出。此输出是指函数构建进程在构建服务器上生成的容器映像。在“构建配置/结果对象名称”字段中输入标签 go-function-container-image。按“Add(添加)”按钮以创建“Deliver Artifacts(传送构件)”阶段。

第三阶段 - 触发部署管道

在构建管道的概览页面中,单击“交付对象”阶段底部的加号图标。在弹出的上下文菜单中,单击“添加阶段”。此时将显示阶段向导。

单击“Trigger deployment(触发器部署)”。然后单击“Next(下一步)”。

键入阶段的名称:trigger-deployment-of-greeter-function-to-go-on-oci-app 和可选的说明。单击“Select deployment pipeline(选择部署管道)”按钮。选择管道 deploy-greeter-function-to-go-on-oci-app。显示管道的详细信息,包括参数 (imageVersion) 和部署使用的对象。

单击“添加”以完成阶段定义并将其添加到构建管道。

这将完成构建管道:它将获取源,将其处理为可部署构件,将构件发布到映像存储库,并触发部署管道以从那里获取。

逐条处理

运行构建管道和触发部署管道

单击“开始”手动运行。定义参数 imageVersion 的值,例如 0.2.0。单击该按钮以启动构建管道。

现在,完成构建管道并触发新构建函数映像的后续部署需要几分钟时间。

逐条处理

完成所有操作并报告成功后,您可以在 API 网关上调用路由,以引导至 greeter 函数,检查响应是否确实是预期的新响应。




curl -X "GET" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse"}' https:///my-api/greeting
{"message":"Extremely hot greetings from your automatically built and deployed function dear  Mickey Mouse"}

	  

这是一个小小的庆祝的时刻。我们实现了自动化的端到端流程,从代码存储库获取 Go 应用源,并在 linting、审查、测试和构建后提供实时运行无服务器 OCI 函数的新版本。为了进一步更新函数,我们需要做的就是提交更改并触发构建管道。接下来,我们甚至可以在代码资料档案库中发生(特定)提交时自动触发构建管道。

在本文的第二部分,我们将讨论从 Go 应用程序使用 OCI 服务,尤其是从 Go 函数使用 OCI 服务。引入了适用于 OCI 的 Go SDK,并演示了与 OCI 对象存储服务的交互。

Go SDK for OCI —与 Go 应用中的 OCI Object Storage 交互

Oracle Cloud Infrastructure 是一个可以构建和运行在 Go 中开发的应用的平台。无论是在计算实例中还是在无服务器函数中,还是在托管的 Kubernetes 集群上容器化(我们将在本系列的第五部分中讨论)。值得注意的是,OCI 对于 Go 开发团队来说比运行时平台要重要得多。OCI 提供许多可从应用中利用的服务。用于存储文件、关系或“NoSQL”数据的服务,用于处理消息的发布和使用。用于传输和分析数据的服务。以及支持应用程序操作的服务,例如监视。

可以通过适用于所有服务的各个方面的 REST API 与 OCI 服务进行交互。通过 HTTP 调用具有 JSON 有效负载的 REST 服务非常简单。有一个复杂因素:这些 API 调用需要签名。Oracle Cloud Infrastructure 签名使用“签名”验证方案(带有授权标头),签名过程并不完全简单。有关此签名流程的详细信息,请参阅 OCI 文档— OCI REST API 请求签名

幸运的是,为了开发调用 OCI 服务的 Go 应用,我们可以使用适用于 OCI 的 Go SDK。此开源开发工具包有助于对 OCI API 请求进行签名。使用 SDK,使用强类型预定义结构对 OCI 服务进行本地调用,而不是处理 JSON 请求和响应主体。适用于 OCI 的 Go SDK 使用的配置文件与以前用于 Fn CLI 和 OCI CLI 的配置文件相同。此文件的默认位置是 $HOME/.oci。此文件使用为用户设置的私有密钥对的一半,将 Go SDK 定向到用户账户的特定租户和区域。使用 OCI Go SDK 的 Go 应用可以简单地在此配置上构建,而无需处理任何详细信息。

有关适用于 OCI 的 Go SDK 的文档,请参阅 OCI 文档—适用于 Go 的 SDK

在本部分中,我们将开发一个简单的 Go 应用程序,该应用程序使用 OCI 对象存储服务来创建和读取文件。起初,这是一个可以在任何地方编译和运行的独立应用程序(只要 OCI 配置文件可用)。然后,我们讨论如何在 OCI 中运行 Go 应用—在 VM 上或作为函数运行。这一特别关注点非常重要,因为在 OCI 中使用 Go SDK 时,它可以利用运行应用的组件的身份和权限。这意味着在 OCI 上运行的代码不需要自带 OCI 配置文件,因此更简单。

与 OCI Object Storage Service 交谈的任何 Go 应用

首先,让我们创建一个非常简单的 Go 应用,通过 SDK 连接到 OCI。接下来,我们将使用此基础添加与对象存储服务的交互。

OCI 的简单 Go 客户端

使用适用于 OCI 的 Go SDK 与 OCI 通信的简单 Go 应用创建如下:

  1. 为应用程序创建目录
  2. 为 OCI 程序包创建依赖于 Go SDK 的 go.mod 文件
  3. 运行 go mod tidy 以创建 go.sum 文件并下载所需的软件包
  4. 使用与 OCI 通信的代码创建文件 OCI-client.go

假设本文前面讨论的 OCI 配置文件位于默认位置,默认名称为 $HOME/.oci/config。如果文件位于其他位置,则可以使用软件包 github.com/oracle/oci-go-sdk/v65/common 中的函数 ConfigurationProviderFromFile,该函数接受配置文件的定制位置。

go.mod 文件包含以下内容:






module oci-client

go 1.16

require github.com/oracle/oci-go-sdk/v65 v65.2.0



	  

github.com/oracle/oci-go-sdk/v65 位引用 Go SDK 的最新(在编写时)版本。源将根据此引用下载。在 Go 应用中,这意味着我们可以利用 SDK 中的程序包来访问 OCI 产品组合中的各种服务。

文件 oci-client.go 包含以下使用公用程序包和身份程序包的代码:




package main

import (
"context"
"fmt"

"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/identity"
)

func main() {    
c, _ := identity.NewIdentityClientWithConfigurationProvider(common.DefaultConfigProvider())
tenancyID, _ := common.DefaultConfigProvider().TenancyOCID()
request := identity.GetCompartmentRequest{
    CompartmentId: &tenancyID,
}
response, _ := c.GetCompartment(context.Background(), request)
fmt.Printf("Name of Root Compartment: %v", *response.Compartment.Name)
}


	  

函数中的第一行获取随后可用于与 OCI 的大多数交互的客户端,例如返回根区间详细信息的 GetCompartment 调用。

可以使用 go run oci-client.go 执行应用程序,并生成非常简单的输出:




Name of Root Compartment: 

	  

尽管结果并不特别引人注目,但有产出的事实证明了 OCI 的 Go SDK 正在发挥作用,并已准备好用于更崇高的活动。

请注意,代码将完全忽略可能发生的错误。如果遇到错误,请将下划线替换为变量以捕获和处理该错误。

在 OCI Object Storage Service 中和从中创建和检索对象

OCI 对象存储服务可为不同类型的数据提供廉价的持久性存储。大大小小的对象可以以编程方式存储在此服务上,以便保留很短或很长一段时间,并且可以通过编程或直接 URL 进行访问。对象存储提供版本控制、不同的保留规则、对任何存储数据的加密、对象生命周期管理、自动按时删除和不同的存储层。

对象存储服务上的对象按存储桶进行组织。存储桶是特定区间中对象的逻辑容器。可以对存储桶中的所有对象执行一些操作。

适用于 OCI 的 Go SDK 提供多种功能和类型,使您能够轻松地直接使用 Go 应用中的对象存储服务。可以随时执行操作来处理存储桶以及创建、删除和检索对象。

有关 OCI Go SDK 中对象存储包的详细信息,请查看以下参考: OCI Go SDK 中对象存储包的文档

本文的源资料档案库包含文件夹应用程序/store-n-retrieve,该文件夹应用程序具有一个简单的 Go 应用程序,该应用程序连接到您的 OCI 租户并创建存储桶,然后创建对象并检索同一对象。应用程序使用与上一节相同的 DefaultConfigProvider,该 DefaultConfigProvider 使用 $HOME/.oci/config 文件将请求签名到 OCI API。

此应用程序对 OCI Go SDK 的依赖性在 go.mod 中定义。

逐条处理

代码的第一部分将创建一个 ObjectStorageClient 实例。可以在底层接口上定义许多函数,所有这些函数都支持与对象存储服务进行某种形式的交互。ObjectStorageClient 是使用 common.DefaultConfigProvider() 创建的(与以前一样),使用默认的 OCI 配置文件以及包含私有密钥的文件的引用。

调用函数 getNamespace 以获取当前租户的名称空间。然后使用存储桶的名称调用 ensureBucketExists,以创建该存储桶(如果该存储桶尚不存在)。






package main

import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"

"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/objectstorage"
)

const (
bucketName      = "go-bucket" // feel free to use a different name for the bucket
compartmentOCID = "" // replace with the OCID of the go-on-oci compartment in your tenancy
objectName      = "welcome.txt" // feel free to use a different name for the object
)

func main() {
objectStorageClient, cerr := objectstorage.NewObjectStorageClientWithConfigurationProvider(common.DefaultConfigProvider())
if cerr != nil {
fmt.Printf("failed to create ObjectStorageClient : %s", cerr)
}
ctx := context.Background()
namespace, cerr := getNamespace(ctx, objectStorageClient)
if cerr != nil {
fmt.Printf("failed to get namespace : %s", cerr)
} else {
fmt.Printf("Namespace : %s", namespace)
}

err := ensureBucketExists(ctx, objectStorageClient, namespace, bucketName, compartmentOCID)
if err != nil {
fmt.Printf("failed to read or create bucket : %s", err)
}
.........................



	  

在此代码片段中调用的用于检索名称空间并检查存储桶是否存在(如果不存在,请创建)的函数非常简单。它们的定义如下:





func getNamespace(ctx context.Context, client objectstorage.ObjectStorageClient) (string, error) {
request := objectstorage.GetNamespaceRequest{}
response, err := client.GetNamespace(ctx, request)
if err != nil {
    return *response.Value, fmt.Errorf("failed to retrieve tenancy namespace : %w", err)
}
return *response.Value, nil
}

func ensureBucketExists(ctx context.Context, client objectstorage.ObjectStorageClient, namespace string, name string, compartmentOCID string) error {
req := objectstorage.GetBucketRequest{
    NamespaceName: &namespace,
    BucketName:    &name,
}
// verify if bucket exists.
response, err := client.GetBucket(ctx, req)
if err != nil {
    if response.RawResponse.StatusCode == 404 {
        err = createBucket(ctx, client, namespace, name, compartmentOCID)
        return err
    }
    return err
}
fmt.Printf("bucket %s already exists", bucketName)
return nil
}

// bucketname needs to be unique within compartment. there is no concept of "child" buckets. using "/" separator characters in the name, the suggestion of nested bucket can be created
func createBucket(ctx context.Context, client objectstorage.ObjectStorageClient, namespace string, name string, compartmentOCID string) error {
request := objectstorage.CreateBucketRequest{
    NamespaceName: &namespace,
}
request.CompartmentId = &compartmentOCID
request.Name = &name
request.Metadata = make(map[string]string)
request.PublicAccessType = objectstorage.CreateBucketDetailsPublicAccessTypeNopublicaccess
_, err := client.CreateBucket(ctx, request)
if err != nil {
    return fmt.Errorf("failed to create bucket on OCI : %w", err)
} else {
    fmt.Printf("created bucket : %s", bucketName)
}
return nil
}




	  

此应用程序的第二部分在存储桶中创建对象,并随后检索该对象。执行完应用程序后,它将产生持久性影响:存储桶已创建(如果不存在),并且创建或更新了对象(如果以前运行过应用程序)。您可以在 OCI 控制台中签入存储桶 Go-bucket 的详细信息页面,以查看存储桶和创建的对象。





..................

contentToWrite := []byte("We would like to welcome you in our humble dwellings. /n We consider it a great honor. Bla, bla.")
objectLength := int64(len(contentToWrite))
err = putObject(ctx, objectStorageClient, namespace, bucketName, objectName, objectLength, ioutil.NopCloser(bytes.NewReader(contentToWrite)))
if err != nil {
    fmt.Printf("failed to write object to OCI Object storage : %s", err)
}

var contentRead []byte
contentRead, err = getObject(ctx, objectStorageClient, namespace, bucketName, objectName)
if err != nil {
    fmt.Printf("failed to get object %s from OCI Object storage : %s", objectName, err)
}
fmt.Printf("Object read from OCI Object Storage contains this content: %s", contentRead)
}

func putObject(ctx context.Context, client objectstorage.ObjectStorageClient, namespace string, bucketName string, objectname string, contentLen int64, content io.ReadCloser) error {
request := objectstorage.PutObjectRequest{
    NamespaceName: &namespace,
    BucketName:    &bucketName,
    ObjectName:    &objectname,
    ContentLength: &contentLen,
    PutObjectBody: content,
}
_, err := client.PutObject(ctx, request)
fmt.Printf("Put object %s in bucket %s", objectname, bucketName)
if err != nil {
    return fmt.Errorf("failed to put object on OCI : %w", err)
}
return nil
}

func getObject(ctx context.Context, client objectstorage.ObjectStorageClient, namespace string, bucketName string, objectname string) (content []byte, err error) {
request := objectstorage.GetObjectRequest{
    NamespaceName: &namespace,
    BucketName:    &bucketName,
    ObjectName:    &objectname,
}
response, err := client.GetObject(ctx, request)
if err != nil {
    return nil, fmt.Errorf("failed to retrieve object : %w", err)
}
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(response.Content)
if err != nil {
    return nil, fmt.Errorf("failed to read content from object on OCI : %w", err)
}
return buf.Bytes(), nil
}


	  

运行此应用程序 - go run object-organizer.go - 导致以下输出:



Namespace : idtwlqf2hanz
created bucket : go-bucket
Put object welcome.txt in bucket go-bucket
Object read from OCI Object Storage contains this content: We would like to welcome you in our humble dwellings. /n We consider it a great honor. Bla, bla.


	  

基于 OCI 函数与 OCI 对象存储服务通话

上一节讨论了通过 SDK 与 OCI 服务交互的任何 Go 应用。关于 OCI Functions in Go,还有什么可说的?请继续阅读,因为事实证明,当您开发并要使用 Go SDK 的 Go 代码部署为 OCI 函数或在 OCI 中的计算实例上运行时,我们可以使生活变得更简单。在这些情况下,我们可以利用资源主用户身份验证(用于函数)和实例主用户身份验证(用于在计算实例上运行的代码),这意味着我们不必包括 OCI 配置文件,而且与 SDK 通信的代码可能要简单一些。

有关资源主用户身份验证的更多信息,请参见 OCI Documentation on Resource Principal Authentication for Functions

在本节中,我们将讨论名为 object-broker 的 OCI 函数。您将在本文的源存储库中找到路径函数/object-broker 中的源。与以前一样,该函数使用带元数据的 func.yaml 文件和带函数与 Fn 框架之间链接的 func.go 文件进行定义。文件 go.mod 定义函数的相关性。与 OCI Object Storage Service 交互的逻辑位于文件 object-organizer.go 中。这定义了从 func.go 调用的公共函数 CreateObject。

CreateObject 在指定的存储桶中创建具有指定名称的对象。对象 organizer.go 中函数 CreateObject 中的第一行进行了一些修改,以便使用此资源主用户验证。现在使用的 auth.ResourcePrincipalConfigurationProvider() 不要求在应用程序中包括 OCI 配置文件和私有密钥。它假设代码在 OCI 中运行,更具体地说是在资源(可以是函数或 DevOps 构建服务器)中运行,该资源称为资源主体,因为它包含在动态组中并从该组成员资格继承权限。在太久之前,您将获得采取此措施所需的措施的说明。





func CreateObject(objectName string, bucketName string, compartmentOCID string) (string, err) {
configurationProvider, err := auth.ResourcePrincipalConfigurationProvider()
if err != nil {
    fmt.Printf("failed to get oci configurationprovider based on resource principal authentication : %s", err)
}
objectStorageClient, cerr := objectstorage.NewObjectStorageClientWithConfigurationProvider(configurationProvider)


	  

让我们把注意力转向 func.go 旁边。Func(tion) myHandler 处理函数触发器。它会调用 CreateObject,但不会在确定请求应生成的对象的名称以及要包含对象的存储桶的存储桶名称之前调用。这些名称具有默认值,但是该函数尝试查找提供特定值的 HTTP 请求查询参数值。请注意,这仅适用于函数的 HTTP 触发器,不适用于使用 fn 调用进行的调用。





func myHandler(ctx context.Context, in io.Reader, out io.Writer) {
objectName := "defaultObjectName.txt"
bucketName := "the-bucket"
fnctx := fdk.GetContext(ctx)             // fnctx contains relevant elements about the Function itself
fnhttpctx, ok := fnctx.(fdk.HTTPContext) // fnhttpctx contains relevant elements about the HTTP Request that triggered it
if ok {                                  // an HTTPContent was found which means that this was an HTTP request (not an fn invoke) that triggered the function
    u, err := url.Parse(fnhttpctx.RequestURL())
......


	  

您可能需要在 Project Fn Go FDK 文档中检查有关 Fn 上下文的详细信息,特别是在示例中。

该函数需要知道存储桶应驻留在哪个 OCI 区间中。此类运行时设置通常通过配置在应用程序的函数上定义。可以在上下文中的映射中从函数中读取配置值,如以下行所示:





	if compartmentOCID, ok := fnctx.Config()["compartmentOCID"]; ok {

	  

使用 Fn CLI 构建和部署函数

假设您位于安装 Fn CLI 的本地开发环境中的终端,并且当前目录是 functions/object-broker,则可以使用详细输出执行函数的本地构建:




fn -v build

	  

当构建看起来不错时,下一步要采取的步骤是部署函数。如果 Fn 上下文设置为使用关联上下文(使用 fn 列表上下文进行检查),则这将将该函数部署到 OCI 上的关联应用程序:




fn -v deploy --app go-on-oci-app

	  

仅当为区间 OCID 值定义了配置时,该函数才能执行有意义的工作。可以通过控制台或通过 Fn CLI 使用以下语句来实现这一点:




fn cf f go-on-oci-app object-broker compartmentOCID 


	  

现在该函数已部署并具有其配置。您可能希望使用此命令调用函数会成功:




fn invoke go-on-oci-app object-broker

	  

但是,还有一个要处理的问题:该函数使用 OCI Object Storage Service API 来处理存储桶和对象,但需要明确授予权限才能执行此操作!我们使用一个动态组和两个策略来实现这一点。

用于处理对象和存储桶的函数的权限

正如我们使用动态组创建表示部署管道和构建管道的被授权者一样,我们还需要创建一个动态组,其中包含要向其授予权限的函数。要创建动态组,请在搜索栏中键入 dyn。单击搜索结果窗格中的“动态组”链接。

在动态组的概览页上,单击“创建动态组”。

输入部署管道的动态组的名称,例如,fun-in-go-oci,并根据需要键入说明。定义以下规则,以选择区间中的所有函数:




All {resource.type = 'fnfunc', resource.compartment.id = ''}

	  

当然,请将 替换为您正在使用的区间的标识符。然后按“Create(创建)”。

要在控制台中创建策略:请在搜索栏中键入 poli,然后在搜索结果弹出式菜单的“服务”区域中单击“策略”>“身份”。这将转到当前区间的“策略概览”页。

第一个策略语句定义函数管理区间中对象的权限。第二条语句添加了管理存储桶的权限。定义名称、说明和以下语句:




allow dynamic-group functions-in-go-on-oci to manage objects in compartment go-on-oci
allow dynamic-group functions-in-go-on-oci to manage buckets in compartment go-on-oci

	  

下图描述了保存包含这些语句的策略时现在应用于函数的权限:

逐条处理

现在,该函数可以调用,并且应该能够使用存储桶和对象的默认名称来执行它。

fn invoke go-on-oci-app object-broker

验证存储桶是否已创建,以及它是否包含使用控制台从 OCI URL 到存储桶页新建的对象。

将 API 网关中的路由添加到触发器函数

为了能够从任何位置通过 HTTP 调用 object-broker 函数,我们将再次使用 API 网关。在控制台的搜索栏中键入 gat。单击 > 网关 > API 管理。单击 api-gateway 的链接。单击“Deployments(部署)”。在包含部署(包含单个部署)的列表中,单击 myserver-api 链接。

单击“编辑”以打开部署规范。单击第二步的链接:路由。向下滚动并单击 + 其他路由。

键入 /object-broker 作为此新路由的路径。选择 GET 作为方法,选择 Oracle Functions 作为后端的类型。选择应用程序 go-on-oci-app,然后将函数名称设置为 object-broker。按“下一步”,然后按“保存更改”以应用更改并使新路由变为实际。

现在从 HTTP 使用者通过 API Gateway 到 Function 配置的端到端图片,最后是存储桶和对象如下所示:

逐条处理

使用以下方法从浏览器调用函数或在命令行中使用 curl:




curl -X "GET" "http:///my-api/object-broker?objectName=new-exciting-object.txt&bucketName=the-ultimate-collection"


	  

自动构建和部署

此函数 object-broker 是使用 Fn CLI 在命令行上手动部署的。当然,这很好。但是,如果您现在开始开发此函数,在经历多个开发周期时,您可能希望在构建和部署过程中引入自动化。

正如我们之前所做的那样,您可以在 OCI DevOps 中轻松设置所需的元素,以实现部署(容器映像注册表中的函数容器映像)和构建的自动化管道(从代码资料档案库开始,并为函数生成新生成的容器映像)。特定于函数的主要元素是构建管道中托管构建阶段的构建规范文件。此文件作为 go-function-build-spec.yaml 提供在与 func.go 和 func.yaml 相同的目录中。

为函数容器映像、函数环境以及用于构建和部署的两个管道创建 DevOps 对象后,自动设置的 DevOps 进程将如下所示:

逐条处理

总结

本文的一个重点领域是无服务器函数,使用 Go 编写并在 Oracle Cloud Infrastructure 上运行。讨论了这些函数的自动构建和部署,以及使用 API Gateway 向外部 HTTP 使用者提供对函数的访问。

第二个主要主题是适用于 OCI 的 Go SDK,用于从 Go 应用与 OCI 服务进行交互。文章展示了如何使用 Go 代码访问对象存储服务来存储和检索文件。

两个主题在函数 object-broker 中组合。此 OCI 函数利用通过动态组授予的资源主用户验证和权限。通过运行时配置,该函数可了解当前特定于环境的设置。

在下一篇文章中,与 Oracle Database 交互将是主要主题。创建从 Go 应用程序到本地 Oracle Database 的连接以及在 OCI 上运行的 Autonomous Database 的连接,并在您熟悉的 Go 应用程序中对这些数据库执行 SQL 操作。其他主题包括使用 Oracle Wallet 正确管理数据库身份证明(包括在部署过程中使用 wallet),以及在单个应用程序中结合与 OCI Object Storage 和 Autonomous Database 服务的交互。

注:为免疑义,本网页所用以下术语专指以下含义:

  1. 除Oracle隐私政策外,本网站中提及的“Oracle”专指Oracle境外公司而非甲骨文中国。
  2. 相关Cloud或云术语均指代Oracle境外公司提供的云技术或其解决方案。