这是关于 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 代码以最简单的形式展示了一些机制,以实现最大清晰度和最小依赖性。读者不应该期望有意义的功能或生产就绪的代码。
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
使用者通过 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 并接收其响应。
因此,我们现在已经建立了从 API 网关到无服务器功能的端到端流。我们还有一种方法可以基于本地开发环境中的源手动部署函数。为了利用我们的工作,您可以在 func.go 中对源代码进行一些更改,然后再次部署该函数(带有 Fn CLI 的单个命令),并在 API 网关上调用问候路由,以查看我们的更改是否已生效。
例如:将设置消息值的行更改为
Msg: fmt.Sprintf("Warmest greetings from your function dear %s", p.Name)
# 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}
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
完成所有操作并报告成功后,您可以在 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 应用可以简单地在此配置上构建,而无需处理任何详细信息。
..................
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 通信的代码可能要简单一些。
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())
......
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