Cloud Functions — это масштабируемая реализация от Google Cloud Platform (GCP) архитектурного шаблона function-as-a-service, FaaS («функция как услуга») с оплатой по мере использования, позволяющая запускать код без управления сервером.
Ради большего удобства восприятия напишем Cloud Function на Python, хотя это можно сделать также на Node.js, Go, Java, .NET, Ruby и PHP. Так что смело выбирайте любимый язык и вперед!
Terraform — это инструмент разработки, применяющий подход Infrastructure as Code (IaC), то есть «инфраструктура как код». Он позволяет создавать и менять инфраструктуру, а также обновлять ее версии безопасно, эффективно и с возможностью воспроизводимости. В Terraform используется декларативный способ описания инфраструктуры. Для этого облачные API (GCP, AWS, Azure…) переносятся в конфигурационные файлы.
В итоге, загрузив файл в Google Cloud Storage bucket, мы запустим Cloud Function.
Что потребуется?
- Terraform, установленный на локальном компьютере;
- там же установить Google Cloud SDK;
- проект Google Cloud Platform, настроенный и привязанный к учетной записи для выставления счетов. Обязательно активируем API для Cloud Functions.
Затем проходим аутентификацию в GCP на локальном компьютере, введя в терминале gcloud auth application-default login
.
Цели
Цель — с помощью Terraform развернуть в GCP-проекте:
- bucket для загрузки файлов;
- bucket для хранения исходного кода Cloud Function;
- Cloud Function, которая запускается каждый раз, когда файл загружается в первый bucket, и исходный код которой находится во втором bucket.
Структура проекта
Создаем новую папку cloud_function_project
, а в ней — файлы:
.cloud_function_project/
│
├── terraform/
│ │
│ ├── backend.tf
│ ├── function.tf
│ ├── main.tf
│ ├── storage.tf
│ └── variables.tf
│
└── src/
│
├── main.py
└── requirements.txt
Оставим их пока пустыми и, прежде чем начинать заполнять, кратко разберем каждый из них.
В папке src
находится исходный код Cloud Function. Структура src
характерна для Python:
main.py
: исходный код Cloud Functions.requirements.txt
: список библиотек Python для запускаmain.py
(здесь они нам не понадобятся).
В папке terraform
содержатся конфигурационные файлы среды для развертывания:
backend.tf
: объявление бэкенда Terraform.main.tf
: основные объявления среды.variables.tf
: определение переменных.storage.tf
: объявление Google Cloud Storage bucket.function.tf
: объявление Cloud Function.
Создаем Cloud Function
Написание и запуск Cloud Function здесь не так важны. Развернем функцию, в которой будет записываться полезная информация о файле, загруженном в bucket для отслеживания:
def hello_gcs(event, context):
"""Background Cloud Function to be triggered by Cloud Storage.
This generic function logs relevant data when a file is changed.
Args:
event (dict): The dictionary with data specific to this type of event.
The `data` field contains a description of the event in
the Cloud Storage `object` format described here:
https://cloud.google.com/storage/docs/json_api/v1/objects#resource
context (google.cloud.functions.Context): Metadata of triggering event.
Returns:
None; the output is written to Stackdriver Logging
"""
print('Event ID:' , context.event_id)
print('Event type:', context.event_type)
print('Bucket:', event['bucket'])
print('File:', event['name'])
print('Metageneration:', event['metageneration'])
print('Created:', event['timeCreated'])
print('Updated:', event['updated'])
# пустой
Примечание № 1: эта функция не имеет необходимых требований, поэтому
requirements.txt
остается пустым.
Примечание № 2: это Cloud Function для Python, для других языков источник кода можно поменять.
Создаем инфраструктуру в Terraform
Backend
Начнем объявлять среду, указав бэкенд Terraform. Выбираем local
, чтобы все файлы состояния сохранялись в локальном каталоге. Можно также выбрать бэкенд gcs
, но не будем усложнять:
terraform {
backend "local" {}
}
Variables
Объявляем переменные, используемые в файлах Terraform. Нужно поменять переменную project_id
на идентификатор проекта, в котором будут развернуты ресурсы. region
тоже можно поменять:
variable "project_id" {
default = "<YOUR-PROJECT-ID>"
}
variable "region" {
default = "europe-west1"
}
Main
Объявляем подключение к провайдеру Google:
provider "google" {
project = var.project_id
region = var.region
}
Google Cloud Storage
Объявляем один Google Cloud Storage bucket для хранения кода Cloud Function, а другой — для загрузки файлов:
resource "google_storage_bucket" "function_bucket" {
name = "${var.project_id}-function"
location = var.region
}
resource "google_storage_bucket" "input_bucket" {
name = "${var.project_id}-input"
location = var.region
}
Примечание № 3: уникальность названий bucket обеспечивается префиксами
project_id
.
Google Cloud Function
Последний шаг: объявляем Cloud Function. Для этого сжимаем исходный код в zip-файл и загружаем его в bucket для хранения. Затем получаем доступ к исходному коду при создании Cloud Function с помощью Terraform.
Примечание № 4: файл длинноват, так что для понимания логики смотрите комментарии:
# Создает архив исходного кода в виде сжатого .zip file.
data "archive_file" "source" {
type = "zip"
source_dir = "../src"
output_path = "/tmp/function.zip"
}
# Добавляем zip-файл с исходным кодом в bucket для Cloud Function
resource "google_storage_bucket_object" "zip" {
source = data.archive_file.source.output_path
content_type = "application/zip"
# Добавляем к контрольной сумме MD5 содержимого файлов,
# чтобы после изменения zip-файл обновлялся принудительно
name = "src-${data.archive_file.source.output_md5}.zip"
bucket = google_storage_bucket.function_bucket.name
# Зависимости выводятся автоматически, поэтому эти строки можно удалить
depends_on = [
google_storage_bucket.function_bucket, # объявленный в «storage.tf»
data.archive_file.source
]
}
# Создаем Cloud Function, запускаемую с помощью события «Finalize» в bucket
resource "google_cloudfunctions_function" "function" {
name = "function-trigger-on-gcs"
runtime = "python37" # можно поменять, конечно
# Получаем исходный код Cloud Function в zip-файле
source_archive_bucket = google_storage_bucket.function_bucket.name
source_archive_object = google_storage_bucket_object.zip.name
# Должно совпадать с именем функции в исходном коде Cloud Function «main.py»
entry_point = "hello_gcs"
#
event_trigger {
event_type = "google.storage.object.finalize"
resource = "${var.project_id}-input"
}
# Зависимости выводятся автоматически, поэтому эти строки можно удалить
depends_on = [
google_storage_bucket.function_bucket, # объявленный в «storage.tf»
google_storage_bucket_object.zip
]
}
Развёртываем среду
К развертыванию все готово. Переходим в корневой каталог проекта в терминале, затем в корневой каталог папки terraform
:
$ cd ./terraform
Инициализируем код, чтобы загрузить указанные в нем требования:
$ terraform init
Просматриваем изменения:
$ terraform plan
Принимаем их и применяем к реальной инфраструктуре:
$ terraform apply
Тестируем Cloud Function
Проверим, что все работает нормально:
- Открываем консоль Google Cloud Console и подключаемся к проекту.
- Переходим в браузер Google Cloud Storage browser.
- Видим оба bucket:
<YOUR-PROJECT-ID>-function
и<YOUR-PROJECT-ID>-input
. Нажимаем на bucket<YOUR-PROJECT-ID>-input
и, загрузив в него любой файл, запускаем Cloud Function. - Чтобы убедиться, что запуск произошел, переходим в список Cloud Functions. Там должна быть Cloud Function с именем
function-trigger-on-gcs
. Нажимаем на этом названии и переходим на вкладкуLOGS
, чтобы увидеть, что Cloud Function запущена с помощью загруженного нами файла.
Что дальше?
Процесс развертывания Cloud Function можно дополнить, например:
- установить политику IAM;
- добавить переменные и сделать из этого кода модуль Terraform, чтобы использовать его многократно;
- интегрировать в процесс облачной сборки, чтобы развертывание было непрерывным.
Читайте также:
- Отправка push-уведомлений с помощью Firebase Cloud Messaging
- Обработка событий по времени в бессерверной архитектуре
- Как автоматизировать сравнение датасетов с Terraform и BigQuery
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Axel Thevenot: Deploy Cloud Functions on GCP with Terraform