Собрался наконец-то перенести RTFM на Debian 10, решил делать без автоматизации — будем поднимать стандартный LEMP для хостинга WordPress руками.
Что-то похожее последний раз писалось в 2016 — Debian: установка LEMP — NGINX + PHP-FPM + MariaDB, в этот раз получился более полный обзор процесса.
Также, когда-то делал автоматизацию для настройки сервера под RTFM, но последний раз она использовалась в 2018 (см. AWS: миграция RTFM 3.0 (final) — CloudFormation и Ansible роли), а теперь многое поменялось/добавилось, и переделывать заново смысла нет — та автоматизация делалась больше для того, что бы потрогать Ansible/CloudFormation и вообще автоматизацию, чем для реального применения.
И снова — планировался краткий пост чисто по установке NGINX + PHP + MySQL, а в результате получается длинност по сетапу полноценного сервера — и мониторинг, и логи, и вот это вот всё.
Что будем делать:
- создадим дроплет в DigitalOcean
- SSL — используем Let’s Encrypt
- NGINX
- PHP-FPM
- MySQL (MariaDB) в роли базы данных
- NGINX Amplify agent — мониторинг и алертинг (Grafana и Loki не прижились, т.к. жрут слишком много ресурсов, но сам сетап был интересный, см. Prometheus: мониторинг для RTFM — Grafana, Loki и promtail)
- WordPress backup script — самописный скрипт на Python, см. Python: скрипт бекапа файлов и баз MySQL в AWS S3
- Logz.io — сбор логов в ELK-стек
unattended-upgrades
— автоапдейты Debian и пакетовlogrotate
— ротация логов
Содержание
DigitalOceal: создание дроплета
Переехал на DigitalOcean из AWS кажется в прошлом году.
Причина — стоимость, так как DigitalOcean обходится намного дешевле. Работает в целом без нареканий, потому пока тут и остаюсь.
Создаём дроплет:
Будет Debian 10, 2 CPU, 2 GB RAM.
Например, на текущем сервере той же конфигурации нагрузка и потребление памяти выглядят так (график из NGINX Amplify):
Выбираем систему и тип инстанса:
Выберем Франкфурт, и включим дополнительный мониторинг — на дроплете сразу будет запущен DigitalOcean-агент, который отрисует больше графиков в панели управления самого DO:
Создание RSA ключа для SSH
На рабочей машине создаём новую пару ключа:
[simterm]
$ ssh-keygen -f ~/Dropbox/AWS/setevoy-do-nextcloud-production-d10-03-11 Generating public/private rsa key pair. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/setevoy/Dropbox/AWS/setevoy-do-nextcloud-production-d10-03-11 Your public key has been saved in /home/setevoy/Dropbox/AWS/setevoy-do-nextcloud-production-d10-03-11.pub ...
[/simterm]
Копируем содержимое публичной части:
[simterm]
$ cat /home/setevoy/Dropbox/AWS/setevoy-do-nextcloud-production-d10-03-11.pub ssh-rsa AAAAB3NzaC1***Ht3UEYuGtdQgc0= setevoy@setevoy-arch-work
[/simterm]
В DO создаём новый ключ:
Выбираем количество дроплетов 1, и задаём имя хоста rtfm-do-production-d10:
По желанию — включаем бекапы, и создаём дроплет:
Firewall
Пока создаётся дроплет — добавим для него фаервол:
Добавляем правила — SSH, ICMP — ограниченные только IP, на котором я сейчас, HTTP/S — отовсюду, хотя на время миграции имеет смысл ограничить доступ, что бы Google не проиндексировал блог, как копию оригинального сайта:
Подключаем к новому дроплету:
Floating IP
Аналог Elasitc IP в AWS, добавляем новый IP для нового сервера:
Тут, собственно, всё.
Теперь к серверу.
LEMP — Linux, NGINX, PHP, MySQL
Ещё раз — что нам надо будет насетапить на сервере?
- nginx
- php-fpm
- lets encrypt
- mysql
- amplify agent — мониторинг
- backup script
- logz.io — есть free-доступ, правда логи хранит только деень — но мне больше и не надо
- unattended-upgrades — автоапдейты системы и установленных пакетов
- logrotate — на Debian есть по дефолту, проверим его настройки
- msmtp — для отправки почты
Подключаемся на хост:
[simterm]
$ chmod 400 Dropbox/AWS/setevoy-do-nextcloud-production-d10-03-11 $ ssh -i Dropbox/AWS/setevoy-do-nextcloud-production-d10-03-11 [email protected] root@rtfm-do-production-d10:~#
[/simterm]
Обновляем систему, перезагружаемся:
[simterm]
root@rtfm-do-production-d10:~# apt update && apt -y upgrade
[/simterm]
Устанавливаем пакеты для LEMP:
[simterm]
root@rtfm-do-production-d10:~# apt -y install certbot nginx php php-xml php-curl php-gd php-zip php-mysql php-mbstring php-fpm mariadb-server
[/simterm]
Проверяем NGINX:
[simterm]
root@rtfm-do-production-d10:~# curl localhost <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ...
[/simterm]
Устанавливаем прочие полезные пакеты:
[simterm]
root@rtfm-do-production-d10:/data# apt -y install htop git wget unzip unattended-upgrades apt-listchanges dnsutils telnet python-pip python-boto3 mailutils
[/simterm]
С mailutils
есть проблема при использовании mailx
с msmtp
, пришлось заменить на bsd-mailx
, см. mailx и msmtp — отправка почты с сервера.
Let’s Encrypt SSL
Используем сертификат от Let’s Encrypt.
Let’s Encrypt DNS validation
Возникает вопрос по валидации домена при получении сертификата — ведь сейчас домен направлен на старый сервер, значит стандартный метод с валидацией через веб-сервер и .well-known
директорию не подойдёт.
Что сделаем — используем DNS-валидацию при получении сертификата, а потом, когда уже будет сертификат и настроенный NGINX — перенастроим certbot
на валидацию через веб-сервер, т.к. DNS-валидация не поддерживает (но это не точно) renew
сертификатов.
Стопаем NGINX:
[simterm]
root@rtfm-do-production-d10:~# systemctl stop nginx
[/simterm]
Получаем сертификат:
[simterm]
root@rtfm-do-production-d10:~# certbot certonly --preferred-challenges dns -d rtfm.co.ua --manual --email [email protected] --agree-tos Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator manual, Installer None Obtaining a new certificate Performing the following challenges: dns-01 challenge for rtfm.co.ua - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NOTE: The IP of this machine will be publicly logged as having requested this certificate. If you're running certbot in manual mode on a machine that is not your server, please ensure you're okay with that. Are you OK with your IP being logged? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: Y - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please deploy a DNS TXT record under the name _acme-challenge.rtfm.co.ua with the following value: ORWOP6KR4C3csx-ngoSWbqVAJuVo8kFDgV8AqNFUemg Before continuing, verify the record is deployed. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Press Enter to Continue
[/simterm]
На DNS домена добавляем запись:
Проверяем:
[simterm]
$ dig _acme-challenge.rtfm.co.ua TXT +short "ORWOP6KR4C3csx-ngoSWbqVAJuVo8kFDgV8AqNFUemg"
[/simterm]
Возвращаемся к серверу, жмём Enter — готово:
[simterm]
... Press Enter to Continue Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/rtfm.co.ua/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/rtfm.co.ua/privkey.pem Your cert will expire on 2021-02-01. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew"
[/simterm]
NGINX
Генерируем DF-ключ (см. ClientKeyExchange
) для NGINX:
[simterm]
root@rtfm-do-production-d10:~# openssl dhparam -out /etc/nginx/dhparams.pem 2048
[/simterm]
Удаляем конфиг default
— у нас RTFM будет дефолтным виртуалхостом:
[simterm]
root@rtfm-do-production-d10:~# rm /etc/nginx/sites-enabled/default
[/simterm]
Создаём конфиг виртуалхоста для РТФМ — /etc/nginx/conf.d/rtfm.co.ua.conf
. Он есть на старом сервере, можно просто скопировать.
У меня тут достаточно длинный конфиг получился, переношу уже его уже лет с 5 разных серверов, не меняется ничего кроме настроек SSL.
Последний тест на https://www.ssllabs.com всё ещё отдаёт уровень А+, так что можно использовать.
Вообще, удобная штука генераторы конфигов, например https://www.serverion.com/nginx-config или SSL Configuration Generator от Mozilla.
В моём конфиге под WordPress дополнительно блокируется доступ к /wp-admin
и wp-login.php
:
server { listen 80 default_server; server_name rtfm.co.ua www.rtfm.co.ua; server_tokens off; return 301 https://rtfm.co.ua$request_uri; } server { listen 443 ssl default_server; server_name rtfm.co.ua; root /data/www/rtfm/rtfm.co.ua; add_header Strict-Transport-Security "max-age=31536000; includeSubdomains" always; server_tokens off; # access_log /var/log/nginx/rtfm.co.ua-access.log main_ext; error_log /var/log/nginx/rtfm.co.ua-error.log warn; ssl_certificate /etc/letsencrypt/live/rtfm.co.ua/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/rtfm.co.ua/privkey.pem; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers on; ssl_dhparam /etc/nginx/dhparams.pem; ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; ssl_stapling on; ssl_stapling_verify on; error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } client_max_body_size 1024m; location ~ /\.ht { deny all; } location ~* \.(jpg|swf|jpeg|gif|png|css|js|ico)$ { root /data/www/rtfm/rtfm.co.ua; expires 24h; } location /wp-admin/admin-ajax.php { location ~ \.php$ { include /etc/nginx/fastcgi_params; fastcgi_pass unix:/var/run/rtfm.co.ua-php-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } } location /wp-admin/ { index index.php, index.html; auth_basic_user_file /data/www/rtfm/.htpasswd_rtfm; auth_basic "Password-protected Area"; # office allow 194.***.***.24/29; # home 397 LocalNet allow 31.***.***.117/32; # home 397 Lanet allow 176.***.***.237; deny all; location ~ \.php$ { include /etc/nginx/fastcgi_params; fastcgi_pass unix:/var/run/rtfm.co.ua-php-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } } location /wp-config.php { deny all; } location /.user.ini { deny all; } location /wp-login.php { auth_basic_user_file /data/www/rtfm/.htpasswd_rtfm; auth_basic "Password-protected Area"; # office allow 194.***.***.24/29; # home 397 LocalNet allow 31.***.***.117/32; # home 397 Lanet allow 176.***.***.237; deny all; location ~ \.php$ { include /etc/nginx/fastcgi_params; fastcgi_pass unix:/var/run/rtfm.co.ua-php-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } } location /uploads/noindex { auth_basic_user_file /data/www/rtfm/.htpasswd_rtfm; auth_basic "Password-protected Area"; } location = /favicon.ico { access_log off; log_not_found off; } location / { try_files $uri =404; index index.php; proxy_read_timeout 3000; rewrite ^/sitemap(-+([a-zA-Z0-9_-]+))?\.xml$ "/index.php?xml_sitemap=params=$2" last; rewrite ^/sitemap(-+([a-zA-Z0-9_-]+))?\.xml\.gz$ "/index.php?xml_sitemap=params=$2;zip=true" last; rewrite ^/sitemap(-+([a-zA-Z0-9_-]+))?\.html$ "/index.php?xml_sitemap=params=$2;html=true" last; rewrite ^/sitemap(-+([a-zA-Z0-9_-]+))?\.html.gz$ "/index.php?xml_sitemap=params=$2;html=true;zip=true" last; if (!-f $request_filename){ set $rule_1 1$rule_1; } if (!-d $request_filename){ set $rule_1 2$rule_1; } if ($rule_1 = "21"){ rewrite /. /index.php last; } } location ~ \.php$ { try_files $uri =404; proxy_read_timeout 3000; include /etc/nginx/fastcgi_params; fastcgi_pass unix:/var/run/rtfm.co.ua-php-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; } }
Проверяем, запускаем (выключали, пока SSL получали):
[simterm]
root@rtfm-do-production-d10:~# nginx -t && systemctl start nginx nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
[/simterm]
Проверяем.
На рабочей машине добавляем в /etc/hosts
:
139.59.205.180 rtfm.co.ua
Пробуем открыть:
Отлично — SSL есть, NGINX работает.
И быстро отключаем, потому что в этот момент пишу этот пост на старом сервере 🙂
PHP-FPM
Аналогично — копируем конфиг со старого сервера.
Используем FPM-пулы, каждый под своим юзером.
См. PHP-FPM: Process Manager — dynamic vs ondemand vs static.
Linux: non-login user
Добавляем non-login юзера:
[simterm]
root@rtfm-do-production-d10:~# adduser --system --no-create-home --group rtfm Adding system user `rtfm' (UID 109) ... Adding new group `rtfm' (GID 115) ... Adding new user `rtfm' (UID 109) with group `rtfm' ... Not creating home directory `/home/rtfm'.
[/simterm]
Создаём конфиг /etc/php/7.3/fpm/pool.d/rtfm.co.ua.conf
:
[rtfm.co.ua] user = rtfm group = rtfm listen = /var/run/rtfm.co.ua-php-fpm.sock listen.owner = www-data listen.group = www-data pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 ;pm.process_idle_timeout = 10s; ;pm.max_requests = 500 catch_workers_output = yes chdir = / pm.status_path = /status slowlog = /var/log/nginx/rtfm.co.ua-slow.log php_flag[display_errors] = off ;php_admin_value[display_errors] = 'stderr' php_admin_value[display_errors] = off php_admin_value[error_log] = /var/log/nginx/rtfm.co.ua-php-error.log php_admin_flag[log_errors] = on php_admin_value[session.save_path] = /var/lib/php/session/rtfm php_value[session.save_handler] = files php_value[session.save_path] = /var/lib/php/session php_admin_value[upload_max_filesize] = 128M php_admin_value[post_max_size] = 128M
Проверяем синтаксис:
[simterm]
root@rtfm-do-production-d10:~# php-fpm7.3 -t [03-Nov-2020 11:45:24] NOTICE: configuration file /etc/php/7.3/fpm/php-fpm.conf test is successful
[/simterm]
Перечитываем конфиги:
[simterm]
root@rtfm-do-production-d10:~# systemctl reload php7.3-fpm.service
[/simterm]
Рут в NIGNX у нас:
... root /data/www/rtfm/rtfm.co.ua; ...
Создаём каталог:
[simterm]
root@rtfm-do-production-d10:~# mkdir -p /data/www/rtfm/rtfm.co.ua
[/simterm]
Создём тестовый файл с вызовом phpinfo()
, что бы проверить работу NGINX + PHP:
[simterm]
root@rtfm-do-production-d10:~# echo "<?php phpinfo(); ?>" > /data/www/rtfm/rtfm.co.ua/info.php
[/simterm]
Проверяем (снова через обновление локального /etc/hosts
):
Отлично — всё работает.
MySQL
На Debian уже достаточно давно используется MariaDB вместо MySQL, но разницы особой нет. И в целом MariaDB показывает себя в тестах лучше, где-то делал сравнение.
Запускаем первичную настройку:
[simterm]
root@rtfm-do-production-d10:~# mysql_secure_installation ... Set root password? [Y/n] y New password: Re-enter new password: Password updated successfully! Reloading privilege tables.. ... Success! ... Remove anonymous users? [Y/n] y ... Success! ... Disallow root login remotely? [Y/n] y ... Success! ... Remove test database and access to it? [Y/n] y - Dropping test database... ... Success! - Removing privileges on test database... ... Success! ... Reload privilege tables now? [Y/n] y ... Success! Cleaning up... All done! If you've completed all of the above steps, your MariaDB installation should now be secure. Thanks for using MariaDB!
[/simterm]
Создаём базу для RTFM:
[simterm]
MariaDB [(none)]> create database rtfm_db1_production; Query OK, 1 row affected (0.000 sec)
[/simterm]
Пользователя rtfm с доступом к базе rtfm_db1_production только с localhost с паролем password:
[simterm]
MariaDB [(none)]> GRANT ALL PRIVILEGES ON rtfm_db1_production.* TO 'rtfm'@'localhost' IDENTIFIED BY 'password'; Query OK, 0 rows affected (0.001 sec)
[/simterm]
Проверяем:
[simterm]
root@rtfm-do-production-d10:~# mysql -u rtfm -p -e 'show databases;' Enter password: +--------------------+ | Database | +--------------------+ | information_schema | | rtfm_db1_production| +--------------------+
[/simterm]
Всё готово к миграции.
Миграция блога
Тут мне придётся сделать паузу на время создание дампа и перенос файлов.
После переноса — продолжу писать уже с нового сервера.
Что делаем:
- создаём архив файлов
- создаём дамп базы
- переносим их на новый сервер
- меняем запись на DNS
Сохраняем этот пост в черновиках — он будет добавлен в базу данных, которую сдамплю и перенесу, а потом продолжу с этого места.
Архив файлов
Готовим архив с файлами:
[simterm]
root@rtfm-do-production:/home/setevoy# cd /data/www/rtfm/ root@rtfm-do-production:/data/www/rtfm# ll total 20 drwxr-xr-x 8 rtfm rtfm 20480 Nov 3 12:11 rtfm.co.ua
[/simterm]
Создаём архив со сжатием:
[simterm]
root@rtfm-do-production:/data/www/rtfm# tar cvpfz rtfm.co.ua.tar.gz rtfm.co.ua/
[/simterm]
Проверяем:
[simterm]
root@rtfm-do-production:/data/www/rtfm# ls -lh total 2.4G drwxr-xr-x 8 rtfm rtfm 20K Nov 3 12:11 rtfm.co.ua -rw-r--r-- 1 root root 2.4G Nov 3 14:05 rtfm.co.ua.tar.gz
[/simterm]
Дамп базы MySQL
Создаём дамп базы (прочтите сначала WordPress: Error establishing a database connection про опцию -d
) :
[simterm]
root@rtfm-do-production:/data/www/rtfm# mysqldump -u rtfm -p -d rtfm_db1_production > rtfm_db1_production.sql Enter password:
[/simterm]
Проверяем его:
[simterm]
root@rtfm-do-production:/data/www/rtfm# head rtfm_db1_production.sql -- MySQL dump 10.16 Distrib 10.1.47-MariaDB, for debian-linux-gnu (x86_64) -- -- Host: localhost Database: rtfm_db1_production -- ------------------------------------------------------ -- Server version 10.1.47-MariaDB-0+deb9u1 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8mb4 */;
[/simterm]
В фаерволе старого сервера разрешаем SSH с нового, и копируем данные со старого на новый:
[simterm]
root@rtfm-do-production-d10:/data# scp -i /root/.ssh/rtfm-do-old [email protected]:/data/www/rtfm/rtfm.co.ua.tar.gz . [email protected]'s password: rtfm.co.ua.tar.gz 100% 2409MB 101.3MB/s 00:23 root@rtfm-do-production-d10:/data# scp -i /root/.ssh/rtfm-do-old [email protected]:/data/www/rtfm/rtfm_db1_production.sql . [email protected]'s password: rtfm_db1_production.sql
[/simterm]
Распаковываем файлы:
[simterm]
root@rtfm-do-production-d10:/data# tar xfpzv rtfm.co.ua.tar.gz
[/simterm]
Проверяем:
[simterm]
root@rtfm-do-production-d10:/data# ll total 2466844 drwxr-xr-x 8 rtfm rtfm 4096 Nov 3 10:11 rtfm.co.ua -rw-r--r-- 1 root root 2525973949 Nov 3 12:14 rtfm.co.ua.tar.gz -rw-r--r-- 1 root root 59424 Nov 3 12:14 rtfm_db1_production.sql drwxr-xr-x 3 root root 4096 Nov 3 11:47 www
[/simterm]
Переносим каталог rtfm.co.ua
в /data/www/rtfm
:
[simterm]
root@rtfm-do-production-d10:/data# rm -rf www/rtfm/rtfm.co.ua/ root@rtfm-do-production-d10:/data# mv rtfm.co.ua www/rtfm/
[/simterm]
Проверяем:
[simterm]
root@rtfm-do-production-d10:/data# ll www/rtfm/rtfm.co.ua/ total 388 -rw-r--r-- 1 rtfm rtfm 64 Nov 6 2018 1a24c4e2948b4047d3d1ed8516b5ca39e452ccfdb2f81a46a8984b921261bd1e.txt -rw-r--r-- 1 rtfm rtfm 24 Nov 6 2018 404.html -rw-r--r-- 1 root root 58 Jul 25 2019 ads.txt -rw-r--r-- 1 rtfm rtfm 28522 Nov 6 2018 bin_dec.html -rw-r--r-- 1 rtfm rtfm 30682 Nov 6 2018 favicon.ico -rw-r--r-- 1 rtfm rtfm 405 Apr 1 2020 index.php -rw-r--r-- 1 rtfm rtfm 3080 Nov 6 2018 keybase.txt -rw-r--r-- 1 rtfm rtfm 19915 Aug 12 07:46 license.txt -rw-r--r-- 1 rtfm rtfm 20 Nov 6 2018 live-4d939769.tx ...
[/simterm]
Загружаем дамп базы в новую базу:
[simterm]
root@rtfm-do-production-d10:/data# mysql -u rtfm -p rtfm_db1_production < rtfm_db1_production.sql Enter password:
[/simterm]
Обновляем локальный /etc/hosts
— и:
WTF?
WordPress: Error establishing a database connection
Проверяем данные в базе — вроде всё на месте:
[simterm]
MariaDB [rtfm_db1_production]> show tables; +--------------------------------+ | Tables_in_rtfm_db1_production | +--------------------------------+ | b2s_posts | | b2s_posts_network_details | | b2s_posts_sched_details | | b2s_user | | b2s_user_contact | | b2s_user_network_settings | ...
[/simterm]
PHP: проверить подключение к MySQL
Пробуем РНР-скрипт:
<?php $link = @mysqli_connect('localhost', 'rtfm', 'Ta6paidie7Ie'); if(!$link) { die("Failed to connect to the server: " . mysqli_connect_error()); } else { echo "Connected\n"; } if(!@mysqli_select_db($link, 'rtfm_db1_production')) { die("Failed to connect to the database: " . mysqli_error($link)); } else { echo "DB found\n"; } ?>
Запускаем:
[simterm]
root@rtfm-do-production-d10:/data/www/rtfm/rtfm.co.ua# php mysql.php Connected DB found
[/simterm]
Тоже всё хорошо.
WordPress: WP_ALLOW_REPAIR
Копируем со старого сервера .htpasswd_rtfm
, пробуем выполнить repair
— в wp-config.php
перед строкой «‘That’s all, stop editing! Happy blogging’» добавлем:
define('WP_ALLOW_REPAIR', true);
И открываем https://rtfm.co.ua/wp-admin/maint/repair.php:
Вроде бы всё ОК — но нет:
Последним вариантом проверки было засетапить голый WordPress, который завёлся без проблем.
Понятно было, что проблема в данных в самом дампе — но какая?
Причина «Error establishing a database connection»
Пошёл я посмотреть опции mysqldump
, и тут меня осенило:
-d , --no-data |
Do not write any table row information (that is, do not dump table contents). This is useful if you want to dump only the CREATE TABLE statement for the table (for example, to create an empty copy of the table by loading the dump file). See also —ignore-table-data . |
😀
С какого перепугу я добавил -d
при создании дампа — не знаю. Аукнулась работа с миграцией даных в AWS Database Migration Service, где надо было создать дамп чисто схемы, без самих данных.
Собственно — пересоздаём дамп уже без -d
:
[simterm]
root@rtfm-do-production:/home/setevoy# mysqldump -u rtfm -p rtfm_db1_production > rtfm_db1_production.sql Enter password:
[/simterm]
Повторяем все операции, и всё завелось — продолжаю писать уже с нового сервера:
[simterm]
13:59:52 [setevoy@setevoy-arch-work ~] $ dig rtfm.co.ua +short 139.59.205.180
[/simterm]
Что дальше?
Надо настроить certbot
на валидацию через webroot, добавить его в крон для автообновления сертификатов.
И закончить со оставшимися сервисами:
- amplify agent
- backup script
- logz.io
- unattended-upgrades
- msmtp
SSL: webroot validation
У нас уже есть сертификат, но мы делали валидацию через DNS.
Она вроде не работает при renew
, поэтому перенастроим на валидацию через webroot.
Let’s Encrypt: webroot валидация
Вызываем certbot
для домена rtfm.co.ua, передаём --webroot-path
вместо dns
— он должен увидеть имеющийся сертификат, и предложить сохранить, или сгенерировать новый.
На первый вопрос «How would you like to authenticate with the ACME CA?» отвечаем «Place files in webroot directory (webroot)«, на второй — «You have an existing certificate […]» — «Renew & replace the cert (limit ~5 per 7 days)«, что бы сгенерировался новый конфиг для Let’s Ecnrypt:
[simterm]
root@rtfm-do-production-d10:/data# certbot certonly -d rtfm.co.ua --email [email protected] --agree-tos --webroot-path /data/www/rtfm/rtfm.co.ua/.well-known/ Saving debug log to /var/log/letsencrypt/letsencrypt.log How would you like to authenticate with the ACME CA? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1: Spin up a temporary webserver (standalone) 2: Place files in webroot directory (webroot) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2 Plugins selected: Authenticator webroot, Installer None Cert not yet due for renewal You have an existing certificate that has exactly the same domains or certificate name you requested and isn't close to expiry. (ref: /etc/letsencrypt/renewal/rtfm.co.ua.conf) What would you like to do? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1: Keep the existing certificate for now 2: Renew & replace the cert (limit ~5 per 7 days) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2 Renewing an existing certificate IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/rtfm.co.ua/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/rtfm.co.ua/privkey.pem ...
[/simterm]
Да, всё хорошо — теперь проверим конфиг, который будет использоваться при обновлении сертификата:
[simterm]
root@rtfm-do-production-d10:/data# cat /etc/letsencrypt/renewal/rtfm.co.ua.conf # renew_before_expiry = 30 days version = 0.31.0 archive_dir = /etc/letsencrypt/archive/rtfm.co.ua cert = /etc/letsencrypt/live/rtfm.co.ua/cert.pem privkey = /etc/letsencrypt/live/rtfm.co.ua/privkey.pem chain = /etc/letsencrypt/live/rtfm.co.ua/chain.pem fullchain = /etc/letsencrypt/live/rtfm.co.ua/fullchain.pem # Options used in the renewal process [renewalparams] account = 868c8164304408984fefbbff845d4f48 authenticator = webroot server = https://acme-v02.api.letsencrypt.org/directory webroot_path = /data/www/rtfm/rtfm.co.ua/.well-known, [[webroot_map]]
[/simterm]
Отлично — можно добавлять cronjob
.
certbot renew
— автообновление
Добавляем в cron задачу — раз в неделю выполнять certbot renew
.
Редактируем crontab
:
[simterm]
root@rtfm-do-production-d10:/data# crontab -e
[/simterm]
Добавляем:
@weekly certbot renew &> /var/log/letsencrypt/letsencrypt.log
Let’s Encrypt hook — NGINX reload
Последний шаг — релоадить NGINX после обновления сертификата.
Можно было ,s добавить в cron, например так:
@weekly certbot renew &> /var/log/letsencrypt/letsencrypt.log && service nginx reload
Но тогда если один из сертификатов не обновится по какой-то причине — NGINX не применит изменения вообще.
Поэтому делаем более правильно — добавляем хук в конфиг нашего домена — /etc/letsencrypt/renewal/rtfm.co.ua.conf
.
В блоке renewalparams
добавляем renew_hook
, теперь он выглядит так:
[renewalparams] account = 868c8164304408984fefbbff845d4f48 authenticator = webroot server = https://acme-v02.api.letsencrypt.org/directory webroot_path = /data/www/rtfm/rtfm.co.ua/.well-known, renew_hook = systemctl reload nginx
Собственно, с SSL мы закончили.
Amplify — мониторинг
Достаточно базовый мониторинг — но удобен, и сетапится в несколько минут. См. NGINX: Amplify — SaaS мониторинг от NGINX.
Официальная документация — тут>>>.
Загружаем установочный скрипт:
Передаём API ключ в переменной и запускаем скрипт:
[simterm]
root@rtfm-do-production-d10:/tmp# API_KEY='967***e31' sh ./install.sh
[/simterm]
Пару минут на установку — и новый хост появляется в дашборде:
Интереса ради — нагрузка на старом сервере после переключения rtfm.co.ua на новый сервер:
Backup скрипт для сайтов
Использую самописный скрипт, который делался ещё 3 года тому — https://github.com/setevoy2/simple-backup. Скрипт архивирует файлы, и создаёт дамп базы, плюс умеет загружать в AWS S3.
Вообще, для WordPress куча плагинов бекапа, которые умеют сразу и в AWS-корзины загружать — но пока тоже руки не доходят их потрогать, поэтому сделаю по старинке.
Клонируем его:
[simterm]
root@rtfm-do-production-d10:/tmp# cd /opt/ root@rtfm-do-production-d10:/opt# git clone https://github.com/setevoy2/simple-backup
[/simterm]
Интересно — работает ли та копия, которая в репозиториии…
Точно помню, что когда-то поломалась загрузка архивов в AWS S3, и я её так и не починил. Может, в отпуске дойдут руки.
Пока пробуем, как есть:
[simterm]
root@rtfm-do-production-d10:/opt# python simple-backup/sitebackup.py -h usage: sitebackup.py [-h] [-c CONFIG] optional arguments: -h, --help show this help message and exit -c CONFIG, --config CONFIG
[/simterm]
Ну, вроде да, работает.
Для работы скрипта нужен каталог /backups
, который монтируется отдельным диском, и файл настроек.
Добавим сначала диск.
Диски и разделы на сервере сейчас:
[simterm]
root@rtfm-do-production-d10:/opt# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT vda 254:0 0 60G 0 disk ├─vda1 254:1 0 60G 0 part / └─vda2 254:2 0 2M 0 part vdb 254:16 0 466K 1 disk
[/simterm]
DigitalOcean Volume
Переходим в DigitalOcean, создаём Volume:
Проверяем:
[simterm]
root@rtfm-do-production-d10:/opt# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 50G 0 disk /mnt/rtfm_do_production_d10_backups vda 254:0 0 60G 0 disk ├─vda1 254:1 0 60G 0 part / └─vda2 254:2 0 2M 0 part vdb 254:16 0 466K 1 disk
[/simterm]
Linux: монтирование диска
DigitalOcean Volume по-умолчанию монтируется в /mnt/rtfm_do_production_d10_backups
, и не создаёт запись в fstab
:
[simterm]
root@rtfm-do-production-d10:/opt# cat /etc/fstab # /etc/fstab: static file system information. UUID=4e8b8101-6a06-429a-aaca-0ccd7ff14aa1 / ext4 errors=remount-ro 0 1
[/simterm]
Отмонтируем его:
[simterm]
root@rtfm-do-production-d10:/opt# umount /mnt/rtfm_do_production_d10_backups
[/simterm]
Создаём каталог /backups
:
[simterm]
root@rtfm-do-production-d10:/opt# mkdir /backups
[/simterm]
Получаем UUID диска:
[simterm]
root@rtfm-do-production-d10:/opt# blkid /dev/sda /dev/sda: UUID="a6e27193-4079-4d9d-812e-6ba29c702b75" TYPE="ext4"
[/simterm]
Обновляем /etc/fstab
— добавляем монтирование этого диска в /backups
, в opts с помощью nofail
указываем, что его монтирование не обязательно, что бы система могла загрузиться, если этого диска не будет:
# /etc/fstab: static file system information. UUID=4e8b8101-6a06-429a-aaca-0ccd7ff14aa1 / ext4 errors=remount-ro 0 1 UUID=a6e27193-4079-4d9d-812e-6ba29c702b75 /backups ext4 nofail 0 0
Пробуем смонтировать все разделы, указанные в /etc/fstab
:
[simterm]
root@rtfm-do-production-d10:/opt# mount -a
[/simterm]
Проверяем:
[simterm]
root@rtfm-do-production-d10:/opt# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 50G 0 disk /backups vda 254:0 0 60G 0 disk ├─vda1 254:1 0 60G 0 part / └─vda2 254:2 0 2M 0 part vdb 254:16 0 466K 1 disk
[/simterm]
Вроде всё ок, и данные на месте:
[simterm]
root@rtfm-do-production-d10:/opt# ll /backups/ total 16 drwx------ 2 root root 16384 Nov 4 12:44 lost+found
[/simterm]
Ещё можно перезагрузить машину, что бы убедиться, что всё работает — но это попозже, когда допишу пост.
Со старого сервера копируем конфиг для simple-backup
, и пробуем его запустить:
[simterm]
root@rtfm-do-production-d10:/opt# /opt/simple-backup/sitebackup.py -c /usr/local/etc/production-simple-backup.ini Got own settings: backup_root_path = /backups backup_files_dir = /backups/files backup_db_dir = /backups/databases Checking directories: /backups - found, OK. /backups/files - found, OK. /backups/databases - found, OK. Creating WWW backup for: site: rtfm from: /data/www/rtfm/rtfm.co.ua/ to: /backups/files/04-11-2020-12-58_rtfm_rtfm.co.ua.gz WWW backup done. Creating DB backup for: site: rtfm host: localhost database: rtfm_db1_production user: rtfm to: /backups/databases/04-11-2020-12-58_rtfm_rtfm_db1_production.sql DB backup done. Checking for dependencies: boto3 library already installed - OK. Uploading /backups/files/04-11-2020-12-58_rtfm_rtfm.co.ua.gz to S3 bucket setevoy-rtfm-simple-backups-production as 04-11-2020-12-58_rtfm_rtfm.co.ua.gz Uploading /backups/databases/04-11-2020-12-58_rtfm_rtfm_db1_production.sql to S3 bucket setevoy-rtfm-simple-backups-production as 04-11-2020-12-58_rtfm_rtfm_db1_production.sql Existing data in the setevoy-rtfm-simple-backups-production bucket: 04-11-2020-12-58_rtfm_rtfm.co.ua.gz 04-11-2020-12-58_rtfm_rtfm_db1_production.sql ... Starting local backups storage cleanup... Keeping local data: /backups/files/04-11-2020-12-52_rtfm_rtfm.co.ua.gz Keeping local data: /backups/files/04-11-2020-12-58_rtfm_rtfm.co.ua.gz Keeping local data: /backups/databases/04-11-2020-12-58_rtfm_rtfm_db1_production.sql Keeping local data: /backups/databases/04-11-2020-12-52_rtfm_rtfm_db1_production.sql
[/simterm]
Вау!
И даже загрузка в AWS S3 снова работает!
Круто. Окей — с этим тоже закончили.
Что там дальше?
- logz.io
- unattended-upgrades
- logrotate
- msmtp
Logz.io, Filebeat и логи NGINX
Добавим сбор логов NGINX и отправку в Logz.io.
Регистрируем аккаунт, переходим к инструкции по сбору логов NGINX — https://app.logz.io/#/dashboard/data-sources/nginx.
Надо установить Filebeat, используем 7, устанавливаем его:
[simterm]
root@rtfm-do-production-d10:/opt# cd /tmp/ root@rtfm-do-production-d10:/tmp# curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.9.3-amd64.deb root@rtfm-do-production-d10:/tmp# dpkg -i filebeat-7.9.3-amd64.deb
[/simterm]
Получаем публичный сертификат для Logz.io:
[simterm]
root@rtfm-do-production-d10:/tmp# sudo curl https://raw.githubusercontent.com/logzio/public-certificates/master/AAACertificateServices.crt --create-dirs -o /etc/pki/tls/certs/COMODORSADomainValidationSecureServerCA.crt
[/simterm]
Настраиваем Filebeat.
Бекапим конфиг:
[simterm]
root@rtfm-do-production-d10:/tmp# cp /etc/filebeat/filebeat.yml /etc/filebeat/filebeat.yml-origin
[/simterm]
Редактируем его, обновляем, как сказано в документации:
... - type: log paths: - /var/log/nginx/access.log - /var/log/nginx/rtfm.co.ua-access.log fields: logzio_codec: plain token: JzR***ZmW type: nginx_access fields_under_root: true encoding: utf-8 ignore_older: 3h - type: log paths: - /var/log/nginx/error.log - /var/log/nginx/rtfm.co.ua-error.log fields: logzio_codec: plain token: JzR***ZmW type: nginx_error fields_under_root: true encoding: utf-8 ignore_older: 3h ...
И outputs
комментируем output.elasticsearch
, и описываем output.logstash
, как сказано в документации:
... # ------------------------------ Logstash Output ------------------------------- # ... output.logstash: hosts: ["listener.logz.io:5015"] ssl: certificate_authorities: ['/etc/pki/tls/certs/COMODORSADomainValidationSecureServerCA.crt' ...
Проверяем синтаксис конфига:
[simterm]
root@rtfm-do-production-d10:/tmp# filebeat test config Config OK
[/simterm]
И подключение к Logz.io:
[simterm]
root@rtfm-do-production-d10:/tmp# filebeat test output logstash: listener.logz.io:5015... connection... parse host... OK dns lookup... OK addresses: 23.22.183.192 dial up... OK TLS... security: server's certificate chain verification is enabled handshake... OK TLS version: TLSv1.2 dial up... OK talk to server... OK
[/simterm]
Перезапускаем его:
[simterm]
root@rtfm-do-production-d10:/tmp# systemctl restart filebeat
[/simterm]
Проверяем логи:
Данные пошли.
Остались unattended-upgrades
и logrotate
.
Установка unattended-upgrades
Описывалось в Debian: автоматические обновления с помощью unattended-upgrades и отправка почты через AWS SES, настроим на новом сервере, только без AWS SES, наверно.
Официальная документация — тут>>>.
unattended-upgrades
и apt-listchanges
мы уже установили, осталось настроить обновления.
Вызываем dpkg-reconfigure unattended-upgrades
:
Отвечаем Yes.
Проверим /etc/apt/apt.conf.d/20auto-upgrades
:
APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::Enable
для включения апдейтов уже не требуется, достаточно этих двух строк.
Проверим /etc/apt/apt.conf.d/50unattended-upgrades
.
В целом, там всё можно оставить по-умолчанию, но стоит добавить:
Unattended-Upgrade::Mail
— получать письма об установленных апдейтахUnattended-Upgrade::Automatic-Reboot
— на своё усмотрение, пока можно оставить в false, включить позжеUnattended-Upgrade::Automatic-Reboot-Time
— если включаем автоматическую перезагрузку — лучше ребутаться под утро, часов в 5-6 — я просыпаюсь в 7-8, если что — с утра сразу увижу, что сервер не поднялся
Запускаем пробный апгрейд:
[simterm]
root@rtfm-do-production-d10:/tmp# unattended-upgrade -v -d --dry-run ... No packages found that can be upgraded unattended and no pending auto-removals
[/simterm]
Окей.
Теперь глянем настройки logrotate
.
logrotate
Собственно, тут уже всё настроено до нас, но проверить надо.
Все конфиги logrotate
:
[simterm]
root@rtfm-do-production-d10:/tmp# ll /etc/logrotate.d/ total 60 -rw-r--r-- 1 root root 120 Apr 19 2019 alternatives -rw-r--r-- 1 root root 122 Sep 23 2019 amplify-agent -rw-r--r-- 1 root root 173 May 12 09:57 apt -rw-r--r-- 1 root root 79 Feb 13 2019 aptitude -rw-r--r-- 1 root root 130 Aug 28 2018 btmp -rw-r--r-- 1 root root 82 May 26 2018 certbot -rw-r--r-- 1 root root 112 Apr 19 2019 dpkg -rw-r--r-- 1 root root 146 May 13 16:01 exim4-base -rw-r--r-- 1 root root 126 May 13 16:01 exim4-paniclog -rw-r--r-- 1 root root 802 Oct 12 17:46 mysql-server -rw-r--r-- 1 root root 329 Aug 24 10:18 nginx -rw-r--r-- 1 root root 155 Jul 5 06:46 php7.3-fpm -rw-r--r-- 1 root root 501 Feb 26 2019 rsyslog -rw-r--r-- 1 root root 235 Jun 8 2019 unattended-upgrades -rw-r--r-- 1 root root 145 Feb 19 2018 wtmp
[/simterm]
Ротация логов NGINX:
[simterm]
root@rtfm-do-production-d10:/tmp# cat /etc/logrotate.d/nginx /var/log/nginx/*.log { daily missingok rotate 14 compress delaycompress notifempty create 0640 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 }
[/simterm]
Возможно, тут надо будет добавить параметр size
.
Проверяем работу:
[simterm]
root@rtfm-do-production-d10:/tmp# logrotate -f -v /etc/logrotate.conf ... considering log /var/log/kern.log Now: 2020-11-04 14:25 Last rotated at 2020-11-04 00:00 log needs rotating ...
[/simterm]
Некоторые логи уже должны будут ротейтнуться, но для NGINX пока рано.
mailx
и msmtp
— отправка почты с сервера
Пользователю root будут сыпаться всякие служебные письма — хотелось бы их получать на свой почтовый ящик.
Для начала — проверяем /etc/aliases
:
[simterm]
root@rtfm-do-production-d10:/tmp# cat /etc/aliases # /etc/aliases mailer-daemon: postmaster postmaster: root nobody: root hostmaster: root usenet: root news: root webmaster: root www: root ftp: root abuse: root noc: root security: root root: [email protected]
[/simterm]
Если делали изменения в этом конфиге — выполняем:
[simterm]
root@rtfm-do-production-d10:/tmp# newaliases
[/simterm]
550 001.RDNS/PTR error. Rejected
Письма для root пойдут на [email protected], но если сейчас на него отправить почту — письмо не уйдёт:
[simterm]
root@rtfm-do-production-d10:/tmp# echo Test | mailx -s Test [email protected]
[/simterm]
Потому что идёт через MTA Exim. Его лог:
[simterm]
root@rtfm-do-production-d10:/tmp# tail /var/log/exim4/mainlog ... 2020-11-04 14:38:16 1kaJvU-00032w-7q <= root@rtfm-do-production-d10 U=root P=local S=405 ... 2020-11-04 14:39:08 1kaJvI-00032T-Dx ** [email protected] <root@rtfm-do-production-d10> R=dnslookup T=remote_smtp H=mx1.mail7.freehost.com.ua [194.0.200.210] X=TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256 CV=no DN="CN=*.freehost.com.ua": SMTP error from remote mail server after RCPT TO:<[email protected]>: 550 001.RDNS/PTR error. Rejected
[/simterm]
«550 001.RDNS/PTR error. Rejected» — потому что не настроена PTR-запись для FloatingIP, и у DigitalOcean мы сами нормально ей управлять не можем.
Потому — ставим msmtp
, что бы слать через внешний SMTP-сервер, а не локальный:
[simterm]
root@rtfm-do-production-d10:/tmp# apt -y install msmtp msmtp-mta
[/simterm]
Раньше был ssmtp
, он уже депрекейтнулся, потому используем msmtp
.
msmtp-mta
создаст симлинк /usr/sbin/sendmail
— когда mailx
будет пытаться отправить почту через sendmail
, который является дефолтным MTA — он использует msmtp
:
[simterm]
root@rtfm-do-production-d10:/tmp# ls -l /usr/sbin/sendmail lrwxrwxrwx 1 root root 12 Feb 15 2019 /usr/sbin/sendmail -> ../bin/msmtp
[/simterm]
Настраиваем /etc/msmtprc
:
defaults port 25 tls on tls_trust_file /etc/ssl/certs/ca-certificates.crt account freehost host freemail.freehost.com.ua from [email protected] auth on user [email protected] password password # Set a default account account default : freehost
Проверяем:
[simterm]
root@rtfm-do-production-d10:/tmp# echo "test username." | msmtp -a default [email protected]
[/simterm]
mailx: cannot send message: process exited with a non-zero status
Что бы отправлять почту через mailx
с помощью msmtp
— устанавливаем bsd-mailx
вместо mailutils
:
[simterm]
root@rtfm-do-production-d10:/tmp# apt -y purge mailutils root@rtfm-do-production-d10:/tmp# apt -y install bsd-mailx
[/simterm]
Иначе будем получать ошибки «mailx: cannot send message: process exited with a non-zero status» и «msmtp: no recipients found«.
Пробуем отправку с mailx
:
[simterm]
root@rtfm-do-production-d10:/tmp# echo Test | mailx -s Test [email protected]
[/simterm]
Теперь письма от unattended-upgrades
должны приходить на ящик, заданный в Unattended-Upgrade::Mail
.
В целом — на этом вроде бы всё.