Вопрос или проблема
Я написал простой bash-скрипт на машине AWS ec2 для ежедневного резервного копирования базы данных mysql (5.7).
ОС:
NAME="Amazon Linux"
VERSION="2"
ID="amzn"
ID_LIKE="centos rhel fedora"
VERSION_ID="2"
PRETTY_NAME="Amazon Linux 2"
Версия Bash
bash --version
GNU bash, версия 4.2.46(2)-release (x86_64-koji-linux-gnu)
Скрипт:
#!/bin/bash
некоторые переменные здесь
/usr/bin/xtrabackup --defaults-file=/etc/my.cnf \
--backup \
--kill-long-queries-timeout=10 \
--kill-long-query-type="select" \
--slave-info \
--login-path=dba \
--tmpdir=/prod/tmp \
--parallel=4 \
--use-memory=1024MB \
--stream=xbstream 2> /backup.log |
/usr/bin/lz4 stdin /backup_`date +%F`.lz4
if [ $? -eq 0 ]; then
echo "Ежедневное резервное копирование успешно завершено."
else
echo "Резервное копирование не удалось."
fi
когда я запускаю bash -x daily_full_bkp.sh
, я получаю следующий вывод:
+ /usr/bin/lz4 stdin /backup_2025-01-14.lz4
+ /usr/bin/xtrabackup --defaults-file=/etc/my.cnf --backup --kill-long-queries-timeout=10 --kill-long-query-type=select --slave-info --login-path=dba --tmpdir=/pfg_prod/tmp --parallel=4 --use-memory=1024MB --stream=xbstream
+ '[' 0 -eq 0 ']'
+ echo 'Ежедневное резервное копирование успешно завершено.'
Я не понимаю, что именно я упускаю. Единственное, что файл /backup_2025-01-14.lz4
уже существует после предыдущих неудачных запусков. Поэтому в этом случае он не должен возвращать код завершения 0, если я правильно понимаю.
Вопрос только о коде завершения. Происходит ли это из-за перенаправления вывода в файл?
Вот файл журнала резервного копирования:
xtrabackup: распознанные серверные аргументы: --datadir=/prod/mysql/ --tmpdir=/prod/tmp --server-id=124 --open_files_limit=65535 --open_files_limit=65535 --log_bin=/prod/logs/binlog --innodb_io_capacity=4000 --innodb_buffer_pool_size=5G --innodb_log_buffer_size=100M --innodb_log_file_size=4G --innodb_log_files_in_group=3 --innodb_undo_tablespaces=3 --innodb_open_files=65535 --innodb_flush_log_at_trx_commit=1 --innodb_flush_method=O_DIRECT --innodb_write_io_threads=2 --innodb_read_io_threads=2 --innodb_file_per_table=1 --tmpdir=/prod/tmp --parallel=4
xtrabackup: распознанные клиентские аргументы: --user=dba --password=* --host=localhost --socket=/var/lib/mysql/mysql.sock --backup=1 --kill-long-queries-timeout=10 --kill-long-query-type=select --slave-info=1 --use-memory=1024MB --stream=xbstream
250114 06:38:50 version_check Подключение к MySQL серверу с DSN 'dbi:mysql:;mysql_read_default_group=xtrabackup;host=localhost;mysql_socket=/var/lib/mysql/mysql.sock' как 'dba' (используя пароль: YES).
250114 06:38:50 version_check Подключен к MySQL серверу
250114 06:38:50 version_check Выполнение проверки версии на сервере...
250114 06:38:50 version_check Готово.
250114 06:38:50 Подключение к MySQL серверу хост: localhost, пользователь: dba, пароль: установлен, порт: не установлен, сокет: /var/lib/mysql/mysql.sock
Используется серверная версия 5.7.44-log
/usr/bin/xtrabackup версия 2.4.29 на базе MySQL сервера 5.7.44 Linux (x86_64) (идентификатор ревизии: 2e6c0951)
xtrabackup: использует posix_fadvise().
xtrabackup: переход в папку /prod/mysql/
xtrabackup: установлен лимит открытых файлов 65535
xtrabackup: использование следующей конфигурации InnoDB:
xtrabackup: innodb_data_home_dir = .
xtrabackup: innodb_data_file_path = ibdata1:12M:autoextend
xtrabackup: innodb_log_group_home_dir = ./
xtrabackup: innodb_log_files_in_group = 3
xtrabackup: innodb_log_file_size = 4294967296
xtrabackup: использование O_DIRECT
InnoDB: Количество пулов: 1
xtrabackup: Ошибка записи файла 'UNOPENED' (Код ошибки: 32 - сломанная труба)
xb_stream_write_data() не удалось.
xtrabackup: Ошибка: не удалось записать в лог-файл
xtrabackup: Ошибка записи файла 'UNOPENED' (Код ошибки: 32 - сломанная труба)
xtrabackup: Ошибка: xtrabackup_copy_logfile() не удалось.
Я проверил несколько вопросов, но они касаются другого и не объясняют мою проблему. Я все еще ищу больше статей.
bash-скрипт, выполненный через ssh, возвращает неверный код завершения 0
Неудача, если одна команда возвращает ненулевой код в скрипте
Код выхода в bash-скрипте
Вы перезаписываете $?
статусом команд echo
, которые выполнены успешно. Лучший способ:
/usr/bin/xtrabackup --defaults-file=/etc/my.cnf --backup --kill-long-queries-timeout=10 --kill-long-query-type="select" --slave-info --login-path=dba --tmpdir=/prod/tmp --parallel=4 --use-memory=1024MB 2> /backup.log --stream=xbstream | /usr/bin/lz4 stdin /backup_`date +%F`.lz4
bstatus=$?
if [ $bstatus -eq 0 ]; then
echo "Ежедневное резервное копирование успешно завершено."
else
echo "Резервное копирование не удалось. Статус=$bstatus"
fi
exit $bstatus
Ответ или решение
При написании сценариев Bash, одной из распространённых проблем является корректная обработка кода завершения команд, особенно когда они соединены в конвейер. Это может привести к тому, что скрипт возвращает код 0, даже если основная команда потерпела неудачу. Такая ситуация произошла с вашим скриптом для резервного копирования базы данных MySQL с использованием xtrabackup
и lz4
на машине AWS EC2.
Теория:
Когда вы объединяете несколько команд в конвейер с помощью вертикальной черты (|
), Bash по умолчанию возвращает код завершения последней команды в конвейере. Если последняя команда (в этом случае lz4
) выполняется успешно, даже если предыдущая (xtrabackup
) завершилась с ошибкой, общий код завершения конвейера будет 0.
Пример:
В вашем случае, ошибка в xtrabackup
не учитывается, так как Bash проверяет только состояние завершения команды lz4
. Ошибка, такая как "Broken pipe", произошедшая в xtrabackup
, по сути, игнорируется в логике вашего условия, потому что lz4
выполняется корректно и возвращает 0. Перенаправление ошибок в файл лога (2> /backup.log
) из xtrabackup
также не влияет на общий код завершения конвейера.
Применение:
Для исправления, вы можете сохранить код завершения конкретной команды, на которую хотите ориентироваться. Это можно сделать с помощью переменной, как показано в улучшенном примере:
#!/bin/bash
/usr/bin/xtrabackup --defaults-file=/etc/my.cnf \
--backup \
--kill-long-queries-timeout=10 \
--kill-long-query-type="select" \
--slave-info \
--login-path=dba \
--tmpdir=/prod/tmp \
--parallel=4 \
--use-memory=1024MB \
--stream=xbstream 2> /backup.log |
/usr/bin/lz4 stdin /backup_`date +%F`.lz4
bstatus=${PIPESTATUS[0]}
if [ $bstatus -eq 0 ]; then
echo "Daily backup successfully completed."
else
echo "Backup failed. Status=$bstatus"
fi
exit $bstatus
Использование массива PIPESTATUS
позволяет вам получить коды завершения всех команд в конвейере. В данном примере, bstatus=${PIPESTATUS[0]}
сохранит код завершения первой команды xtrabackup
. Таким образом, вы сможете правильно обработать ситуацию, когда xtrabackup
завершился с ошибкой, независимо от успешного выполнения lz4
.
Подобное использование структуры сценария помогает более точно оценить состояние выполнения вашего резервного копирования и избежать ложных сообщений об успешном завершении, когда это не соответствует действительности.