AWS: IAM и bash скрипт бекапа MySQL/MariaDB баз в AWS S3

Автор: | 10/13/2017
 

Задача — набросать скрипт для создания бекапов всех баз сервера БД и сохранять их в корзину.

В общем — всё просто: бекапы делаем с помощью mysqldump, в S3 корзину пушим с помощью AWS CLI.

Далее:

  1. создаём корзину для бекапов
  2. создаём пользователя с read-write политикой для доступа к этой корзине
  3. и сам скрипт

Для простоты — всё вносим в bash-скрипт, его — добавляем в крон.

Интереснее было бы скрипт написать на Python с boto3 — но время.

Подготовка

AWS

Корзина S3 

Создаём корзину, в которой будут храниться бекапы баз данных — create-bucket:

aws s3api create-bucket --bucket rtfm-prod-db-backups --region eu-west-1
{
"Location": "/rtfm-prod-db-backups"
}
IAM политика

Создаём политику доступа только к этой корзине, файл rtfm-prod-db-backups.policy.json:

The policy is separated into two parts because the ListBucket action requires permissions on the bucket while the other actions require permissions on the objects in the bucket.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::rtfm-prod-db-backups"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": ["arn:aws:s3:::rtfm-prod-db-backups/*"]
    }
  ]
}

Подробнее — тут>>>.

Добавляем эту политику в аккаунт — create-policy:

aws iam create-policy --policy-name rtfm-prod-db-backups --policy-document file://rtfm-prod-db-backups.policy.json
{
"Policy": {
"PolicyName": "rtfm-prod-db-backups",
"PolicyId": "ANPAI647ABN34GYLW5I4O",
"Arn": "arn:aws:iam::264***286:policy/rtfm-prod-db-backups",
"Path": "/",
"DefaultVersionId": "v1",
"AttachmentCount": 0,
"IsAttachable": true,
"CreateDate": "2017-10-13T08:55:27.670Z",
"UpdateDate": "2017-10-13T08:55:27.670Z"
}
}

Проверяем:

aws iam list-policies --scope Local
{
"Policies": [
{
"PolicyName": "rtfm-prod-db-backups",
"PolicyId": "ANPAI647ABN34GYLW5I4O",
"Arn": "arn:aws:iam::264***286:policy/rtfm-prod-db-backups",
"Path": "/",
"DefaultVersionId": "v1",
"AttachmentCount": 0,
"IsAttachable": true,
"CreateDate": "2017-10-13T08:55:27Z",
"UpdateDate": "2017-10-13T08:55:27Z"
}
]
}
IAM пользователь

Создаём отдельного пользователя для бекапов:

aws iam create-user --user-name rtfm-prod-db-backups
{
"User": {
"Path": "/",
"UserName": "rtfm-prod-db-backups",
"UserId": "AID***AR2",
"Arn": "arn:aws:iam::264***286:user/rtfm-prod-db-backups",
"CreateDate": "2017-10-13T08:57:40.966Z"
}
}

Подключаем ему созданную ранее политику:

aws iam attach-user-policy --user-name rtfm-prod-db-backups --policy-arn arn:aws:iam::264***286:policy/rtfm-prod-db-backups

Проверяем:

aws iam list-attached-user-policies --user-name rtfm-prod-db-backups
{
"AttachedPolicies": [
{
"PolicyName": "rtfm-prod-db-backups",
"PolicyArn": "arn:aws:iam::264***286:policy/rtfm-prod-db-backups"
}
]
}

Создаём ACCESS_KEY и SECRET_ACCESS_KEY:

aws iam create-access-key --user-name rtfm-prod-db-backups
{
"AccessKey": {
"UserName": "rtfm-prod-db-backups",
"AccessKeyId": "AKI***ZJA",
"Status": "Active",
"SecretAccessKey": "dAQ***oDT",
"CreateDate": "2017-10-13T09:02:32.872Z"
}
}

Проверяем доступ.

Создаём новый именованный профиль для CLI:

aws configure --profile rtfm-prod-db-backups
AWS Access Key ID [None]: AKI***ZJA
AWS Secret Access Key [None]: dAQ***oDT
Default region name [None]: eu-west-1
Default output format [None]: json

Пробуем загрузить файл:

touch testfile
aws s3 --profile rtfm-prod-db-backups cp testfile s3://rtfm-prod-db-backups
upload: ./testfile to s3://rtfm-prod-db-backups/testfile

И пробуем просмотреть корзину:

aws s3 --profile rtfm-prod-db-backups ls s3://rtfm-prod-db-backups
2017-10-13 12:09:02          0 testfile

bash скрипт

Потребуется AWS CLI, на сервере устанавливаем:

apt update && apt -y install python-pip
pip install awscli

Скрипт состоит из трёх функций:

  1. mysql_all_dbs_backup (): получает список всех баз и для каждой (кроме performance_schema и mysql) выполняет mysqldump
  2. push_to_s3 (): загружает файл в AWS S3 через AWS CLI
  3. save_backups (): находит все файлы бекапов, созданные mysql_all_dbs_backup (), и вызывает push_to_s3 () для сохранения их в корзину

Что ещё хорошо бы сделать — это удаление старых бекапов скриптом или настроить AWS S3 Lifecycle политики.

Сам скрипт mysql_all_dbs_backups_to_s3.sh:

#!/usr/bin/env bash

MYSQL_ROOT=root
MYSQL_PASS=password

AWS_ACCESS_KEY_ID=keyid
AWS_SECRET_ACCESS_KEY=secretkey

S3_BACKUPS_BUCKET="rtfm-prod-db-backups"

BACKUPS_LOCAL_PATH="/tmp"
BACKUP_DATE="$(date +"%d_%m_%y")"

mysql_all_dbs_backup () {

    # get all databases list
    databases=$(mysql -u $MYSQL_ROOT -p$MYSQL_PASS -e "SHOW DATABASES;" | tr -d "| " | grep -v Database)

    cd $BACKUPS_LOCAL_PATH || { echo "ERROR: can't cd to the $BACKUPS_LOCAL_PATH! Exit."; exit 1; }

    for db in $databases; do
        if [[ "$db" != "information_schema" ]] && [[ "$db" != "performance_schema" ]] && [[ "$db" != "mysql" ]] && [[ "$db" != _* ]] ; then
            # e.g. 14_10_17_rtfm_db1.sql.gz
            local backup_name="$BACKUP_DATE"_$db.sql.gz
            echo "Dumping database: $db to $BACKUPS_LOCAL_PATH/$backup_name"
            mysqldump -u $MYSQL_ROOT -p$MYSQL_PASS --databases $db | gzip > $backup_name
            [[ -e $backup_name ]] && echo "Database $db saved to $backup_name..." || echo "WARNING: can't find $db dump!"
        fi
    done
}

push_to_s3 () {

    local backup_name=$1

    echo "Uploading file $backup_name..."
    AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY aws s3 cp $backup_name s3://$S3_BACKUPS_BUCKET/$backup_name

}

save_backups () {

    # get all databases list
    databases=$(mysql -u $MYSQL_ROOT -p$MYSQL_PASS -e "SHOW DATABASES;" | tr -d "| " | grep -v Database)

    cd $BACKUPS_LOCAL_PATH || { echo "ERROR: can't cd to the $BACKUPS_LOCAL_PATH! Exit."; exit 1; }

    # like an assert - if $databases empty - exit
    [[ $databases ]] || exit 1

    for db in $databases; do
        if [[ "$db" != "information_schema" ]] && [[ "$db" != "performance_schema" ]] && [[ "$db" != "mysql" ]] && [[ "$db" != _* ]]; then
            # e.g. 14_10_17_rtfm_db1.sql.gz
            local backup_name="$BACKUP_DATE"_$db.sql.gz
            if [[ -e $backup_name ]]; then
                # for testing before run - echo instead of push_to_s3() file instead of rm
                # echo "Pushing $backup_name && file $backup_name && echo "Done."
                push_to_s3 $backup_name && rm $backup_name && echo "Done."
            else
                echo "ERROR: can't find local backup file $backup_name! Exit."
                exit 1
            fi
        fi
    done
}

echo -e "\nStarting MySQL backup at $(date) to /tmp/\n"

if mysql_all_dbs_backup; then
    echo -e "\nLocal backups done."
else
    echo -e "\nERROR during performing backup! Exit.\n"
    exit 1
fi

echo -e "\nStarting S3 upload to s3://$S3_BACKUPS_BUCKET\n"

if save_backups; then
    echo -e "\nUpload done.\n"
else
    echo -e "\nERROR during upload to S3!. Exit.\n"
    exit 1
fi

Запускаем:

root@ip-172-31-64-60:/home/admin# ./scripts/mysql_all_dbs_backups_to_s3.sh
Starting MySQL backup at Sat Oct 14 06:50:48 UTC 2017 to /tmp/
Dumping database: m_blog to /tmp/14_10_17_m_blog.sql.gz
Database m_blog saved to 14_10_17_m_blog.sql.gz...
Dumping database: one to /tmp/14_10_17_one.sql.gz
Database one saved to 14_10_17_one.sql.gz...
Dumping database: rtfm_db1 to /tmp/14_10_17_rtfm_db1.sql.gz
Database rtfm_db1 saved to 14_10_17_rtfm_db1.sql.gz...
Dumping database: setevoy_ki to /tmp/14_10_17_setevoy_ki.sql.gz
Database setevoy_ki saved to 14_10_17_setevoy_ki.sql.gz...
Local backups done.
Starting S3 upload to s3://rtfm-prod-db-backups
Uploading file 14_10_17_m_blog.sql.gz...
upload: ./14_10_17_m_blog.sql.gz to s3://rtfm-prod-db-backups/14_10_17_m_blog.sql.gz
Done.
Uploading file 14_10_17_one.sql.gz...
upload: ./14_10_17_one.sql.gz to s3://rtfm-prod-db-backups/14_10_17_one.sql.gz
Done.
Uploading file 14_10_17_rtfm_db1.sql.gz...
upload: ./14_10_17_rtfm_db1.sql.gz to s3://rtfm-prod-db-backups/14_10_17_rtfm_db1.sql.gz
Done.
Uploading file 14_10_17_setevoy_ki.sql.gz...
upload: ./14_10_17_setevoy_ki.sql.gz to s3://rtfm-prod-db-backups/14_10_17_setevoy_ki.sql.gz
Done.
Upload done.

Проверяем содержимое корзины:

aws s3 ls s3://rtfm-prod-db-backups
2017-10-14 09:50:55     163857 14_10_17_m_blog.sql.gz
2017-10-14 09:50:56    2014052 14_10_17_one.sql.gz
2017-10-14 09:50:58    9775699 14_10_17_rtfm_db1.sql.gz
2017-10-14 09:51:01     300827 14_10_17_setevoy_ki.sql.gz

Осталось добавить скрипт в крон и запускать каждую ночь:

0 2 * * *  /home/admin/scripts/mysql_all_dbs_backups_to_s3.sh > /var/log/mysql/mysql_all_dbs_backups_to_s3.log

Готово.

Скрипт и файл IAM политики можно посмотреть в Github.