Nextcloud: отказоустойчивый деплой для средних компаний

от автора

Есть очень крутой комбайн для совместного ведения проектов, LDAP-авторизацией, синхронизацией файлов с версионированием и чем-то вроде корпоративного мессенджера с видеоконференциями, которые прикрутили в последних версиях. Да, я про Nextcloud. С одной стороны, я сторонник Unix-way и четкого дробления приложений по отдельным функциям. С другой — этот продукт более чем устойчив, работает много лет в нескольких проектах без особых проблем и дополнительные свистелки особо не мешают ему работать. Если очень хочется, то туда можно прикрутить практически любую дичь. Коммьюнити живое и вполне допиливает различные плагины, которые доступны как отдельные приложения.

Сегодня мы будем его разворачивать. Я не буду давать полной пошаговой инструкции, но постараюсь упомянуть про ключевые моменты архитектуры, на которые стоит обратить внимание. В частности, разберем балансировку нагрузки, репликацию БД и регламентное обслуживание без прерывания сервиса.
Деплоить будем в отказоустойчивом варианте для небольшой компании в 150-1000 пользователей, но для домашних пользователей тоже пригодится.

Что нужно компаниям?

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

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

В частности, по моему опыту, в Nextcloud востребовано несколько функций среди небольших компаний:
Предоставление доступов к общим каталогам и синхронизация.

  1. Киллер-фича с предоставлением доступа вовне в рамках федерации. Можно интегрироваться с аналогичным продуктом у коллег и другой компании.
  2. Предоставление доступов вовне по прямой ссылке. Очень помогает, если вы, например, работаете в полиграфии и надо обмениваться с клиентами большими объемами тяжелых данных.
  3. Редактор документов Collabora, который выполняется на стороне сервера и работает как фронтенд к LibreOffice.
  4. Чаты и видеозвонки. Немного спорная, не совсем стабильная функция, но она есть и работает. В последней версии ее уже стабилизировали.

Строим архитектуру

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

Типовой вариант для домашнего использования и единичных инсталляций.

Вариант «все в одном» неплох до тех пор, пока у вас немного пользователей, а вы можете себе позволить простой на время регламентных работ. Например, во время обновления. Также у монолитной схемы, с размещением на одной ноде есть проблемы с масштабированием. Поэтому, мы попробуем второй вариант.

Масштабируемый вариант деплоя, рекомендованный для более высоких нагрузок.

Основные компоненты системы:

  • 1 Балансировщик. Можете взять HAproxy или Nginx. Я рассмотрю вариант с Nginx.
  • 2-4 штуки Application server (web-server). Сама инсталляция Nextcloud с основным кодом на php.
  • 2 БД. В стандартной рекомендованной конфигурации это MariaDB.
  • NFS-хранилище.
  • Redis для кеширования запросов к БД

Балансировщик

В данной архитектуре у вас будет меньше точек отказа. Основная точка отказа — это балансировщик нагрузки. В случае его недоступности пользователи не смогут достучаться до сервиса. К счастью, его конфигурация того же nginx довольно проста, как мы рассмотрим дальше, а нагрузку он держит без особых проблем. Большинство аварий на балансировщике решается рестартом демона, всей ноды целиком или развертыванием из бэкапа. Не будет лишним иметь настроенный холодный резерв в другой локации с ручным переключением трафика на него в DNS.

Обратите внимание, что балансировщик также является точкой терминирования SSL/TLS для ваших клиентов, а общение с бэкендом может идти как по HTTP для доверенных внутренних сетей, так и с дополнительным HTTPS, если трафик до application-сервера идет по общим недоверенным каналам.

База данных

Типовое решение — MySQL/MariaDB в кластерном исполнении в master-slave репликации. При этом у вас активна только одна БД, а вторая работает в режиме горячего резерва на случай аварийного отказа основной или при проведении плановых работ. Можно рассмотреть и вариант с балансировкой нагрузки, но она технически сложнее. При использовании MariaDB Galera Cluster с вариантом master-master репликации нужно использовать нечетное количество нод, но не менее трех. Таким образом минимизируется риск split-brain ситуаций, при рассечении связности между нодами.

Storage

Любое оптимальное для вас решение, которое предоставляет NFS-протокол. В случае высоких нагрузок можно рассмотреть IBM Elastic Storage или Ceph. Также возможно использование S3-совместимого объектного хранилища, но это скорее вариант для особо крупных инсталляций.

HDD или SSD

В принципе, для средних по размеру инсталляций вполне достаточно использования только HDD. Узким участком тут будут iops при чтении из БД, что сильно сказывается на отзывчивости системы, но, при наличии Redis, который кеширует все в RAM, это не будет особой проблемой. Так же часть кеша будет лежать в memcached на application-серверах. Тем не менее, я бы рекомендовал, по возможности, размещать application-сервера на SSD. Веб-интерфейс становится намного отзывчивее по ощущениям. При этом та же синхронизация файлов на десктопных клиентах будет работать примерно так же, как и при использовании HDD для этих узлов.

Скорость синхронизации и отдачи файлов будет определяться уже производительностью вашего NFS-хранилища.

Конфигурируем балансировщик

В качестве примера я приведу простой в базовой конфигурации и эффективный nginx. Да, различные дополнительные failover-плюшки у него доступны только в платной версии, но даже в базовом варианте он отлично выполняет свою задачу. Учтите, что балансировка типа round robin или random нам не подходит, так как application-сервера хранят кеши для конкретных клиентов.
К счастью, это решается использованием метода ip_hash. В таком случае сессии пользователя будут закреплены за конкретным бэкендом, к которому будут направляться все запросы со стороны пользователя. Этот момент описан в документации:

Задаёт для группы метод балансировки нагрузки, при котором запросы распределяются по серверам на основе IP-адресов клиентов. В качестве ключа для хэширования используются первые три октета IPv4-адреса клиента или IPv6-адрес клиента целиком. Метод гарантирует, что запросы одного и того же клиента будут всегда передаваться на один и тот же сервер. Если же этот сервер будет считаться недоступным, то запросы этого клиента будут передаваться на другой сервер. С большой долей вероятности это также будет один и тот же сервер.

К сожалению, при использовании этого метода могут быть проблемы с пользователями, которые находятся за динамическим IP и постоянно его меняют. Например, на клиентах с мобильным интернетом, которых может кидать по разным маршрутам при переключении между сотами. sticky cookie, который решает эту проблему доступен только в платной версии.

В конфигурационном файле nginx это описывается следующим образом:

upstream backend {     ip_hash;      server backend1_nextcloud.example.com;     server backend2_nextcloud.example.com;     server backend3_nextcloud.example.com;     server backend4_nextcloud.example.com; }

В этом случае нагрузка будет по возможности равномерно распределяться между application-cерверами, хотя из-за привязки клиента к конкретной сессии могут возникать перекосы нагрузки. Для небольших и средних инсталляций этим можно пренебречь. Если у вас бэкенды разные по мощности, то можно задать вес каждого из них. Тогда балансировщик будет стараться распределять нагрузку пропорционально заданным весам:

upstream backend {     ip_hash;      server backend1_nextcloud.example.com weight=3;     server backend2_nextcloud.example.com;     server backend3_nextcloud.example.com; } 

В приведенном примере из 5 пришедших запросов 3 уйдут на backend1, 1 на backend2 и 1 на backend3.

В случае, если один из application-серверов откажет, nginx попробует переадресовать запрос следующему серверу из списка бэкендов.

Конфигурируем БД

Подробно Master-Slave конфигурацию можно посмотреть в основной документации.

Разберем несколько ключевых моментов. Для начала создаем пользователя для репликации данных:

create user 'replicant'@'%' identified by 'replicant_password'; grant replication slave on *.* to replicant; flush privileges;

Затем правим конфиг мастера:

sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf 

В районе блока «Logging and Replication» вносим нужные правки:

[mysqld] log-bin         = /var/log/mysql/master-bin log-bin-index   = /var/log/mysql/master-bin.index binlog_format   = mixed server-id       = 01 replicate-do-db = nextcloud bind-address = 192.168.0.6

На Slave конфигурируем конфиг:

sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf 

В районе блока «Logging and Replication» вносим нужные правки:

    [mysqld]     server-id       = 02     relay-log-index = /var/log/mysql/slave-relay-bin.index     relay-log       = /var/log/mysql/slave-relay-bin     replicate-do-db = nextcloud     read-only = 1     bind-address    = 192.168.0.7

Перезапускаем оба сервера:

sudo systemctl restart mariadb

Далее надо будет скопировать БД на Slave.
На Master выполняем для начала выполняем блокировку таблиц:

flush tables with read lock;

А затем смотрим статус:

     MariaDB [(none)]> show master status;     +-------------------+----------+--------------+------------------+     | File              | Position | Binlog_Do_DB | Binlog_Ignore_DB |     +-------------------+----------+--------------+------------------+     | master-bin.000001 |      772 |              |                  |     +-------------------+----------+--------------+------------------+     1 row in set (0.000 sec)

Не выходим из консоли БД, иначе блокировки снимутся!
Нам понадобится отсюда master_log_file и master_log_pos для конфигурации Slave.
Делаем дамп и снимаем блокировки:

 sudo mysqldump -u root nextcloud > nextcloud.sql 

     > unlock tables;     > exit;

Затем на Slave импортируем дамп и рестартуем демон:

 sudo mysqldump -u root nextcloud < nextcloud.sql sudo systemctl restart mariadb

После этого в консоли настраиваем репликацию:

     MariaDB [(none)]> change master 'master01' to          master_host='192.168.0.6',          master_user='replicant',          master_password='replicant_password',          master_port=3306,          master_log_file='master-bin.000001',          master_log_pos=772,          master_connect_retry=10,          master_use_gtid=slave_pos;

Запускаем и проверяем:

 > start slave 'master01'; show slave 'master01' status\G;

В ответе не должно быть ошибок и два пункта укажут на успешность процедуры:

Slave_IO_Running: Yes Slave_SQL_Running: Yes

Деплоим Application ноды

Есть несколько вариантов деплоя:

  1. snap
  2. docker-image
  3. ручное обновление

Snap доступен преимущественно для Ubuntu. Он весьма хорош в доставке сложных проприетарных приложений, но по умолчанию. Но у него есть особенность, которая довольно неприятна в промышленном окружении — он автоматически обновляет свои пакеты несколько раз в сутки. Также придется пропиливать дополнительные доступы наружу, если у вас жестко отграничена внутренняя сеть. При этом, зеркалировать его репозитории внутрь не совсем тривиально.

Да, там есть каналы подписки, а мажорные релизы, по идее он не должен переключать, но все же подумайте. Я бы рекомендовал полный контроль за процессом обновления, тем более, что нередко он сопровождается изменением структуры данных в БД.

Docker-image — хороший вариант, особенно, если у вас часть инфраструктуры крутится уже на Kubernetes. Та же нода Redis, скорее всего уедет в кластер следом за application-серверами.

Если у вас нет инфраструктуры для этого, то ручное обновление и развертывание из tar.gz вполне удобно и контролируемо.

Не забудьте, что на application-сервера вам потребуется установить веб-сервер для обработки входящих запросов. Я бы рекомендовал связку из nginx + php-fpm7.4. С последними версиями php-fmp ощутимо выросло быстродействие и отзывчивость.

Конфигурирование SSL/TLS

Однозначно стоит рассчитывать на TLS 1.3, если вы делаете новую инсталляцию и нет проблем с пакетами nginx, которые зависят от свежести системного openssl. В частности, 0-RTT и другие плюшки позволяют временами ощутимо ускорить повторное подключение клиентов за счет кеширования. Безопасность также выше за счет выпиливания устаревших протоколов.

Приведу актуальный конфиг для nginx application-сервера, который находится общается с балансировщиком по TLS:

Конфиг nginx

upstream php-handler {  server unix:/var/run/php/php7.4-fpm.sock; }  server {     listen 80;     server_name backend1_nextcloud.example.com;     # enforce https     root /var/www/nextcloud/;     return 301 https://$server_name$request_uri; }  server {     listen 443 ssl http2;     ssl_early_data on; #    listen [::]:443 ssl http2;     server_name backend1_nextcloud.example.com;      # Path to the root of your installation     root /var/www/nextcloud/;     # Log path     access_log /var/log/nginx/nextcloud.nginx-access.log;     error_log /var/log/nginx/nextcloud.nginx-error.log;     ### SSL CONFIGURATION ###         ssl_certificate /etc/letsencrypt/live/backend1_nextcloud.example.com/fullchain.pem;         ssl_certificate_key /etc/letsencrypt/live/backend1_nextcloud.example.com/privkey.pem;         ssl_trusted_certificate /etc/letsencrypt/live/backend1_nextcloud.example.com/fullchain.pem;         ssl_dhparam /etc/ssl/certs/dhparam.pem;          ssl_protocols TLSv1.2 TLSv1.3;         ssl_prefer_server_ciphers on;         #ssl_ciphers "EECDH+AESGCM:EECDH+CHACHA20:EECDH+AES256:!AES128";         ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POL>         ssl_session_cache shared:SSL:50m;         ssl_session_timeout 5m;          ssl_stapling on;         ssl_stapling_verify on;         resolver 8.8.4.4 8.8.8.8;          add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains; preload' always; ### КОНЕЦ КОНФИГУРАЦИИ SSL ###      # Add headers to serve security related headers     # Before enabling Strict-Transport-Security headers please read into this     # topic first.     # add_header Strict-Transport-Security "max-age=15768000;     # includeSubDomains; preload;";     #     # WARNING: Only add the preload option once you read about     # the consequences in https://hstspreload.org/. This option     # will add the domain to a hardcoded list that is shipped     # in all major browsers and getting removed from this list     # could take several months.     add_header Referrer-Policy "no-referrer" always;     add_header X-Content-Type-Options "nosniff" always;     add_header X-Download-Options "noopen" always;     add_header X-Frame-Options "SAMEORIGIN" always;     add_header X-Permitted-Cross-Domain-Policies "none" always;     add_header X-Robots-Tag "none" always;     add_header X-XSS-Protection "1; mode=block" always;      # Remove X-Powered-By, which is an information leak     fastcgi_hide_header X-Powered-By;      location = /robots.txt {         allow all;         log_not_found off;         access_log off;     }      # The following 2 rules are only needed for the user_webfinger app.     # Uncomment it if you're planning to use this app.     #rewrite ^/.well-known/host-meta /public.php?service=host-meta last;     #rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json     # last;      location = /.well-known/carddav {       return 301 $scheme://$host/remote.php/dav;     }     location = /.well-known/caldav {       return 301 $scheme://$host/remote.php/dav;     }      # set max upload size     client_max_body_size 512M;     fastcgi_buffers 64 4K;      # Enable gzip but do not remove ETag headers     gzip on;     gzip_vary on;     gzip_comp_level 4;     gzip_min_length 256;     gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;     gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fon>      # Uncomment if your server is build with the ngx_pagespeed module     # This module is currently not supported.     #pagespeed off;      location / {         rewrite ^ /index.php;     }      location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ {         deny all;     } location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) {         deny all;     }      location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) {         fastcgi_split_path_info ^(.+?\.php)(\/.*|)$;         set $path_info $fastcgi_path_info;         try_files $fastcgi_script_name =404;         include fastcgi_params;         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;         fastcgi_param PATH_INFO $path_info;         fastcgi_param HTTPS on;         # Avoid sending the security headers twice         fastcgi_param modHeadersAvailable true;         # Enable pretty urls         fastcgi_param front_controller_active true;         fastcgi_pass php-handler;         fastcgi_intercept_errors on;         fastcgi_request_buffering off;     }      location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) {         try_files $uri/ =404;         index index.php;     }      # Adding the cache control header for js, css and map files     # Make sure it is BELOW the PHP block     location ~ \.(?:css|js|woff2?|svg|gif|map)$ {         try_files $uri /index.php$request_uri;         add_header Cache-Control "public, max-age=15778463";         # Add headers to serve security related headers (It is intended to         # have those duplicated to the ones above)         # Before enabling Strict-Transport-Security headers please read into         # this topic first.         #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;         #         # WARNING: Only add the preload option once you read about         # the consequences in https://hstspreload.org/. This option         # will add the domain to a hardcoded list that is shipped         # in all major browsers and getting removed from this list         # could take several months.         add_header Referrer-Policy "no-referrer" always;         add_header X-Content-Type-Options "nosniff" always;         add_header X-Download-Options "noopen" always;         add_header X-Frame-Options "SAMEORIGIN" always;         add_header X-Permitted-Cross-Domain-Policies "none" always;         add_header X-Robots-Tag "none" always;         add_header X-XSS-Protection "1; mode=block" always;          # Optional: Don't log access to assets         access_log off;     }      location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap)$ {         try_files $uri /index.php$request_uri;         # Optional: Don't log access to other assets         access_log off;     } }

Регламентное обслуживание

Не забудьте, что в промышленном окружении требуется обеспечить минимальный и нулевой простой сервиса при обновлении или тем более резервном копировании. Ключевая сложность тут — зависимость состояния метаданных в БД и самих файлов, которые доступны через NFS или объектное хранилище.
При обновлении application-серверов на новую минорную версию особых проблем не возникает. Но кластер все равно надо переводить в mainenance mode для обновления структуры БД.
Выключаем балансировщик в момент наименьшей нагрузки и приступаем к обновлению.

После этого выполняем на нем процесс ручного обновления из скачанного tar.gz, с сохранением конфигурационного файла config.php. Обновление через веб на крупных инсталляциях — очень плохая идея!
Выполняем обновление через командную строку:

sudo -u www-data php /var/www/nextcloud/occ upgrade

После этого включаем балансировщик и подаем трафик на обновленный сервер. Для этого выводим все необновленные application-сервера из балансировки:

upstream backend {     ip_hash;      server backend1_nextcloud.example.com;     server backend2_nextcloud.example.com down;     server backend3_nextcloud.example.com down;     server backend4_nextcloud.example.com down; }

Остальные ноды постепенно обновляем и вводим в строй. При этом occ upgrade уже выполнять не нужно! Нужно только заменить php-файлы и сохранить конфигурацию.

При резервном копировании нужно останавливать репликацию на Slave и выполнять одновременный дамп метаданных из БД одновременно с созданием снапшота файлов в хранилище. Хранить их нужно попарно. Восстановление аналогично должно происходить из дампа БД и файлов за один и тот же период. В противном случае возможны потери данных, так как файл может лежать в хранилище, но не иметь метаданных в БД.

ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/530548/