隨時隨地使用 Oracle Cloud Infrastructure Functions 和 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 資源。大部分使用的資源都可在 Aways Free Tier (運算執行個體、VCN、Autonomous Database、Object Storage、Logging、資源管理器) 中使用,或者在每月有限使用時使用免費分配層 (Functions、API Gateway、Streaming、Vault、DevOps)。

此系列的第一個部分描述根據 Oracle Linux Cloud Developer 映像檔佈建運算執行處理、開啟以進行輸入和輸出網路活動、建立並執行提供 HTTP 要求服務的 Go 應用程式,以及將應用程式產生的日誌記錄連線至 OCI 日誌記錄。第二部分是使用 OCI DevOps 服務處理軟體工程、建置自動化及部署應用程式。此服務用於儲存 Go 原始程式碼、建立應用程式執行檔、將它儲存為可部署的使用者自建物件,以及將該使用者自建物件部署到運算執行處理。第二篇文章也說明如何透過 OCI API Gateway 顯示應用程式的 HTTP 端點。

第三個部分顯示如何在 Go 中建立無伺服器函數,並在 OCI 上部署這些函數。引進適用於 OCI 的 Go SDK - 最初適用於本機、獨立的 Go 應用程式,隨後使用函數 - 利用資源主體認證。此 SDK 可與 OCI Object Storage 服務互動,以建立儲存桶以及寫入和讀取檔案。

一開始會手動建立和建置函數。路由會新增至 API 閘道中的部署,以從 OCI 外部的從屬端呼叫函數。接著會建立 OCI DevOps 部署管線,以從「容器映像檔登錄」中的映像檔部署函數。最後,組建管線設定為在程式碼儲存區域中取得來源、組建和發布容器映像檔,然後觸發部署管線以進行端對端組建和部署。

隨時可用的 OCI 函數

OCI 中的無伺服器函數是以開源專案 Fn 的技術為基礎。函數的業務邏輯是以您喜愛的語言撰寫 – 在此情況下為 Go – 並內嵌於處理函數生命週期與函數互動的 Fn 架構中。Fn 架構可以在任何地方執行:在您的本機機器、任何雲端的 VM 或內部部署。Oracle Cloud Infrastructure 針對以相同技術為基礎的無伺服器函數,提供完全受管理的 PaaS 服務 OCI 函數。

函數內建於容器映像檔中。此映像檔會推送至容器映像檔登錄檔。若要發布功能,此影像會傳輸至 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 架構在建置時解譯,之後在執行函數時解譯。檔案 go.mod 包含此函數在 fdk-go 套裝軟體上的相依性。實際的函數本身位於 func.go。此處可查看函數的結構及其與 Fn 程式實際執行的互動:函數主要將函數 myHandler 註冊為 Fn 程式實際執行,此函數會指示並讓程式實際執行呼叫收到之任何 HTTP 要求的函數。此函數會接收 io.Reader 參數中要求的主體。它也會接收可寫入回應主體的 io.Writer 輸出。context.Context 參數 ctx 包含原始要求的中繼資料,包括 HTTP 標頭、完整 URL、要求方法和函數本身,包括為其定義的所有組態變數。

目前,myHandler 函數會將要求主體解碼,預期它會包含名稱為欄位的 JSON 有效負載。它會建立其名稱設為此欄位值的人員,或在其缺席時,預設為 World。接著會建立預期的回應:JSON 物件的單一欄位稱為訊息,其中包含由 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 主控台建立應用程式。在搜尋方塊中輸入應用程式,然後按一下「服務區域」中的「應用程式功能」。

按一下「建立應用程式」按鈕。輸入應用程式的名稱:go-on-oci-app。選取在文章系列之一及其一個公用子網路中建立的 VCN。然後按一下「建立」以建立應用程式。

路對聯單車

準備用於 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 。按一下連結「容器登錄」>「容器與使用者自建物件」。
  2. 按一下「建立儲存區域」。輸入儲存區域的名稱:go-on-oci/greeter。這是由儲存區域前置碼和函數的名稱所組成,儲存區域會在其中包含映像檔。設定「公用存取」。
  3. 路對聯單車
  4. 按一下「建立儲存區域」按鈕。在幾秒鐘之後,就會建立新的空白容器映像檔儲存區域,準備好接收將使用 Fn CLI 推送的功能 (容器) 映像檔。

在 Fn CLI 中建立 OCI 的相關資訊環境

移回本機環境的命令行,我們需要為 OCI 上目前的區間建立 Fn 相關資訊環境,然後將其選取以用於 Fn 作業。執行下列命令 (您可以從 go-on-oci-app 頁面的「入門」頁籤複製):

 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 

第一個呼叫將花費很長一段時間,因為函數開始變得冷,而基礎容器映像檔需要建立到執行中的容器中。每個後續的函數呼叫都將更快。請注意,如果您等待 10 分鐘且函數感冷,容器就會停止。

此圖像描述我們到達的情況:

路對聯單車

您可以從 OCI 主控台中查看剛才發生的證據。在主控台的搜尋方塊中輸入 greeter。在「資源」(Resources) 下,將有 >greeter > Functions> 項目。按一下連結,即可移至顯示函數詳細資訊的頁面。您會發現函數影像的參照、記憶體設定以及呼叫函數的端點。在指標下,您應該找到使用 Fn CLI 呼叫函數的證據。

在 greeter 的搜尋結果中,您還可以找到 Container Repository go-on-oci/greeter。當您導覽至儲存區域時,您會找到發布至該儲存區域之映像檔的詳細資訊。

建立函數的 API 閘道路由

不僅可以呼叫 OCI 函數。即使 HTTP 端點似乎建議您只要從瀏覽器或命令行的 Curl 呼叫它們,但實際上並不太簡單。必須簽署對函數的 HTTP 呼叫,而且此簽署處理作業並不簡單且直接。

允許用戶透過 API 閘道呼叫函數的較佳方式。在上一篇文章中,我們使用 API Gateway 開啟在私有運算實例上執行的主伺服器應用程式的公開路由。現在,我們會在 API Gateway 中使用其他路由以及在上篇文章中建立的 myserver-API 部署,對 greeter 函數執行相同的動作。

路對聯單車

設定 API 閘道的 IAM 存取

必須允許 API 閘道使用提供 API 閘道呼叫函數權限的原則來呼叫函數。

建立 API 閘道原則以呼叫函數。若要在主控台中建立原則:在搜尋列中輸入 poli,然後按一下搜尋結果即現式視窗中服務區域的 > 原則 > 識別 >。這會將您帶到目前區間的「原則」總覽頁面。

此原則定義 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 = ''}

取代為區間的名稱,這很可能是關聯性的。將 取代為您目前使用中區間的 ID。

定義 API 閘道部署中函數的路由

在處理權限之後,我們現在可以定義 API 閘道上的路由。在主控台的搜尋列中輸入 gat。按一下閘道 > API 管理。按一下 *the-api-gateway 的連結。按一下「部署」。在部署清單中 (包含單一部署),按一下 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 部署管線進行部署。(請注意,「函數」不必有用 - 它可以根據「暫用」容器影像來進行。)
  3. 部署管線,其「部署管線階段」類型為「函數部署」,可連接構件與環境。
  4. 包含部署管線的動態群組,以及允許動態群組讀取使用者自建物件 (例如函數容器映像檔) 和部署函數 (對外說話、管理 OCI 資源) 的 IAM 原則。

建立函數容器映像檔的 DevOps 使用者自建物件

在 OCI 主控台中,瀏覽至 DevOps Project go-on-OCI 的首頁。開啟「人工因素」頁籤。按一下「新增使用者自建物件」按鈕。請注意,我們在此處定義的是從 DevOps 專案到實際使用者自建物件的連結或代理主機,而不是使用者自建物件本身。

輸入 greeter-function 作為 DevOps 使用者自建物件的名稱。類型應設為「容器」映像檔儲存區域。影像的完整路徑包含區域索引鍵、儲存區域命名空間和前置碼、函數名稱以及函數版本標記。在此情況下,請使用版本標記的預留位置。路徑現在定義如下:

///greeter:${imageVersion}

將此構件中使用的下拉式欄位「取代」參數設為「是」,取代預留位置。

路對聯單車

按一下「新增」按鈕以完成並儲存構件的定義。

定義函數的 DevOps 環境

開啟 DevOps 專案中的「環境」頁籤。它包含為將 myserver 部署至 Compute 執行處理所建立的 go-on-oci-vm 環境 (在上篇文章中)。按一下「建立環境」按鈕。

在第一個步驟中,按一下「基本」資訊並排「函數 - 建立函數的環境」。輸入 greeter-function-in-app-go-on-oci-app 作為環境的名稱。按「下一步」即可移至包含「環境」詳細資訊的第二個步驟。確認區域、區間、應用程式以及功能 – 您可能不需要變更這些設定值。如果這麼做,請確定已選取應用程式 go-on-oci-app 中的函數 greeter。

按一下「建立」環境即可儲存定義。

路對聯單車

建立部署函數的部署管線

在 DevOps 專案的總覽頁面上,按一下「建立管線」。會顯示「建立管線」表單。輸入名稱 (deploy-greeter-function-to-go-on-oci-app) 並視需要輸入描述。然後按一下「建立」管線。已建立部署管線,但其相當空白:不是應該部署的環境、沒有要部署的構件,也沒有組態檔可定義要執行的步驟。

在顯示的業務進程編輯器中,按一下「新增階段」方磚 (或加號圖示)。下一頁顯示階段類型的清單。按一下標記為「使用內建函數更新策略」的磚塊。

按下「下一步」按鈕。

輸入階段名稱,例如 update-function-greeter。選取先前為函數定義的環境:greeter-function-in-app-go-on-oci-app。

在標題「人工因素」下,按一下「選取人工因素」。會顯示 Docker 映像檔類型之 DevOps 專案中的所有使用者自建物件清單。選取先前為「函數容器映像檔」建立的項目。

請注意,「選取使用者自建物件」按鈕已不再啟用:只有單一容器映像檔可以與此階段關聯。

路對聯單車

按一下「新增」。管線階段會在管線中建立。管線現在已可供執行 – 其定義已完成。還是?此管線所使用的使用者自建物件未依不相等定義:容器映像檔路徑中的版本標籤包含預留位置 ${imageVersion}。為了確保部署使用正確的版本,必須以正確的值取代此預留位置。而且,透過在管線中定義稱為 imageVersion 且設為現有版本標籤的參數來排列。

按一下管線的「參數」頁籤。定義名為 imageVersion 的新參數。其預設值可以是任何值,但也可能與 greeter 函數容器映像檔的現有版本標籤對應。儲存參數定義。

業務進程似乎已準備好執行,但我們仍必須確定允許執行其工作。試試看任何破折號之前,請先閱讀下一節。

動態群組和原則

在上一篇文章中,已為區間中的所有部署管線定義動態群組。新的管線會自動成為該群組的成員。我們還定義了將權限授予動態群組以讀取所有使用者自建物件的原則,包括區間容器映像檔登錄儲存區域中的 (函數) 容器映像檔。另一個已經建立的原則會授予動態群組管理區間中所有資源的非常廣泛權限。我們可以從該政策的廣泛範圍中獲益,因為它也涵蓋函數的建立和更新。

執行部署管線

按「執行」管線以執行部署管線。

部署完成後,您會看到成功宣告的綠色標記。不過,沒有其他明顯的跡象顯示這項成功,因為最終結果就是我們從 Fn CLI 命令行手動部署函數所達成的確切情況。

路對聯單車

為了讓事情更有趣,我們會變更函數的程式碼。接著,建立函數的容器映像檔 (本機),然後將新函數映像檔推送至容器映像檔登錄檔。然後,我們將再次啟動部署管線;這次成功時,它將產生新的情況,透過在 API 閘道上呼叫 my-API/greeting 路由來體驗。

變更函數實行

對您本機環境中的 func.go 進行小幅但可見的變更:確保新版函數的回應與目前版本明顯不同。儲存變更。

在接下來的幾節中,我們將從變更的來源建置新版本的函數容器映像檔,最終在 OCI 函數上執行。

建立新的函數容器映像檔 (本機)

下一個指令會先修改用於標記函數的版本標籤,再增加第三個數字 (bm 短於凸起)。接著,會使用變更後的來源建立函數容器映像檔。第三個命令會列出本機容器映像檔,並在其名稱中使用 greeter 篩選映像檔。現在請執行指令。




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 閘道上的路由:




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

	  

自動化的函數組建

到目前為止,我們已使用 Fn CLI 在本機開發環境中手動建立功能容器映像檔。不過,就像我們先前針對「建置業務進程」產生為可執行檔之 Go 應用程式的文章一樣,我們現在會將此函數的建置變成自動化程序。建立的「OCI DevOps 組建管線」可取得「程式碼儲存區域」的來源、執行產生本機容器映像檔的受管理組建階段,然後將此映像檔發布為「容器映像檔登錄」儲存區域的使用者自建物件。最後一步是,「組建管線」會觸發「部署管線」,將函數的最新定義發布至程式實際執行 OCI 函數環境。

當所有元素都到位時,互連的 OCI 元件集總計會在下圖中以視覺化方式呈現。

路對聯單車

此圖中顯示的使用者自建物件和部署管線已經在 DevOps 專案中定義,就像函數之影像的「應用程式」、「函數」和「容器影像登錄」儲存區域一樣。我們將使用上一篇文章中設定的程式碼儲存區域。我們需要建立的所有項目,都是組建管線組建 (greeter) 功能與其三個階段。

建立組建管線

在 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 來源的指示,最後建立函數容器映像檔。

按一下主要程式碼儲存區域底下的「選取」按鈕。我們現在可以指定組建將從哪個程式碼儲存區域取得其來源。選取 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. 執行 go vet 以對 Go 來源執行第一次檢查
  6. 執行 go fmt 以根據一般格式化規則來格式化來源
  7. 根據不同的列印規則執行 golangci-lint 以 lint (check) 源碼
  8. 執行單位測試
  9. 使用 Fn CLI 將函數來源建置到函數容器映像檔 (儲存在本機映像檔登錄檔中)
  10. 以 go-function-container-image 名稱標記「函數容器映像檔」。這是下一個階段用來尋找要發布影像的名稱

這些步驟大致上等同於 Go 應用程式上一篇文章中定義的受管理組建,這些組建最終轉變成在 VM 上部署的二進位執行檔。步驟 9 和 10 不同 – 這些步驟會將 Go 應用程式變成一個 Function Container Image,這是組建的最終產品。

第二階段 – 發佈人工因素

在組建管線的總覽頁面中,按一下目前受管理組建階段底端的加號圖示。在彈出式內容功能表中,按一下「新增」階段。即會出現階段精靈。

按一下「傳遞」人工因素。然後按一下「下一步 (Next)」。

輸入此階段的名稱:publish-greeter-function-container-image。我們必須在要發布的 DevOps 專案中選取使用者自建物件。此使用者自建物件是容器映像檔 go-on-oci/greeter:${imageVersion}。按一下「選取使用者自建物件」,然後選取容器映像檔。

在「將使用者自建物件與組建結果建立關聯」區域中,我們必須針對每個選取的使用者自建物件指示受管理組建階段的結果,是發布使用者自建物件的來源。組建規格定義一個標示為 go-function-container-image 的輸出。此輸出指的是函數組建處理作業在組建伺服器上產生的容器映像檔。在「組建」組態 / 結果使用者自建物件名稱欄位中輸入 go-function-container-image 標籤。按「新增」按鈕以建立「傳遞使用者自建物件」階段。

第三階段 – 觸發部署管線

在組建管線的總覽頁面中,按一下「傳遞」使用者自建物件階段底端的加號圖示。在彈出式內容功能表中,按一下「新增」階段。即會出現階段精靈。

按一下「觸發程式」建置。然後按一下「Next」。

輸入階段的名稱:trigger-deployment-of-greeter-function-to-go-on-oci-app,以及選擇性的描述。按一下「選取部署管線」按鈕。選取管線 deployment-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"}

	  

這是一個小慶祝的時刻。我們已實現自動化端對端流程,從 Code Repository 取得 Go 應用程式來源,並在 linting,vetting,test and Building - 一個無伺服器 OCI 函數的即時執行新版本後提供。若要進一步更新函數,我們只需要確認變更並觸發組建管線。此外,在下一個步驟中,我們甚至可以在程式碼儲存區域發生 (特定) 確認時自動觸發組建管線。

在本文的第二部分中,我們將討論一般使用 Go 應用程式的 OCI 服務,以及特別是 Go 功能。導入 Go SDK for OCI,並示範與 OCI Object Storage Service 的互動。

Go SDK for OCI – 與 Go 應用程式的 OCI Object Storage 互動

Oracle Cloud Infrastructure 是一個可在 Go 中開發並執行應用程式的平台。無論是運算執行處理或無伺服器函數,還是容器化在受管理的 Kubernetes 叢集上 (我們將在本系列的五個部分討論)。了解 OCI 對 Go 開發團隊而言比執行階段平台多。OCI 提供許多可從應用程式運用的服務。用於儲存檔案、關聯式或 "NoSQL" 資料的服務,用於處理發佈及使用訊息。移轉與分析資料的服務。以及支援應用程式作業的服務,例如監控。

您可以透過適用於所有服務各個方面的 REST API,與 OCI 服務互動。透過 HTTP 呼叫具有 JSON 有效負載的 REST 服務,從 Go 應用程式輕鬆完成。有一個複雜因素:需要簽署這些 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 導向使用者帳戶的特定租用戶和區域。前往使用 Go SDK for OCI 的應用程式,只需在此組態上建置,無須處理任何詳細資訊。

有關 OCI Go SDK 的文件,請參閱 OCI 文件 – SDK for Go

在本節中,我們將開發使用 OCI Object Storage Service 來建立和讀取檔案的簡單 Go 應用程式。首先,這是一個可隨時隨地編譯和執行的獨立應用程式 (只要 OCI 組態檔可供使用)。接著,我們討論在 VM 或 Function 上執行 OCI 內的 Go 應用程式。此特殊焦點很相關,因為在 OCI 內使用 Go SDK 時,可以運用執行應用程式之元件的身分識別與權限。這表示在 OCI 上執行的程式碼不需要自備 OCI 組態檔,因此更簡單。

與 OCI Object Storage Service 交談的任何 Go Application

首先,讓我們建立一個非常簡單的 Go 應用程式,透過 SDK 連線至 OCI。接下來,我們將使用此基礎來新增與物件儲存體服務的互動。

OCI 最簡單的 Go Client

使用 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 物件儲存服務中的物件

OCI Object Storage Service 為不同類型的資料提供便宜且持久的儲存。大小和小的物件可以程式設計方式儲存在此服務上,以保留一段短時間或長時間,並可透過程式設計方式或透過直接 URL 進行存取。物件儲存提供版本控制、不同的保留規則、任何已儲存資料的加密、物件生命週期管理、自動化時間型移除,以及不同的儲存層。

物件儲存體服務上的物件會依儲存桶分類。儲存桶是存放在特定區間中的物件邏輯容器。部分作業可以在儲存桶中的所有物件上執行。

適用於 OCI 的 Go SDK 提供功能和類型,讓您輕鬆直接使用 Go 應用程式的物件儲存體服務。可輕鬆使用操控儲存桶以及建立、刪除及擷取物件的作業。

如需 OCI Go SDK 中物件儲存套件的詳細資訊,請參閱此參考資料: OCI Go SDK 中物件儲存套件的文件

此文章的來源儲存區域包含資料夾應用程式 /store-n-retrieve,其中有一個簡單的 Go 應用程式可連線至您的 OCI 租用戶並建立儲存桶,接著建立物件並擷取該相同物件。應用程式使用與上一個區段相同的 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 主控台中存入儲存桶進入儲存桶的詳細資訊頁面,以查看儲存桶和建立的物件。





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

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
}


	  

執行此應用程式 – 執行 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 Object Storage Service 交談的基礎 OCI 函數

上一節討論透過 SDK 與 OCI 服務互動的任何 Go 應用程式。那麼,Go 中的 OCI Functions 還有更多內容?請繼續閱讀,因為當您開發的 Go 程式碼以及想要使用 Go SDK 的 Go 程式碼將部署為 OCI 函數或在 OCI 的運算執行處理上執行時,我們可以讓生活變得更簡單。在這些情況下,我們可以利用資源主體認證 (用於函數) 和執行處理主體認證 (用於在運算執行處理上執行的程式碼),這表示我們不需要包括 OCI 組態檔,而與 SDK 交談的程式碼可以稍為簡單。

請參閱 OCI Documentation on Resource Principal Authentication for Functions 中的更多有關資源主體認證的資訊。

在本節中,我們會討論名為 object-broker 的 OCI 函數。您會在路徑 function/object-broker 中找到此文章之來源儲存庫中的來源。如之前,函數是使用含有中繼資料的 func.yaml 檔案以及含有函數與 Fn 架構之間連結的 func.go 檔案來定義。檔案 go.mod 定義函數的相依性。與 OCI Object Storage Service 互動的邏輯位於檔案物件 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())
......


	  

您可以特別在範例中,檢驗 Documentation for Project Fn Go FDK 中有關 Fn Context 的詳細資訊。

此函數需要知道儲存桶所在的 OCI 區間。這類執行時期設定通常透過組態在應用程式的任一功能上定義。組態值可以從環境定義中的對應在函數中讀取,如這一行所示:





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

	  

使用 Fn CLI 建置及部署函數

假設您位於安裝 Fn CLI 之本機開發環境的終端機中,且目前的目錄為 function/object-broker,則您可以使用詳細輸出來執行函數的本機建置版本:




fn -v build

	  

當組建看起來良好時,下一個要採取的步驟是部署函數。如果 Fn 相關資訊環境設為使用 Go-on-OCI 相關資訊環境 (檢查 fn 清單相關資訊環境),這會將此函數部署至 OCI 上的 go-on-OCI-app 應用程式:




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 來操控儲存桶和物件,但必須已明確授予權限才能這麼做!我們使用動態群組和兩個原則來達成此目標。

函數操控物件和儲存設定 (Bucket) 的權限

就像使用動態群組來建立代表部署管線和組建管線的受權者一樣,我們還需要建立一個動態群組,其中包含要授予權限的功能。若要建立動態群組,請在搜尋列中輸入 dyn。按一下搜尋結果窗格中的「動態群組」連結。

在動態群組的總覽頁面中,按一下「建立動態群組」。

輸入「部署管線」的「動態群組」名稱,例如:自行開機,以及選擇性輸入描述。定義下列規則以選取區間中的所有函數:




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

	  

當然,請將 取代為您目前所在區間的 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 呼叫物件代理程式函數,我們會再次使用 API 閘道。在主控台的搜尋列中輸入 gat。按一下 > 閘道 > API 管理。按一下 API 閘道的連結。按一下「部署」。在包含部署 (包含單一部署) 的清單中,按一下 myserver-api 連結。

按一下「編輯」以開啟部署規格。按一下第二個步驟的連結:路由。向下捲動並按一下 + 其他路由。

輸入 /object-broker 作為此新路由的路徑。選取 GET 作為方法,Oracle Functions 作為類型 (後端)。選取應用程式 go-on-oci-app,然後將函數名稱設為 object-broker。按「下一步」,然後按「儲存變更」以套用變更並使新路線成為實際。

從 HTTP 用戶透過 API 閘道對函數設定的端對端圖片,最後的儲存桶和物件看起來如下:

路對聯單車

從瀏覽器呼叫函數,或使用命令行上的 curl 使用:




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


	  

自動化的組建與部署

此函數 object-broker 使用 Fn CLI 在命令行上手動部署。那樣做得很好。不過,如果您現在要開始開發此功能,在經過多個開發週期後,您可能會想要在建置和部署程序中導入自動化。

就像我們之前完成的一樣,您可以在 OCI DevOps 中輕鬆設定必要的元素,以實現部署的自動化管線 (容器映像檔登錄中的函數容器映像檔) 和組建 (從程式碼儲存區域開始,然後為函數產生全新烘焙的容器映像檔)。函數專用的主要元素是組建管線中受管理組建階段的組建規格檔案。此檔案是在與 func.go 和 func.yaml 相同的目錄中提供為 go-function-build-spec.yaml。

為「函數容器映像檔」建立 DevOps 使用者自建物件、「函數」的環境以及用於建置和建置的兩個管線之後,自動 DevOps 處理作業設定如下所示:

路對聯單車

結論

本文中的一個重點領域是無伺服器功能,使用 Go 撰寫並執行 Oracle Cloud Infrastructure。我們討論了這些功能的自動建置和部署,就像使用 API 閘道為外部 HTTP 用戶提供功能存取權一樣。

第二個主要主題是 Go SDK for OCI,用於與 Go 應用程式的 OCI 服務互動。這篇文章說明如何使用 Go 程式碼存取 Object Storage 服務以儲存和擷取檔案。

兩個主題已結合在函數物件中介中。此 OCI 函數會利用透過動態群組授予的資源主體認證和權限。透過程式實際執行組態,此功能可瞭解目前的環境特定設定值。

在下一篇文章中,與 Oracle Database 互動將會是主要主題。從 Go 應用程式建立連線到本機 Oracle Database,以及在 OCI 上執行的 Autonomous Database,並透過 Go 應用程式的這些資料庫執行 SQL 作業。其他主題包括使用 Oracle Wallet 妥善管理資料庫證明資料 (包括部署處理作業中的公事包),以及在單一應用程式中結合與 OCI Object Storage 和 Autonomous Database 服務的互動。