Разработка InnerLoop с использованием облачных рабочих станций с Python

1. Обзор

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

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

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

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

58a4cdd3ed7a123a.png

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)')

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

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

  1. Скачайте установочный скрипт и сделайте его исполняемым.
wget https://raw.githubusercontent.com/GoogleCloudPlatform/container-developer-workshop/main/labs/python/setup_with_cw.sh
chmod +x setup_with_cw.sh
  1. Откройте файл setup_with_cw.sh и отредактируйте значения паролей, которые в данный момент установлены на CHANGEME.
  2. Запустите скрипт настройки, чтобы развернуть кластер GKE и базу данных Spanner, которые вы будете использовать в этой лабораторной работе.
./setup_with_cw.sh &

Кластер облачных рабочих станций

  1. Откройте Cloud Workstations в Cloud Console. Дождитесь, пока кластер перейдет в состояние READY .

305e1a3d63ac7ff6.png

Создание конфигурации рабочих станций

  1. Если ваша сессия Cloud Shell была разорвана, нажмите «Переподключиться», а затем выполните команду cli gcloud, чтобы установить идентификатор проекта. Перед выполнением команды замените указанный ниже пример идентификатора проекта на идентификатор вашего проекта qwiklabs.
gcloud config set project qwiklabs-gcp-project-id
  1. Загрузите и запустите приведенный ниже скрипт в терминале, чтобы создать конфигурацию облачных рабочих станций.
wget https://raw.githubusercontent.com/GoogleCloudPlatform/container-developer-workshop/main/labs/python/workstation_config_setup.sh
chmod +x workstation_config_setup.sh
./workstation_config_setup.sh
  1. Проверьте результаты в разделе «Конфигурации». Переход в состояние «ГОТОВО» займет 2 минуты.

2e23c2e9983d1ccf.png

  1. Откройте раздел «Облачные рабочие станции» в консоли и создайте новый экземпляр.

a53adeeac81a78c8.png

  1. Измените имя на my-workstation и выберите существующую конфигурацию: codeoss-python .

f052cd47701ec774.png

  1. Проверьте результаты в разделе «Рабочие станции».

Запуск рабочей станции

  1. Запустите рабочую станцию. Запуск рабочей станции займет несколько минут.

682f8a307032cba3.png

  1. Разрешите использование сторонних файлов cookie, нажав на значок в адресной строке. 1b8923e2943f9bc4.png

fcf9405b6957b7d7.png

  1. Нажмите «Сайт не работает?».

36a84c0e2e3b85b.png

  1. Нажмите «Разрешить файлы cookie».

2259694328628fba.png

  1. После запуска рабочей станции вы увидите запущенную среду разработки Code OSS IDE. На странице «Начало работы» в среде разработки рабочей станции нажмите кнопку «Готово».

94874fba9b74cc22.png

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

В этом разделе вы создадите новое приложение на Python.

  1. Откройте новый терминал.

c31d48f2e4938c38.png

  1. Создайте новую директорию и откройте её как рабочее пространство.
mkdir music-service && cd music-service

code-oss-cloud-workstations -r --folder-uri="$PWD"

Если вы видите это сообщение, нажмите кнопку «Разрешить», чтобы иметь возможность скопировать и вставить текст на рабочую станцию.

58149777e5cc350a.png

  1. Создайте файл с именем requirements.txt и скопируйте в него следующее содержимое.

789e8389170bd900.png

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 автоматически создадут образ контейнера, загрузят его в реестр контейнеров, а затем развернут your приложение в GKE. Это происходит в фоновом режиме, абстрагируя детали от процесса разработки.

Войдите в Google Cloud

  1. Нажмите на значок Cloud Code и выберите «Войти в Google Cloud»:

1769afd39be372ff.png

  1. Нажмите «Перейти к входу».

923bb1c8f63160f9.png

  1. Проверьте вывод в терминале и откройте ссылку:

517fdd579c34aa21.png

  1. Войдите в систему, используя свои учетные данные студента Qwiklabs.

db99b345f7a8e72c.png

  1. Выберите «Разрешить»:

a5376553c430ac84.png

  1. Скопируйте проверочный код и вернитесь на вкладку «Рабочая станция».

6719421277b92eac.png

  1. Вставьте проверочный код и нажмите Enter.

e9847cfe3fa8a2ce.png

Добавить кластер Kubernetes

  1. Добавить кластер

62a3b97bdbb427e5.png

  1. Выберите Google Kubernetes Engine:

9577de423568bbaa.png

  1. Выберите проект.

c5202fcbeebcd41c.png

  1. Выберите "python-cluster", созданный при первоначальной настройке.

719c2fc0a7f9e84f.png

  1. Теперь кластер отображается в списке кластеров Kubernetes в разделе Cloud Code. Отсюда вы можете перейти к изучению кластера и просмотреть его содержимое.

7e5f50662d4eea3c.png

Установите идентификатор текущего проекта с помощью gcloud cli.

  1. Скопируйте идентификатор проекта для этой лабораторной работы со страницы qwiklabs.

fcff2d10007ec5bc.png

  1. В терминале выполните команду gcloud cli, чтобы установить идентификатор проекта. Замените пример идентификатора проекта перед выполнением команды. Замените идентификатор проекта перед выполнением команды ниже.
gcloud config set project qwiklabs-gcp-project-id

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

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

d99a88992e15fea9.png

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

bfd65e9df6d4a6cb.png

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

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

9c87ccbf5d06f50a.png

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

804abc8833ffd571.png

После завершения сборки и тестирования в логах вкладки «Вывод» в представлении «Kubernetes: Запуск/Отладка» будет указан 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. 70d6bd947d04d1e6.png
  3. В нижнем меню нажмите на Cloud Code и выберите Debug on Kubernetes , чтобы запустить приложение в режиме debug .
  • В окне «Подробный просмотр Output Kubernetes Run/Debug - Detailed обратите внимание, что skaffold развернет это приложение в режиме отладки.
  1. После завершения процесса вы увидите подключенный отладчик, а на вкладке «Вывод» отобразится сообщение: Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully. , и будет указан URL-адрес http://localhost:8080.
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. При первом запуске появится запрос на указание местоположения исходного кода внутри контейнера. Это значение связано с каталогами, указанными в Dockerfile.

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

fccc866f32b5ed86.png

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

  1. Перезагрузите браузер и обратите внимание, что отладчик останавливает процесс в точке останова и позволяет вам исследовать переменные и состояние приложения, работающего удаленно в GKE.
  2. Прокрутите вниз до раздела ПЕРЕМЕННЫЕ
  3. Нажмите на «Локальные переменные», там вы найдете переменную "message" .
  4. Дважды щелкните по имени переменной "message" и во всплывающем окне измените её значение на что-нибудь другое, например, "Greetings from Python"
  5. Нажмите кнопку «Продолжить» на панели управления отладкой. 607c33934f8d6b39.png
  6. Проверьте ответ в браузере, где теперь отображается обновленное значение, которое вы только что ввели.
  7. Остановите режим отладки, нажав кнопку «Стоп». 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" 

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

   spec:
     containers:
     - name: python-app
       image: python-app
     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 ~/music-service
  1. Удалите проект, чтобы удалить всю связанную с ним инфраструктуру и ресурсы.