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

1. Обзор

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

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

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

  • Создание нового стартового приложения Python
  • Пройдите процесс разработки
  • Разработайте простой сервис отдыха CRUD.

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

Самостоятельная настройка среды

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

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Имя проекта — это отображаемое имя для участников этого проекта. Это строка символов, не используемая API Google, и вы можете обновить ее в любое время.
  • Идентификатор проекта должен быть уникальным для всех проектов Google Cloud и неизменяемым (нельзя изменить после его установки). Cloud Console автоматически генерирует уникальную строку; обычно тебя не волнует, что это такое. В большинстве лабораторий кода вам потребуется указать идентификатор проекта (обычно он обозначается как PROJECT_ID ), поэтому, если он вам не нравится, создайте другой случайный идентификатор или попробуйте свой собственный и посмотрите, доступен ли он. Затем он «замораживается» после создания проекта.
  • Существует третье значение — номер проекта , который используют некоторые API. Подробнее обо всех трех этих значениях читайте в документации .
  1. Затем вам необходимо включить выставление счетов в Cloud Console, чтобы использовать облачные ресурсы/API. Прохождение этой лаборатории кода не должно стоить много, если вообще стоит. Чтобы отключить ресурсы и не платить за выставление счетов за пределами этого руководства, следуйте инструкциям по «очистке», которые можно найти в конце лаборатории кода. Новые пользователи 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. Исходный код этой лабораторной работы находится в мастерской разработчика контейнеров в GoogleCloudPlatform на GitHub. Клонируйте его с помощью команды ниже, а затем перейдите в каталог.
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 позволяет автоматически перезагружать изменения кода в приложение Python Flask. Этот 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 в работающий контейнер.

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

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 .
  • Обратите внимание, что в окне Kubernetes Run/Debug - Detailed представление Output » 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. Дважды щелкните имя переменной «сообщение» и во всплывающем окне измените значение на другое, например "Greetings from Python"
  8. Нажмите кнопку «Продолжить» на панели управления отладкой. 607c33934f8d6b39.png
  9. Просмотрите ответ в своем браузере, который теперь показывает обновленное значение, которое вы только что ввели.
  10. Остановите режим «Отладка», нажав кнопку «Стоп». 647213126d7a4c7b.png и удалите точку останова, снова нажав на точку останова.

5. Разработка простой службы отдыха CRUD

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

Код остальной службы

Приведенный ниже код создает простую службу отдыха, которая использует 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 , добавьте эти переменные в окно Watch.
  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. Удалите проект, чтобы удалить всю связанную инфраструктуру и ресурсы.