Desenvolvimento do InnerLoop com Python

1. Visão geral

Este laboratório mostra as funcionalidades projetadas para agilizar o fluxo de trabalho de desenvolvimento para engenheiros de software responsáveis pelo desenvolvimento de aplicativos Python em um ambiente conteinerizado. O desenvolvimento de contêineres típico exige que o usuário entenda os detalhes dos contêineres e do processo de criação do contêiner. Além disso, os desenvolvedores geralmente precisam interromper o fluxo, 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 efetivamente com aplicativos conteinerizados sem sair do ambiente de desenvolvimento integrado.

O que você vai aprender

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

  • Como criar um novo aplicativo inicial do Python
  • Analisar o processo de desenvolvimento
  • Desenvolver um serviço de descanso CRUD simples

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. Ele é uma string de caracteres que não é usada pelas APIs do Google e pode ser atualizada a qualquer momento.
  • O ID do projeto precisa ser exclusivo em todos os projetos do Google Cloud e não pode ser alterado após a definição. O Console do Cloud gera automaticamente uma string única, geralmente não importa o que seja. Na maioria dos codelabs, você precisará fazer referência ao ID do projeto, que geralmente é identificado como PROJECT_ID. Então, se você não gostar dele, gere outro ID aleatório ou crie um próprio e veja se ele está disponível. Em seguida, ele fica "congelado" depois que o projeto é criado.
  • Há um terceiro valor, um Número de projeto, que algumas APIs usam. Saiba mais sobre esses três valores na documentação.
  1. Em seguida, você precisará ativar 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 encerrar os recursos e não gerar cobranças além deste tutorial, siga as instruções de "limpeza" encontradas no final do codelab. Novos usuários do Google Cloud estão qualificados para o programa de US$ 300 de avaliação sem custos.

Inicie o editor do Cloud Shell

Este laboratório foi elaborado 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 inferior 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 do terminal também deve estar disponível na parte inferior da tela
  3. Se o terminal NÃO estiver aberto, use a combinação de teclas "ctrl+" para abrir uma nova janela de 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)')

Conseguir o código-fonte

  1. O código-fonte deste laboratório está localizado em container-developer-workshop em GoogleCloudPlatform no GitHub. Clone-o com o comando abaixo e mude para o diretório.
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 o terminal NÃO estiver aberto, use a combinação de teclas "ctrl+" para abrir uma nova janela de terminal

Provisionar a infraestrutura usada neste laboratório

Neste laboratório, você vai implantar o código no GKE e acessar os dados armazenados em um banco de dados do Spanner. O script de configuração abaixo prepara essa infraestrutura para você. O processo de provisionamento levará mais de 10 minutos. Siga as próximas etapas enquanto a configuração é processada.

../setup.sh

3. Criar um novo aplicativo inicial do Python

  1. Crie um arquivo chamado requirements.txt e copie o conteúdo abaixo para ele.
Flask
gunicorn
google-cloud-spanner
ptvsd==4.3.2
  1. Crie um arquivo chamado app.py e cole o código a seguir 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 código abaixo 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 alterações de código em um aplicativo flask do Python. Este Dockerfile permite que você transmita esse valor como um argumento de compilação.

Gerar manifestos

No seu terminal, execute o seguinte comando para gerar um skaffold.yaml e 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

  • Alterar o nome padrão do aplicativo
  • Abrir skaffold.yaml
  • Selecione o nome da imagem definido atualmente como dockerfile-image
  • Clique com o botão direito do mouse e escolha "Alterar todas as ocorrências"
  • Digite o novo nome como python-app
  • Edite mais a seção de criação para
  • adicione docker.buildArgs para transmitir FLASK_DEBUG=1
  • Sincronize as configurações para carregar qualquer mudança em 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 ficará 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. Alterar o nome padrão
  • Abrir arquivo deployment.yaml
  • Selecione o nome da imagem definido atualmente como dockerfile-image
  • Clique com o botão direito do mouse e escolha "Alterar todas as ocorrências"
  • Digite o novo nome como python-app

4. Orientações sobre o processo de desenvolvimento

Com a lógica de negócios adicionada, agora é possível implantar e testar seu aplicativo. Na seção a seguir, vamos destacar o uso do plug-in Cloud Code. Entre outras coisas, este plug-in se integra ao skaffold para simplificar seu processo de desenvolvimento. Quando você implantar no GKE nas etapas a seguir, o Cloud Code e o Skaffold vão criar automaticamente a imagem do contêiner, enviá-la para um Container Registry e implantar o aplicativo no GKE. Isso acontece nos bastidores abstraindo os detalhes do fluxo do desenvolvedor.

Implantar no Kubernetes

  1. No painel da parte de baixo do Editor do Cloud Shell, selecione Cloud Code Envio

fdc797a769040839.png

  1. No painel exibido na parte superior, selecione Executar no Kubernetes. Se solicitado, selecione Sim para usar o contexto atual do Kubernetes.

cfce0d11ef307087.png

Esse comando inicia um build do código-fonte e executa os testes. O build e os testes levarão alguns minutos para serem executados. Esses testes 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 de problemas de implantação mesmo enquanto estiver trabalhando no ambiente de desenvolvimento.

  1. Na primeira vez que você executar o comando, um prompt vai aparecer na parte de cima da tela perguntando se você quer o contexto atual do Kubernetes. Selecione "Yes" para aceitar e usar o contexto atual.
  2. Em seguida, será exibido um prompt perguntando qual registro de contêiner usar. Pressione "Enter" para aceitar o valor padrão fornecido
  3. Selecione a guia Output no painel inferior para visualizar o progresso e as notificações

f95b620569ba96c5.png

  1. Selecione "Kubernetes: Run/Debug - Detalhado". no menu suspenso do canal à direita, é possível ver mais detalhes e os registros das transmissões ao vivo dos contêineres.

94acdcdda6d2108.png

Quando o build e os testes forem concluídos, a guia "Output" vai mostrar: Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully., e o URL http://localhost:8080 vai aparecer na lista.

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

Fazer recarga automática

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

Na janela Output, na visualização Kubernetes: Run/Debug, o inspetor 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 perceber que ela reconhece mudanças no arquivo e depois 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 seu navegador para conferir os resultados atualizados.

Depuração

  1. Acesse a Visualização de depuração e interrompa a linha de execução atual 647213126d7a4c7b.png.
  2. 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. Na primeira vez que isso for executado, um prompt 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

583436647752e410.png

Levará alguns minutos para o aplicativo ser criado e implantado.

  1. Quando o processo é concluído. Você vai notar um depurador anexado.
Port forwarding pod/python-app-8bd64cf8b-cskfl in namespace default, remote port 5678 -> http://127.0.0.1:5678
  1. A barra de status inferior 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 foi 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 indicar que o ponto de interrupção está definido
  4. Atualize 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
  5. Clique para baixo na seção "VARIÁVEIS".
  6. Clique em "Locais" e encontre a variável "message".
  7. Clique duas vezes no nome da variável "message" No pop-up, mude o valor para algo diferente, como "Greetings from Python".
  8. Clique no botão "Continuar" no painel de controle de depuração 607c33934f8d6b39.png.
  9. Revise a resposta no navegador, que agora mostra o valor atualizado que você inseriu.
  10. Parar a "Depuração" pressionando o botão de parada 647213126d7a4c7b.png e remova o ponto de interrupção clicando nele novamente.

5. Como desenvolver um serviço CRUD simples de descanso

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

Programar o serviço REST

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

  1. Crie o aplicativo principal substituindo app.py pelo seguinte conteúdo:
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 as identidades de carga de trabalho. Isso permite que o aplicativo atue como a própria conta de serviço 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 da tabulação no exemplo abaixo)
      serviceAccountName: python-ksa
      nodeSelector:
        iam.gke.io/gke-metadata-server-enabled: "true" 

Implante e valide o aplicativo

  1. No painel da 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 "Output" vai mostrar: Resource deployment/python-app status completed successfully e um URL vai estar listado: "Forwarded URL from service python-app: http://localhost:8080"
  3. Adicione algumas entradas.

No terminal cloudshell, 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. Execute o comando abaixo no terminal para testar o GET
curl -X GET http://localhost:8080/singer?singer_id=6
  1. Teste a exclusão: agora tente excluir uma entrada executando o comando a seguir. Mude o valor do 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 a função DELETE, pois ela não está retornando o resultado desejado. Portanto, defina o ponto de interrupção em app.py no método delete_singer.
  • Faça a execução passo a passo e observe as variáveis em cada etapa para observar 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 de observação.
  1. Observe que o valor atribuído a singer_id é None. Mude o código para corrigir o problema.

O snippet de código fixo 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, teste novamente tentando excluir.
  2. Pare a sessão de depuração clicando no quadrado vermelho na barra de ferramentas de depuração 647213126d7a4c7b.png.

6. Limpeza

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

Para fazer a limpeza após a conclusão do laboratório:

  1. Exclua os arquivos usados no laboratório
cd ~ && rm -rf container-developer-workshop
  1. Exclua o projeto para remover toda a infraestrutura e os recursos relacionados