Programowanie InnerLoop w Pythonie

1. Przegląd

W tym laboratorium dowiesz się o funkcjach i możliwościach, które usprawniają przepływ pracy dla inżynierów oprogramowania, których zadaniem jest tworzenie aplikacji w Pythonie w środowisku skonteneryzowanym. Typowe tworzenie kontenerów wymaga od użytkownika znajomości szczegółów dotyczących kontenerów i procesu kompilacji kontenerów. Deweloperzy zwykle muszą też przerywać pracę i opuszczać IDE, aby testować i debugować aplikacje w środowiskach zdalnych. Dzięki narzędziom i technologiom wymienionym w tym samouczku deweloperzy mogą efektywnie pracować z aplikacjami w kontenerach bez opuszczania środowiska IDE.

Czego się nauczysz

W tym module poznasz metody tworzenia aplikacji w kontenerach w GCP, w tym:

  • Tworzenie nowej aplikacji startowej w Pythonie
  • Omów proces tworzenia
  • Tworzenie prostej usługi REST CRUD

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

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

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Nazwa projektu to wyświetlana nazwa uczestników tego projektu. Jest to ciąg znaków, który nie jest używany przez interfejsy API Google. Możesz go w dowolnym momencie zaktualizować.
  • Identyfikator projektu musi być unikalny we wszystkich projektach Google Cloud i jest niezmienny (nie można go zmienić po ustawieniu). Konsola Cloud automatycznie generuje unikalny ciąg znaków. Zwykle nie musisz się nim przejmować. W większości modułów z kodem musisz odwoływać się do identyfikatora projektu (zwykle oznaczanego jako PROJECT_ID). Jeśli Ci się nie podoba, wygeneruj inny losowy identyfikator lub spróbuj użyć własnego i sprawdź, czy jest dostępny. Po utworzeniu projektu jest on „zamrażany”.
  • Istnieje też trzecia wartość, czyli numer projektu, którego używają niektóre interfejsy API. Więcej informacji o tych 3 wartościach znajdziesz w dokumentacji.
  1. Następnie musisz włączyć płatności w konsoli Cloud, aby korzystać z zasobów i interfejsów API Google Cloud. Ukończenie tego laboratorium nie powinno wiązać się z dużymi kosztami, a nawet z żadnymi. Aby wyłączyć zasoby i uniknąć naliczenia opłat po zakończeniu tego samouczka, postępuj zgodnie z instrukcjami „czyszczenia” na końcu ćwiczenia. Nowi użytkownicy Google Cloud mogą skorzystać z bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.

Uruchamianie edytora Cloud Shell

To laboratorium zostało zaprojektowane i przetestowane pod kątem używania w edytorze Google Cloud Shell. Aby uzyskać dostęp do edytora:

  1. uzyskać dostęp do projektu Google na stronie https://console.cloud.google.com;
  2. W prawym górnym rogu kliknij ikonę edytora Cloud Shell.

8560cc8d45e8c112.png

  1. U dołu 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 środkowej części.
  2. U dołu ekranu powinien być też dostępny panel terminala.
  3. Jeśli terminal NIE jest otwarty, użyj kombinacji klawiszy „Ctrl+`”, aby otworzyć nowe okno terminala.

Konfiguracja środowiska

W Cloud Shell ustaw identyfikator projektu i numer projektu. Zapisz je jako zmienne PROJECT_IDPROJECT_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 laboratorium znajduje się w repozytorium container-developer-workshop w GoogleCloudPlatform na GitHubie. Sklonuj 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.

wdrożyć 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 przygotuje tę infrastrukturę. Proces obsługi administracyjnej potrwa ponad 10 minut. Podczas przetwarzania konfiguracji możesz przejść do kolejnych kroków.

../setup.sh

3. Tworzenie nowej aplikacji startowej w Pythonie

  1. Utwórz plik o nazwie requirements.txt i skopiuj do niego tę treść:
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 kod:
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 wczytywanie zmian w kodzie aplikacji Flask w języku Python. Ten plik Dockerfile umożliwia przekazywanie tej wartości jako argumentu kompilacji.

Generowanie manifestów

W terminalu wykonaj to polecenie, aby wygenerować domyślne pliki skaffold.yaml i deployment.yaml:

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

Gdy pojawi się prośba, użyj strzałek, aby przesunąć kursor, a spacji, aby wybrać opcje.

Wybierz:

  • 8080 dla portu
  • y, aby zapisać konfigurację.

Aktualizowanie konfiguracji Skaffold

  • Zmiana domyślnej nazwy aplikacji
  • Otwórz: skaffold.yaml
  • Wybierz nazwę obrazu, która jest obecnie ustawiona jako dockerfile-image.
  • Kliknij prawym przyciskiem myszy i wybierz Zmień wszystkie wystąpienia.
  • Wpisz nową nazwę w formacie python-app.
  • Dalsze edytowanie sekcji kompilacji
  • dodaj docker.buildArgs do karty FLASK_DEBUG=1
  • Synchronizowanie ustawień w celu wczytania zmian 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'

Modyfikowanie pliku konfiguracyjnego Kubernetes

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

4. Omówienie procesu tworzenia

Po dodaniu logiki biznesowej możesz wdrożyć i przetestować aplikację. W sekcji poniżej dowiesz się, jak korzystać z wtyczki Cloud Code. Ta wtyczka jest między innymi zintegrowana z narzędziem Skaffold, aby usprawnić proces tworzenia. Gdy w kolejnych krokach wdrożysz aplikację w GKE, Cloud Code i Skaffold automatycznie skompilują obraz kontenera, wypchną go do Container Registry, a następnie wdrożą aplikację w GKE. Dzieje się to w sposób niewidoczny, co pozwala deweloperowi skupić się na innych aspektach.

Wdrażanie w Kubernetes

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

fdc797a769040839.png

  1. W panelu, który pojawi się u góry, kliknij Uruchom w Kubernetes. Jeśli pojawi się taka prośba, wybierz Yes (Tak), aby użyć bieżącego kontekstu Kubernetes.

cfce0d11ef307087.png

To polecenie rozpoczyna kompilację kodu źródłowego, a następnie uruchamia testy. Kompilacja i testy potrwają kilka minut. Obejmują one testy jednostkowe i etap weryfikacji, który sprawdza reguły ustawione dla środowiska wdrażania. Ten krok weryfikacji jest już skonfigurowany i zapewnia, że otrzymasz ostrzeżenie o problemach z wdrożeniem, nawet jeśli nadal pracujesz w środowisku programistycznym.

  1. Gdy po raz pierwszy uruchomisz to polecenie, u góry ekranu pojawi się pytanie, czy chcesz użyć bieżącego kontekstu Kubernetes. Aby zaakceptować i użyć bieżącego kontekstu, kliknij „Yes” (Tak).
  2. Następnie pojawi się pytanie o to, którego repozytorium kontenerów chcesz użyć. Naciśnij Enter, aby zaakceptować podaną wartość domyślną.
  3. Wybierz kartę Wyjście w dolnym panelu, aby wyświetlić postęp i powiadomienia.

f95b620569ba96c5.png

  1. Aby wyświetlić dodatkowe szczegóły i logi przesyłane strumieniowo na żywo z kontenerów, w menu po prawej stronie wybierz „Kubernetes: Run/Debug - Detailed” (Kubernetes: uruchamianie/debugowanie – szczegółowe).

94acdcdda6d2108.png

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

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

Gorące przeładowanie

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

Zauważ, że w oknie Output, w 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 przejdziesz do widoku Kubernetes: Run/Debug - Detailed, zauważysz, że rozpoznaje on zmiany w pliku, a następnie kompiluje i ponownie wdraża 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 menu u dołu i wybierz Debug on Kubernetes, aby uruchomić aplikację w trybie debug.
  • W widoku Kubernetes Run/Debug - Detailed okna Output zauważ, że Skaffold wdroży tę aplikację w trybie debugowania.
  1. Przy pierwszym uruchomieniu pojawi się prompt z pytaniem, gdzie w kontenerze znajduje się źródło. Ta wartość jest powiązana z katalogami w pliku Dockerfile.

Aby zaakceptować wartość domyślną, naciśnij Enter.

583436647752e410.png

Kompilacja i wdrożenie aplikacji zajmie kilka minut.

  1. Po zakończeniu procesu. Zauważysz, że dołączony jest debugger.
Port forwarding pod/python-app-8bd64cf8b-cskfl in namespace default, remote port 5678 -> http://127.0.0.1:5678
  1. Dolny pasek stanu zmieni kolor z niebieskiego na pomarańczowy, co oznacza, że jest w trybie debugowania.
  2. W widoku Kubernetes Run/Debug zobaczysz, że uruchomiono 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
***********************************

Wykorzystywanie punktów przerwania

  1. Otwórz plik app.py.
  2. Znajdź zdanie return message
  3. Dodaj punkt przerwania do tego wiersza, klikając puste miejsce po lewej stronie numeru wiersza. Pojawi się czerwony wskaźnik, który oznacza, że punkt przerwania został ustawiony.
  4. Odśwież przeglądarkę i zwróć uwagę, że debuger zatrzymuje proces w punkcie przerwania i umożliwia zbadanie zmiennych i stanu aplikacji, która jest uruchomiona zdalnie w GKE.
  5. Kliknij sekcję ZMIENNE.
  6. Kliknij Zmienne lokalne, aby znaleźć zmienną "message".
  7. Kliknij dwukrotnie nazwę zmiennej „message” i w wyskakującym okienku zmień wartość na inną, np. "Greetings from Python"
  8. W panelu sterowania debugowaniem kliknij przycisk Dalej. 607c33934f8d6b39.png
  9. Sprawdź odpowiedź w przeglądarce, w której powinna się teraz wyświetlać wpisana przez Ciebie zaktualizowana wartość.
  10. Zatrzymaj tryb „Debugowanie”, naciskając przycisk zatrzymania 647213126d7a4c7b.png, i usuń punkt przerwania, klikając go ponownie.

5. Tworzenie prostej usługi REST CRUD

Na tym etapie aplikacja jest w pełni skonfigurowana pod kątem tworzenia skonteneryzowanych aplikacji, a Ty znasz już podstawowy przepływ pracy programisty w Cloud Code. W kolejnych sekcjach przećwiczysz zdobytą wiedzę, dodając punkty końcowe usługi REST, które łączą się z zarządzaną bazą danych w Google Cloud.

Kodowanie usługi REST

Poniższy kod tworzy prostą usługę REST, która używa Spannera jako bazy danych obsługującej aplikację. Utwórz aplikację, kopiując do niej ten kod.

  1. Utwórz główną aplikację, zastępując app.py poniższą 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)

Dodawanie konfiguracji bazy danych

Aby bezpiecznie połączyć się z usługą Spanner, skonfiguruj aplikację tak, aby korzystała z tożsamości obciążeń. Dzięki temu aplikacja może działać jako własne konto usługi i mieć indywidualne uprawnienia dostępu do bazy danych.

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

Wdrażanie i weryfikowanie aplikacji

  1. W panelu u dołu edytora Cloud Shell kliknij Cloud Code, a następnie u góry ekranu kliknij Debug on Kubernetes.
  2. Po zakończeniu kompilacji i testów na karcie Wyniki pojawi się komunikat Resource deployment/python-app status completed successfully i adres URL: „Forwarded URL from service python-app: http://localhost:8080”.
  3. Dodaj kilka wpisów.

W terminalu Cloud Shell uruchom to 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 żądanie GET, uruchamiając w terminalu to polecenie:
curl -X GET http://localhost:8080/singer?singer_id=6
  1. Testowanie usuwania: spróbuj teraz usunąć wpis, uruchamiając to polecenie. W razie potrzeby zmień wartość identyfikatora produktu.
curl -X DELETE http://localhost:8080/singer?singer_id=6
    This throws an error message
500 Internal Server Error

Identyfikowanie i rozwiązywanie problemu

  1. Użyj trybu debugowania, aby znaleźć problem. Oto kilka porad:
  • Wiemy, że z poleceniem DELETE jest coś nie tak, ponieważ nie zwraca ono oczekiwanego wyniku. Punkt przerwania należy ustawić w app.py w metodzie delete_singer.
  • Uruchom wykonywanie krok po kroku i obserwuj zmienne na każdym etapie, aby zobaczyć wartości zmiennych lokalnych w oknie po lewej stronie.
  • Aby obserwować konkretne wartości, takie jak singer_idrequest.args, dodaj te zmienne do okna Obserwuj.
  1. Zwróć uwagę, że wartość przypisana do singer_id to None. Aby rozwiązać problem, zmień kod.

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 spróbuj usunąć plik.
  2. Zakończ sesję debugowania, klikając czerwony kwadrat na pasku narzędzi debugowania 647213126d7a4c7b.png.

6. Czyszczenie

Gratulacje! W tym module udało Ci się utworzyć od podstaw nową aplikację w Pythonie i skonfigurować ją tak, aby skutecznie działała z kontenerami. Następnie wdrożyliśmy i debugowaliśmy aplikację w zdalnym klastrze GKE, korzystając z tego samego przepływu pracy dewelopera, który jest stosowany w tradycyjnych stosach aplikacji.

Aby zwolnić miejsce po ukończeniu modułu:

  1. Usuwanie plików użytych w laboratorium
cd ~ && rm -rf container-developer-workshop
  1. Usuwanie projektu w celu usunięcia całej powiązanej infrastruktury i zasobów