Разработка InnerLoop на Python

1. Обзор

В этой лабораторной работе демонстрируются функции и возможности, разработанные для оптимизации рабочего процесса разработки программного обеспечения для инженеров-программистов, занимающихся разработкой приложений на Python в контейнеризированной среде. Типичная разработка в контейнерах требует от пользователя понимания деталей контейнеров и процесса сборки контейнеров. Кроме того, разработчикам обычно приходится прерывать свой рабочий процесс, выходя из IDE для тестирования и отладки своих приложений в удаленных средах. С помощью инструментов и технологий, упомянутых в этом руководстве, разработчики могут эффективно работать с контейнеризированными приложениями, не покидая свою IDE.

Что вы узнаете

В этой лабораторной работе вы изучите методы разработки с использованием контейнеров в GCP, в том числе:

  • Создание нового стартового приложения на Python
  • Проследите за процессом разработки.
  • Разработайте простой REST-сервис для операций CRUD (создание, выполнение, обновление, удаление)

2. Настройка и требования

Настройка среды для самостоятельного обучения

  1. Войдите в консоль Google Cloud и создайте новый проект или используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Название проекта — это отображаемое имя участников данного проекта. Это строка символов, не используемая API Google, и вы можете изменить её в любое время.
  • Идентификатор проекта должен быть уникальным для всех проектов Google Cloud и неизменяемым (его нельзя изменить после установки). Консоль Cloud автоматически генерирует уникальную строку; обычно вам неважно, какая она. В большинстве практических заданий вам потребуется указать идентификатор проекта (обычно он обозначается как PROJECT_ID ), поэтому, если он вам не нравится, сгенерируйте другой случайный идентификатор или попробуйте свой собственный и посмотрите, доступен ли он. Затем он "замораживается" после создания проекта.
  • Существует третье значение — номер проекта , который используется некоторыми API. Подробнее обо всех трех значениях можно узнать в документации .
  1. Далее вам потребуется включить оплату в консоли Cloud, чтобы использовать ресурсы/API Cloud. Выполнение этого практического задания не должно стоить дорого, если вообще что-либо. Чтобы отключить ресурсы и избежать дополнительных расходов после завершения этого урока, следуйте инструкциям по «очистке», приведенным в конце практического задания. Новые пользователи Google Cloud имеют право на бесплатную пробную версию стоимостью 300 долларов США .

Запустить редактор Cloudshell

Данная лабораторная работа разработана и протестирована для использования с редактором Google Cloud Shell. Для доступа к редактору,

  1. Получите доступ к своему проекту Google по адресу https://console.cloud.google.com .
  2. В правом верхнем углу нажмите на значок редактора облачной оболочки.

8560cc8d45e8c112.png

  1. В нижней части вашего окна откроется новое окно.
  2. Нажмите кнопку «Открыть редактор».

9e504cb98a6a8005.png

  1. В начале редактора справа будет изображен исследователь, а в центре — сам редактор.
  2. В нижней части экрана также должна быть доступна панель терминала.
  3. Если терминал НЕ открыт, используйте комбинацию клавиш `Ctrl+`, чтобы открыть новое окно терминала.

Настройка среды

В Cloud Shell укажите идентификатор проекта и номер проекта. Сохраните их как переменные PROJECT_ID и PROJECT_ID .

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

Получите исходный код

  1. Исходный код для этой лабораторной работы находится в репозитории container-developer-workshop на GitHub в GoogleCloudPlatform. Клонируйте его с помощью приведенной ниже команды, а затем перейдите в нужную директорию.
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git &&
cd container-developer-workshop/labs/python
mkdir music-service && cd music-service 
cloudshell workspace .

Если терминал НЕ открыт, используйте комбинацию клавиш `Ctrl+`, чтобы открыть новое окно терминала.

Обеспечьте наличие инфраструктуры, используемой в этой лаборатории.

В этой лабораторной работе вы развернете код в GKE и получите доступ к данным, хранящимся в базе данных Spanner. Приведенный ниже скрипт настройки подготовит для вас эту инфраструктуру. Процесс развертывания займет более 10 минут. Вы можете продолжить выполнение следующих шагов, пока идет процесс настройки.

../setup.sh

3. Создайте новое стартовое приложение на Python.

  1. Создайте файл с именем requirements.txt и скопируйте в него следующее содержимое.
Flask
gunicorn
google-cloud-spanner
ptvsd==4.3.2
  1. Создайте файл с именем app.py и вставьте в него следующий код.
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. Создайте файл с именем Dockerfile и вставьте в него следующий код.
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"]

Примечание : Параметр FLASK_DEBUG=1 позволяет автоматически перезагружать изменения кода в приложении Flask на Python. В этом Dockerfile вы можете передать это значение в качестве аргумента сборки.

Создать манифесты

В терминале выполните следующую команду, чтобы сгенерировать файлы skaffold.yaml и deployment.yaml по умолчанию.

  1. Инициализируйте Skaffold с помощью следующей команды.
skaffold init --generate-manifests

При появлении запроса используйте стрелки для перемещения курсора и пробел для выбора параметров.

Выбирать:

  • 8080 для порта
  • Нажмите y для сохранения конфигурации.

Обновить конфигурации Skaffold

  • Изменить название приложения по умолчанию
  • Откройте skaffold.yaml
  • Выберите имя образа, которое в данный момент установлено как dockerfile-image
  • Щелкните правой кнопкой мыши и выберите «Изменить все вхождения».
  • Введите новое имя как python-app
  • Дополнительно отредактируйте раздел сборки, чтобы
  • Добавьте docker.buildArgs , чтобы передать FLASK_DEBUG=1
  • Синхронизация настроек для загрузки любых изменений в файлы *.py из IDE в запущенный контейнер.

После внесения изменений раздел build в файле skaffold.yaml будет выглядеть следующим образом:

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

Изменение файла конфигурации Kubernetes

  1. Изменить имя по умолчанию
  • Откройте файл deployment.yaml
  • Выберите имя образа, которое в данный момент установлено как dockerfile-image
  • Щелкните правой кнопкой мыши и выберите «Изменить все вхождения».
  • Введите новое имя как python-app

4. Обзор процесса разработки.

После добавления бизнес-логики вы можете развернуть и протестировать свое приложение. В следующем разделе будет рассмотрено использование плагина Cloud Code. Помимо прочего, этот плагин интегрируется со Skaffold для оптимизации процесса разработки. При развертывании в GKE на следующих шагах Cloud Code и Skaffold автоматически создадут образ контейнера, загрузят его в реестр контейнеров, а затем развернут ваше приложение в GKE. Это происходит в фоновом режиме, абстрагируя детали от процесса разработки.

Развертывание в Kubernetes

  1. В нижней части окна редактора Cloud Shell выберите Cloud Code.

fdc797a769040839.png

  1. В появившейся вверху панели выберите «Запустить в Kubernetes» . Если появится запрос, выберите «Да», чтобы использовать текущий контекст Kubernetes.

cfce0d11ef307087.png

Эта команда запускает сборку исходного кода, а затем выполняет тесты. Сборка и тестирование займут несколько минут. Эти тесты включают модульные тесты и этап проверки, который проверяет правила, установленные для среды развертывания. Этот этап проверки уже настроен и гарантирует получение предупреждений о проблемах развертывания, даже если вы все еще работаете в среде разработки.

  1. При первом запуске команды в верхней части экрана появится запрос, спрашивающий, хотите ли вы использовать текущий контекст Kubernetes. Выберите «Да», чтобы принять запрос и использовать текущий контекст.
  2. Далее появится запрос о том, какой реестр контейнеров использовать. Нажмите Enter, чтобы принять предоставленное значение по умолчанию.
  3. Чтобы просмотреть ход выполнения и уведомления, выберите вкладку «Вывод» в нижней панели.

f95b620569ba96c5.png

  1. Выберите «Kubernetes: Запуск/Отладка — Подробная информация» в раскрывающемся списке каналов справа, чтобы просмотреть дополнительные сведения и журналы, транслируемые в режиме реального времени из контейнеров.

94acdcdda6d2108.png

После завершения сборки и тестирования на вкладке «Вывод» отображается сообщение: Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully. , а также указан URL-адрес http://localhost:8080.

  1. В терминале Cloud Code наведите курсор на первый URL-адрес в выводе (http://localhost:8080), а затем во всплывающей подсказке выберите «Открыть предварительный просмотр веб-страницы».
  2. Откроется новая вкладка браузера, на которой отобразится сообщение Hello, World!

Горячая перезарядка

  1. Откройте файл app.py
  2. Измените приветственное сообщение на Hello from Python

Обратите внимание, что в окне Output , в представлении Kubernetes: Run/Debug , наблюдатель синхронизирует обновленные файлы с контейнером в 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. Если вы переключитесь на Kubernetes: Run/Debug - Detailed просмотр, вы заметите, что он распознает изменения файлов, а затем выполняет сборку и повторное развертывание приложения.
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. Обновите страницу в браузере, чтобы увидеть обновленные результаты.

Отладка

  1. Перейдите в режим отладки и остановите текущий поток. 647213126d7a4c7b.png .
  2. В нижнем меню нажмите на Cloud Code и выберите Debug on Kubernetes , чтобы запустить приложение в режиме debug .
  • В окне «Подробный просмотр Output Kubernetes Run/Debug - Detailed обратите внимание, что skaffold развернет это приложение в режиме отладки.
  1. При первом запуске появится запрос на указание местоположения исходного кода внутри контейнера. Это значение связано с каталогами, указанными в Dockerfile.

Нажмите Enter, чтобы принять значение по умолчанию.

583436647752e410.png

Сборка и развертывание приложения займут несколько минут.

  1. Когда процесс завершится, вы увидите, что к нему подключен отладчик.
Port forwarding pod/python-app-8bd64cf8b-cskfl in namespace default, remote port 5678 -> http://127.0.0.1:5678
  1. Цвет нижней строки состояния меняется с синего на оранжевый, указывая на то, что устройство находится в режиме отладки.
  2. В окне Kubernetes Run/Debug обратите внимание, что запущен отлаживаемый контейнер.
**************URLs*****************
Forwarded URL from service python-app: http://localhost:8080
Debuggable container started pod/python-app-8bd64cf8b-cskfl:python-app (default)
Update succeeded
***********************************

Используйте точки останова

  1. Откройте файл app.py
  2. Найдите оператор, который гласит return message
  3. Установите точку останова на этой строке, щелкнув по пустому месту слева от номера строки. Красный индикатор покажет, что точка останова установлена.
  4. Перезагрузите браузер и обратите внимание, что отладчик останавливает процесс в точке останова и позволяет вам исследовать переменные и состояние приложения, работающего удаленно в GKE.
  5. Прокрутите вниз до раздела ПЕРЕМЕННЫЕ
  6. Нажмите на «Локальные переменные», там вы найдете переменную "message" .
  7. Дважды щелкните по имени переменной "message" и во всплывающем окне измените её значение на что-нибудь другое, например, "Greetings from Python"
  8. Нажмите кнопку «Продолжить» на панели управления отладкой. 607c33934f8d6b39.png
  9. Проверьте ответ в браузере, где теперь отображается обновленное значение, которое вы только что ввели.
  10. Остановите режим отладки, нажав кнопку «Стоп». 647213126d7a4c7b.png и снимите точку останова, снова щелкнув по ней.

5. Разработка простого REST-сервиса с операциями CRUD.

На этом этапе ваше приложение полностью настроено для контейнерной разработки, и вы прошли базовый рабочий процесс разработки с помощью Cloud Code. В следующих разделах вы попрактикуетесь в применении полученных знаний, добавив конечные точки REST-сервиса, подключающиеся к управляемой базе данных в Google Cloud.

Напишите код для REST-сервиса

Приведённый ниже код создаёт простой REST-сервис, использующий Spanner в качестве базы данных для работы приложения. Создайте приложение, скопировав следующий код в своё приложение.

  1. Создайте основное приложение, заменив содержимое app.py следующим содержимым.
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)

Добавить настройки базы данных

Для безопасного подключения к Spanner настройте приложение на использование идентификаторов рабочей нагрузки. Это позволит вашему приложению выступать в качестве собственной учетной записи службы и иметь индивидуальные разрешения при доступе к базе данных.

  1. Обновите deployment.yaml . Добавьте следующий код в конец файла (убедитесь, что вы сохранили отступы табуляции, как в примере ниже).
      serviceAccountName: python-ksa
      nodeSelector:
        iam.gke.io/gke-metadata-server-enabled: "true" 

Развертывание и проверка приложения

  1. В нижней части окна редактора Cloud Shell выберите Cloud Code , а затем в верхней части экрана выберите Debug on Kubernetes .
  2. После завершения сборки и тестирования на вкладке «Вывод» отображается сообщение: Resource deployment/python-app status completed successfully , а также указан URL-адрес: «Перенаправленный URL-адрес из сервиса python-app: http://localhost:8080».
  3. Добавьте пару записей.

В терминале CloudShell выполните следующую команду.

curl -X POST http://localhost:8080/singer -H 'Content-Type: application/json' -d '{"first_name":"Cat","last_name":"Meow", "singer_id": 6}'
  1. Проверьте выполнение запроса GET, запустив следующую команду в терминале.
curl -X GET http://localhost:8080/singer?singer_id=6
  1. Тестовое удаление: Теперь попробуйте удалить запись, выполнив следующую команду. При необходимости измените значение item-id.
curl -X DELETE http://localhost:8080/singer?singer_id=6
    This throws an error message
500 Internal Server Error

Выявите и устраните проблему.

  1. Включите режим отладки и найдите проблему. Вот несколько советов:
  • Мы знаем, что что-то не так с оператором DELETE, поскольку он не возвращает желаемый результат. Поэтому вам следует установить точку останова в app.py в методе delete_singer .
  • Выполните пошаговое выполнение программы и наблюдайте за переменными на каждом шаге, чтобы увидеть значения локальных переменных в левом окне.
  • Чтобы отслеживать определенные значения, такие как singer_id и request.args , добавьте эти переменные в окно «Отслеживание».
  1. Обратите внимание, что значение, присвоенное singer_id , равно None . Измените код, чтобы исправить эту проблему.

Исправленный фрагмент кода будет выглядеть так.

@app.route('/delete-singer', methods=['DELETE', 'GET'])
def delete_singer():
    try:
        singer_id = request.args.get('singer_id')
  1. После перезапуска приложения проверьте еще раз, попробовав удалить файл.
  2. Остановите сеанс отладки, нажав на красный квадрат на панели инструментов отладки. 647213126d7a4c7b.png

6. Уборка

Поздравляем! В этой лабораторной работе вы создали с нуля новое приложение на Python и настроили его для эффективной работы с контейнерами. Затем вы развернули и отладили свое приложение в удаленном кластере GKE, следуя тому же процессу разработки, что и в традиционных стеках приложений.

После завершения лабораторной работы необходимо провести уборку:

  1. Удалите файлы, использованные в лабораторной работе.
cd ~ && rm -rf container-developer-workshop
  1. Удалите проект, чтобы удалить всю связанную с ним инфраструктуру и ресурсы.