Spark-in.me - Часть 4 - Базовое админство для обычных человеков (postgres и не только)

Или как сделать критические моменты вашей жизни менее критическими

Posted by yara_tchk on June 11, 2017

 

Статьи цикла

Spark-in.me - как, зачем и почему. Часть 1 - почему?

Spark-in.me - как, зачем и почему. Часть 2 - как? Архитектура приложения и структура БД

Spark-in.me Часть 3 - DIY поддержка и админство сайта

Spark-in.me Часть 4 - Базовое админство для обычных человеков (postgres и не только)

Spark-in.me Часть 5 - переход на HTTPS

 

У слова backup есть несколько значений, как и у других похожих слов, например setup - это еще и "прикид", а не только набор натроек, устройств и еще бог весть чего.

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

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

 

Купаться в тазиках можно по-разному...

Я уже успела упомянуть логи. Логи - это такие летописи всякой ерунды, происходящей в вашей системе. Ведут эти летописи как сама система, так и разного рода приложения. Приложения могут писать в отдельные файлы, а могут и в системные. Зачем нужны логи? Логи позволяют "найти все" - найти ошибку и подробности ее возникновения, что здорово помогает затем ее локализовать и устранить. Логи незаменимы, когда ошибку трудно воспроизвести и кажется, что условием возникновения ошибки является святой рандом. Есть целые энтерпрайз решения для хранения, структуризации и анализа логов it-систем, и, надо сказать, это очень удобно, здорово и дорого.

Бекапы бд Postgres

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

Мы не админы, мы обычные человеки, которые поставили себе прекрасную СУБД. Но данные терять не хотим и не хотим предоставить воле случая возможность испепелить наше детище. Что же делать? Настраивать бекапы и логи, конечно же. В самом простом и неказистом виде.

Расскажу, как делаю я. Как вы понимаете, правильнее открыть документацию, прочитать пару статей и сделать выбор. Но можно обойтись и так. Потом можно осознать, что вам нужно что-то другое, что у этого подхода есть свои минусы и перейти на что-то более соответсвующее именно вам.

Положим, я создала в кластере две базы данных. Я делаю бекап каждой из них и складываю бекапы в папку, папку называю текущей датой. Делается это так:

TIME=`date '+%m-%d-%Y'`
mkdir -p /path/to-your-db-backups/$TIME pg_dump mydb1 > /path/to-your-db-backups/$TIME/mydb1.sql
pg_dump mydb2 > /path/to-your-db-backups/$TIME/mydb2.sql

 

Если у вас бекапы хранятся на отдельном диске, на котором рейд массив, это приемлемо. В противном случае бекапы лучше утащить куда-нибудь в более стабильное место. Если у вас есть такое стабильное место (например, отдельная помойка или сервер, на котором ничего потенциально деструктивного не происходит, потому что там торчит необновляющийся контент вашего древнего сайта) и туда есть доступ по ssh, то лучше перенести бекапы туда. Иначе тазик накроет вместе с системой и ваши бекапы тоже, и вы ничего не спасете.

Чтобы делать это автоматически, лучше настроить ssh без пассфразы (кому интересно, почитайте про Алису и Боба и RSA, стандартная штука). Хороший и простой гайд, который я периодически навещаю, - тут. Нужно только определиться, где вы будете выполнять скрипт бекапа. Делать это можно как на системе, которую вы бекапите, так и на системе, в которую вы складываете бекап. Для переноса файлов используйте secure copy - scp. После переноса не забудьте удалить файл из системы, которую бекапили, чтобы не захламлять. И будьте осторожны с удалением.

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

TIME=`date '+%m-%d-%Y'`
mkdir -p /path/to/your/backups/local/backup-$TIME
ssh sshuser@remote "pg_dump mydb > /etc/postgresql/x.y/backups/backup-$TIME.sql"
scp sshuser@remote:/etc/postgresql/x.y/backups/backup-$TIME.sql /path/to/you/backups/local/backup-$TIME
sleep 2m
ssh rsshuser@remote "rm /etc/postgresql/x.y/backups/backup-$TIME.sql"

 

Вот пример скрипта, который бекапит одну из наших систем целиком. Скрипт запускается по крону на домашнем сервере, выполняя ssh-команды на удаленном сервере.

#START
# This Command will add date in Backup File Name.
TIME=`date +%b-%d-%y`
# Here i define Backup file name format.
FILENAME=some-system-backup-$TIME.tar.gz
# Backed up folder (system root) location
SRCDIR=/
# Destination of backup file
DESDIR=/home/backups
# exclude folder list
EXCLUDE='--exclude /home/backups --exclude=/another'
# Do not include files on a different filesystem
ONEFSYSPARAM='--one-file-system'
# test command validity
# echo -e tar -cvpzf $DESDIR/$FILENAME $EXCLUDE $ONEFSYSPARAM $SRCDIR
ssh sshuser@remote "tar -cpzf $DESDIR/$FILENAME $EXCLUDE $ONEFSYSPARAM $SRCDIR"
scp sshuser@remote:$DESDIR/$FILENAME /place/backup/here/
ssh sshuser@remote "echo 'WHOLE_SYSTEM_BACKUP is successful: $(date)' >> /home/bash-scripts/cron_log.log"
ssh sshuser@remote "rm $DESDIR/$FILENAME"
#END

 

Немного о выборе системы, на которой выполнять скрипт. Я предпочитаю использовать систему, которая хранит бекапы. Во-первых, вы настроите порядок всех бекапов в кроне так, чтобы процессы не мешали друг другу (а системные бекапы жрут много и выполняются долго). Во-вторых, обязательно настраивайте ssh так, чтобы в случае, если система перестанет вам принадлежать, у нее не было безпарольного доступа на ваши сервера. То есть, если помойка всегда будет вашей, настройте безпарольный доступ с нее на условно чужую систему (арендованный сервер),  не наоборот.

Ну и наконец не забывайте о порядке. Лучше всего делать бекапы автоматически и так же автоматически их удалять. Закиньте скрипты в крон (crontab -e). Храните не один последний бекап, храните несколько. Пример команды для автоматической чистки, которая удаляет все кроме последних 4 файлов в директории (настоятельно рекомендую прочесть, почему не надо парсить ответ ls и почему эта команда не подходит для бекапов с названиями, содержащими пробел):

DIR=/path/to-your-backups/somesystem/somedate
ls -dt $DIR/* | tail -n +5 | xargs rm -r --
 

И, конечно же, не забывайте просматривать свои бекапы вручную время от времени. Они могут перестать создаваться - смотрите на даты. Заглядывайте в папки на предмет отсутсвия содержимого. Еще рекомендую чекать размер бекапов. Если бекап три дня назад весил 14 гб, а сегодня 9 гб, при этом вы не помните масштабной чистки в системе, это повод задуматься. Если бекап маленький, можно даже заглянуть внутрь архива.

Разворачиваем бекапы

Я ни разу не восстанавливала систему из архива файлов, не пробовала. Знаю только, что для этого нужен загрузочный диск и физический доступ к серверу) Тут я ничего особо не смогу рассказать. Вероятно, cтоит как-нибудь научиться этому на примере распберри. Статьи, обязательные к прочтению, на эту тему - раз, два, три.

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

tar -cpzf /destination/directory/filename --exclude /exclude/directory1 --exclude /exclude/directory2 --one-file-system /source/directory

Мне эти бекапы нужны в одном случае - когда мои бекапы (то есть бекапы базы данных) утеряны или не существовали никогда (было такое).

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

psql mydb1 < /path/to-your-db-backup/mydb1.sql

 

Что касается разворачивания базы из архива, то это можно сделать, надо лишь учесть несколько нюансов. Восстановите данные из /var/lib/postgresql, а настройки из /etc/postgresql (если ваши директории нестандартные - восстанавливайте оттуда). Если настройки утеряны - восстановите вручную, по сути, просто нужно поправить конфиг. Юзеров в моем случае приходится пересоздавать. Все файлы должны иметь соответсвующие настройки доступа (chmod в помощь) и оунером, конечно же, должен быть юзер postgres (поможет команда chown).

Логирование в postgres

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

В постгрес логи настраиваются очень просто, через postgresql.conf в разделе error reporting and logging. Вам просто нужно задать удобные для вас настройки. Лучше всего в таком случае открыть документацию и выбрать нужные настройки. Рекомендую настроить логи по дням, хранить несколько неделю (в документации есть пример того, как это сделать), а дальше в зависмости от уровня паранойи и уровня загрузки базы: можно логировать любые входы, любые запросы, а можно логировать только запросы, которые выполняются долго. Еще можно логировать дедлоки (почитайте про взаимные блокировки). Такие штуки постгрес разруливает сам, но в общем, не забывайте про их существование.

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

Создать пользователя - минимум прав и чтение базы

Рано или поздно нужно делиться данными и работой с ними с кем-то еще. Приходится создавать новых пользователей в базе (никогда не шарьте одного и того же юзера на всех, потом не узнаете, кто это грохнул все и почему. Один человек = 1 или больше юзеров). Есть много хороших статей, которые расскажут вам, как все сделать по канону. С группами, наследованием ролей, доступом в конкретную схему или конкретные таблицы. Все это можно сделать, если нужно.

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

create role somerole nocreaterole nocreateuser nocreatedb noinherit login noreplication connection limit 5 password 'somepassword';alter role somerole set statement_timeout=30000;
grant select on all tables in schema public to somerole;
 

Терминировать зомби-коннекты

Зачастую так бывает, что коннект был создан, но не был закрыт. Такие коннекты продолжают висеть некоторое время, потом постгрес сам их находит и предает праведному огню. Чтобы такие коннекты не плодились почем зря, мы и задали нашему юзеру ограничение по количеству коннектов. Тем не менее, может случиться так, что нужно все-таки вручную запустить чистку зомбаков. Я использую решение отсюда, его можно подредактировать под ваши нужды.

WITH inactive_connections AS (
    SELECT
        pid,
        rank() over (partition by client_addr order by backend_start ASC) as rank
    FROM 
        pg_stat_activity
    WHERE
        -- Exclude the thread owned connection (ie no auto-kill)
        pid <> pg_backend_pid( )
    AND
        -- Exclude known applications connections
        application_name !~ '(?:psql)|(?:pgAdmin.+)'
    AND
        -- Include connections to the same database the thread is connected to
        datname = current_database() 
    AND
        -- Include connections using the same thread username connection
        usename = current_user 
    AND
        -- Include inactive connections only
        state in ('idle', 'idle in transaction', 'idle in transaction (aborted)', 'disabled') 
    AND
        -- Include old connections (found with the state_change field)
        current_timestamp - state_change > interval '5 minutes' 
)
SELECT
    pg_terminate_backend(pid)
FROM
    inactive_connections 
WHERE
    rank > 1 -- Leave one connection for each application connected to the database

 

Остается только пожелать удачи и поменьше тазиков =)

 

 

Статьи цикла

Spark-in.me - как, зачем и почему. Часть 1 - почему?

Spark-in.me - как, зачем и почему. Часть 2 - как? Архитектура приложения и структура БД

Spark-in.me Часть 3 - DIY поддержка и админство сайта

Spark-in.me Часть 4 - Базовое админство для обычных человеков (postgres и не только)

Spark-in.me Часть 5 - переход на HTTPS