Esecuzione locale di test dell’endpoint di Google Cloud

Benvenuti in un altro dei miei articoli in cui prendo appunti su cosa ho fatto per creare framework / strumenti relativi a GCP per fare ciò che voglio. Questa volta stavo cercando di configurare Clound Endpoint / gRPC per funzionare localmente in modo da poter eseguire i test.

Si noti che tutto qui è a partire da maggio 2018. A volte GCP cambia / risolve rapidamente le cose, quindi si prega di fare sempre la propria ricerca.

  • Voglio implementare un server gRPC in Go
  • … a cui si accede tramite ESP, controllato da Cloud Endpoint (che è il modo per eseguire server gRPC dietro Cloud Endpoint in questo momento)
  • Voglio testare cosa sta succedendo nell’ambiente di sviluppo locale
  • Voglio che sia automatizzato il più possibile, quindi gli altri devono solo eseguire go test

Quindi ecco i miei appunti.

Attività precedenti

Ecco le mie note precedenti su come eseguire i server ESP localmente.

Configurazione di Cloud Endpoint ESP per l’esecuzione su un Mac locale

Stavo cercando di far funzionare Extensible Service Proxy (ESP) su un computer locale, quindi potevo essere sicuro di sapere che il mio gRPC …

medium.com

Cloud Endpoint ed ESP

Mentre sono sicuro che qualcosa di più va dietro le quinte, dal punto di vista di un normale utente di Joe, Cloud Endpoints è un proxy HTTP glorificato e metadati / configurazione associati che possono essere impostati da una definizione OpenAPI o da una definizione di buffer di protocollo.

Creando un nuovo servizio di endpoint e una configurazione con versione, è possibile indicare a GCP quali percorsi sono disponibili per il proprio servizio, quali parametri sono disponibili, ecc. Questa conoscenza viene quindi utilizzata da un server proxy chiamato Extensible Service Proxy (ESP), che è responsabile della guida della maggior parte dell’elaborazione.

ESP è convenientemente confezionato come contenitore, quindi puoi eseguirlo facilmente ovunque. Potresti trovare alcune informazioni utili nel mio articolo che è stato menzionato in precedenza.

L’account del servizio

L’esecuzione di un contenitore ESP richiede di passargli un dato non banale, che è l’ account di servizio che l’ESP dovrebbe usare durante la gestione delle richieste.

Ora, ovviamente, è sufficiente creare un account di servizio e passare il file che contiene le sue informazioni. Fai quello che devi fare sulla console IAM:

Google Cloud Platform

Google Cloud Platform ti consente di creare, distribuire e ridimensionare applicazioni, siti Web e servizi sulla stessa infrastruttura …

console.cloud.google.com

Probabilmente dovrai condividere queste informazioni con il tuo team, che desidera eseguire i test locali.

Mentre nella maggior parte dei casi l’aggiunta del file JSON che contiene le credenziali nel tuo repository non causerà alcun problema, dovresti sempre chiederti ” Cosa SE il repository in qualche modo è diventato visibile al pubblico ?” Voglio dire, non lo sai mai.

Quindi mi irrita aggiungere questo file così com’è in una memoria persistente, figuriamoci git.

La mia opinione su questo era usare Cloud KMS.

Servizio di gestione delle chiavi cloud | Google Cloud

Cloud KMS gestisce in modo sicuro chiavi di crittografia e segreti su Google Cloud Platform. Generare, utilizzare, ruotare e distruggere le chiavi …

cloud.google.com

Ho preso il file gcloud kms encrypt originale, l’ho crittografato usando gcloud kms encrypt e l’ gcloud kms encrypt nel nostro repository. Quindi, il mio script che avvia il contenitore ESP decodifica questo file e lo passa al contenitore.

Ovviamente devi autorizzare il tuo team a decifrare il file (che può essere aggiunto ai membri del tuo team dalla console), ma almeno in questo modo hai il controllo su chi può eseguire questa operazione.

Lo script ESP avrà un aspetto simile al seguente:

  # snippet di script abbreviatoENCRYPTED_SERVICE_ACCOUNT_FILE = percorso / a / esp-serviceaccount.json.enc 
SERVICE_ACCOUNT_FILE = tmp / serviceaccount.json # Questa funzione elimina semplicemente il file dell'account di servizio
# non lo teniamo in giro
funzione delete_service_account_file {
rm $ SERVICE_ACCOUNT_FILE
}
trap delete_service_account_file EXIT # Usa KMS per decrittografare le informazioni dell'account del servizio crittografato
gcloud kms decrypt \
--ciphertext-file = $ ENCRYPTED_SERVICE_ACCOUNT_FILE \
--plaintext-file = $ SERVICE_ACCOUNT_FILE \
--location = globale \
--keyring = $ YOUR_KEYRING \
--key = $ YOUR_KEY # La directory "tmp" è montata dal contenitore ESP
# in modo che ESP possa leggere i file da esso, incluso il file
# file dell'account di servizio
docker run \
--rm \
--name = $ ESP_CONTAINER_NAME \
--publish = $ ESP_PORT: $ ESP_PORT \
-v tmp: / esp \
gcr.io/endpoints-release/endpoints-runtime:1 \
--service = $ SERVICE_NAME \
--version = $ CONFIG_ID \
--http_port = $ ESP_PORT \
--backend = grpc: // $ GRPC_HOST: $ GRPC_PORT \
--service_account_key = / esp / serviceaccount.json

Calci ESP dai test Go

Una volta che questo script funziona, dovrebbe essere abbastanza banale avviare il contenitore ESP per i test Go.

Innanzitutto, probabilmente dovresti utilizzare la funzione TestMain modo da poter configurare il server prima di eseguire qualsiasi altro test.

  func TestMain (m * testing.M) { 
setupESP () // setup
defer tearDownESP () // smontaggio automatico os.Exit (m.Run ()) // m.Run () esegue i test
}

E in setupESP devi solo usare os/exec .

  containerName: = "esp-test" 
cmd: = exec.Command (espScriptFile)
cmd.Env = append (os.Environ (), "ESP_CONTAINER_NAME", containerName)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
go cmd.Run ()

Qui, sto esplicitamente passando il nome del contenitore attraverso la variabile di ambiente ESP_CONTAINER_NAME . Se osservi attentamente lo script che ho pubblicato in precedenza, uno degli argomenti del comando docker run era un nome contenitore esplicito. Invece di lasciare che Docker mi dia un nome casuale, il nome viene passato in modo che sia più facile arrestare il contenitore ESP al termine dei test.

  defer func () { 
stopCmd: = exec.Command ("docker", "stop", containerName)
stopCmd.Stdout = os.Stdout
stopCmd.Stderr = os.Stderr
stopCmd.Run ()
} ()

Collegamento ESP e gRPC

All’inizio, sono sicuro che configurerai erroneamente ESP e / o gRPC. È normale e molto atteso.

Ma prima di andare e tirar fuori qualsiasi fu-debugging che potresti avere, suggerirò fortemente di aggiungere qualcosa come il seguente immediatamente dopo aver avviato il contenitore ESP. Questo codice fondamentalmente dà al contenitore ESP 5 secondi almeno per iniziare ad ascoltare le connessioni in entrata .

  connStr: = "... previsto ESP addr: porta per connettersi a ..." ctx, cancel: = context.WithTimeout (context.Background (), 5 * time.Second) 
differisci annullamento () espTicker: = time.NewTicker (500 * time.Millisecond)
differisci espTicker.Stop () per loop: = true; ciclo continuo; {
Selezionare {
case <-ctx.Done ():
restituire errori.Nuovo ("errore durante l'attesa per la disponibilità della porta ESP")
case <-espTicker.C:
se conn, err: = net.DialTimeout ("tcp", connStr, 100 * time.Millisecond); err == zero {
conn.Close ()
loop = false
}
}
}

Vedi, sto programmando abbastanza da sapere che, quando hai problemi a ottenere i tuoi strumenti automatizzati che generano server, la prima cosa che dovresti controllare è se i tuoi server stanno ascoltando l’indirizzo corretto .

Per quanto banale possa sembrare, ti farà risparmiare innumerevoli ore. Segui il consiglio.

Ora che sai che il contenitore ESP accetta connessioni, purché tutto il resto sia configurato correttamente, puoi iniziare a inviare richieste JSON al server ESP e aspettarti di vedere qualcosa sul tuo server gRPC.

Abilita registrazione gRPC

Per così dire, probabilmente non riceverai le tue richieste gRPC. Hai rovinato qualcosa.

La prima cosa da verificare è assicurarsi che la registrazione sia abilitata nel server gRPC. Usa il tuo intercettore preferito: ad esempio, per usare lo zap logger (disponibile qui), fai semplicemente una cosa del genere:

  func GRPCServer (l * zap.Logger) * grpc.Server { 
return grpc.NewServer (
grpc_middleware.WithUnaryServerChain (
grpc_zap.UnaryServerInterceptor (l),
),
grpc_middleware.WithStreamServerChain (
grpc_zap.StreamServerInterceptor (l),
),
)
}

Se ciò non ha aiutato, ovvero non si vedono registri dal server gRPC, è possibile che sia presente una configurazione errata nella configurazione di Cloud Endpoint, il che sta causando un comportamento strano di ESP.

ESP inserisce automaticamente i dati in Stackdriver, quindi ci saranno registri lì per aiutarti, ma ad essere sincero trovo che Stackdriver sia confuso da usare per un semplice debug come questo.

Sarebbe davvero bello se potessimo dire a ESP di sputare più roba, ma al momento della stesura, non sono riuscito a trovare alcuna opzione da riga di comando per farlo.

Modifica di nginx Config, Take 1

Fortunatamente, ESP è solo nginx con steroidi. Ciò significa che finché potremo modificare il file di configurazione di nginx, dovremmo essere in grado di ottenere l’output di debug.

Il primo modo che sto descrivendo qui è il modo più aggressivo per farlo: basta eseguire direttamente nel contenitore ESP:

  docker exec -it $ nome-del-tuo-contenitore-ESP / bin / bash 

Installa il tuo editor di testo preferito (o usa sed, qualunque cosa) e modifica /etc/nginx/endpoints/nginx.conf . La linea che si desidera modificare è

  error_log stderr warn; 

Basta cambiare questo nel modo seguente:

  error_log debug stderr; 

E quindi rilascia kill -HUP 1.

Questo costringerà Nginx a ricaricare la configurazione, e ora che sta emettendo messaggi di debug, dovresti avere un’idea molto migliore di ciò che sta succedendo.

Modifica di nginx Config, Take 2

L’altro modo è usare l’opzione --nginx_config . Ciò consente di specificare un file modello alternativo (il file /etc/nginx/endpoints/nginx.conf viene generato dinamicamente all’avvio del contenitore), dove si dovrebbe essere in grado di modificare le impostazioni di registrazione come desiderato.

Il file modello si trova in /etc/nginx/nginx-auto.conf.template : puoi prendere il contenuto di quel file emettendo il seguente comando:

  docker run --rm --entrypoint / bin / cat \ 
gcr.io/endpoints-release/endpoints-runtime:1 \
/etc/nginx/nginx-auto.conf.template

L’uso di questo flag insieme a qualunque script wrapper sembra una soluzione più solida, tranne il messaggio di aiuto per questa opzione che dice:

  -n NGINX_CONFIG, --nginx_config NGINX_CONFIG 
Utilizzare un file di configurazione nginx personalizzato anziché la configurazione
template /etc/nginx/nginx-auto.conf.template. Se tu
specifica questa opzione, quindi tutte le opzioni di porta sono
ignorato.

Ora, non ho giocato abbastanza per capire cosa significhi esattamente, ma se mi impedisse di specificare le porte su cui ascoltare, sarebbe un punto fermo per me.

Se è necessario attivare costantemente il log di debug, è possibile indagare un po ‘di più. Personalmente avevo solo bisogno di avviare manualmente il contenitore ESP alcune volte per rendermi conto dei miei errori, quindi l’ho hackerato ricaricando manualmente nginx.

Accesso alle risorse protette

Gli endpoint cloud consentono di specificare che le risorse devono essere protette da richieste non autorizzate.

Se i test vengono eseguiti tramite ESP, è necessario disporre dell’autorizzazione adeguata per accedere al servizio gRPC dietro di esso, quindi è necessario configurare anche questo.

L’autorizzazione può essere un token JWT incorporato nell’intestazione Authorization: Bearer oppure può essere una chiave API nel parametro della stringa di query, sotto forma di http://your-esp-address/path?key=API-KEY

Per i test, credo che usare una chiave API sia molto più semplice. Vai alla console Credenziali API e crea una nuova chiave API per i tuoi test (ricorda di limitare le capacità della tua chiave API solo al servizio Cloud Endpoint per il quale desideri utilizzarlo):

Google Cloud Platform

Google Cloud Platform ti consente di creare, distribuire e ridimensionare applicazioni, siti Web e servizi sulla stessa infrastruttura …

console.cloud.google.com

Proprio come le informazioni sull’account del servizio di cui abbiamo parlato, queste devono essere condivise dai membri del team. E di nuovo, si tratta di informazioni riservate, quindi suggerirei di evitare di aggiungerle direttamente a una memoria persistente come Git. Ciò significa che è qui che KMS è di nuovo utile. Utilizzare KMS per crittografare un file che contiene questa chiave API e aggiungerlo al repository.

Questa volta, il componente che ha bisogno di questa informazione è il test Go. Potresti voler scrivere una funzione come questa per afferrare la chiave API:

  func getAPIKey () (stringa, errore) { 
apiKeyFile: = `path / to / raw-apikey.txt`
se _, err: = os.Stat (apiKeyFile); err! = zero {
cryptedFile: = `path / to /crypted-apikey.enc`
cmd: = exec.Command ("gcloud", "kms", "decrypt",
"--Ciphertext-file =" + encryptedFile,
"--Plaintext-file =" + apiKeyFile,
"--Location = globale",
"--Keyring = your-portachiavi",
"--Key = your-chiave",
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err: = cmd.Run (); err! = zero {
return "", errors.Wrapf (err, `non è riuscito a decrittografare% s per ottenere la chiave api ESP` ,cryptedFile)
}
} buf, err: = ioutil.ReadFile (apiKeyFile)
if err! = zero {
return "", errors.Wrapf (err, `impossibile leggere da% s`, apiKeyFile)
} return string (buf), zero
}

Quindi puoi aggiungere la chiave API che hai ottenuto dall’alto a tutte le tue richieste a ESP. Sarebbe piuttosto noioso fare u += "?key=" + apiKey per ogni chiamata HTTP che potresti avere nel tuo test, ti suggerirei di creare un http.RoundTripper questo modo:

  digitare espRoundTripper struct { 
stringa chiave
} func newESPRoundTripper (stringa di chiavi) http.RoundTripper {
return & espRoundTripper {chiave: chiave}
} func (t * espRoundTripper) RoundTrip (r * http.Request) (* http.Response, errore) {
se r.URL == zero {
return nil, errors.New (`URL vuoto nella richiesta HTTP`)
} se v: = r.URL.Query (); v.Get (`key`) ==" "{
v.Set (`key`, t.key)
r.URL.RawQuery = v.Encode ()
} return http.DefaultClient.Do (r)
}

E imposta l’elemento Transport del tuo http.Client su espRoundTripper sopra:

  cl: = & http.Client { 
Trasporto: newESPRoundTripper (apiKey),
}

Questo assicurerà di avere automaticamente la chiave API incorporata nella richiesta.


Per ora è tutto. Spero che questo ti sia stato utile.

Happy hacking!