Questa è la terza puntata di una serie di cinque parti su Go e Oracle Cloud Infrastructure. Questa serie descrive come creare ed eseguire le applicazioni Go su Oracle Cloud Infrastructure (OCI) nelle istanze di computazione (VM), containerizzate su Kubernetes o come funzioni serverless. Gli articoli mostrano come automatizzare la creazione e la distribuzione di queste applicazioni Go utilizzando OCI DevOps. Un argomento importante è come utilizzare i servizi OCI delle applicazioni Go, sia quelli in esecuzione su OCI che quelli Go in esecuzione altrove. Alcuni dei servizi OCI discussi sono Object Storage, Streaming, Key Vault e Autonomous Database.
Per seguire questi articoli, i lettori dovrebbero avere almeno una conoscenza di base su come creare applicazioni Go. Si presume che i lettori abbiano accesso al proprio ambiente di sviluppo Go. Alcuni esempi e screenshot menzioneranno specificamente VS Code come strumento di sviluppo. Tuttavia, possono essere utilizzati anche altri editor e IDE. Il codice Go presentato in questi articoli dimostra una serie di meccanismi nella loro forma più semplice per la massima chiarezza e con le meno dipendenze. I lettori non dovrebbero aspettarsi funzionalità significative o codice pronto per la produzione.
Gli articoli descrivono come passare a OCI. Per provare gli esempi, i lettori dovranno avere accesso a una tenancy OCI con le autorizzazioni per creare le risorse OCI discusse in questi articoli. La maggior parte delle risorse utilizzate è disponibile nel livello gratuito Aways (istanza di computazione, VCN, Autonomous Database, storage degli oggetti, log, Resource Manager) o dispone di un livello di assegnazione gratuito per un uso mensile limitato (Funzioni, gateway API, streaming, vault, DevOps).
La prima parte di questa serie descrive il provisioning di un'istanza di computazione basata sull'immagine sviluppatore cloud Oracle Linux, l'apertura per l'attività di rete in entrata e in uscita, la creazione e l'esecuzione di un'applicazione Go che soddisfa le richieste HTTP e la connessione dei log prodotti dall'applicazione a OCI Logging. La seconda parte riguarda l'ingegneria del software, l'automazione della creazione e la distribuzione dell'applicazione con il servizio DevOps OCI. Questo servizio viene utilizzato per memorizzare il codice sorgente Go, creare l'eseguibile dell'applicazione, memorizzarlo come artifact distribuibile e distribuire tale artifact in un'istanza di computazione. Il secondo articolo mostra anche come esporre un endpoint HTTP per l'applicazione tramite un gateway API OCI.
Questa terza parte mostra come creare funzioni serverless in Go e distribuirle su OCI. Viene introdotto Go SDK per OCI, prima per le applicazioni Go locali, autonome e successivamente per l'uso dalle funzioni, utilizzando l'autenticazione del principal delle risorse. Questo SDK viene utilizzato per interagire con il servizio di storage degli oggetti OCI per creare bucket e scrivere e leggere file.
Inizialmente la funzione viene creata e distribuita manualmente. Viene aggiunto un instradamento alla distribuzione nel gateway API per richiamare la funzione da un client esterno a OCI. Viene quindi creata una pipeline di distribuzione DevOps OCI per distribuire la funzione da un'immagine nel registro delle immagini del contenitore. Infine, viene impostata una pipeline di build per acquisire origini nel repository di codici, creare e pubblicare un'immagine contenitore, quindi attivare la pipeline di distribuzione per la build e la distribuzione end-to-end.
La funzione serverless in OCI si basa sulla tecnologia del Project Fn open source. La logica di business della funzione è scritta nella tua lingua preferita - in questo caso Go - e incorporata nel framework Fn che gestisce il ciclo di vita della funzione e le interazioni con la funzione. Il framework Fn può essere eseguito ovunque: sul tuo computer locale, in una VM su qualsiasi cloud o on-premise. Oracle Cloud Infrastructure offre un servizio PaaS completamente gestito OCI Functions per funzioni serverless basate sulla stessa tecnologia.
Una funzione è incorporata in un'immagine contenitore. Questa immagine viene inviata a un registro di immagini del contenitore. Per pubblicare la funzione, questa immagine viene trasferita a un server Fn. Ogni volta che la funzione viene richiamata, un contenitore viene avviato dall'immagine e la richiesta viene elaborata. I container vengono mantenuti in esecuzione per un certo periodo di tempo dopo la gestione di un richiamo, in uno stato attivo pronto a gestire richieste aggiuntive. Quando il numero di richieste concorrenti aumenta, i container aggiuntivi per la stessa funzione verranno avviati dal server Fn per garantire che tutte le richieste possano essere gestite.
L'attrazione delle funzioni per sviluppatori e operatori di applicazioni è il fatto che nessuna energia deve essere riversata nella progettazione, creazione e gestione della piattaforma che gestisce la logica aziendale. Tutto può essere incentrato sulla scrittura di questa logica.
Ora esamineremo la creazione della funzione in Go, creandola in un'immagine contenitore, distribuendola ed eseguendola localmente. Successivamente, porteremo questa funzione al servizio OCI Functions e la faremo eseguire sul lato cloud.
Per sviluppare funzioni, è necessario un ambiente che supporti Project Fn. Fn è una piattaforma di funzioni serverless basata su Docker leggera che puoi eseguire su laptop, server o cloud. È possibile installare facilmente Fn su Linux o MacOS seguendo le istruzioni in Fn Project Tutorials - Install Fn.
Puoi scegliere di lavorare sull'istanza di computazione go-app-vm che abbiamo creato nella prima e utilizzato anche nella seconda rata di questa serie. Questo ambiente Oracle Linux non viene fornito con l'impostazione Fn, ma l'installazione è abbastanza semplice.
In alternativa, puoi lavorare in OCI Cloud Shell. Questo ambiente accessibile ai browser è impostato con Fn. Per utilizzare Fn in OCI Cloud Shell, consulta la sezione relativa alle funzioni della documentazione di OCI: introduzione all'uso di Cloud Shell.
Nell'ambiente di sviluppo in cui è installata l'interfaccia CLI Fn, passare a una directory in cui si desidera creare la sottodirectory della funzione. Nella riga di comando, immettere questo comando ed eseguirlo:
fn init --runtime go --trigger http greeter
Viene creata una sottodirectory chiamata greeter. Entra in esso e controlla il suo contenuto:
cd greeter ls -l
Il file func.yaml contiene i metadati relativi alla funzione, che devono essere interpretati dalla struttura Fn durante la creazione e successivamente durante l'esecuzione della funzione. Il file go.mod contiene la dipendenza della funzione dal pacchetto fdk-go. La funzione effettiva è in func.go. La struttura della funzione e la sua interazione con il runtime Fn possono essere viste qui: la funzione principale registra la funzione myHandler con il runtime Fn, che istruisce e consente al runtime di richiamare questa funzione per qualsiasi richiesta HTTP ricevuta. La funzione riceve il corpo della richiesta in un parametro io.Reader. Riceve anche l'output di io.Writer, a cui può essere scritto il corpo della risposta. Il parametro context.Context ctx contiene i metadati per la richiesta originale, incluse le intestazioni HTTP, l'URL completo, il metodo di richiesta e la funzione stessa, incluse tutte le variabili di configurazione definite per la richiesta.
Attualmente, la funzione myHandler decodifica il corpo della richiesta, prevedendo che contenga un payload JSON con un campo denominato nome. Crea una persona con il nome impostato sul valore di questo campo o, in sua assenza, il valore predefinito è Mondo. Quindi crea la risposta prevista: un oggetto JSON con un singolo campo chiamato messaggio, che contiene una stringa composta da Hello e il valore del nome.
Anche se non fa nulla di veramente spettacolare, la funzione è sana e completa e possiamo distribuirla e invocarla localmente. Per questo abbiamo bisogno di un contesto locale e del server Fn locale attivo e in esecuzione. Controllare i contesti utilizzando:
fn list contexts
Viene visualizzato un elenco di almeno un contesto, probabilmente più di uno. Per utilizzare il server Fn locale, assicurarsi che il contesto predefinito sia quello attivo. Se necessario per impostare il contesto corrente come predefinito, utilizzare:
fn use context default
Ora crea un'applicazione come host per la funzione:
fn create app go-oci-app fn list apps
Se la prima di queste istruzioni non riesce con la connessione rifiutata, è probabile che il server non sia ancora in esecuzione. Utilizzare il comando successivo per avviare il server, quindi riprovare a creare l'applicazione.
fn start
Con la creazione dell'applicazione riuscita, la funzione può ora essere distribuita in essa. Il comando successivo si occupa di questa distribuzione; è preceduto dal processo di creazione dell'immagine del contenitore.
fn --verbose deploy --app go-oci-app --local
Se si specifica --local, la distribuzione viene eseguita sul server locale, ma non viene eseguito il PUSH dell'immagine della funzione su un registro Docker, operazione necessaria se la distribuzione viene eseguita su un server Fn remoto.
Poiché scatena una quantità impressionante di messaggi di log da produrre, il flag --verbose non è uno che utilizzerai sempre. Tuttavia, ti dà una buona visione di ciò che sta succedendo. Vengono estratte diverse immagini di container, quindi viene eseguito un processo di creazione di container a due fasi per creare un'immagine di container per la funzione greeter. Le immagini predefinite del progetto Fn vengono utilizzate per la fase di build (fnproject/go:1.15-dev al momento della scrittura) e come base per l'immagine runtime (fnproject/go:1.15).
L'output finale sarà simile al seguente:
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
L'immagine della funzione si chiama greeter:0.0.2. Troverai questa immagine nel registro container locale con:
docker images | grep greeter
La funzione può essere richiamata tramite l'interfaccia CLI Fn, utilizzando il nome e l'applicazione, come segue:
fn invoke go-oci-app greeter
La funzione prevede un payload JSON contenente un campo nome. Fornire quindi esattamente le informazioni riportate di seguito.
echo -n '{"name":"Clark Kent"}' | fn invoke go-oci-app greeter --content-type application/json
L'output della distribuzione della funzione ci ha anche fornito l'endpoint di trigger per la funzione. Si tratta di un endpoint HTTP a cui è possibile inviare una richiesta HTTP e far sì che la funzione venga attivata. Non abbiamo alcuna interazione (visibile) con Fn, anche se l'endpoint che chiamiamo è in realtà l'endpoint di Fn Server. Il percorso URL indica a Fn l'applicazione e la funzione specifica da attivare.
curl -X "POST" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse"}' http://localhost:8080/t/go-oci-app/greeter
Ora, creiamo questa funzione su OCI anziché solo nel nostro ambiente di sviluppo. I passaggi sono molto simili a quelli che abbiamo usato per creare la funzione localmente; abbiamo solo bisogno di usare un contesto diverso. Non il contesto locale, ma uno per OCI.
Crea applicazione
Prima di tutto, creiamo l'applicazione tramite OCI Console. Digitare l'app nella casella di ricerca e fare clic su Funzioni applicazioni nell'area Servizi.
Fare clic sul pulsante Crea applicazione. Digitare il nome dell'applicazione: go-on-oci-app. Selezionare la VCN creata nella parte uno della serie di articoli e la relativa subnet pubblica. Quindi fare clic su Crea per creare l'applicazione.
Prepara ambiente locale per push immagine interazione e funzione OCI
Una volta creata l'applicazione, vengono presentate le informazioni generali per l'applicazione. La pagina contiene inoltre istruzioni per la creazione della prima funzione, in OCI Cloud Shell o in un'impostazione locale (che, naturalmente, potrebbe anche essere l'istanza di computazione go-app-vm).
Se si utilizza OCI Cloud Shell, i passi per creare questo contesto sono leggermente diversi (e più semplici) rispetto a quando si lavora in un ambiente di sviluppo regolare. Puoi seguire l'impostazione di OCI Shell. In questo articolo, prenderemo l'altro percorso, utilizzato per qualsiasi ambiente di sviluppo locale.
In un ambiente locale sono disponibili una serie di passi da eseguire (in cui è stata installata l'interfaccia CLI Fn):
Dopo questi passi, è possibile provare a eseguire i passi 1, 2 e 4 con questo comando, che dovrebbe restituire una lista dei compartimenti nella tenancy:
oci iam compartment list
Facoltativo: crea repository registro immagini contenitore
Se l'account utente utilizzato per la distribuzione della funzione dispone delle autorizzazioni IAM necessarie, la distribuzione creerà il repository per le immagini delle funzioni nel registro delle immagini del contenitore. Se tali privilegi non sono disponibili o si desidera preparare il repository, è possibile effettuare le operazioni riportate di seguito.
Creare un contesto per OCI nell'interfaccia CLI Fn
Tornando alla riga di comando dell'ambiente locale, dobbiamo creare un contesto Fn per il compartimento corrente nell'infrastruttura OCI e successivamente selezionarlo per l'uso nelle operazioni Fn. Eseguire i seguenti comandi (che è possibile copiare dalla scheda Introduzione nella pagina go-on-oci-app):
fn create context go-on-oci --provider oracle fn use context go-on-oci
Copiare i comandi nel passo 4 per aggiornare il contesto con l'OCID del compartimento e l'URL dell'API Oracle Functions. Nel mio caso:
fn update context oracle.compartment-id ocid1.compartment.oc1..aaaaaaaaqb4vxvxuho5h7eewd3fl6dmlh4xg5qaqmtlcmzjtpxszfc7nzbyq fn update context api-url https://functions.us-ashburn-1.oraclecloud.com
Il comando sarà simile ma diverso per te.
Fornire il prefisso del nome repository univoco. Utilizzare go-on-oci e specificare il compartimento che contiene il repository del registro immagini in cui deve essere pubblicata l'immagine della funzione:
fn update context registry iad.ocir.io/idtwlqf2hanz/go-on-oci fn update context oracle.image-compartment-id
Eseguire il login al registro utilizzando il token di autenticazione come password:
docker login iad.ocir.io
Nel mio caso, la regione in cui lavoro è Ashburn, identificata dalla chiave della regione iad.ocir.io. Viene richiesto il nome utente. Si tratta di una stringa che include il prefisso dello spazio di nomi incluso nel nome del registro delle immagini del contenitore e di ogni repository. Successivamente viene richiesta la password. Qui, si fornisce un token di autenticazione impostato per l'utente, che abbiamo utilizzato in precedenza nell'articolo precedente quando il login è stato eseguito nel repository di codici.
Il comando successivo mostra un elenco delle applicazioni nel contesto Fn corrente:
fn list apps
L'elenco contiene un'applicazione, denominata go-on-oci-app.
La funzione greeter creata, distribuita localmente e richiamata in precedenza può ora anche essere distribuita nell'applicazione OCI per diventare una funzione serverless cloud nativa. Il comando che usiamo per la distribuzione è lo stesso che abbiamo usato in precedenza. Il suo effetto è drammaticamente diverso a causa del contesto cambiato. Invece di un contesto locale, ora esiste un contesto basato su un provider OCI e collegato a una tenancy e a un compartimento OCI. L'immagine del contenitore viene sottoposta a PUSH nel registro delle immagini del contenitore OCI e la funzione viene creata nel servizio Funzione OCI.
fn -v deploy --app go-on-oci-app
L'output è simile a quello generato in precedenza, ma il processo di generazione è esattamente lo stesso. Una volta che l'immagine del contenitore di funzione è pronta, le cose iniziano a deviare. Viene eseguito il PUSH dell'immagine in OCI Container Image Registry e la funzione viene distribuita nel cloud. Le linee correlate nell'output:
=> 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
A questo punto, la funzione si trova nel cloud e può essere richiamata (ancora utilizzando l'interfaccia CLI Fn):
fn invoke go-on-oci-app greeter
La prima chiamata richiederà del tempo perché la funzione inizia a freddo e l'immagine del contenitore sottostante deve essere istanziata in un contenitore in esecuzione. Ogni successiva invocazione della funzione sarà molto più veloce. Tenere presente che se si attendono dieci minuti e la funzione diventa fredda, il contenitore viene arrestato.
Questa immagine descrive la situazione in cui siamo arrivati:
Puoi eseguire il check-in di OCI Console per le prove di ciò che è appena successo. Digitare greeter nella casella di ricerca della console. Sotto Risorse ci sarà una voce >greeter > Functions>. Fare clic sul collegamento per andare alla pagina che mostra i dettagli della funzione. Troverete riferimenti all'immagine della funzione, all'impostazione della memoria e all'endpoint per richiamare la funzione. Sotto le metriche dovresti trovare la prova della chiamata alla funzione effettuata utilizzando l'interfaccia CLI Fn.
Nei risultati della ricerca per greeter, troverai anche il Container Repository go-on-oci/greeter. Quando si passa al repository, vengono visualizzati i dettagli relativi alle immagini pubblicate in tale repository.
Le funzioni OCI non possono essere solo richiamate. Anche se hanno un endpoint HTTP che sembra suggerire di poterli chiamare semplicemente dal browser o arricciare sulla riga di comando, in realtà non è così semplice. Le chiamate HTTP alle funzioni devono essere firmate e questo processo di firma non è semplice e diretto.
Un modo migliore per consentire ai consumatori di richiamare le funzioni è tramite un gateway API. Nell'articolo precedente è stato utilizzato un gateway API per aprire un instradamento pubblico all'applicazione myserver in esecuzione in un'istanza di computazione privata (potenzialmente). Ora faremo lo stesso per la funzione greeter utilizzando un percorso aggiuntivo nel gateway API the-API-gateway e il myserver-API di distribuzione creato nell'articolo precedente.
Imposta l'accesso IAM per il gateway API
Il gateway API deve essere autorizzato a richiamare la funzione utilizzando un criterio che consenta al gateway API di richiamare le funzioni.
Creare il criterio per il gateway API per richiamare le funzioni. Per andare a creare un criterio nella console: digitare poli nella barra di ricerca e fare clic su >Criteri > Identità> nell'area Servizi del popup dei risultati della ricerca. Viene visualizzata la pagina di panoramica dei criteri per il compartimento corrente.
Il criterio definisce l'autorizzazione per i gateway API per accedere alle risorse nel compartimento. Creare un nuovo criterio, digitare un nome (invoke-function-for-api-gateway), una descrizione e la seguente istruzione:
ALLOW any-user to use functions-family in compartment where ALL {request.principal.type= 'ApiGateway', request.resource.compartment.id = ' '}
Sostituire
Definire l'instradamento per la funzione nella distribuzione nel gateway API
Con le autorizzazioni prese in considerazione, ora possiamo definire l'instradamento sul gateway API. Digitare gat nella barra di ricerca nella console. Fare clic su Gateway > Gestione API. Fare clic sul collegamento per *the-api-gateway. Fare clic su Distribuzioni. Nella lista delle distribuzioni (che contiene una singola distribuzione), fare clic sul collegamento myserver-api.
Fare clic sul pulsante Modifica per aprire la specifica di distribuzione. Fare clic sul collegamento per il secondo passo: Percorsi. Scorri verso il basso e fai clic sul pulsante + Altro percorso.
Digitare /greeting come percorso per questo nuovo percorso. Selezionare GET come metodo e Oracle Functions come tipo (di backend). Selezionare l'applicazione go-on-oci-app, quindi impostare il nome della funzione su greeter.
Premere Avanti. Quindi premere Salva modifiche per applicare le modifiche e rendere reale il nuovo percorso.
Richiama la funzione tramite il gateway API
Con il nuovo instradamento impostato e la distribuzione aggiornata sul gateway API, ora possiamo effettuare una richiesta HTTP semplice e diretta all'endpoint pubblico del gateway API, attivando indirettamente la funzione in modo più ecologico e ricevendo la sua risposta.
Utilizzando questo URL in qualsiasi browser, dovresti essere in grado di ottenere la risposta della funzione:
https:///my-api/greeting
La risposta è un po' deludente, ma ci si aspetta con una funzione così semplicistica.
Utilizzando curl, è possibile inviare un payload JSON alla funzione e ricevere una risposta leggermente più interessante.
curl -X "GET" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse"}' https:///my-api/greeting
La risposta è {"message":"Hello Mickey Mouse"}.
Quindi ora abbiamo stabilito il flusso end to end dal gateway API alla funzione serverless. E abbiamo un modo per distribuire manualmente la funzione in base alle fonti nel nostro ambiente di sviluppo locale. Per sfruttare il nostro lavoro, è possibile apportare alcune modifiche al codice sorgente in func.go e quindi distribuire la funzione ancora una volta, un singolo comando con l'interfaccia CLI Fn, e richiamare il percorso di saluto sul gateway API per vedere che il nostro cambiamento è attivo.
Ad esempio: modificare la riga che imposta il valore per il messaggio su
Msg: fmt.Sprintf("Warmest greetings from your function dear %s", p.Name)
Salvare l'origine func.go aggiornata. Quindi eseguire questi comandi per distribuire la funzione aggiornata e successivamente richiamarla:
fn -v deploy --app go-on-oci-app
curl -X "GET" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse"}' https:///my-api/greeting
Ciò dovrebbe portare a una migliore risposta. Il processo di build e deploy viene condensato in un unico comando manuale in un ambiente preparato. Successivamente esamineremo un processo di distribuzione automatizzato per le funzioni che utilizzano OCI DevOps, seguito dal precedente processo di build automatizzato basato sull'origine in un repository di codici. Poi ci sposteremo su funzioni che fanno un po 'di più che restituire semplici saluti.
Nella puntata precedente di questa serie, abbiamo visto l'uso di OCI DevOps Deployment Pipelines per distribuire un'applicazione in un'istanza di computazione. Ora utilizzeremo una pipeline per la distribuzione automatica di una funzione. L'approccio generale e gli ingredienti sono simili. Per leggere l'artifact e distribuire la funzione, sono necessarie un artifact, un ambiente (destinazione) e la pipeline di distribuzione con una fase di distribuzione delle funzioni, nonché le autorizzazioni IAM per la pipeline.
Questi ingredienti in dettaglio:
Nella console OCI, andare alla home page per il progetto DevOps go-on-OCI. Aprire la scheda Artifact. Fare clic sul pulsante Aggiungi artifact. Si noti che qui viene definito un collegamento o un proxy dal progetto DevOps a un artifact effettivo, non l'artifact stesso.
Immettere greeter-function come nome dell'artifact DevOps. Il tipo deve essere impostato su Repository immagini contenitore. Il percorso completamente qualificato dell'immagine è costituito dalla chiave dell'area, dallo spazio di nomi e dal prefisso del repository, dal nome della funzione e dalla tag della versione della funzione. In questo caso, utilizzare un segnaposto per il tag della versione. Il percorso è ora definito come indicato di seguito.
///greeter:${imageVersion}
Impostare il campo a discesa Sostituisci parametri utilizzati in questo artifact su Sì. Sostituire i segnaposto.
Fare clic sul pulsante Aggiungi per completare e salvare la definizione dell'artifact.
Aprire la scheda Ambienti nel progetto DevOps. Contiene l'ambiente go-on-oci-vm creato per la distribuzione di myserver nell'istanza di computazione (nell'articolo precedente). Fare clic sul pulsante Crea ambiente.
Nel primo passo, Informazioni di base, fare clic sulla casella Funzioni - Creare un ambiente per una funzione. Immettere greeter-function-in-app-go-on-oci-app come nome dell'ambiente. Premere Avanti per passare al secondo passo con i dettagli dell'ambiente. Confermare l'area, il compartimento, l'applicazione e la funzione. Probabilmente non è necessario modificare alcuna di queste impostazioni. In tal caso, assicurarsi che la funzione greeter nell'applicazione go-on-oci-app sia selezionata.
Fare clic su Crea ambiente per salvare la definizione.
Nella pagina di panoramica del progetto DevOps, fare clic su Crea pipeline. Viene visualizzato il form Crea pipeline. Digitare un nome (deploy-greeter-function-to-go-on-oci-app) e facoltativamente una descrizione. Quindi fare clic su Crea pipeline. La pipeline di distribuzione viene creata, anche se è piuttosto vuota: non è un ambiente in cui deve essere distribuita, non sono presenti artifact da distribuire e non è presente un file di configurazione per definire i passi da eseguire.
Nell'editor di pipeline visualizzato, fare clic sulla casella Aggiungi fase (o sull'icona più). Nella pagina successiva viene visualizzato un elenco di tipi di fase. Fare clic sulla casella con etichetta Utilizza la strategia di aggiornamento Funzioni integrata.
Premere il pulsante Avanti.
Digitare il nome dello stadio, ad esempio update-function-greeter. Selezionare l'ambiente definito in precedenza per la funzione: greeter-function-in-app-go-on-oci-app.
Sotto l'intestazione Artifact, fare clic su Seleziona artifact. Viene visualizzata la lista di tutti gli artifact nel progetto DevOps di tipo Immagine Docker. Selezionare l'unica voce, quella creata in precedenza per l'immagine contenitore funzioni.
Si noti che il pulsante Seleziona artifact non è più abilitato: a questa fase può essere associata solo una singola immagine contenitore.
Fare clic su Aggiungi. La fase della pipeline viene creata nella pipeline. E la pipeline è ora pronta per essere eseguita - la sua definizione è completa. O è così? L'artifact di cui viene utilizzata questa pipeline non è definito in modo inequivocabile: l'etichetta della versione nel percorso per l'immagine del contenitore contiene il segnaposto ${imageVersion}. Per garantire che venga utilizzata la versione corretta per la distribuzione, è necessario sostituire questo segnaposto con il valore corretto. Questa operazione viene organizzata mediante la definizione di un parametro nella pipeline denominato imageVersion e impostato su un'etichetta di versione esistente.
Fare clic sulla scheda Parametri per la pipeline. Definire un nuovo parametro denominato imageVersion. Il suo valore predefinito può essere qualsiasi cosa, ma potrebbe anche corrispondere a un'etichetta di versione esistente per l'immagine del contenitore della funzione greeter. Salvare la definizione del parametro.
Sembrerebbe che la pipeline sia pronta per essere eseguita, ma dobbiamo ancora assicurarci che sia autorizzata a svolgere il suo lavoro. Prima di provare qualsiasi eruzione cutanea, leggere la sezione successiva.
Nell'articolo precedente è stato definito un gruppo dinamico per tutte le pipeline di distribuzione nel compartimento. La nuova pipeline è automaticamente membro di tale gruppo. Abbiamo anche definito un criterio che ha concesso le autorizzazioni al gruppo dinamico per leggere tutti gli artifact, che include le immagini del contenitore (funzione) nei repository del registro delle immagini del contenitore del compartimento. Un altro criterio già creato concede al gruppo dinamico l'autorizzazione molto ampia per gestire tutte le risorse nel compartimento. Possiamo beneficiare dell'ampio campo di applicazione di tale politica, in quanto riguarda anche la creazione e l'aggiornamento delle funzioni.
Eseguire la pipeline di distribuzione premendo Esegui pipeline.
Una volta completata la distribuzione, vedrai i marcatori verdi che proclamano il successo. Tuttavia, non c'è altra ovvia indicazione di questo successo perché il risultato finale è esattamente la situazione che avevamo raggiunto con la distribuzione manuale della funzione dalla riga di comando Fn CLI.
Per rendere le cose un po 'più interessanti, faremo un cambiamento al codice della funzione. Quindi, creare l'immagine del contenitore per la funzione (localmente) e inviare la nuova immagine della funzione al registro delle immagini del contenitore. Quindi avvieremo di nuovo la pipeline di distribuzione; questa volta, quando avrà successo, presenterà una nuova situazione che possiamo sperimentare richiamando l'instradamento my-API/greeting sul gateway API.
Implementazione funzione di modifica
Modificare in modo piccolo ma visibile func.go nell'ambiente locale: assicurarsi che la risposta della nuova versione della funzione sia notevolmente diversa dalla versione corrente. Salvare la modifica.
Nelle sezioni successive, creeremo una nuova versione dell'immagine del contenitore delle funzioni dall'origine modificata e alla fine la faremo funzionare su OCI Functions.
Crea una nuova immagine contenitore funzioni (localmente)
Questi comandi successivi modificheranno prima l'etichetta della versione utilizzata per contrassegnare la funzione con un aumento della terza cifra (bm è l'abbreviazione di bump). Successivamente, l'immagine del contenitore delle funzioni viene creata utilizzando le origini modificate. Il terzo comando elenca le immagini del contenitore locale, filtrando le immagini con greeter nel loro nome. Ora esegui i comandi.
fn bm
fn build
docker images | grep greeter
Dovresti essere in grado di trovare l'immagine appena creata con il nome completamente qualificato, inclusa la chiave dell'area OCI, lo spazio di nomi, il prefisso del repository e il nome della funzione greeter, con l'etichetta della versione aggiunta.
Tag immagine contenitore con etichetta nuova versione e push nel registro
Definiamo un nuovo identificativo per l'immagine, utilizzando questo comando che imposta l'etichetta della versione su 0.1.0:
docker tag : :0.1.0
Quindi eseguire il PUSH della nuova immagine del contenitore di funzioni nel repository OCI Container Image Registry utilizzando:
docker push :0.1.0
Si noti che a questo punto la funzione non è stata ridistribuita in base a questa nuova versione dell'immagine del contenitore. Tutto ciò che abbiamo fatto è creare l'immagine e inviarla al registro su OCI. Il richiamo della funzione OCI non mostra alcuna differenza.
Eseguire la pipeline di distribuzione (per la nuova immagine della funzione)
Eseguire ancora una volta la pipeline di distribuzione. Impostare il valore del parametro imageVersion su 0.1.0.
Quando la pipeline viene completata correttamente, la nuova versione della funzione con tutte le modifiche interessanti applicate è attiva.
Richiama la nuova funzione distribuita
È possibile vedere la nuova versione della funzione in azione richiamandola sulla riga di comando utilizzando l'interfaccia CLI Fn:
fn invoke go-on-oci-app greeter
Poiché il contesto dell'interfaccia CLI Fn è ancora attivo dal provider Oracle e configurato per il compartimento go-on-OCI che contiene la funzione greeter, questa chiamata verrà indirizzata alla funzione OCI, che a questo punto si basa sulla nuova versione dell'immagine del contenitore.
In alternativa, puoi ricollegare l'instradamento sul gateway API che richiama la funzione:
curl -X "GET" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse"}' https:///my-api/greeting
Fino ad ora, abbiamo costruito l'immagine del contenitore di funzioni a mano nell'ambiente di sviluppo locale utilizzando l'interfaccia CLI Fn. Tuttavia, proprio come abbiamo fatto nel precedente articolo per l'applicazione Go che è stata prodotta come eseguibile da una pipeline di build, ora trasformeremo la costruzione della funzione in un processo automatizzato. Viene creata una pipeline di build DevOps OCI per acquisire origini dal repository di codici, eseguire una fase di build gestita che produce un'immagine contenitore locale, quindi pubblicare questa immagine come artifact nel repository del registro immagini contenitore. Come ultimo passo, la pipeline di build attiva la pipeline di distribuzione per pubblicare la definizione più recente della funzione nell'ambiente OCI Functions di runtime.
Quando tutti gli elementi sono in posizione, il set totale di componenti OCI interconnessi viene visualizzato nella figura successiva.
L'artifact e la pipeline di distribuzione mostrata in questa figura sono già definiti nel progetto DevOps, così come il repository Application, Function e Container Image Registry per le immagini della funzione. Utilizzeremo il Code Repository impostato nell'articolo precedente. Tutto ciò che dobbiamo creare è la build-greeter-function della pipeline di build con le sue tre fasi.
Creare la pipeline di build
Nella pagina di panoramica per DevOps Project go-on-oci, fare clic sul pulsante Crea pipeline di build. Viene visualizzata una pagina per specificare il nome, ad esempio build-greeter-function, e una descrizione. Premere Crea per aggiungere la pipeline di build al progetto DevOps.
Fare clic sul collegamento build-greeter-function nell'elenco per andare alla pagina dei dettagli.
Prima fase - Build gestita
La prima fase di qualsiasi pipeline di build è una fase di build gestita. Questa fase fornisce istruzioni per la pipeline per ottenere un server di build, copiare le origini specificate dai repository di codici al server ed eseguire una serie di azioni su tale server. Al momento della scrittura, possiamo utilizzare una singola immagine per il server di build. Si tratta di un'immagine Oracle Linux (8 GB di memoria, 1 OCPU) con una serie di strumenti preinstallati e tempi di esecuzione del linguaggio. Per creare l'immagine del contenitore delle funzioni, è importante che il server di build sia dotato di CLI Docker e Fn.
Fare clic sull'icona più o sulla scheda Aggiungi fase. Viene visualizzata la procedura guidata Aggiungi fase in due passaggi. Al primo passo della procedura guidata, assicurarsi che la scheda Build gestita sia selezionata per il tipo di fase. Premere Avanti.
Viene visualizzata la seconda pagina. Definire un nome per la fase di build: build-go-source-to-function-container-image. Facoltativamente, aggiungere una descrizione.
Al momento, non possiamo selezionare un'immagine di build diversa, quindi ci accontentiamo di quella disponibile, che va bene per i nostri scopi.
Impostare il percorso del file delle specifiche di build su /functions/greeter/go-function-build-spec.yaml. Questo file contiene le istruzioni per la creazione delle origini Go nella funzione greeter (o qualsiasi altra funzione Go) e infine la creazione dell'immagine del contenitore delle funzioni.
Fare clic sul pulsante Seleziona nel repository di codici primario. Ora possiamo specificare da quale repository di codice la build otterrà le origini. Selezionare il repository di codici OCI come tipo di connessione di origine. Quindi selezionare il repository go-on-oci-repo. Lavoreremo con le fonti sul ramo principale, quindi non modificare quel default. Digitare go-on-oci-sources come valore per il nome dell'origine build. Una fase della build gestita può utilizzare origini da più repository. Nella specifica della build, è possibile fare riferimento a ciascuna delle posizioni radice di queste origini utilizzando l'etichetta definita come nome di origine della build. Fare clic su Salva.
Premere il pulsante Aggiungi. Questa operazione completa la definizione della fase di build gestita. Questo è tutto ciò che è necessario per prendere le fonti ed elaborarle in artefatti. Le istruzioni dettagliate eseguite da questa fase di build gestita e sul server di build sono definite nel file go-function-build-spec.yaml. È questo file che contiene le istruzioni per i passi dettagliati effettivi eseguiti sul server di build.
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
La specifica di costruzione è composta da tre parti:
I passi della build possono essere riepilogati come:
Questi passi sono in gran parte equivalenti alla build gestita definita nell'articolo precedente per l'applicazione Go che è stata finalmente trasformata in un eseguibile binario distribuito su una VM. I passi 9 e 10 sono diversi: trasformano l'applicazione Go in un'immagine del contenitore delle funzioni che è il prodotto finale della build.
Seconda fase - Pubblica artifact
Nella pagina di panoramica per la pipeline di build, fare clic sull'icona con il segno più nella parte inferiore della fase di build gestita corrente. Nel menu di scelta rapida visualizzato, fare clic su Aggiungi fase. Viene visualizzata la procedura guidata fase.
Fare clic su Distribuisci artifact. Quindi fare clic su Avanti.
Immettere il nome per questa fase: publish-greeter-function-container-image. È necessario selezionare l'artifact nel progetto DevOps che si desidera pubblicare. Questo artifact è l'immagine del contenitore go-on-oci/greeter:${imageVersion}. Fare clic su Seleziona artifact e selezionare l'immagine del contenitore.
Nell'area Associa artifact al risultato della build, è necessario indicare per ciascuno degli artifact selezionati quale dei risultati di una fase della build gestita è l'origine per la pubblicazione dell'artifact. La specifica di build definisce un output denominato go-function-container-image. Questo output si riferisce all'immagine del contenitore prodotta sul server di build dal processo di creazione delle funzioni. Immettere l'etichetta go-function-container-image nel campo Nome artifact configurazione/risultato build. Fare clic sul pulsante Aggiungi per creare la fase Consegna artifact.
Terza fase - Attiva pipeline di distribuzione
Nella pagina di panoramica per la pipeline di build, fare clic sull'icona con il segno più nella parte inferiore della fase Consegna artifact. Nel menu di scelta rapida visualizzato, fare clic su Aggiungi fase. Viene visualizzata la procedura guidata fase.
Fare clic su Attiva distribuzione. Quindi fare clic su Avanti.
Digitare un nome per la fase: trigger-deployment-of-greeter-function-to-go-on-oci-app e, facoltativamente, una descrizione. Fare clic sul pulsante Seleziona pipeline di distribuzione. Selezionare la pipeline deploy-greeter-function-to-go-on-oci-app. Vengono visualizzati i dettagli della pipeline, inclusi i parametri (imageVersion) e l'artifact utilizzato dalla distribuzione.
Fare clic su Aggiungi per completare la definizione della fase e aggiungerla alla pipeline di build.
Questa operazione completa la pipeline di build: acquisisce le origini, le elabora in un artifact distribuibile, pubblica l'artifact nel repository di immagini e attiva la pipeline di distribuzione per portarlo da lì.
Fare clic su Avvia esecuzione manuale. Definire un valore per il parametro imageVersion, ad esempio 0.2.0. Fare clic sul pulsante per avviare la pipeline di build.
Il completamento della pipeline di build e la successiva distribuzione dell'immagine della funzione appena creata richiederanno alcuni minuti.
Quando tutto è fatto e viene segnalato il successo, è possibile richiamare l'instradamento sul gateway API che porta alla funzione greeter per verificare se la risposta è effettivamente quella nuova prevista.
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"}
Questo è un momento per una piccola festa. Abbiamo raggiunto un processo end-to-end automatizzato che prende le origini delle applicazioni Go dal repository di codici e offre, dopo linting, il controllo, il test e la creazione, una nuova versione in esecuzione live di una funzione OCI serverless. Per produrre ulteriori aggiornamenti alla funzione, tutto ciò che dobbiamo fare è eseguire il commit della modifica e attivare la pipeline di build. E come passo successivo, possiamo anche attivare automaticamente la pipeline di build quando si verifica un commit (specifico) nel repository di codici.
Nella seconda parte di questo articolo, discuteremo dell'utilizzo dei servizi OCI dalle applicazioni Go in generale e dalle funzioni Go in particolare. Viene introdotto l'uso dell'SDK Go per OCI e vengono dimostrate le interazioni con il servizio di storage degli oggetti OCI.
Oracle Cloud Infrastructure è la piattaforma in grado di creare ed eseguire applicazioni sviluppate in Go. Sia nelle istanze di computazione che come funzioni serverless e anche containerizzate su un cluster Kubernetes gestito (come discuteremo nella parte cinque di questa serie). È bello rendersi conto che OCI è molto di più per i team di sviluppo Go che per una piattaforma runtime. OCI offre molti servizi che possono essere sfruttati dalle applicazioni. Servizi per la memorizzazione di file, dati relazionali o "NoSQL", per la gestione della pubblicazione e del consumo dei messaggi. Servizi per il trasferimento e l'analisi dei dati. E servizi che supportano le operazioni sulle applicazioni, come il monitoraggio.
L'interazione con i servizi OCI può essere eseguita tramite le API REST disponibili per ogni aspetto di tutti i servizi. La chiamata dei servizi REST con payload JSON tramite HTTP è abbastanza semplice da un'applicazione Go. C'è un fattore complicante: queste chiamate API devono essere firmate. La firma di Oracle Cloud Infrastructure utilizza lo schema di autenticazione "Signature" (con un'intestazione di autorizzazione) e il processo di firma non è esattamente banale. Per i dettagli su questo processo di firma, vedere Documentazione OCI - Firme delle richieste API REST OCI.Fortunatamente per lo sviluppo di applicazioni Go che richiamano i servizi OCI, possiamo utilizzare l'SDK Go per OCI. Questo kit di sviluppo open source facilita la firma delle richieste API OCI. Utilizzando l'SDK, le chiamate ai servizi OCI vengono effettuate come chiamate locali utilizzando strutture predefinite con tipi elevati invece di gestire i corpi di richiesta e risposta JSON. L'SDK Go per OCI utilizza lo stesso file di configurazione utilizzato in precedenza per l'interfaccia CLI Fn e l'interfaccia CLI OCI. La posizione predefinita per questo file è $HOME/.oci. Questo file indirizza il kit Go SDK a una tenancy e un'area specifiche per un account utente utilizzando la metà della chiave privata della coppia impostata per l'utente. Le applicazioni Go che utilizzano l'SDK Go per OCI possono semplicemente basarsi su questa configurazione senza dover gestire alcun dettaglio.
La documentazione per Go SDK per OCI è disponibile nella Documentazione OCI - SDK for Go.
In questa sezione, svilupperemo una semplice applicazione Go che utilizza il servizio di storage degli oggetti OCI per creare e leggere i file. All'inizio, si tratta di un'applicazione standalone che può essere compilata ed eseguita ovunque (a condizione che il file di configurazione OCI sia disponibile). Successivamente, discutiamo dell'esecuzione dell'applicazione Go all'interno di OCI, su una VM o come funzione. Questo focus speciale è rilevante perché quando l'SDK Go viene utilizzato all'interno di OCI, può sfruttare l'identità e i privilegi del componente che esegue l'applicazione. Ciò significa che il codice in esecuzione su OCI non deve portare il proprio file di configurazione OCI ed è quindi ancora più semplice.
In primo luogo, creiamo un'applicazione Go molto semplice in grado di connettersi a OCI tramite l'SDK. Successivamente, utilizzeremo questa base per aggiungere l'interazione con il servizio di storage degli oggetti.
Il client Go più semplice di OCI
La più semplice applicazione Go che parla a OCI utilizzando Go SDK per OCI viene creata come segue:
L'ipotesi è che il file di configurazione OCI discusso in precedenza in questo articolo si trovi nella posizione predefinita con il nome predefinito: $HOME/.oci/config. Se il file si trova in una posizione diversa, è possibile utilizzare la funzione ConfigurationProviderFromFile nel pacchetto github.com/oracle/oci-go-sdk/v65/common, che accetta la posizione personalizzata del file di configurazione.
Il file go.mod contiene i seguenti contenuti:
module oci-client
go 1.16
require github.com/oracle/oci-go-sdk/v65 v65.2.0
Il bit github.com/oracle/oci-go-sdk/v65 fa riferimento alla versione più recente (al momento della scrittura) del kit SDK Go. Le origini vengono scaricate in base a questo riferimento. Nell'applicazione Go, ciò significa che possiamo sfruttare i pacchetti nell'SDK per accedere a vari servizi nel portfolio OCI.
Il file oci-client.go contiene questo codice che utilizza i pacchetti comuni e di identità:
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)
}
La prima riga della funzione acquisisce il client che può essere successivamente utilizzato per la maggior parte delle interazioni con OCI, ad esempio la chiamata GetCompartment che restituisce i dettagli per il compartimento radice.
L'applicazione può essere eseguita utilizzando go run oci-client.go e produrrà un output molto semplice:
Name of Root Compartment:
Anche se il risultato non è particolarmente notevole, il fatto che esista un output dimostra che l'SDK Go per OCI sta funzionando ed è pronto per essere sfruttato per attività più elevate.
Si noti che il codice ignora completamente gli errori che possono verificarsi. Se si verificano errori, sostituire i caratteri di sottolineatura con una variabile per acquisire e gestire tale errore.
Vai all'applicazione per la creazione e il recupero di oggetti nel e dal servizio di storage degli oggetti OCI
Il servizio di storage degli oggetti OCI offre storage economico e persistente per diversi tipi di dati. Gli oggetti, grandi e piccoli, possono essere programmaticamente memorizzati su questo servizio per essere conservati per brevi o lunghi periodi di tempo e per essere consultati programmaticamente o tramite URL diretti. Lo storage degli oggetti offre controllo delle versioni, regole di conservazione diverse, cifratura di qualsiasi dato memorizzato, gestione del ciclo di vita degli oggetti, rimozione automatizzata basata sul tempo e livelli di storage diversi.
Gli oggetti nel servizio di storage degli oggetti sono organizzati in bucket. Un bucket è un contenitore logico di oggetti che vive in un compartimento specifico. Alcune operazioni possono essere eseguite su tutti gli oggetti in un bucket.
Go SDK for OCI offre funzioni e tipi che semplificano e semplificano l'utilizzo del servizio di storage degli oggetti dalle applicazioni Go. Le operazioni per manipolare i bucket e creare, eliminare e recuperare gli oggetti sono prontamente disponibili.
Per i dettagli sul package di storage degli oggetti nell'SDK Go per OCI, consulta questo riferimento: Documentazione per il package di storage degli oggetti nell'SDK Go per OCI.
Il repository di origine per questo articolo contiene le applicazioni/store-n-retrieve delle cartelle, che dispone di una semplice applicazione Go che si connette alla tenancy OCI e crea un bucket, seguito dalla creazione di un oggetto e dal recupero dello stesso oggetto. L'applicazione utilizza lo stesso DefaultConfigProvider utilizzato nella sezione precedente per firmare le richieste alle API OCI utilizzando il file $HOME/.oci/config.
La dipendenza di questa applicazione nell'SDK Go per OCI è definita in go.mod.
La prima parte del codice crea un'istanza ObjectStorageClient. Molte funzioni sono definite nell'interfaccia di base, tutte che supportano una qualche forma di interazione con il servizio di storage degli oggetti. Il file ObjectStorageClient viene creato utilizzando common.DefaultConfigProvider() (proprio come prima), utilizzando il file di configurazione OCI predefinito con riferimento a un file contenente la chiave privata.
La funzione getNamespace viene chiamata per ottenere lo spazio di nomi per la tenancy corrente. Viene quindi chiamata ensureBucketExists con il nome del bucket per creare il bucket se non esiste già.
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)
}
.........................
Le funzioni richiamate in questo snippet per recuperare lo spazio di nomi e verificare l'esistenza del bucket (e crearlo se non esiste) sono abbastanza semplici. Sono definite come segue:
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
}
La seconda parte di questa applicazione crea un oggetto nel bucket e successivamente recupera tale oggetto. Al termine dell'esecuzione, l'applicazione avrà un effetto persistente: il bucket è stato creato (se non esiste già) e un oggetto è stato creato o aggiornato (se l'applicazione è stata eseguita in precedenza). Puoi eseguire il check-in della pagina dei dettagli per il go-bucket del bucket nella console OCI per visualizzare sia il bucket che l'oggetto creato.
..................
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
}
L'esecuzione di questa applicazione - go run object-organizer.go - restituisce questo output:
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.
Nella sezione precedente si è discusso di qualsiasi applicazione Go che interagisce con i servizi OCI tramite l'SDK. Quindi, cosa c'è da dire di più sulle OCI Functions in Go? Continua a leggere perché si è scoperto che possiamo semplificare la vita quando il codice Go sviluppato e da cui desideri lavorare con l'SDK Go verrà distribuito come funzione OCI o eseguito su un'istanza di computazione in OCI. In questi casi, possiamo sfruttare l'autenticazione del principal delle risorse (per Funzioni) e l'autenticazione del principal dell'istanza (per il codice in esecuzione su un'istanza di computazione), il che significa che non è necessario includere il file di configurazione OCI e il nostro codice che parla all'SDK può essere un po' più semplice.
Ulteriori informazioni sull'autenticazione del principal delle risorse sono disponibili nella documentazione OCI sull'autenticazione del principal delle risorse per le funzioni.
In questa sezione viene illustrata una funzione OCI denominata object-broker. Troverete le fonti nel repository di origine per questo articolo, nelle funzioni di percorso/object-broker. Come in precedenza, la funzione viene definita utilizzando un file func.yaml con metadati e un file func.go con il collegamento tra la funzione e la struttura Fn. Il file go.mod definisce le dipendenze per la funzione. La logica per l'interazione con OCI Object Storage Service si trova nell'oggetto file organizer.go. Definisce un func pubblico CreateObject chiamato da func.go.
CreateObject crea un oggetto con il nome specificato nel bucket specificato. Le prime righe della funzione CreateObject nell'oggetto organizer.go sono state modificate per utilizzare questa autenticazione del principal risorsa. La funzione auth.ResourcePrincipalConfigurationProvider() utilizzata ora non richiede l'inclusione nell'applicazione di un file di configurazione OCI e di una chiave privata. Si presume che il codice venga eseguito all'interno di OCI e più specificamente all'interno di una risorsa (che può essere una funzione o, ad esempio, un server di build DevOps) nota come principal risorsa perché è inclusa in un gruppo dinamico ed eredita le autorizzazioni da tale appartenenza al gruppo. Prima di troppo tempo, si arriva alle istruzioni per l'adozione delle misure necessarie per questo.
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)
Rivolgiamo la nostra attenzione a func.go. Func(tion) myHandler gestisce il trigger della funzione. Richiama CreateObject, ma non prima di aver determinato il nome dell'oggetto che la richiesta deve produrre e il nome del bucket che deve contenere l'oggetto. Questi nomi hanno un valore predefinito, ma la funzione cerca di trovare i valori dei parametri di query della richiesta HTTP che forniscono valori specifici. Si noti che questo funziona solo per un trigger HTTP alla funzione e non per una chiamata effettuata utilizzando il richiamo 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())
......
È possibile esaminare i dettagli relativi al contesto Fn in Documentazione per FDK Project Fn Go, in particolare nell'esempio.
La funzione deve sapere in quale compartimento OCI deve risiedere il bucket. Tali impostazioni di runtime sono in genere definite sulla funzione dell'applicazione tramite una configurazione. I valori delle configurazioni possono essere letti nella funzione da una mappa nel contesto, come mostrato in questa riga:
if compartmentOCID, ok := fnctx.Config()["compartmentOCID"]; ok {
Crea e distribuisci la funzione utilizzando l'interfaccia CLI Fn
Supponendo di trovarsi in un terminale nell'ambiente di sviluppo locale in cui è installata l'interfaccia CLI Fn e la directory corrente è funzioni/object-broker, è possibile eseguire una build locale della funzione con output dettagliato:
fn -v build
Quando la build ha un bell'aspetto, il passo successivo da fare è la distribuzione della funzione. Se il contesto Fn è impostato per utilizzare il contesto go-on-OCI (controllare con i contesti della lista fn), la funzione verrà distribuita nell'applicazione go-on-OCI-app su OCI:
fn -v deploy --app go-on-oci-app
La funzione può eseguire un job significativo solo se la configurazione è stata definita per il valore OCID del compartimento. È possibile eseguire questa operazione tramite la console o utilizzando la seguente istruzione con l'interfaccia CLI Fn:
fn cf f go-on-oci-app object-broker compartmentOCID
Ora la funzione viene distribuita e ha la sua configurazione. È possibile che una chiamata alla funzione con questo comando abbia esito positivo:
fn invoke go-on-oci-app object-broker
Tuttavia, c'è un aspetto finale da gestire: la funzione utilizza le API del servizio di storage degli oggetti OCI per manipolare bucket e oggetti, ma per farlo deve essere stata concessa in modo esplicito. Usiamo un gruppo dinamico e due politiche per raggiungere questo obiettivo.
Autorizzazioni per le funzioni per la manipolazione di oggetti e bucket
Proprio come abbiamo usato i gruppi dinamici per creare un assegnatario che rappresenta le pipeline di distribuzione e le pipeline di build, dobbiamo anche creare un gruppo dinamico che contenga le funzioni a cui vogliamo concedere le autorizzazioni. Per creare il gruppo dinamico, digitare dyn nella barra di ricerca. Fare clic sul collegamento Gruppi dinamici nel riquadro dei risultati della ricerca.
Nella pagina di panoramica per i gruppi dinamici, fare clic su Crea gruppo dinamico.
Immettere il nome del gruppo dinamico per le pipeline di distribuzione, ad esempio functions-in-go-on-oci, e facoltativamente digitare una descrizione. Definire la regola seguente che seleziona tutte le funzioni che fanno parte del compartimento:
All {resource.type = 'fnfunc', resource.compartment.id = ''}
Naturalmente, sostituire
Per creare un criterio nella console: digitare poli nella barra di ricerca e fare clic su Criteri > Identità nell'area Servizi nel popup dei risultati della ricerca. Viene visualizzata la pagina di panoramica dei criteri per il compartimento corrente.
La prima istruzione criterio definisce l'autorizzazione per la funzione a gestire gli oggetti nel compartimento. La seconda istruzione aggiunge l'autorizzazione per la gestione dei bucket. Definire un nome, una descrizione e le istruzioni riportate di seguito.
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
La figura successiva mostra le autorizzazioni che ora si applicano alla funzione quando viene salvato il criterio contenente queste istruzioni:
Ora la funzione può essere richiamata e dovrebbe essere in grado di eseguire le operazioni utilizzando i nomi predefiniti per bucket e oggetto.
fn invoke go-on-oci-app object-broker
Verificare che il bucket venga creato e che contenga l'oggetto appena creato utilizzando la console dall'URL OCI alla pagina dei bucket.
Aggiungi instradamento in gateway API alla funzione di trigger
Per poter richiamare la funzione object-broker su HTTP da qualsiasi luogo, utilizzeremo nuovamente il gateway API. Digitare gat nella barra di ricerca nella console. Fare clic su >Gateway > Gestione API. Fare clic sul collegamento per the-api-gateway. Fare clic su Distribuzioni. Nella lista con distribuzioni - che contiene una singola distribuzione - fare clic sul collegamento myserver-api.
Fare clic su Modifica per aprire la specifica di distribuzione. Fare clic sul collegamento per il secondo passo: Percorsi. Scorrere verso il basso e fare clic su + Altro percorso.
Digitare /object-broker come percorso per questo nuovo percorso. Selezionare GET come metodo e Oracle Functions come tipo (di backend). Selezionare l'applicazione go-on-oci-app, quindi impostare il nome della funzione su object-broker. Premere Avanti, quindi premere Salva modifiche per applicare le modifiche e rendere reale il nuovo percorso.
L'immagine end-to-end ora configurata dal consumer HTTP tramite il gateway API alla funzione e, infine, il bucket e l'oggetto hanno il seguente aspetto:
Richiamare la funzione dal browser o utilizzando curl sulla riga di comando utilizzando:
curl -X "GET" "http:///my-api/object-broker?objectName=new-exciting-object.txt&bucketName=the-ultimate-collection"
Creazione e distribuzione automatiche
Questo oggetto-broker funzione è stato distribuito manualmente sulla riga di comando utilizzando l'interfaccia CLI Fn. Questo ha funzionato bene, ovviamente. Tuttavia, se ora dovessi iniziare lo sviluppo su questa funzione, passando attraverso più cicli di sviluppo, probabilmente vorresti introdurre l'automazione nel processo di build-and-deploy.
Proprio come abbiamo fatto in precedenza, puoi facilmente impostare gli elementi necessari in OCI DevOps per ottenere pipeline automatizzate per la distribuzione (dell'immagine del contenitore di funzioni nel registro delle immagini del contenitore) e la build (a partire dal repository di codici e ottenendo un'immagine del contenitore appena sfornata per la funzione). L'elemento principale specifico della funzione è il file di specifica della build per la fase di build gestita nella pipeline di build. Questo file viene fornito come go-function-build-spec.yaml nella stessa directory di func.go e func.yaml.
Dopo aver creato un artifact DevOps per l'immagine del contenitore delle funzioni, un ambiente per la funzione e le due pipeline per la build e la distribuzione, l'impostazione del processo DevOps automatizzato sarà simile alla seguente:
Un'area di interesse in questo articolo erano le funzioni serverless, scritte in Go e in esecuzione su Oracle Cloud Infrastructure. La creazione e la distribuzione automatizzate di queste funzioni sono state discusse, così come l'uso del gateway API per fornire l'accesso alla funzione ai consumer HTTP esterni.
Il secondo argomento principale era Go SDK per OCI per l'interazione con i servizi OCI dalle applicazioni Go. L'articolo mostra come utilizzare il codice Go per accedere al servizio di storage degli oggetti per memorizzare e recuperare i file.
I due argomenti sono stati combinati in funzione oggetto-broker. Questa funzione OCI sfrutta l'autenticazione del principal risorsa e le autorizzazioni concesse tramite un gruppo dinamico. Attraverso una configurazione runtime, la funzione apprende le impostazioni specifiche dell'ambiente corrente.
Nel prossimo articolo, l'interazione con Oracle Database sarà l'argomento principale. Creazione di una connessione da un'applicazione Go a un Oracle Database locale, nonché a un Autonomous Database in esecuzione su OCI ed esecuzione di operazioni SQL con questi database comodamente dall'applicazione Go. Altri argomenti includono l'utilizzo di un Oracle Wallet per la corretta gestione delle credenziali del database, incluso il wallet nel processo di distribuzione, e la combinazione delle interazioni con lo storage degli oggetti OCI e i servizi Autonomous Database in una singola applicazione.