Sviluppo InnerLoop con Python

1. Panoramica

Questo lab illustra caratteristiche e funzionalità progettate per semplificare il flusso di lavoro di sviluppo per i tecnici del software incaricati di sviluppare applicazioni Python in un ambiente containerizzato. Lo sviluppo tipico dei container richiede che l'utente comprenda i dettagli dei container e del processo di compilazione dei container. Inoltre, gli sviluppatori in genere devono interrompere il flusso, uscendo dal loro IDE per testare ed eseguire il debug delle loro applicazioni in ambienti remoti. Con gli strumenti e le tecnologie citati in questo tutorial, gli sviluppatori possono lavorare in modo efficace con le applicazioni containerizzate senza uscire dall'IDE.

Cosa imparerai a fare

In questo lab imparerai i metodi per lo sviluppo con container in Google Cloud, tra cui:

  • Creazione di una nuova applicazione iniziale Python
  • Scopri il processo di sviluppo
  • Sviluppa un semplice servizio REST CRUD

2. Configurazione e requisiti

Configurazione dell'ambiente autogestito

  1. Accedi alla console Google Cloud e crea un nuovo progetto o riutilizzane uno esistente. Se non hai ancora un account Gmail o Google Workspace, devi crearne uno.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Il Nome progetto è il nome visualizzato dei partecipanti del progetto. Si tratta di una stringa di caratteri non utilizzata dalle API di Google e può essere aggiornata in qualsiasi momento.
  • L'ID progetto deve essere univoco in tutti i progetti Google Cloud ed è immutabile (non può essere modificato dopo essere stato impostato). La console Cloud genera automaticamente una stringa univoca; di solito non ti importa cosa sia. Nella maggior parte dei codelab, devi fare riferimento all'ID progetto (che solitamente è identificato come PROJECT_ID), quindi, se non ti piace, generane un altro a caso oppure puoi fare un tentativo personalizzato e controllare se è disponibile. Poi c'è "congelato" dopo la creazione del progetto.
  • C'è un terzo valore, il numero di progetto, utilizzato da alcune API. Scopri di più su tutti e tre questi valori nella documentazione.
  1. Successivamente, dovrai abilitare la fatturazione nella console Cloud per utilizzare le risorse/le API Cloud. Eseguire questo codelab non dovrebbe costare molto. Per arrestare le risorse in modo da non incorrere in fatturazione oltre questo tutorial, segui eventuali "pulizie" istruzioni riportate alla fine del codelab. I nuovi utenti di Google Cloud sono idonei al programma prova senza costi di 300$.

Avvia editor Cloudshell

Questo lab è stato progettato e testato per l'utilizzo con l'editor di Google Cloud Shell. Per accedere all'editor,

  1. accedi al tuo progetto Google all'indirizzo https://console.cloud.google.com.
  2. Nell'angolo in alto a destra, fai clic sull'icona dell'editor di Cloud Shell

8560cc8d45e8c112.png

  1. Si aprirà un nuovo riquadro nella parte inferiore della finestra
  2. Fai clic sul pulsante Apri editor

9e504cb98a6a8005.png

  1. L'editor si apre con un Explorer a destra e l'editor nell'area centrale
  2. Nella parte inferiore dello schermo dovrebbe essere disponibile anche un riquadro del terminale
  3. Se il terminale NON è aperto, utilizza la combinazione di tasti "Ctrl+" per aprire una nuova finestra del terminale

Configurazione dell'ambiente

In Cloud Shell, imposta l'ID e il numero del progetto. Salvale come variabili PROJECT_ID e PROJECT_ID.

export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID \
    --format='value(projectNumber)')

Recupera il codice sorgente

  1. Il codice sorgente di questo lab si trova nel container-developer-workshop in GoogleCloudPlatform su GitHub. Clonalo con il comando seguente, quindi passa alla directory.
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git &&
cd container-developer-workshop/labs/python
mkdir music-service && cd music-service 
cloudshell workspace .

Se il terminale NON è aperto, utilizza la combinazione di tasti "Ctrl+" per aprire una nuova finestra del terminale

Esegui il provisioning dell'infrastruttura utilizzata in questo lab

In questo lab eseguirai il deployment del codice su GKE e accedi ai dati archiviati in un database Spanner. Lo script di configurazione riportato di seguito prepara l'infrastruttura per te. Il processo di provisioning richiederà più di 10 minuti. Puoi continuare con i passaggi successivi durante l'elaborazione della configurazione.

../setup.sh

3. Crea una nuova applicazione iniziale Python

  1. Crea un file denominato requirements.txt e copia al suo interno i seguenti contenuti
Flask
gunicorn
google-cloud-spanner
ptvsd==4.3.2
  1. Crea un file denominato app.py e incollaci il codice seguente
import os
from flask import Flask, request, jsonify
from google.cloud import spanner

app = Flask(__name__)

@app.route("/")
def hello_world():
    message="Hello, World!"
    return message

if __name__ == '__main__':
    server_port = os.environ.get('PORT', '8080')
    app.run(debug=False, port=server_port, host='0.0.0.0')

  1. Crea un file denominato Dockerfile e incollaci il codice seguente
FROM python:3.8
ARG FLASK_DEBUG=0
ENV FLASK_DEBUG=$FLASK_DEBUG
ENV FLASK_APP=app.py
WORKDIR /app
COPY requirements.txt .
RUN pip install --trusted-host pypi.python.org -r requirements.txt
COPY . .
ENTRYPOINT ["python3", "-m", "flask", "run", "--port=8080", "--host=0.0.0.0"]

Nota: FLASK_DEBUG=1 consente di ricaricare automaticamente le modifiche al codice in un'app Flask Python. Questo Dockerfile consente di passare questo valore come argomento di build.

Genera manifest

Nel terminale, esegui questo comando per generare un file skaffold.yaml e un deployment.yaml predefiniti.

  1. Inizializza Skaffold con il seguente comando
skaffold init --generate-manifests

Quando richiesto, utilizza le frecce per spostare il cursore e la barra spaziatrice per selezionare le opzioni.

Scegli:

  • 8080 per la porta
  • y per salvare la configurazione

Aggiornamento configurazioni Skaffold

  • Modificare il nome predefinito dell'applicazione
  • Apri skaffold.yaml
  • Seleziona il nome dell'immagine attualmente impostato su dockerfile-image
  • Fai clic con il tasto destro del mouse e scegli Modifica tutte le occorrenze
  • Digita il nuovo nome come python-app.
  • Modifica ulteriormente la sezione relativa alla build in
  • aggiungi docker.buildArgs per superare FLASK_DEBUG=1
  • Sincronizza le impostazioni per caricare eventuali modifiche ai file *.py dall'IDE al contenitore in esecuzione

Dopo le modifiche, la sezione di compilazione nel file skaffold.yaml sarebbe quella seguente:

build:
 artifacts:
 - image: python-app
   docker:
     buildArgs:
       FLASK_DEBUG: 1
     dockerfile: Dockerfile
   sync:
     infer:
     - '**/*.py'

Modifica file di configurazione Kubernetes

  1. Modificare il nome predefinito
  • Apri file deployment.yaml
  • Seleziona il nome dell'immagine attualmente impostato su dockerfile-image
  • Fai clic con il tasto destro del mouse e scegli Modifica tutte le occorrenze
  • Digita il nuovo nome come python-app.

4. Panoramica del processo di sviluppo

Con la logica di business aggiunta, ora puoi eseguire il deployment e testare la tua applicazione. La seguente sezione evidenzia l'utilizzo del plug-in Cloud Code. Tra le altre cose, questo plug-in si integra con skaffold per semplificare il processo di sviluppo. Quando esegui il deployment su GKE nei passaggi seguenti, Cloud Code e Skaffold creano automaticamente l'immagine container, ne eseguono il push a un Container Registry e quindi il deployment dell'applicazione su GKE. Ciò avviene dietro le quinte, astraendo i dettagli dal flusso degli sviluppatori.

Eseguire il deployment in Kubernetes

  1. Nel riquadro nella parte inferiore dell'editor di Cloud Shell, seleziona Cloud Code }.{/

fdc797a769040839.png

  1. Nel riquadro visualizzato in alto, seleziona Esegui su Kubernetes. Se richiesto, seleziona Sì per utilizzare il contesto Kubernetes attuale.

cfce0d11ef307087.png

Questo comando avvia una build del codice sorgente e quindi esegue i test. L'esecuzione della build e dei test richiederà alcuni minuti. Questi test includono i test delle unità e una fase di convalida che controlla le regole impostate per l'ambiente di deployment. Questo passaggio di convalida è già configurato e garantisce che tu riceva avvisi relativi a problemi di deployment anche mentre stai utilizzando l'ambiente di sviluppo.

  1. La prima volta che esegui il comando, nella parte superiore dello schermo viene visualizzato un prompt che chiede se vuoi il contesto attuale di Kubernetes. Seleziona "Sì". per accettare e utilizzare il contesto corrente.
  2. A seguire, viene visualizzato un prompt in cui si chiede quale Container Registry utilizzare. Premi Invio per accettare il valore predefinito fornito
  3. Seleziona la scheda Output nel riquadro inferiore per visualizzare l'avanzamento e le notifiche

f95b620569ba96c5.png

  1. Seleziona "Kubernetes: Esegui/Debug - Dettagliato" nel menu a discesa del canale a destra per visualizzare ulteriori dettagli e i log trasmessi in live streaming dai container

94acdcdda6d2108.png

Una volta completati la build e i test, nella scheda Output è indicato Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully. e viene visualizzato l'URL http://localhost:8080.

  1. Nel terminale Cloud Code, passa il mouse sopra il primo URL nell'output (http://localhost:8080), quindi nella descrizione comando visualizzata seleziona Apri anteprima web.
  2. Viene visualizzata una nuova scheda del browser con il messaggio Hello, World!

Ricarica a caldo

  1. Apri il file app.py
  2. Cambia il messaggio di benvenuto in Hello from Python

Nota immediatamente che nella finestra Output, nella visualizzazione Kubernetes: Run/Debug, il visualizzatore sincronizza i file aggiornati con il container in Kubernetes

Update initiated
Build started for artifact python-app
Build completed for artifact python-app

Deploy started
Deploy completed

Status check started
Resource pod/python-app-6f646ffcbb-tn7qd status updated to In Progress
Resource deployment/python-app status updated to In Progress
Resource deployment/python-app status completed successfully
Status check succeeded
...
  1. Se passi alla visualizzazione Kubernetes: Run/Debug - Detailed, noterai che riconosce le modifiche ai file, quindi crea ed esegue nuovamente il deployment dell'app
files modified: [app.py]
Syncing 1 files for gcr.io/veer-pylab-01/python-app:3c04f58-dirty@sha256:a42ca7250851c2f2570ff05209f108c5491d13d2b453bb9608c7b4af511109bd
Copying files:map[app.py:[/app/app.py]]togcr.io/veer-pylab-01/python-app:3c04f58-dirty@sha256:a42ca7250851c2f2570ff05209f108c5491d13d2b453bb9608c7b4af511109bd
Watching for changes...
[python-app] * Detected change in '/app/app.py', reloading
[python-app] * Restarting with stat
[python-app] * Debugger is active!
[python-app] * Debugger PIN: 744-729-662
  1. Aggiorna il browser per visualizzare i risultati aggiornati.

Debug

  1. Vai alla visualizzazione debug e interrompi il thread corrente 647213126d7a4c7b.png.
  2. Fai clic su Cloud Code nel menu in basso e seleziona Debug on Kubernetes per eseguire l'applicazione in modalità debug.
  • Nella visualizzazione Kubernetes Run/Debug - Detailed della finestra Output, nota che skaffold eseguirà il deployment di questa applicazione in modalità di debug.
  1. La prima volta che viene eseguita, un prompt chiederà dove si trova l'origine all'interno del container. Questo valore è correlato alle directory nel Dockerfile.

Premi Invio per accettare il valore predefinito

583436647752e410.png

La creazione e il deployment dell'applicazione impiegheranno un paio di minuti.

  1. Al termine del processo. Noterai un debugger allegato.
Port forwarding pod/python-app-8bd64cf8b-cskfl in namespace default, remote port 5678 -> http://127.0.0.1:5678
  1. La barra di stato in basso cambia colore da blu ad arancione per indicare che è in modalità di debug.
  2. Nella vista Kubernetes Run/Debug, nota che è stato avviato un container di cui è possibile eseguire il debug
**************URLs*****************
Forwarded URL from service python-app: http://localhost:8080
Debuggable container started pod/python-app-8bd64cf8b-cskfl:python-app (default)
Update succeeded
***********************************

Utilizza i punti di interruzione

  1. Apri il file app.py
  2. Individua l'istruzione return message.
  3. Aggiungi un punto di interruzione alla riga facendo clic sullo spazio vuoto a sinistra del numero di riga. Viene visualizzato un indicatore rosso per indicare che il punto di interruzione è impostato
  4. Ricarica il browser e nota che il debugger interrompe il processo in corrispondenza del punto di interruzione e ti consente di esaminare le variabili e lo stato dell'applicazione in esecuzione da remoto in GKE
  5. Fai clic nella sezione VARIABILI.
  6. Fai clic su Locali per trovare la variabile "message".
  7. Fai doppio clic sul nome della variabile "message" e, nel popup, modifica il valore in modo diverso, ad esempio "Greetings from Python".
  8. Fai clic sul pulsante Continua nel pannello di controllo del debug 607c33934f8d6b39.png
  9. Esamina la risposta nel browser, che ora mostra il valore aggiornato che hai appena inserito.
  10. Interrompi il "Debug" premendo il pulsante di interruzione 647213126d7a4c7b.png e rimuovere il punto di interruzione facendo di nuovo clic sul punto di interruzione.

5. Sviluppo di un servizio REST CRUD semplice

A questo punto la tua applicazione è completamente configurata per lo sviluppo containerizzato e hai seguito il flusso di lavoro di sviluppo di base con Cloud Code. Nelle sezioni seguenti metterai in pratica ciò che hai imparato aggiungendo endpoint di servizio REST che si connettono a un database gestito in Google Cloud.

Codifica il servizio rimanente

Il codice seguente crea un semplice servizio REST che utilizza Spanner come database a supporto dell'applicazione. Crea l'applicazione copiando il seguente codice nell'applicazione.

  1. Crea l'applicazione principale sostituendo app.py con il seguente contenuto
import os
from flask import Flask, request, jsonify
from google.cloud import spanner


app = Flask(__name__)


instance_id = "music-catalog"

database_id = "musicians"

spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)


@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

@app.route('/singer', methods=['POST'])
def create():
    try:
        request_json = request.get_json()
        singer_id = request_json['singer_id']
        first_name = request_json['first_name']
        last_name = request_json['last_name']
        def insert_singers(transaction):
            row_ct = transaction.execute_update(
                f"INSERT Singers (SingerId, FirstName, LastName) VALUES" \
                f"({singer_id}, '{first_name}', '{last_name}')"
            )
            print("{} record(s) inserted.".format(row_ct))

        database.run_in_transaction(insert_singers)

        return {"Success": True}, 200
    except Exception as e:
        return e



@app.route('/singer', methods=['GET'])
def get_singer():

    try:
        singer_id = request.args.get('singer_id')
        def get_singer():
            first_name = ''
            last_name = ''
            with database.snapshot() as snapshot:
                results = snapshot.execute_sql(
                    f"SELECT SingerId, FirstName, LastName FROM Singers " \
                    f"where SingerId = {singer_id}",
                    )
                for row in results:
                    first_name = row[1]
                    last_name = row[2]
                return (first_name,last_name )
        first_name, last_name = get_singer()  
        return {"first_name": first_name, "last_name": last_name }, 200
    except Exception as e:
        return e


@app.route('/singer', methods=['PUT'])
def update_singer_first_name():
    try:
        singer_id = request.args.get('singer_id')
        request_json = request.get_json()
        first_name = request_json['first_name']
        
        def update_singer(transaction):
            row_ct = transaction.execute_update(
                f"UPDATE Singers SET FirstName = '{first_name}' WHERE SingerId = {singer_id}"
            )

            print("{} record(s) updated.".format(row_ct))

        database.run_in_transaction(update_singer)
        return {"Success": True}, 200
    except Exception as e:
        return e


@app.route('/singer', methods=['DELETE'])
def delete_singer():
    try:
        singer_id = request.args.get('singer')
    
        def delete_singer(transaction):
            row_ct = transaction.execute_update(
                f"DELETE FROM Singers WHERE SingerId = {singer_id}"
            )
            print("{} record(s) deleted.".format(row_ct))

        database.run_in_transaction(delete_singer)
        return {"Success": True}, 200
    except Exception as e:
        return e

port = int(os.environ.get('PORT', 8080))
if __name__ == '__main__':
    app.run(threaded=True, host='0.0.0.0', port=port)

Aggiungi configurazioni di database

Per connetterti a Spanner in modo sicuro, configura l'applicazione in modo che utilizzi le identità dei carichi di lavoro. Ciò consente all'applicazione di agire come proprio account di servizio e di disporre di autorizzazioni individuali quando accede al database.

  1. Aggiorna deployment.yaml. Aggiungi il seguente codice alla fine del file (assicurati di mantenere i rientri di tabulazione nell'esempio riportato di seguito)
      serviceAccountName: python-ksa
      nodeSelector:
        iam.gke.io/gke-metadata-server-enabled: "true" 

Esegui il deployment e convalida l'applicazione

  1. Nel riquadro nella parte inferiore dell'editor di Cloud Shell, seleziona Cloud Code, quindi Debug on Kubernetes nella parte superiore dello schermo.
  2. Una volta completati la build e i test, la scheda Output riporta il seguente messaggio: Resource deployment/python-app status completed successfully e un URL: "Forwarded URL from service python-app: http://localhost:8080"
  3. Aggiungi un paio di voci.

Dal terminale Cloudshell, esegui il comando seguente

curl -X POST http://localhost:8080/singer -H 'Content-Type: application/json' -d '{"first_name":"Cat","last_name":"Meow", "singer_id": 6}'
  1. Testa GET eseguendo il comando seguente nel terminale
curl -X GET http://localhost:8080/singer?singer_id=6
  1. Test Delete: ora prova a eliminare una voce eseguendo questo comando. Modifica il valore dell'ID articolo, se necessario.
curl -X DELETE http://localhost:8080/singer?singer_id=6
    This throws an error message
500 Internal Server Error

Identifica e risolvi il problema

  1. Modalità di debug e individua il problema. Ecco alcuni suggerimenti:
  • Sappiamo che qualcosa non va con DELETE perché non restituisce il risultato desiderato. Devi quindi impostare il punto di interruzione in app.py nel metodo delete_singer.
  • Esegui l'esecuzione passo passo e osserva le variabili in ogni passaggio per osservare i valori delle variabili locali nella finestra di sinistra.
  • Per osservare valori specifici come singer_id e request.args nella finestra di visualizzazione, aggiungi queste variabili alla finestra di visualizzazione.
  1. Tieni presente che il valore assegnato a singer_id è None. Modifica il codice per risolvere il problema.

Lo snippet di codice corretto avrà il seguente aspetto.

@app.route('/delete-singer', methods=['DELETE', 'GET'])
def delete_singer():
    try:
        singer_id = request.args.get('singer_id')
  1. Una volta riavviata l'applicazione, riprova a eliminarla.
  2. Interrompi la sessione di debug facendo clic sul quadrato rosso nella barra degli strumenti di debug 647213126d7a4c7b.png

6. Esegui la pulizia

Complimenti! In questo lab hai creato da zero una nuova applicazione Python e l'hai configurata per funzionare in modo efficace con i container. Hai quindi eseguito il deployment dell'applicazione ed eseguito il debug in un cluster GKE remoto seguendo lo stesso flusso di sviluppatori che si trova negli stack di applicazioni tradizionali.

Per eseguire la pulizia dopo aver completato il lab:

  1. Elimina i file utilizzati nel lab
cd ~ && rm -rf container-developer-workshop
  1. Elimina il progetto per rimuovere tutte le infrastrutture e le risorse correlate