Serverless хостинг в Oracle Cloud для рубиста

от автора

Зачем?

Ой, не спрашивайте. Хотя почему бы и не поддаться современным веяниям, и не реализовать REST api на лямбдах.

Проверить так ли уж необходимы Rails и попробовать минимизировать количество зависимостей.

Попробовать декомпозировать веб приложение в терминах облачных сервисов.

Oracle free tier,  хм, насколько это бесплатно в реальности?


Стек

В отличие от Amazon, у Oracle не так много сервисов. С одной стороны это плохо, а с другой — будет проще прикинуть архитектуру.

Итак, если лямбда в Oracle, значит это https://fnproject.io/. Free tier предоставляет 2000000 вызовов лямбд в месяц бесплатно. А еще это значит что наши лямбды будут поставляться как doker образы.

Если лямбда для веба, значит роутить запросы в облаке будет Api Gateway, это бесплатный миллион запросов в месяц (~ 1 запрос в 2.5 секунды). Этот же сервис умеет проксировать запрос к статическому контенту, предоставит https соединение. Он же может авторизовывать запросы и обрабатывать path параметры. Считайте что это аналогия роутера в Rails.

В качестве ORM не хочется брать тяжеловесный ActiveRecord, благо есть Sequel. Он более модульный и имеет адаптеры для большинства БД. Берем.

Для поднятия инфраструктуры в облаке будем использовать Terraform. Вы только гляньте на oracle провайдер от облака, он умеет все, класс.

В качестве бд я возьму Postgresql. Делаю это потому что привык и потому что на бесплатных вариантах Оракл может удалить вашу бдшечку при отсутствии запросов к ней, что неприятно. Но вообще в облаке есть варианты и с документными бд и с оракл бд (куда уж без нее). Использование этих вариантов считаю предпочтительней, поскольку вы можете управлять их схемой через тот же терраформ и хранить ее в стейте.

Итак, поехали.

Начинать стоит с авторизации SDK запросов к облачному Oracle апи. Подробно не будем останавливаться (вот и вот ссылочки в помощь), надо лишь сделать ключ и сохранить конфиг.

Структура проекта

каталог проекта
каталог проекта

/app — здесь лежит gem с логикой, которая шарится между всеми лямбдами. Согласитесь, хочется пошарить модели/сериалайзеры и прочее.

/client — код SPA фронтенда

/functions — здесь лежат наши лямбды

.tf файлы брошены в корень, терраформ любит знать про все зависимости сразу, пусть так, потом разберемся. В качестве бекенда можно использовать оракловые же бакеты, все как и в амазоне:

//infrastructure.tf  terraform {   required_version = ">= 1.0.0"   required_providers {     oci = {       version = ">= 4.76.0"       source = "oracle/oci"     }   } }  provider "oci" {   config_file_profile = "DEFAULT" }  terraform {   backend "s3" {     bucket   = "terraform-states"     key      = "terraform.tfstate"     region   = "{region-name}"     profile  = "terraform"     endpoint = "https://{namespace}.compat.objectstorage.{region-name}.oraclecloud.com".cm"     shared_credentials_file     = "~/.oci/terraform.secret"     skip_region_validation      = true     skip_credentials_validation = true     skip_metadata_api_check     = true     force_path_style            = true   } }

Деплой приложения происходит в десколько шагов — сначала билдим app гем, потом билдим и заливаем в облако образы функций, применяем изменения в инфраструктуре. С инфраструктурой справляется terraform, апи гейтвей выглядит как то так:

resource "oci_apigateway_gateway" "api_gateway" {   compartment_id = oci_identity_compartment.compartment.id   endpoint_type = "PUBLIC"   subnet_id = data.oci_core_subnet.net.id    display_name = "API Gateway"   freeform_tags = local.default_tags }  resource "oci_apigateway_deployment" "app" {   #Required   compartment_id = oci_identity_compartment.compartment.id   gateway_id     = oci_apigateway_gateway.api_gateway.id   path_prefix    = "/"    specification { routes {       path = "/users.create"       methods = ["POST", "OPTIONS"]        backend {         type        = "ORACLE_FUNCTIONS_BACKEND"         function_id = local.users_function_id       } } }

Типичная лямбда выглядит так

папка с лямбда-функцией
папка с лямбда-функцией

Yaml-файл описания фунции в fnproject. Под капотом собирается докер образ из указанных image, указаны параметры рантайма.

//func.yaml  schema_version: 20180708 name: users version: 0.0.27 runtime: ruby build_image: oci-app:latest // образ для билда run_image: oci-app-run:latest // релиз-образ entrypoint: ruby func.rb memory: 256 timeout: 120

Гемфайл зависимостей лямбды. По-хорошему наш гем стоит указать здесь, я же хотел чтобы гем ставился из локальной папки в проекте, с этим возникли сложности, поэтому гем предустанавливается в базовые докер образы oci-app, oci-app-run

# Gemfile  source "https://www.rubygems.org" do   gem "fdk", ">= 0.0.31"   gem "oci", ">= 2.17.0" end

Сам код функции, все классы Application::* берутся из нашего гема.

require "fdk"  require "oci/common" require "oci/object_storage/object_storage" require "oci/auth/auth" require "oci/secrets/secrets"  require "application"  class UsersController < Application::Controller   def show(input:)     authenticate do       user = input.key?("id") ? Application::User.with_pk!(id: input["id"]) : current_user        Application::UserSerializer.new(user).serializable_hash     end   end    def create(input:)     user = Application::User.new(input["data"]["attributes"].slice("name"))      if user.save       Application::UserSerializer.new(user, include: [:jwt_token]).serializable_hash     else       context.http_context.status_code = 400     end   end end  def route(context:, input:)   UsersController.new(context).route(input: input) end  FDK.handle(target: :route)

Переходим к самому гему.

папка гема
папка гема

Докер файлы для базовых образов функций:

// Dockerfile  FROM fnproject/ruby:2.7-dev as build-stage WORKDIR /app_lib RUN microdnf install libpq-devel COPY pkg/* /app_lib RUN gem install /app_lib/* 
// Dockerfile.run  FROM fnproject/ruby:2.7 RUN microdnf install tzdata libpq && microdnf clean all

Билд гема и его включение в базовые образы:

rake build docker build -f Dockerfile -t oci-app:latest . docker build -f Dockerfile.run -t oci-app-run:latest .

Управление лямбдами решил осуществлять средствами команд fnproject: fn start|build|deploy . Удалось организовать локальную среду, то есть те же самые функции работают в локальном fn сервере и отвечают на такие же запросы как и ApiGateway в облаке.

Итог:

  • Имеем костяк приложения с базовым роутингом

  • Имеем локальную девелопмент среду

  • Имеем возможность локально запускать аналог rails консоли из папки гема с коннектом к локальной бд

  • Минимум зависимостей от сторонних библиотек, нет rails, нет activesupport

  • Практически готовый деплой и возможность деплоить по частям

  • Фронтенд и его сборка полностью отдельно и никак не зависит от бекенда, все живет в папке client и деплоится в бакет терраформом. При желании все билд команды можно прописать в терраформе и тогда деплой будет выглядеть просто как `terraform apply`.

  • Масштабирование из коробки. Забудьте про скейл группы и мониторинг метрик нагрузки.

  • Приложение практически готово к развертыванию в собственном kubernetes кластере

  • Благодаря щедротам Oracle, это все бесплатно в известных пределах)

Минусы тоже есть:

  • Время холодного старта функций может доходить до 20-30 секунд

  • У Оракла существенно меньше облачных сервисов, закрыт буквально минимальный набор

  • FnProject выглядит последнее время заброшенным, не знаю насколько у Оракла в планах поддерживать его.


ссылка на оригинал статьи https://habr.com/ru/post/675854/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *