Pull to refresh

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

Reading time 5 min
Views 1.6K

Зачем?

Ой, не спрашивайте. Хотя почему бы и не поддаться современным веяниям, и не реализовать 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 выглядит последнее время заброшенным, не знаю насколько у Оракла в планах поддерживать его.

Tags:
Hubs:
+7
Comments 0
Comments Leave a comment

Articles