Проблемы с разрешениями на импорт Cloud SQL для корзины Cloud Storage - PullRequest
0 голосов
/ 19 февраля 2019

Я пишу облачную функцию в:

  • Экспорт Облачная SQL (postgresql) БД в файл в хранилище облачного хранилища
  • Импортируйте обратно в другой экземпляр / БД Cloud SQL (по-прежнему postgresql)

Примечание: Я хочу, чтобы этот код запускался сам по себе каждую ночь, чтобы скопировать продуктБД в промежуточной среде, поэтому я планирую запустить ее с помощью Cloud Scheduler.
Если у вас есть лучшее / более простое решение для извлечения этого в GCP, у меня все на слух:)

Вот мойкод (действительная функция - clone_db внизу файла):

from os import getenv
from datetime import datetime
from time import sleep

from googleapiclient import discovery
from googleapiclient.errors import HttpError
from oauth2client.client import GoogleCredentials
from google.cloud import storage

GS_BUCKET = getenv("GS_BUCKET")
GS_FOLDER = "sql-exports"
GS_EXPORT_PATH = f"gs://{GS_BUCKET}/{GS_FOLDER}"


def __sql_file_name(db: str, timestamp: datetime):
    return f"{db}-{timestamp.strftime('%Y-%m-%d')}.sql.gz"


def __sql_file_uri(db: str, timestamp: datetime):
    return f"{GS_EXPORT_PATH}/{__sql_file_name(db, timestamp)}"


def __export_source_db(service, project: str, timestamp: datetime, instance: str, db: str):
    context = {
        "exportContext": {
            "kind": "sql#exportContext",
            "fileType": "SQL",
            "uri": __sql_file_uri(db, timestamp),
            "databases": [db],
        }
    }

    return service.instances().export(project=project, instance=instance, body=context).execute()


def __import_target_db(service, project: str, timestamp: datetime, instance: str, db: str):
    context = {
        "importContext": {
            "kind": "sql#importContext",
            "fileType": "SQL",
            "uri": __sql_file_uri(db, timestamp),
            "database": db,
        }
    }

    return service.instances().import_(project=project, instance=instance, body=context).execute()


def __drop_db(service, project: str, instance: str, db: str):
    try:
        return service.databases().delete(project=project, instance=instance, database=db).execute()
    except HttpError as e:
        if e.resp.status == 404:
            return {"status": "DONE"}
        else:
            raise e


def __create_db(service, project: str, instance: str, db: str):
    database = {
        "name": db,
        "project": project,
        "instance": instance,
    }

    return service.databases().insert(project=project, instance=instance, body=database).execute()


def __update_export_permissions(file_name: str):
    client = storage.Client()
    file = client.get_bucket(GS_BUCKET).get_blob(f"{GS_FOLDER}/{file_name}")
    file.acl.user(getenv("TARGET_DB_SERVICE_ACCOUNT")).grant_read()
    file.acl.save()


def __delete_sql_file(file_name: str):
    client = storage.Client()
    bucket = client.get_bucket(GS_BUCKET)
    bucket.delete_blob(f"{GS_FOLDER}/{file_name}")


def __wait_for(operation_type, operation, service, project):
    if operation["status"] in ("PENDING", "RUNNING", "UNKNOWN"):
        print(f"{operation_type} operation in {operation['status']} status. Waiting for completion...")

        while operation['status'] != "DONE":
            sleep(1)
            operation = service.operations().get(project=project, operation=operation['name']).execute()

    print(f"{operation_type} operation completed!")


def clone_db(_):
    credentials = GoogleCredentials.get_application_default()
    service = discovery.build('sqladmin', 'v1beta4', credentials=credentials)

    # Project ID of the project that contains the instance to be exported.
    project = getenv('PROJECT_ID')

    # Cloud SQL instance ID. This does not include the project ID.
    source = {
        "instance": getenv("SOURCE_INSTANCE_ID"),
        "db": getenv("SOURCE_DB_NAME")
    }

    timestamp = datetime.utcnow()

    print(f"Exporting database {source['instance']}:{source['db']} to Cloud Storage...")
    operation = __export_source_db(service, project, timestamp, **source)

    __wait_for("Export", operation, service, project)

    print("Updating exported file permissions...")
    __update_export_permissions(__sql_file_name(source["db"], timestamp))
    print("Done.")

    target = {
        "instance": getenv("TARGET_INSTANCE_ID"),
        "db": getenv("TARGET_DB_NAME")
    }

    print(f"Dropping target database {target['instance']}:{target['db']}")
    operation = __drop_db(service, project, **target)
    __wait_for("Drop", operation, service, project)

    print(f"Creating database {target['instance']}:{target['db']}...")
    operation = __create_db(service, project, **target)
    __wait_for("Creation", operation, service, project)

    print(f"Importing data into {target['instance']}:{target['db']}...")
    operation = __import_target_db(service, project, timestamp, **target)
    __wait_for("Import", operation, service, project)

    print("Deleting exported SQL file")
    __delete_sql_file(__sql_file_name(source["db"], timestamp))
    print("Done.")

Все работает отлично, пока я не пытаюсь импортировать экспортированные данные в мой целевой экземпляр.

При вызове import_ функция завершается с ошибкой:

Error: function crashed. Details:
<HttpError 403 when requesting https://www.googleapis.com/sql/v1beta4/projects/<project_id>/instances/<instance_id>/import?alt=json returned "The service account does not have the required permissions for the bucket.">

Я читал об этой ошибке во многих других вопросах и ответах здесь и в Интернете, но я не могуне могу понять, как заставить все работать.
Вот что я сделал:

  • Cloud FuncЭта операция запускается как моя «учетная запись службы по умолчанию для Compute Engine», для которой установлена ​​роль Project Editor в IAM
  • . Учетная запись службы целевого экземпляра Cloud SQL добавляется в разрешения корзины как Storage Object Admin.Я пробовал различные другие комбинации ролей (устаревший читатель / владелец, просмотрщик хранилища и т. Д.) Безрезультатно
  • Как видно из кода функции, я специально предоставляю доступ на чтение к службе целевого экземпляра.учетная запись для экспортированного файла, и это правильно отражается на разрешениях объекта в облачном хранилище:

GCS object permissions

  • Я попытался отключитьразрешения на уровне объекта для этого сегмента и убедитесь, что разрешения моего первого пункта выше были правильно установлены, но они также не работали

Интересно, когда я пытаюсь вручную импортировать тот же файл натот же экземпляр из консоли GCP Cloud SQL, все работает отлично.
После того, как это сделано, я вижу, что разрешения моего экспортированного файла были обновлены и теперь включают учетную запись службы экземпляра как Reader, как я это сделалв моем коде в конце, чтобы попытаться воспроизвести поведение.

Так что мне здесь не хватает?
Какие разрешенияssions я должен установить, для какой учетной записи службы, чтобы это работало?

Ответы [ 3 ]

0 голосов
/ 03 апреля 2019

У меня была та же проблема, и я пробовал много разных вещей.Даже после предоставления прав владельца учетной записи службы DB для проекта, корзины и SQL-файлов это не работало, а импорт / экспорт из / в другие файлы всегда работал.

Так что я переименовал своюimport-file и, как ни странно, тогда это сработало (прежнее имя файла было довольно длинным и подчеркивало его, как в вашем примере).Но я не могу найти ничего в документации по таким ограничениям именования, и в этот момент я даже не могу сказать, связана ли эта проблема с именем файла или использованием подчеркивания.Но, возможно, стоит попробовать это.

0 голосов
/ 17 мая 2019

Проблема с вашим кодом, а не с Cloud SQL.

При вызове функции _import_target_db вы ищете файл, которого нет в вашем хранилище Cloud Storage.

Получение подробностей:

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

gs://yourBucket/sql-exports/exportedDatabaseName-yyyy-mm-dd.sql.gz

Однако, когда вы пытаетесь импортировать его, функция импорта ищет файл с именем:

gs://yourBucket/sql-exports/importDatabaseName-yyyy-mm-dd.sql.gz

Этот файл не существует в вашей корзине, и по соображениям безопасности возвращается ошибка 403 Forbidden.

0 голосов
/ 22 февраля 2019

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

Вам необходимо найти учетную запись службы вашего экземпляра - Cloud SQL-> имя кластера -> Сервисный аккаунт

enter image description here

Затем вы берете вышеуказанный сервисный аккаунт и даете ему разрешение на запись / чтение для соответствующего сегмента

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...