Русский
Русский
English
Статистика
Реклама

Apache2

Свой сервер видеоконференций Jitsi. Часть 1

03.02.2021 00:17:16 | Автор: admin
Это первая статья, в которой я расскажу, как поднять свой собственный сервер видеоконференций Jitsi-meet. Я планирую выпустить три статьи на эту тему:
  1. Свой сервер видеоконференций Jitsi.
    Jitsi и все необходимые службы работают на одном сервере + сервис Jibri (для записи видеоконференций на отдельном сервере).
  2. Свой высоконагруженный сервис видеоконференций Jitsi.
    Jitsi и все необходимые зависимые службы работают на разных серверах для получения высокой производительности.
  3. Свой мессенджер Matrix-synapse в связке с Jitsi-meet.
    Настройка Matrix на своем сервере и объединение с Jitsi для видеозвонков.

В данной статье я расскажу, как поднять собственный сервер видеоконференций jitsi-meet. Для нормального функционирования мне пришлось собирать информацию по крупицам, общаться с разработчиками данной утилиты, перечитать всю документацию и облазить кучу форумов. Тут я собрал все в один мануал на русском.


В моем случае схема была немного другой, потому что у меня сервер Jitsi и Jibri находятся за NAT, в качестве которого стоит сервер с debian 9 и на котором есть внешний и внутренний IP. Сервисы Jitsi и Jibri также можно разместить на одном сервере, но тогда на него будет большая нагрузка и, следовательно, серверу надо дать больше ресурсов. Ниже я дам мануал, который составил, собирая свою службу (на три сервера), но каждый может преобразовать его под себя в зависимости от верований и убеждений, ну и возможностей конечно.
Итак, подготовительный этап прост и известен каждому, кто умеет в linux. А если не знаком, то в этих ваших интернетах полно инструкций по развёртыванию linux-системы. Мы же поднимем с вами три debian 9 сервера (можно использовать другой дистрибутив, просто адаптировав мануал).
Рекомендуемые системные требования:
Для сервера с Jitsi-meet:
4 ядра с 2.0ГГц и 4 RAM
Для сервера с Jibri:
4 ядра с 2.0ГГц и 4 RAM
Для сервера с Jitsi и Jibri:
8 ядер с 2.0ГГц и 8 RAM
Ну, и, естественно, интернет с большой пропускной способностью.
Производительность с разными вариациями характеристик сервера можно посмотреть тут.

Еще одно немаловажное требование большинство команд надо делать от привилегированного пользователя, поэтому на сервере (на каждом сервере) сразу получим root-права.
# sudo -i

или
# su - root

Сразу хочу отметить тот факт, что сервер под Jibri должен быть без GUI (графического интерфейса) иначе Jibri, который имеет свой xorg, будет конфликтовать и откажется работать (долгие танцы с бубном помогут уладить их конфликт, но зачем все усложнять!?).
NAT-сервер
Если мы будем использовать NAT-сервер, тогда нам нужно настроить на нем безопасность и перенаправление, а для этого мы обратимся к iptables (хотя тут опять-таки дело вкуса). Создаем скрипт, которым настроим iptables:
# touch iptables_rules.sh

Дальше открываем файл, вставляем и корректируем под свои значения:
iptables_rules.sh
#! /bin/bashIPT="iptables"## Внешний интерфейсWAN=ens224WAN_IP=ВАШ_внешний_IP## Внутренний интерфейсLAN1=ens192LAN1_IP=ВАШ_внутренний_IPLAN1_IP_RANGE=192.168.0.0/24LAN2=IP_Jitsi## Очищаем все цепочки перед применением новых правил$IPT -F$IPT -F -t nat$IPT -F -t mangle$IPT -X$IPT -t nat -X$IPT -t mangle -X## Блокируем весь трафик, который не соответствует ни одному из правил$IPT -P INPUT DROP$IPT -P OUTPUT DROP$IPT -P FORWARD DROP## Разрешаем весь трафик в локалхост и локальной сети$IPT -A INPUT -i lo -j ACCEPT$IPT -A INPUT -i $LAN1 -j ACCEPT$IPT -A OUTPUT -o lo -j ACCEPT$IPT -A OUTPUT -o $LAN1 -j ACCEPT## Этот блок разрешает icmp-запросы (пинговать сервер) (опционально)$IPT -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT$IPT -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT$IPT -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT$IPT -A INPUT -p icmp --icmp-type echo-request -j ACCEPT## Открываем доступ в интернет самому серверу$IPT -A OUTPUT -o $WAN -j ACCEPT#$IPT -A INPUT -i $WAN -j ACCEPT## Разрешаем все установленные соединения и дочерние от них$IPT -A INPUT -p all -m state --state ESTABLISHED,RELATED -j ACCEPT$IPT -A OUTPUT -p all -m state --state ESTABLISHED,RELATED -j ACCEPT$IPT -A FORWARD -p all -m state --state ESTABLISHED,RELATED -j ACCEPT## Защита от наиболее распространенных сетевых атак. (опционально)## Отбрасываем все пакеты без статуса (опционально)$IPT -A INPUT -m state --state INVALID -j DROP$IPT -A FORWARD -m state --state INVALID -j DROP## Блокируем нулевые пакеты (опционально)$IPT -A INPUT -p tcp --tcp-flags ALL NONE -j DROP## Закрываемся от syn-flood атак (опционально)$IPT -A INPUT -p tcp ! --syn -m state --state NEW -j DROP$IPT -A OUTPUT -p tcp ! --syn -m state --state NEW -j DROP## Запрет доступа с определенных IP (опционально)#$IPT -A INPUT -s ipaddres -j REJECT## Разрешаем доступ в интернет из локальной сети$IPT -A FORWARD -i $LAN1 -o $WAN -j ACCEPT## Запрещаем доступ из интернета в локальную сеть (опционально)#$IPT -A FORWARD -i $WAN -o $LAN1 -j REJECT## Чтобы в локальной сети был интернет включаем nat$IPT -t nat -A POSTROUTING -o $WAN -s $LAN1_IP_RANGE -j MASQUERADE##Должен быть включен ip forwarding## Разрешаем ssh (порт 22 - это порт по умолчанию для ssh соединения, если вы его меняли, то обязательно значение 22 надо заменить своим, иначе будет потерян доступ к серверу)$IPT -A INPUT -i $WAN -p tcp --dport 22 -j ACCEPT## Перенаправление трафика на сервер JITSI$IPT -A FORWARD -i $WAN -d $LAN2 -p tcp -m tcp --dport 80 -j ACCEPT$IPT -A FORWARD -i $WAN -d $LAN2 -p tcp -m tcp --dport 443 -j ACCEPT$IPT -A FORWARD -i $WAN -d $LAN2 -p udp -m udp --dport 10000 -j ACCEPT$IPT -t nat -A PREROUTING -i $WAN -p tcp --dport 80 -j DNAT --to $LAN2$IPT -t nat -A PREROUTING -i $WAN -p tcp --dport 443 -j DNAT --to $LAN2$IPT -t nat -A PREROUTING -i $WAN -p udp --dport 10000 -j DNAT --to $LAN2$IPT -t nat -A POSTROUTING -j MASQUERADE

Сохраняем и закрываем файл.

После этого надо сделать файл исполняемым и запустить:
# chmod +x iptables_rules.sh# ./iptables_rules.sh

Правила мы установили, однако после перезагрузки сервера они пропадут. Для того, чтобы применить их перманентно, выполним следующее:
# iptables-save > /etc/iptables-conf/iptables_rules.ipv4

Откроем файл и вставим в конец файла правило для восстановления настроек:
# vim /etc/network/interfaces

post-up /sbin/iptables-restore < /etc/iptables-conf/iptables_rules.ipv4

Ну, и, естественно, надо разрешить пересылку пакетов, открываем файл:
# vim /etc/sysctl.conf

Находим строку и раскомментируем (если стоит 0, то меняем на 1)
net.ipv4.ip_forward = 1

Применяем правила:
# sysctl -p


Настраиваем сервер Jitsi
Первый сервер готов, и мы переходим ко второму серверу Jitsi.
Тут опять надо разрешить пересылку пакетов открываем файл:
# vim /etc/sysctl.conf

Находим строку и раскомментируем (если стоит 0, то меняем на 1)
net.ipv4.ip_forward = 1

Применяем правила:
# sysctl -p

Установим необходимые утилиты:
# apt-get update# apt-get install ufw iftop htop wget net-tools vim y


Далее настроим ufw (нам нет нужды прописывать похожие правила как на предыдущем сервере, а достаточно просто открыть некоторые порты):
# ufw allow 22# ufw allow 80/tcp# ufw allow 443/tcp# ufw allow 10000/udp# ufw enable

Будет запрошено подтверждение, вводим y (порт 22 это порт по умолчанию для ssh соединения, если вы его меняли, то обязательно значение 22 надо заменить своим, иначе будет потерян доступ к серверу).

Далее необходимо настроить имя хоста:
# hostnamectl set-hostname jitsi.mydnsname.com

Стоит отметить, что для полноценного использования Jitsi-meet необходимо доменное имя и белый IP, к которому привязано имя.
Переходим к установке.
# echo 'deb https://download.jitsi.org stable/' >> /etc/apt/sources.list.d/jitsi-stable.list# wget -qO -  https://download.jitsi.org/jitsi-key.gpg.key | apt-key add -

# apt-get install -y apt-transport-https# apt-get update

Если у вас еще не установлен веб-сервер, то самое время это сделать. Выберите сами apache2 или nginx, и go:
# apt-get -y install nginx (apache2)# apt-get -y install jitsi-meet

В процессе установки будет запрошен IP сервера или его dns имя. Если сервер должен работать с мобильными приложениями, то должно быть dns имя и ssl сертификат. Далее утилита спросит про сертификат ( первый вариант применим для автоматического подписания сертификата, при условии что IP адрес белый и есть dns-имя, второй если уже есть сертификат, но тогда надо будет еще указать пути, где лежат сертификаты). Важное замечание: для подключения андроид устройств нужно добавить третий сертификат и прописать к нему путь в apache/nginx.
По завершению установки мы перейдем к настройке.
Как уже и говорил, надо добавить еще один ssl-сертификат для устройств android. Для этого мы в конфигурацию добавим путь к этому сертификату (скажу сразу, что у вас пути могут отличаться, если вы делали это другим способом). Открываем файл конфигурации (apache2 или nginx):
Apache2:
# vim /etc/apache2/saites-avalieble/example.org.conf

Добавляем строку (внимательно смотрим пути и подставляем свои значения):
SSLCertificateChainFile /etc/ssl/example.org/SectigoRSADomainValidationSecureServerCA.crt

Должно получиться так:
SSLProtocol TLSv1 TLSv1.1 TLSv1.2SSLEngine onSSLProxyEngine onSSLCertificateFile /etc/ssl/jitsi.dnsname.com/jitsi.dnsname.com.crtSSLCertificateChainFile /etc/ssl/jitsi.dnsname.com/SectigoRSADomainValidationSecureServerCA.crtSSLCertificateKeyFile /etc/ssl/jitsi.dnsname.com/jitsi.dnsname.com.keySSLCipherSuite "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA256:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EDH+aRSA+AESGCM:EDH+aRSA+SHA256:EDH+aRSA:EECDH:!aNULL:!eNULL:!MEDIUM:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4:!SEED"SSLHonorCipherOrder onHeader set Strict-Transport-Security "max-age=31536000"

Сохраняем и закрываем файл.

Nginx:
# vim  /etc/nginx/sites-available/jitsi.dnsname.com.conf

# Mozilla Guideline v5.4, nginx 1.17.7, OpenSSL 1.1.1d, intermediate configuration    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # TLSv1.3;    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;    ssl_prefer_server_ciphers off;    ssl_session_timeout 1d;    ssl_session_cache shared:SSL:10m;  # about 40000 sessions    ssl_session_tickets off;    add_header Strict-Transport-Security "max-age=63072000" always;    ssl_certificate /etc/ssl/jitsi.dnsname.com/fullchain.pem;     ssl_certificate_key /etc/ssl/jitsi.dnsname.com/privkey.pem;     root /usr/share/jitsi-meet;


Сохраняем и закрываем файл.
Для nginx мы не указываем отдельный сертификат, а объединяем два сертификата в один:
# cat /etc/ssl/jitsi.dnsname.com/jitsi.dnsname.com.crt /etc/ssl/jitsi.dnsname.com/SectigoRSADomainValidationSecureServerCA.crt >> /etc/ssl/jitsi.dnsname.com/fullchain.pem


Проверить цепочку сертификатов можно тут.

Далее настраиваем систему. Добавляем туда следующее (отметим, что в локальный адрес прописываем адрес сервера Jitsi, а в публичный белый IP, у нас это IP адрес первого сервера):
# vim /etc/jitsi/videobridge/sip-communicator.properties

org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=<Local.IP.Address>org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=<Public.IP.Address>

Комментируем следующую строку:
org.ice4j.ice.harvest.STUN_MAPPING_HARVESTER_ADDRESSES


Далее для поддержания конференции на большое количество людей (более 100) добавляем следующие строки в файл:
# vim /etc/systemd/system.conf

system.conf
DefaultLimitNOFILE=65000DefaultLimitNPROC=65000DefaultTasksMax=65000


Сохраняем и закрываем файл.

Далее:
# apt-get -y install jigasi# systemctl daemon-reload# systemctl restart jitsi# cat /proc/`cat /var/run/jitsi-videobridge/jitsi-videobridge.pid`/limits


Настраиваем сервер Jibri
Теперь нам осталось только поднять сервер с Jibri. Это опционально и нужно только для того, чтобы иметь возможность транслировать конференцию или вести запись конференции.
Jibri можно поставить рядом с Jitsi на одном сервере, но тогда на сервер будет идти бОльшая нагрузка. Мы сделаем это на отдельном сервере, но практически те же самые шаги надо предпринять, чтобы поднять Jibri рядом с Jitsi.
И так, приступим.
Тут опять надо разрешить пересылку пакетов, открываем файл:
# vim /etc/sysctl.conf

Находим строку и раскомментируем (если стоит 0, то меняем на 1)
net.ipv4.ip_forward = 1

Применяем правила:
# sysctl -p

На следующем шаге нам необходимо убедиться, что модуль обратной связи доступен и работает:
# echo "snd-aloop" >> /etc/modules# modprobe snd-aloop# lsmod | grep snd_aloop

Далее установим стабильную версию Google Chrome:
# curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add# echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list# apt-get -y update# apt-get -y install google-chrome-stable

Создадим директорию и пропишем политики:
# mkdir -p /etc/opt/chrome/policies/managed# echo '{ "CommandLineFlagSecurityWarningsEnabled": false }' >> /etc/opt/chrome/policies/managed/managed_policies.json

Скачаем и установим chromedriver:
# CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`# wget -N http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -P ~/# unzip ~/chromedriver_linux64.zip -d ~/# rm ~/chromedriver_linux64.zip# mv -f ~/chromedriver /usr/local/bin/chromedriver# chown root:root /usr/local/bin/chromedriver# chmod 0755 /usr/local/bin/chromedriver

Установим зависимости и необходимые пакеты:
# apt-get install ffmpeg curl alsa-utils icewm xdotool xserver-xorg-input-void xserver-xorg-video-dummy

Теперь устанавливаем Jibri:
Если установка на сервер Jitsi
# apt-get install -y jibri


Установка на отдельный сервер
# wget -qO - https://download.jitsi.org/jitsi-key.gpg.key | sudo apt-key add -# sh -c "echo 'deb https://download.jitsi.org stable/' > /etc/apt/sources.list.d/jitsi-stable.list"# apt-get update# apt-get install -y jibri


Далее необходимо добавить Jibri в группы пользователей:
# usermod -aG adm,audio,video,plugdev jibri

Для работы Jibri необходима Java 8:
# wget -O - https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public | sudo apt-key add -# add-apt-repository https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/# apt-get update# apt-get install -y adoptopenjdk-8-hotspot

Нужно настроить Java 8 по умолчанию для Jibri. Для этого открываем файл и заменяем слово java на полный путь:
# vim /opt/jitsi/jibri/launch.sh

/usr/lib/jvm/adoptopenjdk-8-hotspot-amd64/bin/java

Для хранения видео нам надо отвести на сервере специальную директорию. Для этого мы создадим отдельную директорию (по желанию можно создать другую директорию, ее надо будет указать в конфигурации):
# mkdir /srv/recordings

Дадим Jibri права на нее и сделаем владельцем:
# chown jibri:jibri /srv/recordings

Далее мы настроим Jitsi-сервер для того, чтобы он знал о существовании Jibri и дадим все необходимые настройки и разрешения. Если сервис Jibri на отдельном сервере, тогда переходим на сервер Jitsi.
Начнем с конфигурации Prosody. Открываем файл и вставляем в конец файла с подстановкой своих значений:
# vim /etc/prosody/conf.avail/jitsi.dnsname.com.cfg.lua

jitsi.dnsname.com.cfg.lua
-- internal muc component, meant to enable pools of jibri and jigasi clientsComponent "internal.auth.jitsi.dnsname.com" "muc"    modules_enabled = {        "ping";    }    storage = "memory"    muc_room_cache_size = 1000    VirtualHost "recorder.jitsi.dnsname.com"    modules_enabled = {        "ping";    }    authentication = "internal_plain"


Теперь создадим учетные записи для пользователей jibri и recorder (пароли, которые мы тут установим, понадобятся нам далее):
# prosodyctl register jibri auth.jitsi.dnsname.com PAsswDJibRI# prosodyctl register recorder recorder.jitsi.dnsname.com PaSSwdReCORD

Следующим нашим действием станет прописывание конфигурации Jicofo. Откроем и добавим в файл следующие строки:
# vim /etc/jitsi/jicofo/sip-communicator.properties

jicofo/sim-communicator-properties
org.jitsi.jicofo.jibri.BREWERY=JibriBrewery@internal.auth.jitsi.dnsname.comorg.jitsi.jicofo.jibri.PENDING_TIMEOUT=90


Далее настроим Jitsi-meet. Добавим/раскомментируем строки в файле:
# vim /etc/jitsi/meet/jitsi.dnsname.com-config.js

config.js
fileRecordingsEnabled: true, liveStreamingEnabled: true, hiddenDomain: 'recorder.jitsi.dnsname.com',


Хочу обратить внимание, что следующий шаг необходимо выполнить только в том случае, когда сервис Jibri находится на отдельном сервере.
Ранее мы уже установили на сервере Jitsi ufw и открыли порты 22, 80, 443. Сейчас нам надо открыть еще порт 5222, для этого:
# ufw allow 5222/tcp

Теперь вернемся на сервер Jibri и добавим в файл jibri.conf следующую конфигурацию (не забываем подставлять свои значения, тут же нам надо указать и пароли для jibri и recorder):
# vim /etc/jitsi/jibri/jibri.conf

jibri.conf
jibri {  // A unique identifier for this Jibri  // TODO: eventually this will be required with no default  id = ""  // Whether or not Jibri should return to idle state after handling  // (successfully or unsuccessfully) a request.  A value of 'true'  // here means that a Jibri will NOT return back to the IDLE state  // and will need to be restarted in order to be used again.  single-use-mode = false  api {    http {      external-api-port = 2222      internal-api-port = 3333    }    xmpp {      // See example_xmpp_envs.conf for an example of what is expected here      environments = [      {                name = "prod environment"                xmpp-server-hosts = ["jitsi.dnsname.com"]                xmpp-domain = "jitsi.dnsname.com"                control-muc {                    domain = "internal.auth.jitsi.dnsname.com"                    room-name = "JibriBrewery"                    nickname = "jibri-nickname"                }                control-login {                    domain = "auth.jitsi.dnsname.com"                    username = "jibri"                    password = "PAsswDJibRI"                }                call-login {                    domain = "recorder.jitsi.dnsname.com"                    username = "recorder"                    password = "PaSSwdReCORD"                }                strip-from-room-domain = "conference."                usage-timeout = 0                trust-all-xmpp-certs = true            }]    }  }  recording {    recordings-directory = "/srv/recordings"    # TODO: make this an optional param and remove the default    finalize-script = "/path/to/finalize_recording.sh"  }  streaming {    // A list of regex patterns for allowed RTMP URLs.  The RTMP URL used    // when starting a stream must match at least one of the patterns in    // this list.    rtmp-allow-list = [      // By default, all services are allowed      ".*"    ]  }  chrome {    // The flags which will be passed to chromium when launching    flags = [      "--use-fake-ui-for-media-stream",      "--start-maximized",      "--kiosk",      "--enabled",      "--disable-infobars",      "--autoplay-policy=no-user-gesture-required"    ]  }  stats {    enable-stats-d = true  }  webhook {    // A list of subscribers interested in receiving webhook events    subscribers = []  }  jwt-info {    // The path to a .pem file which will be used to sign JWT tokens used in webhook    // requests.  If not set, no JWT will be added to webhook requests.    # signing-key-path = "/path/to/key.pem"    // The kid to use as part of the JWT    # kid = "key-id"    // The issuer of the JWT    # issuer = "issuer"    // The audience of the JWT    # audience = "audience"    // The TTL of each generated JWT.  Can't be less than 10 minutes.    # ttl = 1 hour  }  call-status-checks {    // If all clients have their audio and video muted and if Jibri does not    // detect any data stream (audio or video) comming in, it will stop    // recording after NO_MEDIA_TIMEOUT expires.    no-media-timeout = 30 seconds    // If all clients have their audio and video muted, Jibri consideres this    // as an empty call and stops the recording after ALL_MUTED_TIMEOUT expires.    all-muted-timeout = 10 minutes    // When detecting if a call is empty, Jibri takes into consideration for how    // long the call has been empty already. If it has been empty for more than    // DEFAULT_CALL_EMPTY_TIMEOUT, it will consider it empty and stop the recording.    default-call-empty-timeout = 30 seconds  }}


Теперь нам осталось только перезапустить систему. Для этого введем следующие две команды: первую на сервере Jitsi, а вторую на Jibri соответственно (если оба сервиса стоят на одном сервере, то обе команды ввести на одном сервере):
# systemctl restart jitsi-videobridge2 prosody jicofo# systemctl enable --now jibri


Вот и готово. Теперь можно перейти в браузере на jitsi.dnsname.com запустить конференцию и включить ее запись. Видеофайлы можно найти на сервере Jibri в директории /srv/recordings. Забрать их с сервера можно множеством способов, я оставлю это на ваш полет фантазии, подскажу лишь несколько:
1. Настроить проброс портов (если сервер Jibri вы поставили за NAT), затем подключиться к серверу, например через WinSCP, и стянуть файл себе. То же самое можно сделать, только без проброса портов, если Jibri не за NAT и имеет внешний IP.
2. С помощью утилиты scp.

Сообщество Jitsi. Тут же взял мануал для Jibri.

Благодарю за внимание!
Благодарю Алексея Байко, Дарью Гулькович и Владислава Гедвило за внесенные правки.
Подробнее..

Из песочницы HTTP Error 503. Service Unavailable случай в поддержке хостинга

08.07.2020 00:21:16 | Автор: admin
Работа в поддержке хостинга в основном однотипная, большинство запросов от клиентов решаются по проработанной схеме, но иногда всё же приходится сталкиваться с нетривиальными проблемами. Тогда главная задача инженера найти тот самый единственно верный путь, который приведёт к её решению. В этой статье хочу рассказать о том, как мы столкнулись с плавающей ошибкой HTTP Error 503. Service Unavailable на нашем shared-хостинге, как пытались её отловить, провели диагностику и получили неожиданный финал.

Начало


Хостинг предоставляет пользователям типичный стек Linux + Apache + Mysql + PHP и оболочку для управления. В нашем случае это ISP Manager 5 Bussines на базе Centos 7 с конвертацией в CloudLinux. Со стороны административной части, CloudLinux предоставляет инструменты для управления лимитами, а так же PHP-селектор с различными режимами работы (CGI, FastCGI, LSAPI).

В этот раз к нам обратился клиент со следующей проблемой. Его сайт на движке Wordpress периодически начал отдавать 503 ошибку, о чём он нам и сообщил.

Коды ответа, начинающиеся с 50х, относятся к проблемам на стороне сервера. Это могут быть проблемы как самого сайта, так и веб-сервера, который их обслуживает.

Типичные ситуации, при которых мы получаем следующие ошибки:

  • 500 Internal Server Error довольно часто связана либо с синтаксическими ошибками в коде сайта, либо с отсутствующими библиотеками / не поддерживаемой версией PHP. Так же могут быть проблемы с подключением к базе данных сайта или неверными правами на файлы / каталоги
  • 502 Bad Gateway например, если Nginx ссылается на неправильный порт веб-сервера Apache или процесс Apache по какой-то причине перестал работать
  • 504 Gateway Timeout ответ от Apache не был получен в течение заданного в конфигурации веб-сервера времени
  • 508 Resource limit is reached превышен лимит, выделяемых пользователю ресурсов

В данном списке приведены лишь некоторые, наиболее распространённые случаи. Также стоит отметить, что при превышении лимитов пользователь может получить как 500, так и 503 ошибку.

При выполнении диагностики данных ошибок, первым делом проверяем журналы веб-сервера. Обычно, этого достаточно, чтобы определить виновника и исправить проблему.

Касаемо 503 ошибки в нашем случае, в логах мы видели запись:
[lsapi:error] [pid 49817] [client x.x.x.x:6801] [host XXX.XX] Error on sending request(GET /index.php HTTP/1.0); uri(/index.php) content-length(0): ReceiveAckHdr: nothing to read from backend (LVE ID 8514), check docs.cloudlinux.com/mod_lsapi_troubleshooting.html
На основании только этого лога, определить в чём может быть проблема не представлялось возможным.

Первичная диагностика


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

Так же мы изучили рекомендации CloudLinux, по приведённой в журналах ошибок ссылке.
Изменение каких-либо параметров результата не принесло.

Сайт использовал базу данных на сервере Mysql 5.7, который работает на этом же сервере в контейнере Docker. В логах контейнера присутствовали сообщения:

[Note] Aborted connection 555 to db: 'dbname' user: 'username' host: 'x.x.x.x' (Got an error reading communication packets)

Как раз, среди этих сообщений были сообщения о прерванном подключении исследуемого сайта. Это дало предположение, о том, что подключение к СУБД выполняется некорректно. Для проверки мы развернули копию сайта на тестовом домене, сконвертировали базу данных сайта под нативную в Centos 7 версию СУБД 5.5.65-MariaDB. На тестовом сайте выполнили несколько сотен запросов с помощью утилиты curl. Ошибку воспроизвести не удалось. Но этот результат был предварительным и после конвертации БД на рабочем сайте проблема так и осталась.

Таким образом, проблема некорректного подключения к СУБД была исключена.

Следующим предположением было проверить нет ли проблем с самим сайтом. Для этого подняли отдельный виртуальный сервер, на нём подняли максимально схожее окружение. Единственное существенное отличие отсутствие CloudLinux. На тестовом сервере проблему воспроизвести не удалось. И так, мы определили, что в коде сайта всё в порядке. Тем не менее, пробовали так же отключать плагины Wordpress, но проблема так же сохранялась.

В результате, пришли к тому, что проблема на нашем хостинге.

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

/var/www/httpd-logs# grep -Rl "ReceiveAckHdr: nothing to read from backend" ./ | wc -l99

В ходе тестирования обнаружили, что только что установленная чистая CMS Wordpress также периодически выдаёт ошибку 503.

Примерно за 2 месяца до этого мы проводили работы по модернизации сервера, в частности изменили режим работы Apache с Worker на Prefork, с целью получить возможность использовать PHP в режиме LSAPI, вместо медленного CGI. Было предположение, о том, что это могло повлиять, либо требуются какие-то дополнительные настройки Apache, но вернуть обратно режим Worker мы уже не могли. В ходе изменения режима работы Apache выполняется изменение всех конфигов сайтов, процесс не быстрый и не всё могло пройти гладко.

Корректировка настроек Apache так же не дала желаемого результата.

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

На данном этапе мы собрали имеющуюся информацию и результаты проведённых работ. С ними обратились в поддержку CloudLinux.

Детальная диагностика


В течение нескольких дней сотрудники поддержки CloudLinux вникали в проблему. В основном рекомендации были относительно установленных лимитов пользователей. Этот вопрос мы так же проверяли. При отключенных лимитах (Опция CageFS для пользователя) и с включенными лимитами в режиме PHP как модуль Apache проблема не наблюдалась. Исходя из этого, было сделано предположение, что каким-то образом оказывает влияние CloudLinux. В итоге, к концу недели запрос был эскалирован на 3-ий уровень поддержки, но решения пока не было.

Попутно изучали документацию Apache по режимам работы CGI и LSAPI, подняли второй экземпляр Apache на сервере хостинга на другом порту с тестовым сайтом, исключили влияние Nginx, отправляя запросы напрямую к Apache и получая те же коды ошибок.

Сдвинуться с мёртвой точки помогла документация LSAPI, как раз по диагностике 503 ошибки:
www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:php:503-errors
В секции Advanced Troubleshooting предлагается выполнять трассировку найденных в системе процессов:

while true; do if mypid=`ps aux | grep $USERNAME | grep lsphp | grep $SCRIPTNAME | grep -v grep | awk '{print $2; }' | tail -1`; then strace -tt -T -f -p $mypid; fi ; done

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

При просмотре файлов трассировок, мы видим в некоторых одинаковые строки:

cat trace.* | tail...47307 21:33:04.137893 --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=42053, si_uid=0} ---47307 21:33:04.140728 +++ killed by SIGHUP +++...

Если взглянуть на описание структуры сигналов, отправляемых процессами, то увидим, что

pid_t    si_pid;       /* Sending process ID */

Указывает на идентификатор процесса, отправившего сигнал.

На момент изучения трассировок, процесса с PID 42053 в системе уже нет, поэтому в процессе захвата трассировок решили отслеживать так же процессы, отправившие сигнал SIGHUP.
Под спойлером описаны действия, которые позволили определить что это за процесс, а так же получить его трассировку и дополнительную информацию, о том, каким процессам он отправляет сигнал SIGHUP.

Методика трассировки
Консоль 1.

tail -f /var/www/httpd-logs/sitename.error.log

Консоль 2.

while true; do if mypid=`ps aux | grep $USERNAME | grep lsphp | grep "sitename" | grep -v grep | awk '{print $2; }' | tail -1`; then strace -tt -T -f -p $mypid -o /tmp/strace/trace.$mypid; fi ; done

Консоль 3.

while true; do if mypid=`cat /tmp/strace/trace.* | grep si_pid | cut -d '{' -f 2 | cut -d'=' -f 4 | cut -d',' -f 1`; then ps -aux | grep $mypid; fi; done;

Консоль 4.

seq 1 10000 | xargs -i sh -c "curl -I http://sitename/"

Ждём пока в консоли 1 появятся сообщения, при этом в консоли 4 видим статус запроса с кодом ответа 503, прерываем выполнение в консоли 4.

В итоге, получили название процесса /opt/alt/python37/bin/python3.7 -sbb /usr/sbin/cagefsctl --rebuild-alt-php-ini

Данный процесс выполнялся в системе с периодичностью раз в минуту.

Делаем трассировку нескольких процессов cagefsctl, чтобы отследить хотя бы один от начала до конца:

for i in `seq 1 100`; do strace -p $(ps ax | grep cagefsctl | grep rebuild-alt-php-ini | grep -v grep | awk '{print $1}') -o /tmp/strace/cagefsctl.trace.$(date +%s); done;

Далее изучаем что он делал, например:

cat /tmp/strace/cagefsctl.trace.1593197892 | grep SIGHUP

Так же были получены идентификаторы процессов, которые были завершены сигналом SIGHUP. Завершённые процессы были процессами PHP, выполняющимися в данный момент.

Полученные данные были переданы в поддержку CloudLinux с целью уточнить легитимность данного процесса и должен ли он работать с такой периодичностью.

Позже получили ответ, что работа команды /usr/sbin/cagefsctl --rebuild-alt-php-ini выполняется корректно, единственный нюанс в том, что команда выполняется слишком часто. Обычно вызывается при системном обновлении или изменении параметров PHP.

Единственная зацепка в данном случае осталась проверить, кто является родительским процессом cagefsctl.

Результат не заставил себя долго ждать и какого же было наше удивление родительским процессом для cagefsctl являлся процесс ispmgrnode. Это было немного странно, потому что уровень журналирования для ISP Manager был задан максимальным и в ispmgr.log не увидели вызов cagefsctl.

Теперь данных было достаточно, чтобы обратиться и в поддержку ISP System.

Итоги


Проблема была спровоцирована после выполнения обновления ISP Manager. В целом, обновление ISP Manager штатная ситуация, но она привела к запуску процесса синхронизации, который завершался с ошибкой и перезапускался ежеминутно. Процесс синхронизации вызывал за собой процесс cagefsctl, который в свою очередь завершал процессы PHP.

Причиной зависания процесса синхронизации стали проведённые на хостинге работы по модернизации оборудования. За несколько месяцев до возникновения проблемы, в сервер был установлен PCI-e NVMe-накопитель, создан раздел XFS и смонтирован в каталог /var. На него были перенесены в том числе и файлы пользователей, но не обновились дисковые квоты. Опций монтирования было не достаточно, требовалось так же изменить тип файловой системы в параметрах ISP Manager, т.к. она вызывает команды обновления дисковых квот. Для Ext4 и XFS эти команды отличаются.

Таким образом, проблема дала о себе знать спустя несколько месяцев после проведения работ.

Выводы


Мы сами создали проблему, но это было не ясно до последнего момента. На будущее, будем стараться учесть как можно больше нюансов. Благодаря помощи более подготовленных коллег из поддержки CloudLinux и ISP System, проблема была решена. Теперь наш хостинг работает стабильно. А нами был получен опыт, который пригодится нам в будущей работе.

P.S.: Надеюсь, Вам было интересно ознакомиться с материалом статьи, а кому-нибудь она поможет быстрее решить подобную проблему.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru