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

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

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

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

Далее:

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

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

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

Подготовка

AWS

Корзина S3 

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

[simterm]

$ aws s3api create-bucket --bucket rtfm-prod-db-backups --region eu-west-1
{
    "Location": "/rtfm-prod-db-backups"
}

[/simterm]

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:

[simterm]

$ 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"
    }
}

[/simterm]

Проверяем:

[simterm]

$ 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"
        }
    ]
}

[/simterm]

IAM пользователь

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

[simterm]

$ 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"
    }
}

[/simterm]

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

[simterm]

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

[/simterm]

Проверяем:

[simterm]

$ 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"
        }
    ]
}

[/simterm]

Создаём ACCESS_KEY и SECRET_ACCESS_KEY:

[simterm]

$ 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"
    }
}

[/simterm]

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

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

[simterm]

$ 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

[/simterm]

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

[simterm]

$ 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

[/simterm]

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

[simterm]

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

[/simterm]

bash скрипт

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

[simterm]

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

[/simterm]

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

  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

Запускаем:

[simterm]

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.

[/simterm]

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

[simterm]

$ 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

[/simterm]

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

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

Готово.

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