2024-12-08 17:48:10 +03:00
# Инструкция по развёртыванию Django-проекта на VDS-хостинге (_вир ту а льна я ма шина _) — Django/MariaDB/uWSGI/nginx
## 1: Создание пользователя
Изначально есть только root-доступ. Если мы залогированы под **root** (например, через KVM), то следует создать
пользователя от имени которого мы будем осуществлять все действия (позже root-доступ будет закрыт).
Создадим пользователя (например **your_username** , но можно и другое имя) и дадим ему права администратора.
```shell
sudo useradd -c 'WEB-user' -m your_username
```
Присвоим ему пароль:
```shell
sudo passwd your_username
```
Дадим ему права работать от имени root. Для этого откроем на редактирование конфигурационный файл `/etc/sudoers` :
```shell
nano /etc/sudoers
```
и после строки `root ALL=(ALL:ALL) ALL` добавим в него строку:
```editorconfig
your_username ALL=(ALL:ALL) ALL
```
Сохраняем конфигурационный файл `Ctrl+O` и `Enter` , а выходим из редактора `Ctrl+X` .
**ВАЖНО**: Если по какой-то причине файл `/etc/sudoers` пустой — это означает, что служба sudoers не установлена.
Её нужно установить: `apt-get install sudo` .
Разлогируемся оз под root.
```shell
logout
```
Теперь можно залогироваться от имени пользователя **your_username** .
Установим командную оболочку bash для пользователя:
```shell
chsh -s /bin/bash
```
## 2: Настройка SSH и окружения клиента
### Ограничиваем доступ root и повышение безопасности SSH
После завершения перезагрузки произведённой в конце предыдущего этапа мы можем заходить на наш сервер по SSH под новым, только что созданным, пользователем [user] и паролем [user_pwd]. Теперь следует сделать небольшие изменения в файле `/etc/ssh/sshd_config` — конфигурации ssh-доступа:
```shell
sudo nano /etc/ssh/sshd_config
```
Полное описание настроек sshd_config [находится на сайте Ubuntu ](https://help.ubuntu.ru/wiki/ssh ). Там содержатся
вполне разумные рекомендации по увеличению безопасности. Откроем на редактирование конфигурационный файл `
/etc/ssh/sshd_config`:
```shell
sudo nano /etc/ssh/sshd_config
```
И изменим порт на котором будет отвечать SSH (например на 2002):
```ini
Port 2002
```
Так же дописываем в конце следующие две строки в которых и запрещаем ssh-вход
пользователя **root** и разрешаем доступ нашему пользователю **your_username** :
```
DenyUsers root
AllowUsers your_username
```
Сохраняем конфигурационный файл `Ctrl+O` и `Enter` , а выходим из редактора `Ctrl+X` .
Чтобы настройки подействовали нужно перезапустить ssh-сервис:
```shell
sudo service ssh restart
```
Проверим, что сервис корректно перезапустился:
```shell
sudo service sshd status
```
Увидим, что все работает:
```text
● ssh.service - OpenBSD Secure Shell server
Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2022-05-25 01:01:24 MSK; 2 days ago
Docs: man:sshd(8)
man:sshd_config(5)
Main PID: 647 (sshd)
Tasks: 1 (limit: 1066)
Memory: 5.8M
CGroup: /system.slice/ssh.service
└─647 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
```
Разлогируемся — `logout` . Входим ещё раз и проверяем, что теперь пользователя root больше в систему по SSH не пускают.
Логируется вновь созданным пользователем `your_username` .
### Упрощение идентификации сервера при входе
В конфигурационном файле `/etc/ssh/sshd_config` есть специальный параметр (`Banner`), указывающий на файл который будет
отображён при логировании (по умолчанию `/etc/ssh_banner` ). Е г о отображение упрощает идентификацию сервера при входе:
вам будет сложнее спутать этот сервер с другими, особенно когда работа идёт одновременно с терминалами нескольких
серверов. Н о этот баннер отображается не только при входе через SSH, но и по SFTP. Существует более радикальное
решение, отображающееся при входе по только по KVM и SSH. И оно упростит идентификацию даже если мы работаем
с KVM-консоли. Это файл `/etc/motd` (Message Of The Day).
Очистим `/etc/motd` , и поместим туда какой-нибудь красивый ASCII-арт текст из ASCII-генератора. :
```shell
sudo nano /etc/motd
```
Вот несколько ссылок на онлайн-генераторы текстового ASCII-арта:
* [Текст-арт 1 ](https://fsymbols.com/text-art/ ).
* [Текст-арт 2 ](https://patorjk.com/software/taag/ ).
* [Коллекция ASCII-арт 1 ](https://www.asciiart.website/ ).
* [Коллекция ASCII-арт 2 ](https://www.textartcopy.com/ascii-art.html ).
Например, вот такой баннер:
```
██████╗░░░░░░██╗░█████╗░███╗░░██╗░██████╗░░█████╗
██╔══██╗░░░░░██║██╔══██╗████╗░██║██╔════╝░██╔══██╗
██║░░██║░░░░░██║███████║██╔██╗██║██║░░██╗░██║░░██║
██║░░██║██╗░░██║██╔══██║██║╚████║██║░░╚██╗██║░░██║
██████╔╝╚█████╔╝██║░░██║██║░╚███║╚██████╔╝╚█████╔╝
╚═════╝░░╚════╝░╚═╝░░╚═╝╚═╝░░╚══╝░╚═════╝░░╚════╝ Project
```
Сохраняем баннер `Ctrl+O` и `Enter` , а выходим из редактора `Ctrl+X` .
### Раскрашиваем оболочку bash
Чтобы сделать красочно-разноцветным нашу командную строку (это тоже помогает идентифицировать сервер... например
красный цвет — это сервер продакшена, а зелёный — тестового), откроем на редактирование файл настроек оболочки bash
пользователя `.bashrc` :
```shell
nano ~/.bashrc
```
Находим там строку `#force_color_prompt=yes` раскомментируем её (и удаляем в ней #). И чтобы совсем отпад, находим блок:
2024-12-08 17:50:17 +03:00
```bash
2024-12-08 17:48:10 +03:00
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
```
И меняем на блок
2024-12-08 17:50:17 +03:00
```bash
2024-12-08 17:48:10 +03:00
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u\[\033[01;33m\]@\[\033[01;32m\]\h\[\033[00m\]:\[\033[00;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
```
Всё! Перелогируемся чтобы настройки подействовали.
```shell
logout
```
## 3. Настраиваем службу времени (необязательно)
Устанавливаем службу точного времени `ntp` :
2024-12-08 17:50:56 +03:00
```bash
2024-12-08 17:48:10 +03:00
apt-get install ntp
```
Откроем на редактирование конфигурационный файл `/etc/ntp.conf` :
```shell
sudo nano /etc/ntp.conf
```
Заменяем буржуйские адреса серверов точного времени на отечественные:
```editorconfig
# Specify one or more NTP servers.
# Use servers from the NTP Pool Project. Approved by Ubuntu Technical Board
# on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for
# more information.
pool ntp.msk-ix.ru iburst prefer
pool 194.190.168.1 iburst
pool 2001:6d0:ffd4::1
# pool ntp.ix.ru
# pool 1.ubuntu.pool.ntp.org iburst
# pool 2.ubuntu.pool.ntp.org iburst
# pool 3.ubuntu.pool.ntp.org iburst
# Use Ubuntu's ntp server as a fallback.
pool ntp.msk-ix.ru
```
Сохраняем баннер `Ctrl+O` и `Enter` , а выходим из редактора `Ctrl+X` .
Чтобы настройки подействовали сервис точного времени нужно перезапустить:
```shell
sudo service ntp restart
```
Проверим, что сервис корректно перезапустился:
```shell
sudo service ntp status
```
Увидим, что все ок:
```text
● ntp.service - Network Time Service
Loaded: loaded (/lib/systemd/system/ntp.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2022-05-25 01:01:24 MSK; 2 days ago
Docs: man:ntpd(8)
Main PID: 642 (ntpd)
Tasks: 2 (limit: 1066)
Memory: 1.5M
CGroup: /system.slice/ntp.service
└─642 /usr/sbin/ntpd -p /var/run/ntpd.pid -g -u 112:119
```
Проверить какой сервер точного времени ближе, задержки между серверами и т.п.:
```shell
ntpq -p
```
Увидим что-то типа:
```text
remote refid st t when poll reach delay offset jitter
==============================================================================
ntp.msk-ix.ru .POOL. 16 p - 64 0 0.000 0.000 0.000
194.190.168.1 .POOL. 16 p - 64 0 0.000 0.000 0.000
2001:6d0:ffd4:: .POOL. 16 p - 64 0 0.000 0.000 0.000
ntp.ix.ru .POOL. 16 p - 64 0 0.000 0.000 0.000
*ntp.ix.ru .GPS. 1 u 450 1024 377 2.442 -0.254 0.117
```
## 4. Настраиваем брандмауэр для использования только с допустимыми внешними интерфейсами (защита портов)
Устанавливаем iptables для управления IP-соединения и iptables-persistent для сохранения конфигураций настроенных
соединений и их автоматического подключения после перегрузке компьютера.
```shell
sudo -S apt-get install iptables
```
Далее создадим в домашней папке bash-скрипт `prepare-iptable.sh` :
```shell
nano ~/prepare-iptable.sh
```
...и вставим в него нижеследующий текст (исправьте если необходимо, следуя комментариям в скрипте):
```shell
#!/bin/bash
# You have to set the rules of your firewall on your server only with the
# services used outside the VM.
# Вы должны установить правила брандмауэра на своем сервере только
# с сервисами, используемыми вне виртуальной машины.
echo "Сетевые настройки: разрешения трафика и портов";
echo "";
echo "ЭТОТ СКРИПТ НУЖНО ЗАПУСКАТЬ С ПРАВАМИ АДМИНИСТРАТОРА (ИЗ ПОД SUDO).";
echo "ОБАЗАТЕЛЬНО ИЗ KVM-КОНСОЛИ! ИЗ ПОД SSH МОЖЕТ (скорре всего нет) СЛОМАТЬ ВАШУ ВИРТАЛКУ!";
echo "===================================================================";
echo "";
read -p "Хотите чтобы скрипт сделал это (возможно, и сломает)? (Y/N):" -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
exit 1
fi
# Установим пакет iptables (если он уже установлен, попытка повторной установки "проскочит")
sudo -S apt-get install iptables
# ПРИСВАИВАЕМ ПЕРМЕННЫЕ:
# $IPT = "/usr/sbin/iptables"
# $WAN = текущий внешний сетевой интерфейс виртуалки
# $WAN_IP = текущий IP V4 назначенный на внешний сетевой интерфейс виртуалки
# $WAN_IP6 = текущий IP V6 назначенный на внешний сетевой интерфейс виртуалки
export IPT="$(sudo -S which iptables)"
export WAN="$(ip add | grep 'BROADCAST' | awk '{print $2}' | cut -d ':' -f 1)"
export WAN_IP="$(ip ad | grep 'inet ' | awk '(NR == 2)' | awk '{print $2}' | cut -d '/' -f 1)"
export WAN_IP6="$(ip ad | grep 'inet6 ' | awk '(NR == 2)' | awk '{print $2}' | cut -d '/' -f 1)"
echo "-------------------ТЕКУЩИЕ ПРАВИЛА--------------------";
$IPT -L -v -n
# СБРАСЫВАЕМ старые правила:
$IPT -F
$IPT -X
# БАЗОВЫЕ правила (политика по умолчанию) — все обрубаем!
$IPT -P INPUT DROP
$IPT -P OUTPUT DROP
$IPT -P FORWARD DROP
# Разрешаем локальный траффик для loopback (для localhost)
$IPT -A INPUT -i lo -j ACCEPT
$IPT -A OUTPUT -o lo -j ACCEPT
# Разрешаем пинги
# $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
# Состояние ESTABLISHED говорит о том, что это не первый пакет в соединении.
# Пропускать все уже инициированные соединения, а также дочерние от них.
# Так мы разрешим использование текущего порта на который настроен и
# открыт, в настощий момент, SSH а еще доступ к репозиториям для получения
# новых пакетов и их обновлений.
$IPT -A INPUT -p all -m state --state ESTABLISHED,RELATED -j ACCEPT
# Разрешить новые, а так же уже инициированные и их дочерние соединения
$IPT -A OUTPUT -p all -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
# Разрешить форвардинг для уже инициированных и их дочерних соединений
$IPT -A FORWARD -p all -m state --state ESTABLISHED,RELATED -j ACCEPT
# Включаем фрагментацию пакетов. Необходимо из-за разных значений MTU
$IPT -I FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
# Отбрасывать все пакеты, которые не могут быть идентифицированы
# и поэтому не могут иметь определенного статуса.
$IPT -A INPUT -m state --state INVALID -j DROP
$IPT -A FORWARD -m state --state INVALID -j DROP
# Приводит к связыванию системных ресурсов, так что реальный
# обмен данными становится не возможным, обрубаем
$IPT -A INPUT -p tcp ! --syn -m state --state NEW -j DROP
$IPT -A OUTPUT -p tcp ! --syn -m state --state NEW -j DROP
# Открываем только нужные порты на вход:
# !!! указать свой порт, который вы указали для SSH ранее !!!)
$IPT -A INPUT -i $WAN -p tcp --dport 2002 -j ACCEPT
# порт для your_username сервера (для http)
$IPT -A INPUT -i $WAN -p tcp --dport 80 -j ACCEPT
# порт для your_username сервера (для https)
$IPT -A INPUT -i $WAN -p tcp --dport 443 -j ACCEPT
## Логирование
## В с е что не разрешено, но ломится отправим в цепочку undef
# $IPT -N undef_in
# $IPT -N undef_out
# $IPT -N undef_fw
# $IPT -A INPUT -j undef_in
# $IPT -A OUTPUT -j undef_out
# $IPT -A FORWARD -j undef_fw
# Логируем все из undef
# $IPT -A undef_in -j LOG --log-level info --log-prefix "-- IN — DROP "
# $IPT -A undef_in -j DROP
# $IPT -A undef_out -j LOG --log-level info --log-prefix "-- OUT — DROP "
# $IPT -A undef_out -j DROP
# $IPT -A undef_fw -j LOG --log-level info --log-prefix "-- FW — DROP "
# $IPT -A undef_fw -j DROP
# Записываем правила
$IPT-save > $IPT.rules
# Выводим правила на экран:
echo "----------ПРАВИЛА ДЛЯ TCP-ПОРТОВ УСТАНОВЛЕНЫ----------";
cat $IPT.rules
echo "---------------------И ПРИМЕНЕНЫ----------------------";
$IPT -L -v -n
echo "------------------------------------------------------";
echo "";
echo "Правила сохранены в файле: $IPT.rules";
echo "";
echo "Для восстановления правил запустите скрипт повторно или";
echo "исполните команду:";
echo "";
echo "sudo $IPT-restore < $IPT.rules";
echo "";
echo "";
echo " _._ _,-'\"\"\`-._ ";
echo "(,-.\`._,'( |\\\`-/|";
echo " \`-.-' \\ )-\`( , o o)";
echo " \`- \\\`_\`\"'- Mi-mi-mi... Ok!";
echo "";
```
Сохраняем скрипт `Ctrl+O` и `Enter` , а выходим из редактора `Ctrl+X` .
Далее нужно зайти на виртуалку через KVM-консоль и выполнить:
```shell
sudo bash ~/prepare-iptable.sh
```
После выполнения скрипта все внешние соединения с виртуалкой будут разорваны. После перелогирования через ssh можно
будет увидеть, что новые настройки сетевой фильтрации вступили в силу:
```shell
sudo iptables -L -v -n
```
Увидим примерно следующее:
```text
Chain INPUT (policy DROP 150 packets, 5768 bytes)
pkts bytes target prot opt in out source destination
28 2204 ACCEPT all — lo * 0.0.0.0/0 0.0.0.0/0
81 7202 ACCEPT all — * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
2 84 DROP all — * * 0.0.0.0/0 0.0.0.0/0 state INVALID
8 608 DROP tcp — * * 0.0.0.0/0 0.0.0.0/0 tcp flags:!0x17/0x02 state NEW
1 52 ACCEPT tcp — eth0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:2002
0 0 ACCEPT tcp — eth0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
0 0 ACCEPT tcp — eth0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 TCPMSS tcp — * * 0.0.0.0/0 0.0.0.0/0 tcp flags:0x06/0x02 TCPMSS clamp to PMTU
0 0 ACCEPT all — * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
0 0 DROP all — * * 0.0.0.0/0 0.0.0.0/0 state INVALID
Chain OUTPUT (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
28 2204 ACCEPT all — * lo 0.0.0.0/0 0.0.0.0/0
69 10113 ACCEPT all — * eth0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all — * * 0.0.0.0/0 0.0.0.0/0 state NEW,RELATED,ESTABLISHED
0 0 DROP tcp — * * 0.0.0.0/0 0.0.0.0/0 tcp flags:!0x17/0x02 state NEW
```
Чтобы сохранить настройки и чтобы они автоматически вступали в силу в
случае перезагрузки установим пакет `iptables-persistent` :
```shell
sudo -S apt-get install iptables-persistent
```
Будет запрошено сохранить ли и восстанавливать настройки в случае
перезагрузки для IP-V4 и IP-V6 — о б а раза отвечаем `Y` .
В будущем, если будут меняться правила фильтрации, то сохранить новые настройки можно командой `sudo
netfilter-persistent save`, а восстановить командой `sudo netfilter-persistent start` . Для детальных
разъяснений [см. по ссылке ](https://losst.ru/kak-sohranit-pravila-iptables ).
## 5. Установим защиту DoS (брутфорса) по ssh
DoS и DDoS-атака — это агрессивное внешнее воздействие на сервер, проводимое с целью перегрузить е г о запросами,
доведения е г о до отказа или брутфорс-взлома (brute force — грубая сила, например, подбор паролей перебором и т.п.).
Если атака проводится с одиночного компьютера — е е называют DoS (Denial of Service), если из нескольких,
распределенных в сети компьютеров — DDoS (Distributed Denial of Service).
Защиту от DoS (включая медленные атаки и нежелатеольное сканирование web-сервера) обеспечивает пакет `fail2ban` .
Установим е г о (уже должен быть установлен):
````shell
sudo apt-get install fail2ban
````
Для е г о настройки изменим конфигурационный файл `/etc/fail2ban/jail.local` :
```shell
sudo nano /etc/fail2ban/jail.local
```
И поместим туда следующее (поменяйте `xxx.xxx.xxx.xxx` на свои IP-адрес, с которых будете подключаться):
```editorconfig
# == новый конфиг /etc/fail2ban/jail.local =====================
[DEFAULT]
#email, на который присылать уведомления
destemail = root
# подключить прафила бана из '/etc/fail2ban/action.d/iptables-multiport.conf'
banaction = iptables-multiport
# исключаем из потенциального бана ip машины с которых будем сами подключаться
ignoreip = xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx
#### правила для SSH ####
[sshd]
enabled = true
port = ssh,2002
# filter — подключить правила фильтрации из '/etc/fail2ban/filter.d/sshd.conf'
filter = sshd
# logpath — какой лог наблюдаем (на тот случай, если он не по умолчанию)
logpath = /var/log/auth.log
# bantime — время (секунды) на которое баним. Например, на сутки — 60*60*24=86400
bantime = 86400
# maxretry — число попыток (1) и получаешь бан!
maxretry = 1
# findtime — определяет длительность интервала в секундах, за которое
# событие должно повториться определённое количество раз, после чего санкции
# вступят в силу. Если специально не определить этот параметр, то будет
# установлено значение по умолчанию равное 600 (10 минут). Проблема в том,
# что ботнеты, участвующие в «медленном брутфорсе», умеют обманывать
# стандартное значение. Иначе говоря, при maxretry равным 6,
# атакующий может проверить 5 паролей, затем выждать 10 минут,
# проверить ещё 5 паролей, повторять это снова и снова, и е г о IP забанен
# не будет. В целом, это не угроза, но всё же лучше жестно банить таких ботов
findtime = 7200
# СТАРЫЙ КОНФИГ ПО УМОЛЧАНИЮ
# [DEFAULT]
# bantime = 600
# findtime = 60
# maxretry = 6
# banaction = iptables-multiport
# [sshd]
# enabled = true
```
Сохраняем конфигурационный файл `Ctrl+O` и `Enter` , а выходим из редактора `Ctrl+X` .
Перезапустим fail2ban:
```shell
sudo service fail2ban restart
```
Проверим статус:
```shell
sudo service fail2ban status
```
Увидим, что все работает:
```text
● fail2ban.service - Fail2Ban Service
Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2022-05-17 14:33:22 MSK; 1h 53min ago
Docs: man:fail2ban(1)
Process: 7635 ExecStartPre=/bin/mkdir -p /run/fail2ban (code=exited, status=0/SUCCESS)
Main PID: 7640 (f2b/server)
Tasks: 5 (limit: 1066)
Memory: 11.0M
CGroup: /system.slice/fail2ban.service
└─7640 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
May 17 14:33:22 vm2203242538 systemd[1]: Starting Fail2Ban Service...
May 17 14:33:22 vm2203242538 systemd[1]: Started Fail2Ban Service.
May 17 14:33:22 vm2203242538 fail2ban-server[7640]: Server ready
```
## 5. Установка и настройка СУБД (MariaDB), создание пользователей
Этот пункт можно пропустить, если в вашем проекте используется `sqlite3` . Для `PostgreSQL` (или других SQL-серверов
нужно будет провести аналогичные действия для соответствующего SQL-сервера).
Следуем [инструкциям ](https://www.digitalocean.com/community/tutorials/how-to-install-mariadb-on-ubuntu-20-04 ),
и у нас получится примерно такой порядок действий:
### Установка
Устанавливаем сервер MariaDB (полный аналог MySQL) и пакет MariaDB-клиента для разработчиков (чтобы после корректно
установился MySQL-коннектор для Python):
```shell
sudo apt install mariadb-server libmysqlclient-dev
```
Отредактируем конфигурационный файл, чтобы сменить кодовые таблицы в будущих базах с `utf8mb4` (с о всякими глупыми
эможи) на православную `utf8` (на самом деле, в последних версиях это уже не срабатывает):
```shell
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
```
Найдём в конфигурационном файле строки:
```toml
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci
```
и заменим на:
```toml
character-set-server = utf8
collation-server = utf8_general_ci
```
Сохраняем конфигурационный файл `Ctrl+O` и `Enter` , а выходим из редактора `Ctrl+X` .
Перезапустим систему управления базы данных:
```shell
sudo service mysql restart
```
Проверим статус:
```shell
sudo service mysql status
```
Если все сделали правильно, должны увидеть что-то типа:
```text
● mariadb.service - MariaDB 10.3.32 database server
Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2022-01-20 14:58:12 MSK; 5s ago
Docs: man:mysqld(8)
https://mariadb.com/kb/en/library/systemd/
Process: 7057 ExecStartPre=/usr/bin/install -m 755 -o mysql -g root -d /var/run/mysqld (code=exited, status=0/SUCCESS)
Process: 7068 ExecStartPre=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS)
Process: 7070 ExecStartPre=/bin/sh -c [ ! -e /usr/bin/galera_recovery ] & & VAR= || VAR=`cd /usr/bin/..; /usr/bin/galera_recovery`; [ $? >
Process: 7149 ExecStartPost=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS)
Process: 7151 ExecStartPost=/etc/mysql/debian-start (code=exited, status=0/SUCCESS)
Main PID: 7118 (mysqld)
Status: "Taking your SQL requests now..."
Tasks: 31 (limit: 1037)
Memory: 83.0M
CGroup: /system.slice/mariadb.service
└─7118 /usr/sbin/mysqld
```
### Создание пользователей
Для этого запустим mysql с супер-правами `root` :
```shell
sudo mysql
```
Создаём пользователя `your_username` , для полного доступа ко всему (аналог супер-пользователя) и зададим ему
пароль и дадим полные привилегии:
```mysql
CREATE USER 'your_username'@'localhost' IDENTIFIED BY 'очень-секретный-пароль';
GRANT ALL PRIVILEGES ON *.* TO 'your_username'@'localhost';
```
Создаем базу данных нашего проекта `django_prj` для нашего сайта:
```mysql
CREATE DATABASE django_prj DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;
```
И создадим пользователя `project_username` , пароль для него и дадим ему права работать только с базой сайта:
```mysql
CREATE USER 'project_username'@'localhost' IDENTIFIED BY 'другой-очень-секретный-пароль';
GRANT ALL PRIVILEGES ON django_prj.* TO 'project_username'@'localhost';
```
Проверим, что установлена правильная часовая зона времени:
```mysql
SHOW VARIABLES LIKE '%time_zone%';
```
Типа, она должна быть `MSK` :
```
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | MSK |
| time_zone | SYSTEM |
+------------------+--------+
2 rows in set (0.001 sec)
```
Выходим из MariaDB:
```mysql
quit;
```
переносим базу
## 6. ПОДГОТОВКА WEB-С Е Р В Е Р А NGINX
Устанавливаем nginx
```shell
sudo apt-get install nginx
```
Н а всякий случай (как выяснилось позже), лучше поставить nginx plus. В нем есть динамические модули. И один их
таких динамических модулей — **geoip** — может понадобиться для ограничения посещения сайта только пользователями
России. [Смотрим инструкцию по установке nginx plus ](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/ ).
Убедиться, что он запущен и работает корректно можно командой:
```shell
sudo service nginx status
```
Результат должен быть примерно таким:
```text
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2022-01-20 18:45:16 MSK; 56s ago
Docs: man:nginx(8)
Main PID: 2200 (nginx)
Tasks: 2 (limit: 1037)
Memory: 4.6M
CGroup: /system.slice/nginx.service
├─2200 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
└─2201 nginx: worker process
```
Зайдем по ip нашего сервера c внешнего браузера, или `curl` через localhost:
```shell
curl -I http://localhost/
```
Получим ответ:
```
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 03 May 2022 18:16:46 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 03 May 2022 12:49:44 GMT
Connection: keep-alive
Vary: Accept-Encoding
ETag: "627124e8-264"
Accept-Ranges: bytes
```
### Настройка GZIP
По умолчанию модуль GZIP установлен в NGINX. Он осуществляет сжатие данных
«на лету», они отправляются nginx, и, после получения, распаковываются
браузерами (само-собой теми, которые поддерживают такую возможность). При
этом между веб-сервером и браузером передается меньшее данных, что хорошо
для медленных подключения (но не для сервера, так как он тратит ресурсы на
сжатие). Сжатие использует ресурсы сервера, поэтому лучше всего сжимать только
те файлы, которые хорошо «сжимаются» (а еще лучше, если ещё и могут кэшироваться на
стороне браузера). Текстовые файлы (html, javascript, css и т.п.) сжимаются хорошо,
JPEG или PNG уже сжаты по своей природе и никакого результата при сжатии GZIP можно не
ожидать.
Настроем модуль GZIP в NGINX. Для этого открываем на редактирование
конфигурационный файл nginx `/etc/nginx/nginx.conf` :
```shell
sudo nano /etc/nginx/nginx.conf
```
Находим в нем кусочек касающийся GZIP:
```
##
# Gzip Settings
##
```
и вставляем туда следующее (там будет закомментированный блок, можно использовать е г о ):
```nginx configuration
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_disable "msie6";
gzip_vary on;
gzip_min_length 512;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;
```
Если интересно, расшифруем что тут происходит:
* `gzip_proxied any` — сжимать данные ответов для proxy-серверов.
* `gzip_comp_level 6` — устанавливаем, степень сжатия файлов. Чем выше
число, тем выше уровень сжатия и использование ресурсов. уровень сжатия, 1
- минимальное, 9 - максимальное.
* `gzip_buffers 16 8k` — задаёт число и размер буферов, в которые будет
сжиматься ответ. По умолчанию размер одного буфера равен размеру страницы.
В зависимости от платформы это или 4K, или 8K.
* `gzip_http_version 1.1` — директива используется для ограничения сжатия
gzip для браузеров, поддерживающих протокол HTTP/1.1. Если браузер не
поддерживает е г о , вероятно, что он не поддерживает и gzip.
* `gzip_disable "msie6"` — исключаем IE6 из браузеров, которые будут
получать сжатые файлы (этот древний браузер не поддерживает GZIP).
* `gzip_vary on` — включает добавление в ответ заголовка __ "Vary:
Accept-Encoding"__ (для IE4-6 это приведёт к не кешированию данных из-за
б а г а ).
* `gzip_min_length 512` — сообщаем NGINX не сжимать файлы размером менее
512 байт.
* `gzip_types` — отображает все типы MIME, которые будут сжаты. В этом
случае список включает страницы HTML, таблицы стилей CSS, файлы Javascript
и JSON, файлы XML, иконки (BMP-изображения очень даже сжимаются),
изображения SVG и веб-шрифты.
Сохраняем конфигурационный файл `Ctrl+O` и `Enter` , а выходим из редактора
`Ctrl+X` и перезагружаем nginx:
```shell
sudo service nginx restart
```
#### Проверка новой конфигурации
Выполним тестовый запрос:
```shell
curl -H "Accept-Encoding: gzip" -I http://localhost/
```
Мы получим, например, вот такой ответ:
```
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 03 May 2022 18:16:53 GMT
Content-Type: text/html
Last-Modified: Tue, 03 May 2022 12:49:44 GMT
Connection: keep-alive
Vary: Accept-Encoding
ETag: W/"627124e8-264"
Content-Encoding: gzip
```
Как видим, сжатие включилось.
## 7. Развёртывание окружения проекта
### Настройка виртуального окружения проекта
Установим python с набором для разработчиков (для сборки коннектора к СУБД MariaDB), пакетный менеджер и утилиту для
создания виртуального окружения python:
```shell
sudo apt-get install python3-pip python3-virtualenv python3-dev
```
Проверим, что установлена нужная нам версия Python:
```
python3 -V
```
Теперь создадим папку для нашего Django-проекта (например `~/django_project` ) и развернём в ней
виртуальное окружение, указав, что в нем нужно использовать необходимую для проекта версию Python
(например 3.8 из `/usr/bin/python3.8` ). Будет создан каталог, с файлами виртуального
окружение (версия Python, установщик пакетов pip, wheel, setuptools а , в
будущем, и все пакеты, батарейки, свистелки и хрюкалки нашего проекта).
```shell
cd $HOME
mkdir -p django_project
```
Чтобы для нашего проекта "заморозить" версию Python и все необходимые пакеты, и избежать возможного
конфликта при обновлении системного Python или с пакетами установленными в других проектах создадим
виртуальное окружение в папке нашего сайта (`$HOME/django_project`):
```shell
virtualenv -p /usr/bin/python3.8 $HOME/django_project/env
```
Активируем созданное виртуальное окружение:
```shell
source $HOME/django_project/env/bin/activate
```
Проверить, что теперь мы работаем в виртуальном окружении можно дав команды:
```shell
python -V
pip -V
```
Увидим
> pip 20.0.2 from /home/your_username/django_project/env/lib/python3.8/site-packages/pip (python 3.8)
Можно обновить менеджер пакетов `pip` по последней версии (сперва получим е г о с помощью `curl` в папку tmp,
а после запустим установщик через Python)
```shell
curl https://bootstrap.pypa.io/get-pip.py > ~/tmp/get-pip.py
python3 ~/tmp/get-pip.py
```
Попробуем снова
```shell
pip -V
```
Увидим
> pip 22.0.4 from /home/your_username/django_project/env/lib/python3.8/site-packages/pip (python 3.8)
ФУУ!!
## 8. Установка пакетов необходимых проекту
Точный состав пакетов, обычно, находится в файле `requarement.txt` и чтобы установить все пакеты из этого файла
перейти в папку проекта (в место расположения `requarement.txt` ) и выполните команду:
```shell
pip install -r requirements.txt
```
Также можно установить пакеты по одному. Как приме,р приведу список пакетов
одного своего древнего проекта:
| Пакет | Версия | Назначение | Зависимости |
|-----------------------------------|--------|--------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| django | 3.2.11 | Фреймворк Django | притащит с собой пакеты: __asgiref-3.4.1__ , __pytz-2021.3__ и __sqlparse-0.4.2__ |
| mysqlclient | 2.1.0 | Коннектор MySQL | нет |
| django-filer | 2.0.2 | Система управления медиа-файлами с фишками подготовки ресайз-картинок, превьюшек и прочими плюшками | притащит с собой пакеты: __Unidecode-1.1.2__ , __django-js-asset-1.2.2__ , __django-mptt-0.12.0__ , __django-polymorphic-3.0.0__ , __easy-thumbnails-2.7.1__ и __pillow-8.2.0__ |
| django-ckeditor | 6.2.0 | Wysiwyg-редактор (ckeditor) для админки | нет |
| django-ckeditor-filebrowser-filer | 0.3.0 | Плугин для дружбы Wysiwyg-редквтора (ckeditor) и django-filer | нет, все зависимости уже притащил django-filer |
| pytils-safe | 0.3.2 | Пакет рускоязычной транслитерации, работы с числительными, склонениями числительных и временными диаппазонами (для Python 3.x) | нет |
| urllib3 | 1.26.8 | пакет для работы с web-запросами (проекту этот пакет нужен для работы с API внешний HTML-типографов) | нет |
В с е эти пакеты устанавливаются в виртуальное окружение с помощью пакетного
менеджера `pip` в последовательности:
```shell
pip install django=3.2.11
pip install mysqlclient=2.1.0
pip install django-filer=2.0.2
pip install django-ckeditor=6.2.0
pip install django-ckeditor-filebrowser-filer=0.3.0
pip install pytils-safe=0.3.2
pip install urllib3=1.26.8
```
### Разворачиваем и тестируем проект
Создаём папки для хранения сокета и логов:
```shell
mkdir -p $HOME/django_project/logs
mkdir -p $HOME/django_project/socket
```
Переносим проект... например архивируем проект. __С а мо -собой это надо делать там, где проект находится
в разработке и тесте, не на вир ту а лке __. Например,
так:
```shell
zip -9 -r django_project.zip public/ django_project/ config/
```
Копируем архив по ssh на нашу виртуалку по адресу `XXX.XXX.XXX.XXX` :
```shell
scp -P 2002 django_project.zip your_username@XXX.XXX.XXX.XXX:~/django_project
```
Возвращаемся на нашу виртуалку хостера и разархивируем:
```shell
cd ~/django_project
unzip django_project.zip
```
__В нима ние __, на разных серверах расположение папок проекта может отличаться. При архивации илм после распаковки,
возможно, нам потребуется переместить некоторые папки. Мы должны получить следующую структуру проекта:
```text
+---public
| +---media
| L---static
+---django_project
| +---django_project
| +---templates
| L---web
+---logs
+---config
+---env
L---socket
```
Папки __logs__ , __config__ , __env__ и __socket__ на данном этапе могут отсутствовать. Мы создадим их позже:
Теперь можно произвести перенос статических файлов админки и батареек в папку для web-статики:
```shell
source ~/django_project/env/bin/activate
cd ~/django_project/django_project
python manage.py collectstatic
```
Теперь произведем подготовку нашей базы под проект. Для этого произведем миграции:
```shell
python manage.py makemigrations
python manage.py migrate
```
Миграция создаст все необходимые таблицы в базе данных и сделает файлы миграций `django_prj.django_migration` .
Теперь можно произвести перенос базы с dev-проекта (или с другого рабочего сервера) в базу нашей виртуалки.
Следует учесть, что переносить таблицу `django_prj.django_migration` не следует (мы произвели чистую миграцию, и
если мы перезапишем эту таблицу, то с высокой вероятностью сломаем наш проект).
Настало время проверить, что наше web-приложение Django работает.
Временно откроем порт _8080_ через `iptables` :
```shell
sudo iptables -A INPUT -i eth0 -p tcp --dport 8080 -j ACCEPT
```
Запустим приложение с dev-режиме на нашем IP и этом порту:
```shell
python manage.py runserver 109.70.24.15:8080
```
Если мы обратимся из браузера по адресу __http://109.70.24.15:8080__ , то увидим как на нашей виртуалке "бежит лог".
В браузере будет отображаться что-то похожее на наш проект (с тем отличием, что не будет работать статика... т.е .
мы не увидим картинок, не будут подгружены стили, JavaScript и прочая статика).
Завершаем работу web-сервера разработчика, нажав `Control+C` .
## 8. Конфигурируем nginx под наш проект
-----------
Создаем и правим конфиг `~/django_project/config/django_project.conf` :
```shell
nano ~/django_project/config/django_project.conf
```
Помещаем в него следующее (измените `your_username` на своё имя пользователя и прочее на свои данные):
```nginx configuration
# САЙТ DJANGO_PROJECT
# == Конфикурационный файл nginx django_project.conf
# Описываем апстрим-потоки которые должен подключить Nginx
# Для каждого сайта надо настроить свйо поток, с о своим уникальным именем.
# Если будете настраивать несколько python (django) сайтов - измените название upstream
upstream django_project-uwsgi {
# расположение файла Unix-сокет для взаимодействие с uwsgi
server unix:///home/your_username/django_project/socket/django_project.sock;
# также можно использовать веб-сокет (порт) для взаимодействие с uwsgi. Н о это медленнее
# server 127.0.0.1:8001; # для взаимодействия с uwsgi через веб-порт
}
# конфигурируем сервер
server {
listen 80;
server_name YOUR_SERVER_NAME; # имя сервера
charset utf-8; # кодировка по умолчанию
access_log /home/your_username/django_project/logs/django_project-access.log; # логи с доступом
error_log /home/your_username/django_project/logs/django_project-error.log; # логи с ошибками
client_max_body_size 100M; # максимальный объем файла для загрузки на сайт (max upload size)
error_page 404 /404.html;
error_page 500 /500.html;
location /media { alias /home/your_username/django_project/public/media; } # Расположение media-файлов Django
location /static { alias /home/your_username/django_project/public/static; } # Расположение static-файлов Django
location /favicon.ico { root /home/your_username/django_project/public; } # Расположение favicon.ico
location /favicon.gif { root /home/your_username/django_project/public; } # Расположение favicon
location /favicon.png { root /home/your_username/django_project/public; } # Расположение favicon
location /favicon.svg { root /home/your_username/django_project/public; } # Расположение favicon
# location /robots.txt { root /home/your_username/django_project/public; } # Расположение robots.txt
# location /author.txt { root /home/your_username/django_project/public; } # Расположение author.txt
# Определяем расположение страниц ошибок 404 и 500
location = /404.html {
root /home/your_username/django_project/django_project/templates/404.html;
internal;
}
location = /500.html {
root /home/your_username/django_project/django_project/templates/500.html;
internal;
}
# Определяем расположение файлов robots.txt, author.txt и прочих статичных файлов (xml, html, htm и txt)
location ~ \.(xml|html|htm|txt)$ {
root /home/your_username/django_project/public; # Расположение статичных *.xml, * .html и *.txt
}
location / {
uwsgi_pass django_project-uwsgi; # upstream обрабатывающий обращений
include uwsgi_params; # конфигурационный файл uwsgi;
uwsgi_read_timeout 1800; # некоторые запросы на стабеньких машинках очень долго обрабатываются.
uwsgi_send_timeout 200; # на всякий случай время записи в сокет
}
}
```
Делаем симлинк (символическую ссылку) этого конфигурационного файла в папку конфигов сайтов nginx:
```shell
sudo ln -s ~/django_project/config/django_project.conf /etc/nginx/sites-enabled/
```
Протестируем конфигурацию:
```shell
sudo nginx -t
```
Если все ок, можем "мягко" перезапустить nginx:
```shell
sudo nginx -s reload
```
Или "по-жёсткому":
```shell
sudo service nginx restart
```
Теперь если мы из браузера обратимся по адресу какого-нибудь статического файла (_http://YOUR_SERVER_NAME/robots.txt_, _http://YOUR_SERVER_NAME/favicon.ico_,
_http://YOUR_SERVER_NAME/favicon.png_ или _http://YOUR_SERVER_NAME/favicon.svg_) то увидим соответсвующий контент.
## 9. НАСТРОЙКА uWSGI
uWSGI — это "прокладка" между веб-сервером и приложением Django. Он принимает через сокет http-запросы от
веб-сервера nginx, обрабатывает их и передает приложению Django. Приложение Django обрабатывает запрос и
отправляет ответ обратно через uWSGI в nginx, который в свою очередь отправляет е г о клиенту.
И uWSGI, и Python-плагин uWSGI уже должен быть установлен. Н о если он не установлены, или для проверки, инсталляция
производится из так:
```shell
sudo apt-get install uwsgi uwsgi-plugins-all
```
Проверим, что uWSGI работает:
```shell
sudo service uwsgi status
```
Увидим примерно следующее:
```text
● uwsgi.service - LSB: Start/stop uWSGI server instance(s)
Loaded: loaded (/etc/init.d/uwsgi; generated)
Active: active (exited) since Thu 2022-01-20 20:36:20 MSK; 2min 57s ago
Docs: man:systemd-sysv-generator(8)
Tasks: 0 (limit: 1037)
Memory: 0B
CGroup: /system.slice/uwsgi.service
```
Создаём и правим ini-конфиг `~/django_project/config/django_project.ini` для uWSGI:
```shell
nano ~/django_project/config/django_project.conf
```
Помещаем туда следующее:
```editorconfig
# === Конфикурационный файл uwsgi django_project.ini
[uwsgi]
# НАСТРОЙКИ ДЛЯ DJANGO
# Корневая папка проекта (полный путь)
chdir = /home/your_username/django_project/django_project
# Django wsgi файл django_project/wsgi.py записываем так:
module = django_project.wsgi
# полный путь к виртуальному окружению
home = /home/your_username/django_project/env
# полный путь к файлу сокета
socket = /home/your_username/django_project/socket/django_project.sock
# Исходящие сообщения в лог
daemonize = /home/your_username/django_project/logs/django_project_uwsgi.log
# ЗАГАДОЧНЫЕ НАСТРОЙКИ, ПО ИДЕЕ ОНИ НУЖНЫ, Н О И БЕЗ НИХ ВСЁ РАБОТАЕТ
# расположение wsgi.py
wsgi-file = /home/your_username/django_project/django_project/django_project/wsgi.py
# расположение виртуального окружения (как оно работает если этот параметр не указан, не ясно)
virtualenv = /home/your_username/django_project/env
# имя файла при изменении которого происходит авторестарт приложения
# (когда этого параметра нет, то гичего не авторестартится, но с ним все рестартится.
# Cто ит изменить любой Python-исходник проекта, как изменения сразу вступают в силу.
touch-reload = /home/your_username/django_project/logs/touch-reload
# авторестарт приложения при изменении файлов проекта через 5-секунд
py-autoreload = 5
# НАСТРОЙКИ ОБЩИЕ
# быть master-процессом
master = true
# максимальное количество процессов (воркеров) uWSGI
processes = 1
# если uWSGI установлен как сервис через apt-get то нужно установить еще плугин:
# sudo apt-get install uwsgi-plugin-python
# и добавить в этот конфиг: `plugin = python` или `plugin = python3`
plugin = python3
# права доступа к файлу сокета. По умолчанию должно хватать 664. Н о чтоб наверняка, ставим 666
chmod-socket = 666
# очищать окружение от служебных файлов uwsgi по завершению
vacuum = true
# количество секунд после которых подвисший процес будет перезапущен
# Так как некоторе скрипты требуют изрядно времени (особенно полная переиндексация) то ставим значение побольще
harakiri = 2600
# В общем случае, при некотых значениях harakiri логах uWSGI может вываливаться предупреждение:
# WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers
# можно оставить harakiri закоментированным, но нам нужно 900 и на него не ругается. Ругается на 30.
# разрешаем многопоточность
enable-threads = true
vacuum = true
thunder-lock = true
max-requests = 500
# пользователь и группа пользователей от имени которых запускать uWSGI
# указываем www-data: к этой группе относится nginz, и ранее мы включили в эту группу нашего [user]
# uid = nginx
# gid = nginx
# uid = www-data
# gid = www-data
uid = your_username
gid = your_username
print = ---------------- Запущен uWSGI для django_project ----------------
```
**ВАЖНО**: *Если вы скопируете этот файл из системы под Microsoft Windows, то может ничего не заработать. В Windows
строчки разделяются символом `CRLF` (перевод строки и возврат каретки), а в Linux — только `LF` (перевод строки).
Конфиг uWSGI чувствителен к этому. Поэтому, если вы скопировали этот файл из Windows, то перед запуском uWSGI
е г о надо преобразовать в формат Linux. Для этого воспользуйтесь утилитой `dos2unix` . Или вставьте содержимое
файла в редакторе `nano` и пересохраните е г о .*
Делаем симлинк нашего конфигурационного файла в папку конфигов uwsgi:
```shell
sudo ln -s ~/django_project/config/django_project.ini /etc/uwsgi/apps-enabled/
```
Н е забываем перезапустить uwsgi:
```shell
sudo service uwsgi restart
```
Еще раз проверим, что uWSGI работает:
```shell
sudo service uwsgi status
```
В с е . Сайт развёрнут. Далее можно проводить улучшайзинг и усиливать защиту:
## 10. Нагрузочное тестирование (нужен dockers)
Нагрузочное тестирование будем проводить при помощи Яндекс.Танк. Он будет "обстреливать" наш проект запросами.
Для запускать Яндекс.Танк проще всего использовать Dockers. Т .о . Dockers должен быть установлен на машину, с которой
будем "обстреливать" сайт (и это не виртуалка хостинга... "обстреливать" самих себя бессмысленно).
Создаем каталог `yandex.tank` (в моем случае `M:/VM/yandex.tank` ) и в нем помещаем файл `load.yaml` следующего
содержания (не забываем корректировать `YOUR_SERVER_NAME` и список `uris` под свой проект... их и будет
"обстреливать" Яндекс.Танк):
```yaml
overload:
enabled: true
package: yandextank.plugins.DataUploader
token_file: "token.txt"
phantom:
address: YOUR_SERVER_NAME:80
header_http: "1.1"
headers:
- "[Host: YOUR_SERVER_NAME]"
- "[Connection: close]"
uris:
- /
- /url1/target1
- /url2/target2
- /url3/target3
- /url4/target4
- /url5/target5
load_profile:
load_type: rps
# schedule: line(5, 5000, 3m)
# schedule: const(1,30s) line(1,1000,2m) const(10000,5m)
schedule: line(10,5000,2m) const(100,1m)
ssl: true
autostop:
autostop:
- http(5xx,10%,5s)
console:
enabled: true
telegraf:
enabled: false
```
Так же создаем пустой файл `token.txt` в той же папке. __В Н ИМ А Н ИЕ __ файл должен быть в кодировке ANSI (хоть он и пустой,
но это важно).
Теперь можем запустить Яндекс.Танк:
```shell
docker run --rm -v M:/VM/yandex.tank:/var/loadtest -it direvius/yandex-tank
```
После запуска Яндекс.Танк будет "обстреливать" наш сайт. После окончания теста, Яндекс.Танк выдаст отчет о
нагрузке на сайт. В соответствии с этим отчетом можно спланировать какой сервер будет достаточен для работы сайта.
## 11. SSL
Хорошая [инструкция по установке SSL от Let's Encript ](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-22-04 ).
Если же у нас самоподписанный сертификат или покупной (например *AlphaSSL-Wildcard* ), то создадим папку
для хранения SSL-сертификтов:
```shell
mkdir -p ~/django_project/ssl
```
Скопируем в нее файлы сертификатов:
* `AlphaSSL-Wildcard-certificate--XXXXX.crt` — сертификат.
* `AlphaSSL-Wildcard-certificate-signing-request--XXXXX.csr` — зашифрованный запрос на выпуск сертификата
(Certificate Signing Request).
* `AlphaSSL-Wildcard-private-key.key` — приватный ключ.
Открываем на редактирование созданный нами ранее конфигурационный файл nginx:
```shell
nano ~/django_project/config/django_project.conf
```
И добавляем в него блоки редиректа:
```nginx configuration
# конфигурируем 302-редирект с YOUR_SERVER_NAME
server {
listen 80; # слушает 80 порт
listen 443 ssl; # слушает 443 порт (ssl)
server_name YOUR_SERVER_NAME; # доменное имя сайта
return 302 https://YOUR_SERVER_NAME$request_uri; # и редиректим всё
}
# конфигурируем 301-редирект с 109.70.24.15:80
server {
listen 80; # слушает 80 порт
server_name YOUR_SERVER_NAME; # доменное имя сайта
return 301 https://$host$request_uri; # и редиректим всё
}
```
А в конфигурации нашего сайта надо вносим следующие изменения:
```nginx configuration
server {
listen 443 ssl http2;
server_name YOUR_SERVER_NAME; # доменное имя сайта
ssl_certificate /home/your_username/django_project/ssl/AlphaSSL-Wildcard-certificate--XXXXX.crt;
ssl_trusted_certificate /home/your_username/django_project/ssl/AlphaSSL-Wildcard-certificate--XXXXX.crt;
ssl_certificate_key /home/your_username/django_project/ssl/AlphaSSL-Wildcard-private-key.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;
# ssl_stapling on;
resolver 77.88.8.8;
ssl_session_cache builtin:1000 shared:SSL:25m;
keepalive_requests 200;
# keepalive_time 1h;
keepalive_timeout 75s;
# ...
# ...
# ...
}
```
Для более оптимальной нагрузки на uwsgi в блок `upstream` имеет смысл добавить:
```nginx configuration
keepalive_requests 200;
```
А в блок `location … {uwsgi_pass …}` вставить (но это не точно):
```nginx configuration
fastcgi_keep_conn on;
proxy_set_header Host $host;
```
Еще про настройку ssl можно почитать [здесь ](https://losst.ru/nastrojka-ssl-v-nginx-s-lets-encrypt ) или
[здесь ](https://masterhost.ru/support/faq/ssl/install ). Инструкция как настроить бесплатный ssl-сертификат
приведена [здесь ](https://help.reg.ru/hc/ru/articles/4408046821009--Как-настроить-SSL-сертификат-на-Nginx ).
## 12. Блокируем и баним зловредов (бот-сканеры)
### Ограничения числа запросов в nginx (не обязательно, но вдруг)
Настроим nginx на ограничения числа запросов c одного IP-адреса с помощью модуля Limit Req Module.
Открываем файл `/etc/nginx/nginx.conf` (конфиг для всех сайтов на сервере):
```shell
sudo nano /etc/nginx/nginx.conf
```
И в блок `http {…}` добавляем строку:
```nginx configuration
http {
...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
...
}
```
где:
* `$binary_remote_addr` — переменная Nginx содержащая IP с которого пришёл запрос;
* `zone=one` — имя зоны. Если настройка делается в нескольких vhost файлах, имена должны быть разные;
* `10m` — размер зоны. 1m может содержать 16000 состояний, т.е . 16000 уникальных IP-адресов;
* `rate=1r/s` — разрешено 1 запрос в секунду. Так же можно указать количество запросов в минуту (30r/m — 30 запросов в минуту)
Затем открываем на редактирование конфигурационный файл nginx для нашего сайта:
```shell
nano ~/django_project/config/django_project.conf
```
И в блок `server {…}` , добавим строку:
```nginx configuration
limit_req zone=one burst=20 nodelay;
```
Где:
* `one` — имя зоны настроеной в /etc/nginx/nginx.conf (для всех сайтов сервера) в блоке `http {…}` ;
* `burst` — максимальный всплеск активности, можно регулировать до какого значения запросов в секунду может быть всплеск запросов;
* `nodelay` — незамедлительно, при достижении лимита подключений, выдавать код 503 (Service Unavailable) для этого IP.
Строку `limit_req zone=one burst=5 nodelay;` можно добавить как непосредственно в блок `server {…}` , и тогда будет ограничиваться число запросов ко всем файлам сайта, так и в расположенные в нем блоки `location … {…}` . Целесообразнее ограничить число запросов к `uwsgi_pass` , т.к. он отвечает только за странички сайта (без статики... статика может быть запрощена клиентом довольна, т.к. одна страничка может содержать внутри себя много встроенных картинок, стилей и скриптов).
Таким образом наш блок `location … {…}` будет выглядеть так:
```nginx configuration
location / {
uwsgi_pass django_project-uwsgi; # upstream обрабатывающий обращений
include uwsgi_params; # конфигурационный файл uwsgi;
proxy_set_header Host $host;
limit_req zone=one burst=5 nodelay;
fastcgi_keep_conn on;
uwsgi_read_timeout 1800;
uwsgi_send_timeout 200;
}
```
### Баним ботов и подозрительную активность
Настроим fail2ban для выявления ботов, которые ищут скрипты, дампы баз, логи, ключи и тому подобное, анализируя
error-лог. Создаем конфиг-файл фильтра `/etc/fail2ban/filter.d/nginx-noscript.conf` :
```shell
sudo nano /etc/fail2ban/filter.d/nginx-noscript.conf
```
С о следующим содержанием (добавьте в `failregex` все расширения файлов, которые не должны запрашиваться):
```editorconfig
[Definition]
failregex = .*client: < HOST > .*GET.*(\.php|\.asp|\.aspx|\.exe|\.pl|\.cgi|\.scgi|\.log|\.sql|\.jsp|\.csv|\.sh|\.key|\.py|\.pyc|\.asmx|\.asax)
# failregex = ^<HOST> -.*GET.*(\.php|\.asp|\.aspx|\.exe|\.pl|\.cgi|\.scgi|\.log|\.sql|\.jsp|\.csv|\.sh|\.key|\.py|\.pyc|\.asmx|\.asax)
ignoreregex =
```
Проверим, что фильтр написан верно, проверив е г о на нашем error-логе:
```shell
fail2ban-regex ~/django_project/logs/django_project-error.log /etc/fail2ban/filter.d/nginx-noscript.conf
```
Создаем еще одни фильтр для выявления подозрительной активности (ищут адинки популярных cms, log-и, ключи и тому
подобное) анализируя access лог. Создадим правило `/etc/fail2ban/filter.d/nginx-manual.conf` :
```shell
sudo nano /etc/fail2ban/filter.d/nginx-manual.conf
```
С о следующим содержанием (добавьте свои правила если необходимо, это обычные регулярные выражения):
```editorconfig
[Definition]
failregex = ^< HOST > -.*GET.*wp-content/
^< HOST > -.*GET.*wp-admin/.*
^< HOST > -.*GET.*administrator/
^< HOST > -.*GET.*user/register/
^< HOST > -.*GET.*bitrix/admin/
^< HOST > -.*GET.*minify/
^< HOST > -.*GET.*netcat/
^< HOST > -.*GET.*koobooCMS/
^< HOST > -.*GET.*apanel/
^< HOST > -.*GET.*netcat/
^< HOST > -.*GET*/\.git/.*
^< HOST > -.*GET*/\.env/.*
^< HOST > -.*GET*/cms/.*
^< HOST > -.*GET*/\.well-known/.*
^< HOST > -.*GET*/\.ssh/.*
^< HOST > -.*GET*/\.bash_history/.*
^< HOST > -.*GET.*(\.sql|\.php|\.mdb|\.db|\.yml|\.cgi|\.scgi|\.log|\.sql|\.jsp|\.csv|\.sh|\.key|\.py|\.pyc|\.asmx|\.asax))
ignoreregex =
```
Снова проверим, что фильтр написан верно:
```shell
fail2ban-regex ~/django_project/logs/django_project-access.log /etc/fail2ban/filter.d/nginx-manual.conf
```
Далее, редактируем файл `/etc/fail2ban/jail.local` для настройки параметров подключения и работы фильтра:
```shell
sudo nano /etc/fail2ban/jail.local
```
Дописываем строки:
```editorconfig
# ФИЛЬТР nginx-noscript
[nginx-noscript]
enabled = true
filter = nginx-noscript
port = http,https
action = iptables-multiport[name=NoAuthFailures, port="http,https"]
logpath = /var/log/nginx/*error*.log
/home/your_username/django_project/logs/*error*.log
bantime = 86400
maxretry = 6
findtime = 7200
# ФИЛЬТР manual
[nginx-manual]
enabled = true
filter = nginx-manual
port = http,https
action = iptables-multiport[name=NoAuthFailures, port="http,https"]
logpath = /var/log/nginx/*access*.log
/home/your_username/django_project/logs/*access*.log
bantime = 86400
maxretry = 6
findtime = 7200
```
Перезапустим fail2ban:
```shell
sudo service fail2ban restart
```
Чтобы посмотреть информацию по заблокированным ip по нашему фильтру используем команду:
```shell
sudo fail2ban-client status nginx-noscript
sudo fail2ban-client status nginx-manual
```
Чтобы разбанить IP (например 8.8.8.8) можно воспользоваться командой:
```shell
sudo fail2ban-client set nginx-noscript unbanip 8.8.8.8
```
## 13. Ротация логов
Обычно процесс ротации логов в системе уже установлен и работает. Н о так как мы настроили логирование вручную, в каталое
`~/django_project/logs/` , то нам надо настроить ротацию логов для этой папки самостоятельно (зато может задать свои
правила ротации).
Н а всякий случай установим пакет `logrotate` (если он еще не установлен):
```shell
sudo apt-get install logrotate
```
Для настройки ротации логов нашего сайта нужно отредактировать конфигурационный файл ротации для nginx `/etc/logrotate.d/nginx`
```shell
sudo nano /etc/logrotate.d/nginx
```
И добавить в него (в конце) следующее:
```editorconfig
/home/your_username/django_project/logs/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0644 www-data adm
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
invoke-rc.d nginx rotate >/dev/null 2>& 1
endscript
}
```
После чего процесс ротации надо перезапустить:
```shell
sudo service logrotate restart
```
Убедимся, что ротация работает:
```shell
sudo service logrotate status
```
Увидим:
```text
● logrotate.service - Rotate log files
Loaded: loaded (/lib/systemd/system/logrotate.service; static; vendor preset: enabled)
Active: inactive (dead) since Wed 2022-05-18 16:47:06 MSK; 4s ago
TriggeredBy: ● logrotate.timer
Docs: man:logrotate(8)
man:logrotate.conf(5)
Process: 20860 ExecStart=/usr/sbin/logrotate /etc/logrotate.conf (code=exited, status=0/SUCCESS)
Main PID: 20860 (code=exited, status=0/SUCCESS)
```
## 14. Геограничения и болкировка отдельных юзер-агентов
Если у вас есть необходимость ограничить доступ к сайту по с тр <D182> <D180> на м, то можно воспользоваться модулем `ngx_http_geoip_module` .
Инструкции:
* https://stackoverflow.com/questions/62213884/how-install-the-geoip2-module-on-a-nginx-running-in-a-production-environment
* https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/
* https://docs.nginx.com/nginx/admin-guide/dynamic-modules/geoip2/
* https://nginx.org/ru/docs/http/ngx_http_geoip_module.html
* https://dev.maxmind.com/?lang=en
Если есть желание заблокировать доступ к сайту отдельным юзер-агентам, то можно воспользоваться [отдельной инструкцией,
на примере GPTBot](../nginx/nginx-ban-user-agent.md)