Programowanie InnerLoop w Pythonie

1. Omówienie

W tym module prezentowane są funkcje i możliwości zaprojektowane w celu usprawnienia procesu programowania dla inżynierów oprogramowania, których zadaniem jest tworzenie aplikacji w języku Python w skonteneryzowanym środowisku. Typowe tworzenie kontenerów wymaga, aby użytkownik znał szczegóły kontenerów i proces ich tworzenia. Poza tym deweloperzy zwykle muszą przerwać przepływ pracy, wychodząc z IDE, aby przetestować i debugować aplikacje w środowiskach zdalnych. Dzięki narzędziom i technologiom wspomnianym w tym samouczku deweloperzy mogą wydajnie pracować z aplikacjami skonteneryzowanymi bez opuszczania IDE.

Czego się nauczysz

W tym module nauczysz się, jak tworzyć aplikacje z wykorzystaniem kontenerów w GCP, takich jak:

  • Tworzenie nowej aplikacji startowej w języku Python
  • Omów proces programowania.
  • Opracuj prostą usługę przechowywania CRUD

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

  1. Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub wykorzystaj już istniejący. Jeśli nie masz jeszcze konta Gmail ani Google Workspace, musisz je utworzyć.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Nazwa projektu jest wyświetlaną nazwą uczestników tego projektu. To ciąg znaków, który nie jest używany przez interfejsy API Google i w każdej chwili możesz go zaktualizować.
  • Identyfikator projektu musi być unikalny we wszystkich projektach Google Cloud i nie można go zmienić (nie można go zmienić po ustawieniu). Cloud Console automatycznie wygeneruje unikalny ciąg znaków. zwykle nieważne, co ona jest. W większości ćwiczeń w Codelabs musisz odwoływać się do identyfikatora projektu (który zwykle nazywa się PROJECT_ID), więc jeśli Ci się nie podoba, wygeneruj kolejny losowy projekt lub wypróbuj swój własny identyfikator i sprawdź, czy jest dostępny. Potem urządzenie jest „zawieszone”. po utworzeniu projektu.
  • Występuje trzecia wartość – numer projektu – używany przez niektóre interfejsy API. Więcej informacji o wszystkich 3 wartościach znajdziesz w dokumentacji.
  1. Następnie musisz włączyć płatności w konsoli Cloud, aby móc korzystać z zasobów i interfejsów API Cloud. Ukończenie tego ćwiczenia z programowania nie powinno kosztować zbyt wiele. Aby wyłączyć zasoby, aby nie naliczać opłat po zakończeniu tego samouczka, wykonaj czynności „wyczyść” znajdziesz na końcu tego ćwiczenia. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego o wartości 300 USD.

Uruchom edytor Cloud Shell

Ten moduł został opracowany i przetestowany pod kątem użycia z edytorem Google Cloud Shell. Aby uzyskać dostęp do edytora:

  1. wejdź na stronę swojego projektu Google na https://console.cloud.google.com.
  2. W prawym górnym rogu kliknij ikonę edytora Cloud Shell.

8560cc8d45e8c112.png

  1. Na dole okna otworzy się nowy panel
  2. Kliknij przycisk Otwórz edytor

9E504cb98a6a8005.png

  1. Edytor otworzy się z eksploratorem po prawej stronie i edytorem w obszarze środkowym.
  2. Okienko terminala powinno być też dostępne u dołu ekranu
  3. Jeśli terminal NIE jest otwarty, użyj kombinacji klawiszy „Ctrl+”, aby otworzyć nowe okno terminala

Konfiguracja środowiska

W Cloud Shell ustaw identyfikator i numer projektu. Zapisz je jako zmienne PROJECT_ID i PROJECT_ID.

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

Pobieranie kodu źródłowego

  1. Kod źródłowy tego modułu znajduje się w module container-developer-workshop w GoogleCloudPlatform na GitHubie. Skopiuj go za pomocą poniższego polecenia, a następnie przejdź do katalogu.
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git &&
cd container-developer-workshop/labs/python
mkdir music-service && cd music-service 
cloudshell workspace .

Jeśli terminal NIE jest otwarty, użyj kombinacji klawiszy „Ctrl+”, aby otworzyć nowe okno terminala

Udostępnij infrastrukturę używaną w tym module

W tym module wdrożysz kod w GKE i uzyskasz dostęp do danych przechowywanych w bazie danych Spanner. Poniższy skrypt konfiguracji przygotowuje dla Ciebie tę infrastrukturę. Proces obsługi administracyjnej potrwa ponad 10 minut. W trakcie przetwarzania możesz kontynuować wykonywanie kilku kolejnych czynności.

../setup.sh

3. Tworzenie nowej aplikacji startowej w języku Python

  1. Utwórz plik o nazwie requirements.txt i skopiuj do niego tę zawartość
Flask
gunicorn
google-cloud-spanner
ptvsd==4.3.2
  1. Utwórz plik o nazwie app.py i wklej do niego ten kod
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. Utwórz plik o nazwie Dockerfile i wklej do niego poniższy plik.
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"]

Uwaga: FLASK_DEBUG=1 umożliwia automatyczne ponowne ładowanie zmian kodu w aplikacji w Pythonie na kolbę. Ten plik Dockerfile pozwala przekazać tę wartość jako argument kompilacji.

Generuj pliki manifestu

Wykonaj w terminalu to polecenie, aby wygenerować domyślny plik skaffold.yaml i deploy.yaml.

  1. Zainicjuj Skaffold za pomocą tego polecenia
skaffold init --generate-manifests

Gdy pojawi się odpowiedni komunikat, użyj strzałek, aby poruszać kursorem, a spację, aby wybrać opcje.

Wybierz:

  • 8080 za port
  • y, aby zapisać konfigurację.

Aktualizowanie konfiguracji Skaffold

  • Zmień domyślną nazwę aplikacji
  • Otwórz: skaffold.yaml
  • Wybierz nazwę obrazu, który jest obecnie ustawiony jako dockerfile-image
  • Kliknij prawym przyciskiem myszy i wybierz Zmień wszystkie wystąpienia
  • Wpisz nową nazwę jako python-app
  • Zmodyfikuj sekcję kompilacji, tak aby była
  • dodaj docker.buildArgs, aby wejść do punktu FLASK_DEBUG=1
  • Zsynchronizuj ustawienia, aby wczytać wszelkie zmiany w plikach *.py z IDE do uruchomionego kontenera

Po wprowadzeniu zmian sekcja kompilacji w pliku skaffold.yaml będzie wyglądać tak:

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

Zmodyfikuj plik konfiguracji Kubernetes

  1. Zmiana nazwy domyślnej
  • Otwórz plik deployment.yaml
  • Wybierz nazwę obrazu, który jest obecnie ustawiony jako dockerfile-image
  • Kliknij prawym przyciskiem myszy i wybierz Zmień wszystkie wystąpienia
  • Wpisz nową nazwę jako python-app

4. Omówienie procesu programowania

Dzięki dodanej logice biznesowej możesz teraz wdrożyć i przetestować aplikację. W następnej sekcji omówiono korzystanie z wtyczki Cloud Code. Integruje się ona między innymi ze skaffold, by usprawnić proces programowania. Gdy wdrożysz obraz kontenera w GKE w poniższych krokach, Cloud Code i Skaffold automatycznie skompilują obraz kontenera, wypchnie go do Container Registry, a następnie wdroży aplikację w GKE. Dzieje się to za kulisami, odbierając szczegóły od procesu deweloperskiego.

Wdróż w Kubernetes

  1. W panelu u dołu edytora Cloud Shell wybierz Cloud Code .

fdc797a769040839.png

  1. W panelu, który się pojawi, kliknij Uruchom w Kubernetes. W razie potrzeby wybierz Tak, aby użyć bieżącego kontekstu Kubernetes.

cfce0d11ef307087.png

To polecenie uruchamia kompilację kodu źródłowego, a następnie uruchamia testy. Kompilacja i testy potrwają kilka minut. Te testy obejmują testy jednostkowe oraz etap weryfikacji, który sprawdza reguły ustawione dla środowiska wdrożenia. Ten etap weryfikacji jest już skonfigurowany i dzięki temu ostrzeżemy Cię o problemach z wdrożeniem, nawet jeśli nadal pracujesz w środowisku programistycznym.

  1. Przy pierwszym uruchomieniu polecenia u góry ekranu pojawi się prompt z pytaniem, czy chcesz uzyskać bieżący kontekst Kubernetes. Wybierz „Tak”. aby zaakceptować i wykorzystać bieżący kontekst.
  2. Pojawi się pytanie, którego rejestru kontenerów użyć. Naciśnij Enter, aby zaakceptować podaną wartość domyślną
  3. Wybierz kartę Wyniki w dolnym panelu, aby zobaczyć postęp i powiadomienia.

f95b620569ba96c5.png

  1. Wybierz „Kubernetes: Run/Debug - detail”. w menu kanału po prawej stronie, aby wyświetlić dodatkowe szczegóły i logi, które są przesyłane na żywo z kontenerów.

94acdcdda6d2108.png

Po zakończeniu kompilacji i testów na karcie Dane wyjściowe pojawi się komunikat Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully. i wyświetli się adres URL http://localhost:8080.

  1. W terminalu Cloud Code najedź kursorem na pierwszy adres URL w danych wyjściowych (http://localhost:8080), a następnie w wyświetlonej wskazówce narzędzia wybierz Otwórz podgląd w przeglądarce.
  2. Otworzy się nowa karta przeglądarki i wyświetli się na niej wiadomość: Hello, World!

Ponowne załadowanie „na gorąco”

  1. Otwórz plik app.py.
  2. Zmień wiadomość powitalną na Hello from Python

Od razu zauważysz, że w oknie Output, widoku Kubernetes: Run/Debug obserwator synchronizuje zaktualizowane pliki z kontenerem w 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. Jeśli przełączysz się na widok Kubernetes: Run/Debug - Detailed, zauważysz, że rozpoznaje zmiany w plikach, a następnie skompiluje i ponownie wdroży aplikację
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. Aby zobaczyć zaktualizowane wyniki, odśwież przeglądarkę.

Debugowanie

  1. Otwórz widok debugowania i zatrzymaj bieżący wątek 647213126d7a4c7b.png.
  2. Kliknij Cloud Code w dolnym menu i wybierz Debug on Kubernetes, aby uruchomić aplikację w trybie debug.
  • W widoku Kubernetes Run/Debug - Detailed okna Output zwróć uwagę, że skaffold wdroży tę aplikację w trybie debugowania.
  1. Przy pierwszym uruchomieniu pojawi się pytanie, gdzie w kontenerze znajduje się źródło. Ta wartość jest powiązana z katalogami w pliku Dockerfile.

Naciśnij Enter, aby zaakceptować domyślną wartość

583436647752e410.png

Skompilowanie i wdrożenie aplikacji zajmie kilka minut.

  1. Po zakończeniu procesu. W połączeniu pojawi się debuger.
Port forwarding pod/python-app-8bd64cf8b-cskfl in namespace default, remote port 5678 -> http://127.0.0.1:5678
  1. Kolor dolnego paska stanu zmieni się z niebieskiego na pomarańczowy, co oznacza, że urządzenie działa w trybie debugowania.
  2. Zwróć uwagę na to, że w widoku Kubernetes Run/Debug został uruchomiony kontener z możliwością debugowania.
**************URLs*****************
Forwarded URL from service python-app: http://localhost:8080
Debuggable container started pod/python-app-8bd64cf8b-cskfl:python-app (default)
Update succeeded
***********************************

Wykorzystuj punkty przerwania

  1. Otwórz plik app.py.
  2. Znajdź instrukcję, która brzmi: return message
  3. Dodaj do tego wiersza punkt przerwania, klikając puste miejsce po lewej stronie numeru wiersza. Pojawi się czerwony wskaźnik informujący o ustawieniu punktu przerwania
  4. Załaduj ponownie przeglądarkę. Pamiętaj, że debuger zatrzymuje proces w punkcie przerwania i umożliwia zbadanie zmiennych oraz stanu aplikacji uruchomionej zdalnie w GKE.
  5. Kliknij w dół do sekcji ZMIENNE
  6. Kliknij przycisk Lokalne. Znajdziesz tam zmienną "message".
  7. Kliknij dwukrotnie zmienną o nazwie „wiadomość”. i w wyskakującym okienku zmień wartość na inną, np. "Greetings from Python".
  8. Kliknij przycisk Dalej w panelu sterowania debugowania 607c33934f8d6b39.png.
  9. Sprawdź odpowiedź w przeglądarce, w której wyświetla się wprowadzona przed chwilą zaktualizowana wartość.
  10. Zatrzymaj „Debugowanie” naciśnij przycisk zatrzymania 647213126d7a4c7b.png i usuń punkt przerwania, ponownie klikając go.

5. Opracowanie usługi spoczynku CRUD

Na tym etapie Twoja aplikacja jest w pełni skonfigurowana do programowania skonteneryzowanego i masz już za sobą podstawowy przepływ pracy programistyczny w Cloud Code. W kolejnych sekcjach przećwiczysz zdobyte informacje, dodając punkty końcowe usługi spoczynkowej łączące się z zarządzaną bazą danych w Google Cloud.

Zakoduj resztę usługi

Poniższy kod tworzy prostą usługę spoczynku, która używa Spannera jako bazy danych jako kopii zapasowej aplikacji. Utwórz aplikację, kopiując do niej poniższy kod.

  1. Utwórz główną aplikację, zastępując fragment app.py następującą treścią:
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)

Dodaj konfiguracje bazy danych

Aby połączyć się z usługą Spanner w bezpieczny sposób, skonfiguruj aplikację pod kątem korzystania z tożsamości zadań. Dzięki temu aplikacja może działać jako własne konto usługi i mieć indywidualne uprawnienia podczas uzyskiwania dostępu do bazy danych.

  1. Zaktualizuj urządzenie deployment.yaml. Dodaj następujący kod na końcu pliku (pamiętaj, aby zachować wcięcia tabulacji w przykładzie poniżej)
      serviceAccountName: python-ksa
      nodeSelector:
        iam.gke.io/gke-metadata-server-enabled: "true" 

Wdróż i zweryfikuj aplikację

  1. W panelu u dołu edytora Cloud Shell kliknij Cloud Code, a następnie wybierz Debug on Kubernetes u góry ekranu.
  2. Po zakończeniu kompilacji i testów na karcie Dane wyjściowe pojawi się komunikat Resource deployment/python-app status completed successfully i wyświetli się adres URL: „Przekierowany adres URL z usługi python-app: http://localhost:8080”
  3. Dodaj kilka wpisów.

W terminalu Cloud Shell uruchom poniższe polecenie

curl -X POST http://localhost:8080/singer -H 'Content-Type: application/json' -d '{"first_name":"Cat","last_name":"Meow", "singer_id": 6}'
  1. Przetestuj metodę GET, uruchamiając poniższe polecenie w terminalu
curl -X GET http://localhost:8080/singer?singer_id=6
  1. Testowanie usuwania: teraz spróbuj usunąć wpis, uruchamiając następujące polecenie. W razie potrzeby zmień wartość parametru item-id.
curl -X DELETE http://localhost:8080/singer?singer_id=6
    This throws an error message
500 Internal Server Error

Zidentyfikuj i rozwiąż problem

  1. Włącz tryb debugowania i znajdź problem. Oto kilka porad:
  • Wiemy, że coś jest nie tak z funkcją DELETE, ponieważ nie zwraca ona oczekiwanych wyników. Punkt przerwania należy więc ustawić w metodzie delete_singer w metodzie app.py.
  • Uruchom wykonanie krok po kroku i obserwuj zmienne w każdym kroku, aby obserwować wartości zmiennych lokalnych w lewym oknie.
  • Aby obserwować określone wartości, takie jak singer_id i request.args, dodaj te zmienne do okna odtwarzania filmu.
  1. Zwróć uwagę, że atrybut singer_id ma wartość None. Zmień kod, aby rozwiązać problem.

Poprawiony fragment kodu będzie wyglądać tak.

@app.route('/delete-singer', methods=['DELETE', 'GET'])
def delete_singer():
    try:
        singer_id = request.args.get('singer_id')
  1. Po ponownym uruchomieniu aplikacji przetestuj ją jeszcze raz, próbując ją usunąć.
  2. Zatrzymaj sesję debugowania, klikając czerwony kwadrat 647213126d7a4c7b.png na pasku narzędzi debugowania

6. Czyszczenie

Gratulacje! W tym module udało Ci się utworzyć od zera nową aplikację w Pythonie i skonfigurować ją tak, aby wydajnie współpracowała z kontenerami. Następnie wdrożono i debugowałeś(-aś) aplikację w zdalnym klastrze GKE, postępując zgodnie z procedurą programistyczną obowiązującą w tradycyjnych stosach aplikacji.

Aby posprzątać po ukończeniu modułu:

  1. Usuń pliki używane w module
cd ~ && rm -rf container-developer-workshop
  1. Usuń projekt, aby usunąć całą powiązaną infrastrukturę i zasoby