Desenvolvimento do InnerLoop usando o Cloud Workstations com Python

1. Visão geral

Este laboratório demonstra recursos e funcionalidades projetados para simplificar o fluxo de trabalho de desenvolvimento de engenheiros de software encarregados de desenvolver aplicativos Python em um ambiente conteinerizado. O desenvolvimento típico de contêineres exige que o usuário entenda os detalhes dos contêineres e do processo de build deles. Além disso, os desenvolvedores geralmente precisam interromper o fluxo de trabalho, saindo do ambiente de desenvolvimento integrado para testar e depurar os aplicativos em ambientes remotos. Com as ferramentas e tecnologias mencionadas neste tutorial, os desenvolvedores podem trabalhar de forma eficaz com aplicativos contêinerizados sem sair do ambiente de desenvolvimento integrado.

O que você vai aprender

Neste laboratório, você vai aprender métodos para desenvolver com contêineres no GCP, incluindo:

  • Como criar um novo aplicativo inicial em Python
  • Explicar o processo de desenvolvimento
  • Desenvolver um serviço REST CRUD simples
  • Como implantar no GKE
  • Depuração de um estado de erro
  • Usar pontos de interrupção / registros
  • Implantação dinâmica de mudanças de volta para o GKE

58a4cdd3ed7a123a.png

2. Configuração e requisitos

Configuração de ambiente personalizada

  1. Faça login no Console do Google Cloud e crie um novo projeto ou reutilize um existente. Crie uma conta do Gmail ou do Google Workspace, se ainda não tiver uma.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • O Nome do projeto é o nome de exibição para os participantes do projeto. É uma string de caracteres não usada pelas APIs do Google É possível atualizar o local a qualquer momento.
  • O ID do projeto precisa ser exclusivo em todos os projetos do Google Cloud e não pode ser mudado após a definição. O console do Cloud gera automaticamente uma string exclusiva. Em geral, não importa o que seja. Na maioria dos codelabs, é necessário fazer referência ao ID do projeto, normalmente identificado como PROJECT_ID. Se você não gostar do ID gerado, crie outro aleatório. Se preferir, teste o seu e confira se ele está disponível. Ele não pode ser mudado após essa etapa e permanece durante o projeto.
  • Para sua informação, há um terceiro valor, um Número do projeto, que algumas APIs usam. Saiba mais sobre esses três valores na documentação.
  1. Em seguida, ative o faturamento no console do Cloud para usar os recursos/APIs do Cloud. A execução deste codelab não será muito cara, se tiver algum custo. Para desligar os recursos e evitar cobranças além deste tutorial, exclua os recursos criados ou o projeto inteiro. Novos usuários do Google Cloud estão qualificados para o programa de US$ 300 de avaliação sem custos.

Iniciar o editor do Cloud Shell

Este laboratório foi projetado e testado para uso com o editor do Google Cloud Shell. Para acessar o editor,

  1. Acesse seu projeto do Google em https://console.cloud.google.com.
  2. No canto superior direito, clique no ícone do editor do Cloud Shell.

8560cc8d45e8c112.png

  1. Um novo painel será aberto na parte de baixo da janela
  2. Clique no botão "Abrir editor".

9e504cb98a6a8005.png

  1. O editor será aberto com um explorador à direita e o editor na área central.
  2. Um painel de terminal também vai estar disponível na parte de baixo da tela.
  3. Se o terminal NÃO estiver aberto, use a combinação de teclas "ctrl+`" para abrir uma nova janela do terminal.

Configuração do ambiente

No Cloud Shell, defina o ID e o número do projeto. Salve-as como variáveis PROJECT_ID e PROJECT_ID.

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

Provisionar a infraestrutura usada neste laboratório

Neste laboratório, você vai implantar código no GKE e acessar dados armazenados em um banco de dados do Spanner. Você também vai usar o Cloud Workstations como ambiente de desenvolvimento integrado. O script de configuração abaixo prepara essa infraestrutura para você.

  1. Faça o download do script de configuração e torne-o executável.
wget https://raw.githubusercontent.com/GoogleCloudPlatform/container-developer-workshop/main/labs/python/setup_with_cw.sh
chmod +x setup_with_cw.sh
  1. Abra o arquivo setup_with_cw.sh e edite os valores das senhas que estão definidas como CHANGEME.
  2. Execute o script de configuração para criar um cluster do GKE e um banco de dados do Spanner que você vai usar neste laboratório.
./setup_with_cw.sh &

Cluster do Cloud Workstations

  1. Abra o Cloud Workstations no Console do Cloud. Aguarde até que o cluster esteja no status READY.

305e1a3d63ac7ff6.png

Criar configuração de estações de trabalho

  1. Se a sessão do Cloud Shell foi desconectada, clique em "Reconectar" e execute o comando da CLI gcloud para definir o ID do projeto. Substitua o ID do projeto de exemplo abaixo pelo ID do seu projeto do Qwiklabs antes de executar o comando.
gcloud config set project qwiklabs-gcp-project-id
  1. Faça o download e execute o script abaixo no terminal para criar a configuração do Cloud Workstations.
wget https://raw.githubusercontent.com/GoogleCloudPlatform/container-developer-workshop/main/labs/python/workstation_config_setup.sh
chmod +x workstation_config_setup.sh
./workstation_config_setup.sh
  1. Verifique os resultados na seção "Configurações". A transição para o status READY leva 2 minutos.

2e23c2e9983d1ccf.png

  1. Abra o Cloud Workstations no console e crie uma nova instância.

a53adeeac81a78c8.png

  1. Mude o nome para my-workstation e selecione a configuração atual: codeoss-python.

f052cd47701ec774.png

  1. Verifique os resultados na seção "Estações de trabalho".

Iniciar estação de trabalho

  1. Inicie e abra a estação de trabalho. A inicialização da estação de trabalho leva alguns minutos.

682f8a307032cba3.png

  1. Para permitir cookies de terceiros, clique no ícone na barra de endereço. 1b8923e2943f9bc4.png

fcf9405b6957b7d7.png

  1. Clique em "Site indisponível?".

36a84c0e2e3b85b.png

  1. Clique em "Permitir cookies".

2259694328628fba.png

  1. Quando a estação de trabalho for iniciada, o ambiente de desenvolvimento integrado do Code OSS vai aparecer. Clique em "Marcar como concluído" na página "Como começar" do ambiente de desenvolvimento integrado da estação de trabalho.

94874fba9b74cc22.png

3. Criar um novo aplicativo inicial em Python

Nesta seção, você vai criar um novo aplicativo Python.

  1. Abra um novo terminal.

c31d48f2e4938c38.png

  1. Criar um diretório e abri-lo como um espaço de trabalho
mkdir music-service && cd music-service

code-oss-cloud-workstations -r --folder-uri="$PWD"

Clique no botão "Permitir" se essa mensagem aparecer para poder copiar e colar na estação de trabalho.

58149777e5cc350a.png

  1. Crie um arquivo chamado requirements.txt e copie o conteúdo a seguir nele:

789e8389170bd900.png

Flask
gunicorn
google-cloud-spanner
ptvsd==4.3.2
  1. Crie um arquivo chamado app.py e cole o seguinte código nele:
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. Crie um arquivo chamado Dockerfile e cole o seguinte nele:
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"]

Observação: FLASK_DEBUG=1 permite recarregar automaticamente as mudanças de código em um app Python Flask. Esse Dockerfile permite transmitir esse valor como um argumento de build.

Gerar manifestos

No terminal, execute o comando a seguir para gerar um skaffold.yaml e um deployment.yaml padrão.

  1. Inicialize o Skaffold com o seguinte comando:
skaffold init --generate-manifests

Quando solicitado, use as setas para mover o cursor e a barra de espaço para selecionar as opções.

Opções disponíveis:

  • 8080 para a porta
  • y para salvar a configuração

Atualizar configurações do Skaffold

  • Mudar o nome do aplicativo padrão
  • Abrir skaffold.yaml
  • Selecione o nome da imagem definido como dockerfile-image.
  • Clique com o botão direito do mouse e escolha "Mudar todas as ocorrências".
  • Digite o novo nome como python-app
  • Edite ainda mais a seção de build para
  • adicionar docker.buildArgs ao cartão FLASK_DEBUG=1
  • Sincronize as configurações para carregar as mudanças nos arquivos *.py do ambiente de desenvolvimento integrado para o contêiner em execução.

Após as edições, a seção de build no arquivo skaffold.yaml ficaria assim:

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

Modificar o arquivo de configuração do Kubernetes

  1. Mudar o nome padrão
  • Abrir arquivo deployment.yaml
  • Selecione o nome da imagem definido como dockerfile-image.
  • Clique com o botão direito do mouse e escolha "Mudar todas as ocorrências".
  • Digite o novo nome como python-app

4. Como percorrer o processo de desenvolvimento

Com a lógica de negócios adicionada, agora você pode implantar e testar seu aplicativo. A seção a seguir mostra o uso do plug-in do Cloud Code. Entre outras coisas, esse plug-in se integra ao skaffold para simplificar o processo de desenvolvimento. Ao implantar no GKE nas etapas a seguir, o Cloud Code e o Skaffold vão criar automaticamente a imagem do contêiner, enviá-la por push para um Container Registry e implantar o aplicativo your no GKE. Isso acontece nos bastidores, abstraindo os detalhes do fluxo do desenvolvedor.

Faça login no Google Cloud

  1. Clique no ícone do Cloud Code e selecione "Fazer login no Google Cloud":

1769afd39be372ff.png

  1. Clique em "Prosseguir com login".

923bb1c8f63160f9.png

  1. Confira a saída no terminal e abra o link:

517fdd579c34aa21.png

  1. Faça login com as credenciais de estudante do Qwiklabs.

db99b345f7a8e72c.png

  1. Selecione "Permitir":

a5376553c430ac84.png

  1. Copie o código de verificação e volte à guia "Estação de trabalho".

6719421277b92eac.png

  1. Cole o código de verificação e pressione "Enter".

e9847cfe3fa8a2ce.png

Adicionar cluster do Kubernetes

  1. Adicionar um cluster

62a3b97bdbb427e5.png

  1. Selecione Google Kubernetes Engine:

9577de423568bbaa.png

  1. Selecione o projeto.

c5202fcbeebcd41c.png

  1. Selecione "python-cluster", que foi criado na configuração inicial.

719c2fc0a7f9e84f.png

  1. O cluster agora aparece na lista de clusters do Kubernetes em "Cloud Code". Navegue e explore o cluster aqui.

7e5f50662d4eea3c.png

Definir o ID do projeto atual usando a CLI gcloud

  1. Copie o ID do projeto deste laboratório na página do Qwiklabs.

fcff2d10007ec5bc.png

  1. No terminal, execute o comando da CLI gcloud para definir o ID do projeto. Substitua o ID do projeto de exemplo antes de executar o comando. SUBSTITUA o ID do projeto antes de executar o comando abaixo.
gcloud config set project qwiklabs-gcp-project-id

Implantar no Kubernetes

  1. No painel na parte de baixo do editor do Cloud Shell, selecione Cloud Code 

d99a88992e15fea9.png

  1. No painel que aparece na parte de cima, selecione Executar no Kubernetes. Se solicitado, selecione "Sim" para usar o contexto atual do Kubernetes.

bfd65e9df6d4a6cb.png

Esse comando inicia um build do código-fonte e executa os testes. A criação e os testes vão levar alguns minutos para serem executados. Eles incluem testes de unidade e uma etapa de validação que verifica as regras definidas para o ambiente de implantação. Essa etapa de validação já está configurada e garante que você receba avisos sobre problemas de implantação enquanto ainda está trabalhando no ambiente de desenvolvimento.

  1. Na primeira vez que você executar o comando, uma solicitação vai aparecer na parte de cima da tela perguntando se você quer o contexto atual do Kubernetes. Selecione "Sim" para aceitar e usar o contexto atual.
  2. Em seguida, uma solicitação vai aparecer perguntando qual registro de contêiner usar. Pressione "Enter" para aceitar o valor padrão fornecido.
  3. Selecione a guia "Saída" no painel de baixo para conferir o progresso e as notificações. Use o menu suspenso e selecione "Kubernetes: Run/Debug".

9c87ccbf5d06f50a.png

  1. Selecione "Kubernetes: Run/Debug - Detailed" no menu suspenso do canal à direita para conferir mais detalhes e registros transmitidos ao vivo dos contêineres.

804abc8833ffd571.png

Quando o build e os testes forem concluídos, os registros da guia "Saída" terão o URL http://localhost:8080 listado na visualização "Kubernetes: Run/Debug".

  1. No terminal do Cloud Code, passe o cursor sobre o primeiro URL na saída (http://localhost:8080) e, na dica de ferramenta que aparece, selecione "Abrir visualização na Web".
  2. Uma nova guia do navegador será aberta e vai mostrar a mensagem Hello, World!

Recarga automática

  1. Abra o arquivo app.py.
  2. Mude a mensagem de saudação para Hello from Python

Na janela Output, na visualização Kubernetes: Run/Debug, o observador sincroniza os arquivos atualizados com o contêiner no 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 você mudar para a visualização Kubernetes: Run/Debug - Detailed, vai notar que ela reconhece as mudanças no arquivo, cria e reimplanta o 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. Atualize a guia do navegador em que você viu os resultados anteriores para conferir os resultados atualizados.

Depuração

  1. Acesse a visualização de depuração e pare a linha de execução atual 647213126d7a4c7b.png. Se for solicitado, você pode liberar espaço após cada execução.
  2. 70d6bd947d04d1e6.png
  3. Clique em Cloud Code no menu da parte de baixo e selecione Debug on Kubernetes para executar o aplicativo no modo debug.
  • Na visualização Kubernetes Run/Debug - Detailed da janela Output, observe que o Skaffold vai implantar esse aplicativo no modo de depuração.
  1. Quando o processo for concluído. Você vai notar um depurador anexado e a guia "Saída" vai mostrar: Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully., e o URL http://localhost:8080 vai aparecer.
Port forwarding pod/python-app-8bd64cf8b-cskfl in namespace default, remote port 5678 -> http://127.0.0.1:5678
  1. A barra de status na parte de baixo muda de azul para laranja, indicando que está no modo de depuração.
  2. Na visualização Kubernetes Run/Debug, observe que um contêiner depurável é iniciado.
**************URLs*****************
Forwarded URL from service python-app: http://localhost:8080
Debuggable container started pod/python-app-8bd64cf8b-cskfl:python-app (default)
Update succeeded
***********************************

Usar pontos de interrupção

  1. Abra o arquivo app.py.
  2. Localize a instrução que diz return message.
  3. Adicione um ponto de interrupção a essa linha clicando no espaço em branco à esquerda do número da linha. Um indicador vermelho vai aparecer para mostrar que o ponto de interrupção foi definido.
  4. Na primeira vez que isso for executado, uma solicitação vai perguntar onde a origem está dentro do contêiner. Esse valor está relacionado aos diretórios no Dockerfile.

Pressione "Enter" para aceitar o padrão

fccc866f32b5ed86.png

A criação e a implantação do aplicativo levam alguns minutos.

  1. Recarregue o navegador e observe que o depurador interrompe o processo no ponto de interrupção e permite investigar as variáveis e o estado do aplicativo que está sendo executado remotamente no GKE.
  2. Clique na seção "VARIÁVEIS".
  3. Clique em "Locais" para encontrar a variável "message".
  4. Clique duas vezes no nome da variável "message" e, no pop-up, mude o valor para algo diferente, como "Greetings from Python".
  5. Clique no botão "Continuar" no painel de controle de depuração 607c33934f8d6b39.png.
  6. Revise a resposta no navegador, que agora mostra o valor atualizado que você acabou de inserir.
  7. Pare o modo "Debug" pressionando o botão de parada 647213126d7a4c7b.png e remova o ponto de interrupção clicando nele novamente.

5. Como desenvolver um serviço REST CRUD simples

Neste ponto, seu aplicativo está totalmente configurado para desenvolvimento em contêineres, e você já passou pelo fluxo de trabalho de desenvolvimento básico com o Cloud Code. Nas seções a seguir, você vai praticar o que aprendeu adicionando endpoints de serviço REST que se conectam a um banco de dados gerenciado no Google Cloud.

Codificar o serviço restante

O código abaixo cria um serviço REST simples que usa o Spanner como o banco de dados que oferece suporte ao aplicativo. Crie o aplicativo copiando o código a seguir nele.

  1. Crie o aplicativo principal substituindo app.py pelo conteúdo a seguir:
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)

Adicionar configurações de banco de dados

Para se conectar ao Spanner com segurança, configure o aplicativo para usar identidades da carga de trabalho. Isso permite que seu aplicativo atue como uma conta de serviço própria e tenha permissões individuais ao acessar o banco de dados.

  1. Atualize o deployment.yaml. Adicione o seguinte código ao final do arquivo (mantenha os recuos de tabulação no exemplo abaixo):
      serviceAccountName: python-ksa
      nodeSelector:
        iam.gke.io/gke-metadata-server-enabled: "true" 

Depois das mudanças, a seção de especificações vai ficar assim:

   spec:
     containers:
     - name: python-app
       image: python-app
     serviceAccountName: python-ksa
     nodeSelector:
       iam.gke.io/gke-metadata-server-enabled: "true"

Implantar e validar o aplicativo

  1. No painel na parte de baixo do editor do Cloud Shell, selecione Cloud Code e depois Debug on Kubernetes na parte de cima da tela.
  2. Quando o build e os testes forem concluídos, a guia "Saída" vai mostrar: Resource deployment/python-app status completed successfully, e um URL será listado: "URL encaminhado do serviço python-app: http://localhost:8080"
  3. Adicione algumas entradas.

No terminal do Cloud Shell, execute o comando abaixo:

curl -X POST http://localhost:8080/singer -H 'Content-Type: application/json' -d '{"first_name":"Cat","last_name":"Meow", "singer_id": 6}'
  1. Teste o GET executando o comando abaixo no terminal:
curl -X GET http://localhost:8080/singer?singer_id=6
  1. Teste de exclusão: agora tente excluir uma entrada executando o seguinte comando. Mude o valor de item-id, se necessário.
curl -X DELETE http://localhost:8080/singer?singer_id=6
    This throws an error message
500 Internal Server Error

Identificar e corrigir o problema

  1. Modo de depuração e encontrar o problema. Veja algumas dicas:
  • Sabemos que há algo errado com o DELETE, já que ele não está retornando o resultado desejado. Portanto, você definiria o ponto de interrupção em app.py no método delete_singer.
  • Execute a execução passo a passo e observe as variáveis em cada passo para ver os valores das variáveis locais na janela à esquerda.
  • Para observar valores específicos, como singer_id e request.args, adicione essas variáveis à janela "Watch".
  1. O valor atribuído a singer_id é None. Mude o código para corrigir o problema.

O snippet de código corrigido ficaria assim.

@app.route('/delete-singer', methods=['DELETE', 'GET'])
def delete_singer():
    try:
        singer_id = request.args.get('singer_id')
  1. Depois que o aplicativo for reiniciado, tente excluir de novo.
  2. Clique no quadrado vermelho na barra de ferramentas de depuração 647213126d7a4c7b.png para interromper a sessão.

6. Limpeza

Parabéns! Neste laboratório, você criou um novo aplicativo Python do zero e o configurou para funcionar de maneira eficaz com contêineres. Em seguida, você implantou e depurou o aplicativo em um cluster do GKE remoto seguindo o mesmo fluxo de desenvolvedor encontrado em stacks de aplicativos tradicionais.

Para fazer a limpeza depois de concluir o laboratório:

  1. Excluir os arquivos usados no laboratório
cd ~ && rm -rf ~/music-service
  1. Exclua o projeto para remover toda a infraestrutura e os recursos relacionados.