[Из песочницы] Настройка Minio и Nginx для RoR приложения

habr.png

Minio — что это

Minio это простое, быстрое и совместимое с AWS S3 хранилище объектов. Minio создан для размещения неструктурированных данных, таких как фотографии, видеозаписи, файлы журналов, резервные копии, а также образы виртуальных машин и контейнеров. Небольшой размер позволяет включать его в состав стека приложений, аналогичного Node.js, Redis и MySQL. В minio также поддерживается распределенный режим (distributed mode), который предоставляет возможность подключения к одному серверу хранения объектов множества дисков, в том числе расположенных на разных машинах.


Установка Minio

Установка сервера


Поставить и запустить Minio очень просто


brew install minio/stable/minio
minio server ~/data 


Как советует документация, если была ранее установлена предыдущая версия без указания стабильности, ее необходимо удалить и поставить новую стабильную


brew uninstall minio
brew install minio/stable/minio


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


minio server --address localhost:9000 ~/data


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


Установка клиента и основные команды


С помощью клиента Minio можно работать с хранилищем аналогично командной строке Unix. Поставить клиента и посмотреть команды


brew install minio/stable/mc
mc --help


из полезного:


mc mb minio/my-uploads-store               # создать каталог
mc policy -r public minio/my-uploads-store # дать полный доступ к каталогу
mc ls minio/my-uploads-store               # посмотреть что у нас там есть


В официальном справочном руководстве достаточно подробно все описано


Nginx

Базовые настройки


Nginx позволит сделать много всего полезного, например:


  • Спрятать хранилище во внутренней сети
  • Организовать кеширование
  • Организовать балансировку нагрузки для распределенного хранилища
  • Ну и единое место входа для ваших приложений, которое позволит в дальнейшем безболезнено изменить хранилище


Простейшая настройка Nginx для использования Minio


server {
  listen 80;
  server_name minio.dev;
  location / {
    proxy_set_header Host $http_host;
    proxy_pass       http://localhost:9000;
  }
}


Теперь мы можем обращаться к нашему хранилищу по адресу minio.dev


Кеширование


С кешированием Minio в Nginx как описано в статье у меня были проблемы. Как выяснилось — не только у меня. Выкрутились следующим образом:


  • Пишем в хранилище по прямой ссылке на хранилище мимо Nginx (или через него, но без кеширования)
  • Чтение из хранилища происходит по другой ссылке с кешированием
  • Соответствено в нашем RoR приложении ссылка меняется — при записи одна, при чтении — другая с кешированием


Конфиг Nginx в части хранилища
proxy_cache_path /var/cache/nginx/minio_cache levels=1:2 keys_zone=minio_cache:10m max_size=10m inactive=60m use_temp_path=off;

upstream minio_servers {    # можно добавить еще серверов и настроить Load Balancing
    server localhost:9000;
}

server {
  listen 80;
  server_name minio.dev;

  location / {
    proxy_cache minio_cache;
    add_header X-Cache-Status $upstream_cache_status;
    proxy_cache_revalidate on;
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    proxy_cache_lock on;
    proxy_ignore_headers Set-Cookie;
    proxy_cache_valid 1m;

    proxy_set_header Host $http_host;
    proxy_pass http://minio_servers;
  }
}


RoR приложениe

Общие настройки


Картинки в Minio загружаем с помощью Shrine (все подробности работы — в неплохой официальной документации и прилагаемых к документации статьях, тут — только особенности для Minio). Непосредственно для работы с Minio необходимо добавить в Gemfile


gem 'shrine-fog'
gem 'fog-aws'


config/initializers/shrine.rb
require 'shrine/storage/fog'
require 'fog/aws'
require 'image_processing/mini_magick'

minio = Fog::Storage.new(
  provider:                       'AWS',
  aws_access_key_id:       '',
  aws_secret_access_key: '',
  region:                          'us-east-1',
  endpoint:                      'http://localhost:9000/',   # та самая прямая ссылка
  path_style:                    true,
)

Shrine.storages[:cache] = Shrine::Storage::Fog.new(
  connection: minio,
  directory: '',
  public: true,
)

Shrine.storages[:store] = Shrine::Storage::Fog.new(
  connection: minio,
  directory: '',
  public: true,
)


Само-собой, необходимо через клиента Minio создать соответствующие папки и дать полный доступ к ним (см. раздел выше — клиент Minio).


app/models/avatar_uploader.rb
class AvatarUploader < Shrine
  include ImageProcessing::MiniMagick

  plugin :activerecord
  plugin :determine_mime_type, analyzer: :mime_types
  plugin :logging, logger: Rails.logger
  plugin :remove_attachment
  plugin :store_dimensions
  plugin :remote_url, max_size: 20*1024*1024
  plugin :versions
  plugin :default_url
  plugin :processing
  plugin :host_url, host: 'minio.dev', port: '80'  # это собственный плагин, описан чуть дальше

  Attacher.default_url do |options| # не обязательно, дефолтная картинка
    '/images/default.svg'
  end

  process(:cache) do |io, context|  # не обязательно, просто аватар мы у себя уменьшаем при загрузке
    resize_to_fill!(io.to_io, 300, 300)
  end
end


Собственно, большинство плагинов не обязательны. Подробнее см. официальную документацию к shrine. Обращение как обычно:


User.find(1).avatar_url


Замена ссылки


С заменой ссылки пришлось несколько повозиться. У Shrine достаточно разных плагинов, которые можно использовать для решения большинства задач. Есть плагин и для замены хоста default_url_options, но в данном конкретном случае он не помог — хост он меняет, а порт игнорирует. Хм, что же делать? А давайте напишем свой собственный плагин к Shrine. Код плагина ниже, как подключается — в предыдущем листинге (80й порт по-умолчанию в итоговой ссылке не прописывается, все остальные — появятся. Можно не писать его, кстати).


Код плагина
class Shrine
  module Plugins
    module HostUrl
      def self.configure(uploader, options = {})
        uploader.opts[:host_url] = (uploader.opts[:host_url] || {}).merge(options)
      end

      module FileMethods
        def url(**options)
          new_uri(
            URI.parse(super),
            uploader.opts[:host_url][:host],
            uploader.opts[:host_url][:port]
          )
        end

        private

        def new_uri(uri, new_host, new_port)
          URI::HTTP.new(
            uri.scheme,
            uri.userinfo,
            new_host,
            new_port,
            uri.registry,
            uri.path,
            uri.opaque,
            uri.query,
            uri.fragment
          ).to_s
        end
      end
    end

    register_plugin(:host_url, HostUrl)
  end
end


Код нового плагина положим в lib/shrine/plugins/host_url.rb


Миграция данных


В официальной инструкции, в принципе, все подробно описано. Есть, правда, несколько нюансов — ну там if (attacher = user.avatar_attacher).stored? почему-то возвращает false ну и еще по-мелочи — нужно добавить новое хранилище при существующем старом, сделать миграцию и потом выпилить старое…
Но в данном конкретном случае все можно сделать гораздо проще:


rsync -zavP <юзер>@<удаленный.сервер>:/<путь_к_немигрированным_данным> ~/data/


Собственно все из коробки работает с новым кодом.


Прочее


Да, при переезде на Minio в следующем приложении перенес этот плагин в гем.


Использованные источники

Enterprise-Grade Cloud Storage with NGINX Plus and Minio и русский перевод

© Habrahabr.ru