Привет! У каждого из нас бывает что после какой-то задачи, ты хочешь чем-то поделиться. Но зачастую мотивации хватает только на поделиться в рамках внутреннего Confluence. Сейчас, я реализовал решение которое объявлено в названии статьи. Сразу хотелось бы сказать, что я не претендую на истину в последней инстанции со своим решением, оно просто отражает путь который пройден мной. Более того, СУБД в кластере здесь тоже не предмет для обсуждения.
Итак, задача следующая, создать простое для конечного пользователя решение которое позволит настроить автоматическое резервное копирование базы данных. Я использовал для решения следующие инструменты:
- Python, из него состоит скрипт первичной установки, а также скрипт который выполняет резервное копирование и отправку.
- Docker, для создания образа в котором будет выполняться резервное копирование и отправка в хранилище.
- Gitlab, как система хранения, а также для ci-cd.
- Harbor, как система хранения образов и helm чартов предоставляемых конечным пользователям.
- Helm, как инструмент управления пакетами.
- Kubernetes, в нем собственно и будет развернуты PostgreSQL и CronJob
Здесь я опишу концепцию, а дальше более детально покажу реализацию.
Вкратце, Postgresql не смотрит наружу из кластера, поэтому мы должны создать образ который запустится через CronJob, сделает резервную копию, отправит ее в s3 хранилище и спрячется до следующего раза. Тот кто будет это внедрять не должен выполнять специфических действий, а только ответить на вопросы установщика.
Теперь к реализации. В интернете есть множество статей на эту тему, некоторые не актуальны, некоторые не подходящие. Часть реализации я подсмотрел вот в этой статье, она не актуальна в плане кода и манифестов, но помогла сформировать технический каркас.
Код&Контейнер. А также страсти по PostgreSQL
Для начала нам необходимо написать код, который сможет собрать резервную копию с базы и отправить ее в хранилище. Моя реализация выглядит вот так:
import os
import boto3
from datetime import datetime
def main():
# Выполняет команду pg_dump для создания резервной копии PostgreSQL
os.system('pg_dump -h $PG_HOST -U $PG_USER --section pre-data --section data --section post-data --format custom --blobs $PG_DATABASE > pgsql')
# --encoding UTF8
# Проверяет, создан ли файл резервной копии
if os.path.exists('pgsql'):
source_path = 'pgsql'
# Формирует уникальное имя файла резервной копии на основе текущей даты и времени
destination_filename = source_path + '_' + datetime.strftime(datetime.now(), "%Y.%m.%d.%H:%M") + 'UTC' + '.backup'
# Вызывает функцию для загрузки файла в S3
upload_to_s3(source_path, destination_filename)
def upload_to_s3(source_path, destination_filename):
# Создает клиент S3 с использованием учетных данных из переменных окружения
s3 = boto3.client(
's3',
aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'),
endpoint_url=os.getenv('AWS_HOST'),
region_name=os.getenv('AWS_REGION')
)
# Получает имя бакета из переменной окружения
bucket_name = os.getenv('AWS_BUCKET_NAME')
# Файл резервной копии загружает в указанный бакет
with open(source_path, "rb") as data:
s3.upload_fileobj(data, bucket_name, destination_filename)
if __name__ == '__main__':
# Вызывает функцию main, если скрипт выполняется как самостоятельное приложение
main()
Описание:
main():
- Выполняет команду
pg_dump
для создания резервной копии базы данных PostgreSQL. - Проверяет, успешно ли создан файл резервной копии (
pgsql
). - Формирует уникальное имя файла резервной копии на основе текущей даты и времени.
- Вызывает функцию
upload_to_s3()
для загрузки файла в S3.
- Выполняет команду
upload_to_s3(source_path, destination_filename):
- Создает клиент S3 с использованием учетных данных из переменных окружения.
- Получает имя бакета из переменной окружения.
- Загружает файл резервной копии в указанный бакет с именем (
destination_filename
).
name == 'main':
- Проверяет, выполняется ли скрипт как самостоятельное приложение.
- Если да, вызывает функцию
main()
.
Код достаточно тривиальный, переменные будут переданы от пользователя в дальнейшем. Важным моментом для меня были опции создания резервной копии. Обратившись к знакомым разработчикам 1С (их можно сколько угодно считать не такими как все, но на резервном копировании они съели столько собак, что некоторые из стран столько и не видели), мне подсказали использовать вот такие ключи:
--section pre-data --section data --section post-data --format custom --blobs
Чтобы резервная копия была валидной и актуальной, а самое главное из нее можно было восстановиться. Вот такое описание я нашел для этих ключей:
--section pre-data
:
- Описание: Этот ключ определяет, что нужно включить в резервную копию данные (pre-data). Обычно это включает в себя определения таблиц, внешних ключей, индексов и других структур данных, которые не изменяются в ходе выполнения транзакций.
- Помощь при резервной копии: Включение этого раздела обеспечивает структуру базы данных, необходимую для восстановления схемы данных.
--section data
:
- Описание: Этот ключ указывает на включение данных (data) в резервную копию. Это фактические записи в таблицах.
- Помощь при резервной копии: Без данных резервная копия не будет содержать реальные записи, что делает ее неполной.
--section post-data
:
- Описание: Этот ключ определяет, что следует включить в резервную копию данные (post-data). Это может включать в себя индексы, триггеры и другие элементы, которые могут изменяться в ходе выполнения транзакций.
- Помощь при резервной копии: Включение этого раздела обеспечивает дополнительные элементы, которые могут быть важными для восстановления полной функциональности базы данных.
--format custom
:
- Описание: Этот ключ определяет формат резервной копии. В данном случае, это пользовательский формат.
- Помощь при резервной копии: Пользовательский формат обеспечивает более гибкую и эффективную резервную копию, чем другие форматы, такие как plain или tar.
--blobs
:
- Описание: Этот ключ указывает, что нужно включить большие объекты (BLOBs) в резервную копию. BLOBs могут включать в себя бинарные данные, такие как изображения или документы.
- Помощь при резервной копии: Если в базе данных присутствуют большие объекты, и мы хотим их сохранить, этот ключ необходим для включения этих данных в резервную копию.
Если у кого-то есть какая-то более подробная информация о ключах, или какие-то уточнения, то вы можете поделиться, я исправлю в статье.
Следующим шагом в этой части необходимо написать манифест Dockerfile который в дальнейшем соберем через ci-cd и отправим образ в registry.
FROM python:3.9-alpine
WORKDIR /app
COPY backup.py .
RUN apk --update add \
postgresql \
python3 \
py3-pip \
&& pip3 install --upgrade pip \
&& pip3 install awscli
RUN pip install boto3
CMD ["python", "backup.py"]
В этом манифесте необходимый минимум для рабоспособности решения. Краткое описание:
FROM python:3.9-alpine
:
- Используется базовый образ Python 3.9 на основе Alpine Linux.
WORKDIR /app
:
- Устанавливается рабочая директория**
/app
** внутри контейнера, где будут храниться файлы приложения.
- Устанавливается рабочая директория**
COPY backup.py .
:
- Копируется файл
backup.py
из локальной директории внутрь контейнера в текущую рабочую директорию.
- Копируется файл
RUN apk --update add ...
:
- Устанавливаются пакеты, такие как PostgreSQL, Python 3, pip и AWS CLI.
RUN pip install boto3
:
- Устанавливается библиотека
boto3
для работы с S3.
- Устанавливается библиотека
CMD ["python", "backup.py"]
:
- Определяется команда, которая будет выполнена при запуске контейнера. В данном случае, запускается скрипт
backup.py
- Определяется команда, которая будет выполнена при запуске контейнера. В данном случае, запускается скрипт
Промежуточный манифест
На данном этапе мы можем собрать образ, отправить в некую систему хранения, создать секрет, манифест и запустить это руками в кластер. Здесь я оставлю демонстрационный секрет и манифест, если кому-то этого будет достаточно. Мы же пойдем дальше.
Команда для создания секрета:
kubectl create secret docker-registry docker-config-secret \
--docker-server="registry.ru" \
--docker-username="Alex_Vlan" \
--docker-password="TOKEN" \
--docker-email="[email protected]" \
--namespace="default"
Манифест:
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
spec:
schedule: "*/2 * * * *"
successfulJobsHistoryLimit: 2
concurrencyPolicy: Replace
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: database-backup
image: registry.ru/image:latest
env:
- name: PG_HOST
value: postgresql
- name: PGPASSWORD
value: password
- name: PG_USER
value: postgres
- name: PG_DATABASE
value: example
- name: AWS_ACCESS_KEY_ID
value: access_key
- name: AWS_SECRET_ACCESS_KEY
value: secret_key
- name: AWS_BUCKET_NAME
value: example
- name: AWS_HOST
value: https://s3.ru-1.storage.selcloud.ru
- name: AWS_PORT
value: '443'
- name: AWS_REGION
value: 'ru-1'
imagePullPolicy: Always
imagePullSecrets:
- name: docker-config-secret
В этот манифест нужно дописать(захардкодить) свои данные и запустить через kubectl apply -f manifest.yaml
Значения будут переданы в контейнер при запуске. Здесь я оставлю краткое описание манифеста:
apiVersion: batch/v1
иkind: CronJob
определяют тип ресурса Kubernetes какCronJob
.metadata
содержит метаданные, такие как имяdatabase-backup
.spec.schedule
определяет расписание в формате cron, в данном случае — каждые 2 минуты.spec.successfulJobsHistoryLimit
устанавливает лимит сохраняемых успешно завершенных задач.spec.concurrencyPolicy
определяет политику конкурентности (в данном случае, заменять существующую задачу новой).spec.jobTemplate
определяет шаблон для создания задачи.spec.template.spec.restartPolicy
устанавливает политику перезапуска контейнера.spec.template.spec.containers
содержит настройки контейнера, в том числе используемый образ и переменные окружения.spec.template.spec.imagePullSecrets
используется для указания секрета, необходимого для загрузки образа из регистра контейнеров.
Как и было сказано ранее, этого недостаточно для решения задачи, продолжаем.
Кратко о CI-CD
Учитывая что сейчас ямлики для ci-cd учат писать на всех-всех курсах для мамкиных программистов за много-много денег, здесь останется только минимум, для демонстрации и работоспособности. По условиям задачи мы отправляем проект в платформу управления кодом(конкретно в этом случае gitlab), там через ci-cd собирается образ отправляется в registry Gitlab и в Harbor. Реализация именно такая, Gitlab registry для себя, Harbor для остальных. Объединил все в один job для демонстрации.
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
default:
image: docker:24.0.4
interruptible: true
stages:
- build_deploy
build-deploy-backup-docker:
stage: build_deploy
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY/infra/database-backup:$CI_COMMIT_REF_NAME-latest .
- docker push $CI_REGISTRY/infra/database-backup:$CI_COMMIT_REF_NAME-latest
- docker images
# upload in harbor
- docker login --username $HARBOR_USER --password $HARBOR_TOKEN harbor.regitry.ru
- docker tag $CI_REGISTRY/infra/database-backup:$CI_COMMIT_REF_NAME-latest harbor.regitry.ru/images/database-backup:$CI_COMMIT_REF_NAME-latest
- docker push harbor.regitry.ru/images/database-backup:$CI_COMMIT_REF_NAME-latest
when: manual
Остаеться добавить переменные HARBOR_USER, HARBOR_TOKEN и можно запускать.
Краткое описание манифеста:
variables
:
- Определяет переменные окружения, такие как
DOCKER_HOST
,DOCKER_DRIVER
, иDOCKER_TLS_CERTDIR
, которые используются в рамках сборки и деплоя Docker-образа.
- Определяет переменные окружения, такие как
default
:
- Устанавливает образ по умолчанию для выполнения шагов CI/CD. В данном случае используется образ Docker версии 24.0.4.
stages
:
- Определяет этапы выполнения CI/CD. В данном случае, у нас есть только один этап —
build_deploy
.
- Определяет этапы выполнения CI/CD. В данном случае, у нас есть только один этап —
build-deploy-backup-docker
:
- Определяет задачу для этапа
build_deploy
. Эта задача выполняет сборку и отправку Docker-образа. script
: Содержит команды, которые выполняются внутри job. Это включает в себя логин в реестр GitLab, сборку и пуш Docker-образа, а также загрузку в Harbor.when: manual
: Определяет, что задача должна выполняться вручную, не автоматически.
- Определяет задачу для этапа
Helm. Просто Helm
На этом этапе необходимо подготовить Helm-чарты используя которые можно произвести деплой решения в кластер.
В этой реализации взято два Helm-чарта, первый для создания секрета, взят здесь. Чарт для CronJob самописный. В реализации сейчас они используются как отдельные чарты, будет видно дальше в скрипте. Не хватает времени собрать в один чтобы было более красиво.
Сейчас структура выглядит вот так:
Разберем для начала чарт database-backup-chart.
values.yaml:
image:
repository: ""
tag: ""
# set schedule
schedule: ""
# set env for inside container
env:
PG_HOST: ""
PGPASSWORD: ""
PG_USER: ""
PG_DATABASE: ""
AWS_ACCESS_KEY_ID: ""
AWS_SECRET_ACCESS_KEY: ""
AWS_BUCKET_NAME: ""
AWS_HOST: ""
AWS_PORT: ""
AWS_REGION: ""
В этом манифесте будут заданы переменные для запуска CronJob.
image
:
repository
: Определяет переменную для репозитория Docker-образа.tag
: Определяет переменную для тега Docker-образа.
schedule
:
- Определяет переменную для расписания, которое использоваться в контексте планирования задачи.
env
:
- Задает переменные окружения, которые будут использоваться внутри контейнера. В данном случае, переменные являются параметрами подключения к базе данных PostgreSQL и конфигурацией S3.
Chart.yaml
apiVersion: v2
name: database-backup-chart
description: A Helm chart for create Kubernetes CronJob
version: 0.1.0
apiVersion: v2
:
- Определяет версию Helm (API).
name: database-backup-chart
:
- Задает имя Helm-чарта.
description:
- Содержит краткое описание Helm-чарта.
version: 0.1.0
:
- Устанавливает версию Helm-чарта.
templates/cronjob.yaml
- Устанавливает версию Helm-чарта.
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ .Release.Name }}
spec:
schedule: {{ .Values.schedule }}
successfulJobsHistoryLimit: 2
concurrencyPolicy: Replace
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: {{ .Release.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
env:
- name: PG_HOST
value: {{ .Values.env.PG_HOST | quote }}
- name: PGPASSWORD
value: {{ .Values.env.PGPASSWORD | quote }}
- name: PG_USER
value: {{ .Values.env.PG_USER | quote }}
- name: PG_DATABASE
value: {{ .Values.env.PG_DATABASE | quote }}
- name: AWS_ACCESS_KEY_ID
value: {{ .Values.env.AWS_ACCESS_KEY_ID | quote }}
- name: AWS_SECRET_ACCESS_KEY
value: {{ .Values.env.AWS_SECRET_ACCESS_KEY | quote }}
- name: AWS_BUCKET_NAME
value: {{ .Values.env.AWS_BUCKET_NAME | quote }}
- name: AWS_HOST
value: {{ .Values.env.AWS_HOST | quote }}
- name: AWS_PORT
value: {{ .Values.env.AWS_PORT | quote }}
- name: AWS_REGION
value: {{ .Values.env.AWS_REGION | quote }}
imagePullPolicy: Always
imagePullSecrets:
- name: docker-config-secret-backup
metadata.name: {{ .Release.Name }}
:
- Имя CronJob формируется на основе имени Helm-релиза.
spec.schedule: {{ .Values.schedule }}
:
- Расписание выполнения задачи в формате cron. Это значение передается из файла
values.yaml
.
- Расписание выполнения задачи в формате cron. Это значение передается из файла
spec.successfulJobsHistoryLimit: 2
:
- Ограничение на количество успешных выполненных задач, которые будут сохранены в истории.
spec.concurrencyPolicy: Replace
:
- Политика управления параллелизмом задач. В данном случае, новая задача заменяет предыдущую, если она ещё не завершилась.
spec.jobTemplate.spec.template.spec.restartPolicy: OnFailure
:
- Политика перезапуска контейнера в случае сбоя задачи.
spec.jobTemplate.spec.template.spec.containers
:
- Определение контейнера, который будет выполнен внутри каждой задачи CronJob.
spec.imagePullPolicy: Always
:
- Политика загрузки Docker-образа. В данном случае, образ будет всегда загружаться перед выполнением задачи.
spec.imagePullSecrets: docker-config-secret-backup
:
- Секрет, используемый для аутентификации при загрузке Docker-образа из реестра.
Здесь будут использованы переменные, которые необходимо задать в values.yaml. Конкретно в этой реализации переменные будут передаваться в helm-чарт из скрипта установки.
Теперь helm-чарт секрета dockerconfigjson.
Здесь не будет подробного описания всего чарта, так как это сторонний чарт и описание можно посмотреть у его создателя в репозитории. Останется здесь только values.yaml чтобы не отходить от конекста реализации.
values.yaml
imageCredentials:
- registry: ""
username: ""
accessToken: ""
Здесь передаются переменные для создания секрета.
Теперь можно дополнить манифест пайплайна в ci-cd вот таким джобом:
build_deploy_tgz_chart:
stage: build_deploy
image:
name: alpine/helm
entrypoint: [""]
script:
- chart_package=$(helm package database-backup-chart | awk -F' ' '{print $NF}')
- echo $chart_package
- helm registry login -u $HARBOR_USER -p $HARBOR_TOKEN harbor.regitry.ru
- helm push $chart_package oci://harbor.regitry.ru/helm-charts
- chart_package=$(helm package dockerconfigjson | awk -F' ' '{print $NF}')
- echo $chart_package
- helm push $chart_package oci://harbor.regitry.ru/helm-charts
when: manual
Здесь мы запаковываем и отправляем helm-чарты в Harbor.
stage: build_deploy
: Этот пайплайн выполняется на этапеbuild_deploy
.image: alpine/helm
: Используется образ Alpine Linux с предустановленным Helm. Он предоставляет необходимые инструменты для упаковки и публикации Helm-чартов.entrypoint: [""]
: Переопределяет точку входа, чтобы не запускать команду по умолчанию. Это позволяет использовать Helm-команды напрямую.script
:
chart_package=$(helm package database-backup-chart | awk -F' ' '{print $NF}')
: Упаковывает Helm-чартdatabase-backup-chart
и извлекает имя упакованного файла.echo $chart_package
: Выводит имя упакованного файла в консоль.helm registry login -u $HARBOR_USER -p $HARBOR_TOKEN harbor.regitry.ru
: Логин в Harbor Registry с использованием учетных данных пользователя и токена.helm push $chart_package oci://harbor.regitry.ru/helm-charts
: Публикует упакованный Helm-чарт в Harbor Registry.chart_package=$(helm package dockerconfigjson | awk -F' ' '{print $NF}')
: Упаковывает Helm-чартdockerconfigjson
и извлекает имя упакованного файла.echo $chart_package
: Выводит имя упакованного файла в консоль.helm push $chart_package oci://harbor.regitry.ru/helm-charts
: Публикует упакованный Helm-чартdockerconfigjson
в Harbor Registry.
when: manual
: Пайплайн будет выполняться только вручную.
Сладковатый запах Питона
Итак, плавно можно перейти к реализации скрипта внедрения. Как и у многих девопсов, выбор встал между использовать Bash или Python. Последние года полтора, я предпочитаю такие небольшие вещи делать на Python. Не хочу заводить каких-либо споров, мне просто удобнее.
Здесь будут описаны куски кода, в той же последовательности, в которой они расположены в скрипте. Мой код не самый чистый и я успокаиваю себя тем, что я девопс, а не руки кривые. Поэтому, опытные разработчики, простите если своим кодом оскорбил кого-то, а также если есть какие-то вещи которые режут глаз, сообщите мне об этом, буду делать рефакторинг, изменю.
Для начала импортируем необходимые библиотеки:
import os
import subprocess
import logging
import yaml
import tarfile
from datetime import datetime
import time
import json
import sys
import os
: Модульos
предоставляет функции для взаимодействия с операционной системой.import subprocess
: Модульsubprocess
предоставляет возможность запускать новые процессы, соединять их стандартные потоки ввода/вывода/ошибок, и получать результат выполнения команд.import logging
: Модульlogging
предоставляет инфраструктуру для ведения логов.import yaml
: Модульyaml
обеспечивает работу с данными в формате YAML.import tarfile
: Модульtarfile
предоставляет функциональность для работы с файлами в формате tar.from datetime import datetime
: Этот код импортирует толькоdatetime
из модуляdatetime
.datetime
используется для работы с датой и временем.import sys
: Модульsys
предоставляет доступ к некоторым переменным и функциям, взаимодействующим с интерпретатором Python.
chart_names = ["database-backup-chart", "dockerconfigjson"]
repository = "harbor.repository.ru/images/database-backup"
tag = "main-latest"
Переменные для скрипта:
chart_names
: Helm-чарты которые в дальнейшем будут скачаны, распакованы и применены.repository
: URL репозитория с чартами.tag
: Просто тег для определения окружения.
Настройка логирования:
log_filename = f"installation_log_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.txt"
logging.basicConfig(filename=log_filename, level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
log_filename
: Генерируется имя файла для журнала, содержащее текущую дату и время.basicConfig
: Настройка базовых параметров системы логирования, включая имя файла, уровень логирования (INFO
и выше) и формат сообщений.
Функция выполнения команды:
def run_command(command):
logging.info(f"Выполнение команды: {command}")
try:
result = subprocess.run(command, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=True, check=True)
logging.info(f"Стандартный вывод:\n{result.stdout}")
except subprocess.CalledProcessError as e:
logging.error(f"Ошибка при выполнении команды: {command}")
logging.error(f"Стандартная ошибка:\n{e.stderr}")
exit(1)
run_command
: Это функция, которая принимает команду в виде строки и выполняет ее в системной оболочке.logging.info
: Записывает информационное сообщение о том, какая команда выполняется.subprocess.run
: Выполняет команду в системной оболочке с использованием модуляsubprocess
.logging.error
: В случае ошибки записывает сообщение об ошибке и стандартный вывод ошибки.exit(1)
: Принудительно завершает выполнение программы с кодом ошибки 1.
Функция проверка доступности Kubernetes кластера:
def configure_kubernetes(k8s_config_path):
print(f"\033[94mПуть к файлу конфигурации: {k8s_config_path}\033[0m")
# Проверяем, существует ли файл по указанному пути
if os.path.exists(k8s_config_path):
os.environ["KUBECONFIG"] = k8s_config_path
logging.info(f"Переменная окружения KUBECONFIG успешно установлена.")
print("\033[92mПеременная окружения KUBECONFIG успешно установлена.\033[0m")
# Проверяем доступность кластера Kubernetes по файлу конфигурации
try:
subprocess.run(["kubectl", "version"], check=True)
logging.info(f"Кластер Kubernetes доступен.")
print("\033[92mКластер Kubernetes доступен.\033[0m")
except subprocess.CalledProcessError as e:
logging.error(
f"Ошибка при проверке доступности кластера Kubernetes. Стандартная ошибка:\n{e.stderr}")
print(
"\033[91mОшибка при проверке доступности кластера Kubernetes.\033[0m")
print(f"Стандартная ошибка:\n{e.stderr}")
k8s_config_path = input(
"Введите путь к файлу конфигурации Kubernetes: ")
configure_kubernetes(k8s_config_path)
else:
print("\033[91mФайл по указанному пути не существует.\033[0m")
k8s_config_path = input(
"Введите путь к файлу конфигурации Kubernetes: ")
configure_kubernetes(k8s_config_path)
Эта функция выполняет следующие шаги:
- Выводит путь к файлу конфигурации Kubernetes.
- Проверяет существование файла по указанному пути.
- Если файл существует, устанавливает переменную окружения
KUBECONFIG
, проверяет доступность кластера Kubernetes с использованием командыkubectl version
. - Если доступ к кластеру успешен, сообщает об этом.
- Если файл по указанному пути не существует, сообщает об ошибке, запрашивает новый путь к файлу и рекурсивно вызывает саму себя.
Функция загрузки и распаковки Helm Charts.
def download_and_unpack_helm_charts(username, access_token, registry, chart_names):
run_command(
f"helm registry login -u {username} -p {access_token} {registry}")
logging.info("Авторизация")
original_directory = os.getcwd()
run_command("rm -rf database_backup_helm_charts/")
os.mkdir("database_backup_helm_charts/")
os.chdir("database_backup_helm_charts/")
chart_paths = {}
for chart_name in chart_names:
run_command(
f"helm pull oci://harbor.regitry.ru/helm-charts/{chart_name}")
chart_archive_name = max(
[f for f in os.listdir() if f.endswith(".tgz")])
output_dir = f"./{chart_name}"
with tarfile.open(chart_archive_name, "r:gz") as tar:
tar.extractall(path=output_dir)
os.remove(chart_archive_name)
chart_paths[chart_name] = output_dir
for chart_name, path in chart_paths.items():
print(
f"\033[94mРаспакованный Helm Chart '{chart_name}': {path}\033[0m")
os.chdir(original_directory)
Данный код загружает и распаковывает Helm Charts из удаленного реестра (registry) при использовании Helm CLI. Функция принимает параметры, такие как имя пользователя (username
), токен доступа (access_token
), адрес реестра (registry
) и имена Helm Charts (chart_names
), которые требуется загрузить и распаковать.
Основные шаги, которые выполняет функция:
- Авторизация в реестре:
- Используется Helm CLI для авторизации в Helm registry с предоставленными учетными данными пользователя (
username
) и токеном доступа (access_token
).
- Используется Helm CLI для авторизации в Helm registry с предоставленными учетными данными пользователя (
- Подготовка рабочей директории:
- Скрипт создает временную директорию с именем "database_backup_helm_charts/" и переходит в нее.
- Загрузка и распаковка Helm Charts:
- Для каждого указанного Helm Chart (
chart_name
), скрипт использует командуhelm pull
для загрузки Chart из указанного реестра. - Загруженный архив Helm Chart распаковывается в новую директорию с именем Helm Chart (
chart_name
). - Исходные архивы Helm Charts удаляются после распаковки.
- Для каждого указанного Helm Chart (
- Вывод результатов:
- После успешной загрузки и распаковки каждого Helm Chart, функция выводит информацию о распакованном Helm Chart, предоставляя путь к распакованному содержимому.
- Восстановление исходной директории:
- По завершении всех операций, функция возвращает текущую директорию в исходное состояние.
Функция изменения значений в values файле для создания секрета.
def insert_values_yaml_secrets(registry, username, access_token):
with open('database_backup_helm_charts/dockerconfigjson/dockerconfigjson/values.yaml', 'r') as file:
yaml_data = yaml.safe_load(file)
credentials = yaml_data['imageCredentials'][0]
credentials['registry'] = f"{registry}"
credentials['username'] = f"{username}"
credentials['accessToken'] = f"{access_token}"
with open('database_backup_helm_charts/dockerconfigjson/dockerconfigjson/values.yaml', 'w') as file:
yaml.dump(yaml_data, file)
Данная функция предназначена для изменения значений в файле YAML (values.yaml
) с целью создания секрета, связанного с данными реестра. Функция обеспечивает внесение актуальных учетных данных пользователя (имя пользователя, токен доступа) и адреса registry.
Шаги, выполняемые функцией:
- Открытие YAML файла для чтения:
- Функция открывает файл
values.yaml
.
- Функция открывает файл
- Чтение YAML данных:
- С использованием библиотеки PyYAML (
yaml.safe_load
), функция читает данные из файлаvalues.yaml
.
- С использованием библиотеки PyYAML (
- Обновление учетных данных:
- Значения, такие как
registry
,username
иaccessToken
, обновляются новыми значениями, переданными функции в качестве параметров (registry
,username
,access_token
).
- Значения, такие как
- Запись обновленных данных обратно в файл:
- Функция открывает файл
values.yaml
для записи и используетyaml.dump
для записи обновленных данных обратно в файл.
- Функция открывает файл
Функция изменения значений в values файле для создания CronJob.
def insert_values_yaml_cj(repository, tag, schedule, pg_host, pg_password, pg_user, pg_database, aws_access_key_id, aws_secret_access_key, aws_bucket_name, aws_host, aws_port, aws_region):
with open('database_backup_helm_charts/database-backup-chart/database-backup-chart/values.yaml', 'r') as file:
yaml_data = yaml.safe_load(file)
yaml_data['image']['repository'] = f"{repository}"
yaml_data['image']['tag'] = f"{tag}"
yaml_data['schedule'] = f"{schedule}"
yaml_data['env']['PG_HOST'] = f"{pg_host}"
yaml_data['env']['PGPASSWORD'] = f"{pg_password}"
yaml_data['env']['PG_USER'] = f"{pg_user}"
yaml_data['env']['PG_DATABASE'] = f"{pg_database}"
yaml_data['env']['AWS_ACCESS_KEY_ID'] = f"{aws_access_key_id}"
yaml_data['env']['AWS_SECRET_ACCESS_KEY'] = f"{aws_secret_access_key}"
yaml_data['env']['AWS_BUCKET_NAME'] = f"{aws_bucket_name}"
yaml_data['env']['AWS_HOST'] = f"{aws_host}"
yaml_data['env']['AWS_PORT'] = f"{aws_port}"
yaml_data['env']['AWS_REGION'] = f"{aws_region}"
with open('database_backup_helm_charts/database-backup-chart/database-backup-chart/values.yaml', 'w') as file:
yaml.dump(yaml_data, file, default_flow_style=False)
Данная функция предназначена для изменения значений в файле YAML (values.yaml
), используемом в контексте Helm Chart для создания CronJob.
Шаги, выполняемые функцией:
- Открытие YAML файла для чтения:
- Функция открывает файл
values.yaml
, содержащий конфигурационные параметры Helm Chart для CronJob.
- Функция открывает файл
- Чтение YAML данных:
- С использованием библиотеки PyYAML (
yaml.safe_load
), функция читает данные из файлаvalues.yaml
.
- С использованием библиотеки PyYAML (
- Замена значений:
- Обновляются значения параметров Helm Chart:
repository
иtag
для указания местоположения Docker-образа.schedule
для установки расписания CronJob.- Параметры среды (
env
) для настройки подключения к PostgreSQL (PG_HOST
,PGPASSWORD
,PG_USER
,PG_DATABASE
). - Параметры среды для настройки подключения к S3 хранилищу(
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
,AWS_BUCKET_NAME
,AWS_HOST
,AWS_PORT
,AWS_REGION
).
- Обновляются значения параметров Helm Chart:
- Открытие YAML файла для записи:
- Функция открывает файл
values.yaml
для записи обновленных данных.
- Функция открывает файл
- Запись обновленных данных обратно в файл:
- Используется
yaml.dump
для записи обновленных данных обратно в файл.
- Используется
Функция настройки шедулера.
def get_cron_schedule():
print("Выберите частоту запуска кронджоба:")
print("1. Каждый час")
print("2. Каждую ночь (в полночь)")
print("3. Каждую неделю (в воскресенье в полночь)")
print("4. Свой вариант (введите свой cron-формат)")
choice = input("Введите номер варианта (1-4): ")
if choice == "1":
return "0 * * * *"
elif choice == "2":
return "0 0 * * *"
elif choice == "3":
return "0 0 * * 0"
elif choice == "4":
custom_schedule = input("Введите свой cron-формат: ")
return custom_schedule
else:
print("Некорректный выбор. Пожалуйста, выберите от 1 до 4.")
return get_cron_schedule()
Данная функция предназначена для получения и настройки расписания (schedule) для запуска CronJob, используемого в контексте управления периодическим выполнением задачи резервного копирования базы данных.
Шаги, выполняемые функцией:
- Вывод сообщения с предложением выбора частоты запуска:
- Выводит на экран пользовательское меню с вариантами частоты запуска CronJob.
- Получение выбора пользователя:
- Запрашивает ввод пользователя для выбора одного из вариантов (1-4).
- Обработка выбора пользователя:
- В зависимости от выбора пользователя, функция возвращает соответствующий cron-формат для расписания.
- Варианты:
- "1": Каждый час — возвращает
"0 * * * *"
- "2": Каждую ночь (в полночь) — возвращает *`"0 0 "`**
- "3": Каждую неделю (в воскресенье в полночь) — возвращает
"0 0 * * 0"
- "4": Пользовательский вариант — запрашивает у пользователя ввод собственного cron-формата.
- "1": Каждый час — возвращает
- Проверка корректности выбора:
- Если пользователь ввел некорректный номер варианта, выводит сообщение об ошибке и предлагает повторить ввод.
- Рекурсивный вызов функции:
- В случае некорректного выбора, функция вызывает саму себя рекурсивно, чтобы предоставить пользователю новый выбор.
Функция выбора действия(основное меню).
def display_menu():
print("Вас приветствует программа установки резервного копирования для СУБД PostgreSQL.")
print("Выберите действие:")
print("1. Установить систему резервного копирования")
print("2. Удалить систему резервного копирования")
print("3. Посмотреть информацию о компонентах, используемых для резервного копирования")
print("0. Выйти")
Описание функции:
Данная функция предназначена для отображения текстового меню в консоли, приветствуя пользователя и предоставляя ему варианты действий в контексте установки резервного копирования для СУБД PostgreSQL.
Шаги, выполняемые функцией:
- Приветствие:
- Выводит приветственное сообщение, приветствуя пользователя и сообщая о назначении программы.
- Вывод меню:
- Печатает на экран текстовое меню с доступными действиями для выбора.
- Варианты действий:
- "1": Установить систему резервного копирования
- "2": Удалить систему резервного копирования
- "3": Посмотреть информацию о компонентах, используемых для резервного копирования
- "0": Выйти
Функция установки системы резервного копирования.
def install_backup_system():
print("Установка системы резервного копирования...")
pg_host = input("Введите PostgreSQL host. По умолчанию [backup-infrastructure-postgresql]: ").strip(
) or "backup-infrastructure-postgresql"
pg_password = input("Введите пароль для PostgreSQL []: ").strip() or ""
pg_user = input(
"Введите имя пользователя для PostgreSQL. По умолчанию [postgres]: ").strip() or "postgres"
pg_database = input(
"Введите имя базы данных для которой реализовано резервное копирование. По умолчанию [backup]: ").strip() or "backup"
aws_access_key_id = input(
"Введите ACCESS_KEY_ID для S3 системы хранения, в которой будут храниться резервные копии []: ").strip() or ""
aws_secret_access_key = input(
"Введите SECRET_ACCESS_KEY для S3 системы хранения, в которой будут храниться резервные копии []: ").strip() or ""
aws_bucket_name = input(
"Введите BUCKET_NAME для S3 системы хранения, в котором будут храниться резервные копии []: ").strip() or ""
aws_host = input("Введите URL для S3 системы хранения, в которой будут храниться резервные копии. По умолчанию [https://s3.ru-1.storage.selcloud.ru]: ").strip(
) or "https://s3.ru-1.storage.selcloud.ru"
aws_port = input(
"Введите PORT для S3 системы хранения, в которой будут храниться резервные копии. По умолчанию стандартный https порт [443] : ").strip() or "443"
aws_region = input(
"Введите REGION для S3 системы хранения, в которой будут храниться резервные копии. По умолчанию [ru-1]: ").strip() or "ru-1"
namespace_backup = input(
"Введите namespace в котором развернуто решение PostgreSQL. По умолчанию [backup]: ").strip() or "backup"
registry = "https://harbor.regitry.ru"
username = input(
"Введите имя пользователя для авторизации в системе хранения Harbor []: ").strip() or ""
access_token = input(
"Введите access_token пользователя для авторизации в системе хранения Harbor []: ").strip() or ""
k8s_config_path = input("Введите путь к файлу конфигурации Kubernetes: ")
schedule = get_cron_schedule()
print(f"Выбранная частота запуска кронджоба: {schedule}")
# Вызов функции проверки доступности кластера.
configure_kubernetes(k8s_config_path)
# Установка Helm
print("Установка Helm...")
run_command(
"curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3")
run_command("chmod 700 get_helm.sh")
run_command("./get_helm.sh")
# скачивание и распаковка helm charts
download_and_unpack_helm_charts(
username, access_token, registry, chart_names)
# Запуск функции обработки values файла values.yaml для секрета
insert_values_yaml_secrets(registry, username, access_token)
# Запуск функции обработки values файла values.yaml для Cronjob
insert_values_yaml_cj(repository, tag, schedule, pg_host, pg_password, pg_user, pg_database,
aws_access_key_id, aws_secret_access_key, aws_bucket_name, aws_host, aws_port, aws_region)
# Установка charts
check_existing_release_command = "helm list --all-namespaces --short"
existing_releases = subprocess.check_output(
check_existing_release_command, shell=True, text=True)
if "dockerconfigjson" not in existing_releases:
# Получаем текущую рабочую директорию
original_directory = os.getcwd()
os.chdir("database_backup_helm_charts/dockerconfigjson/")
run_command(
f"helm upgrade --install dockerconfigjson ./dockerconfigjson --namespace {namespace_backup}")
print("\033[92mСекрет успешно cоздан\033[0m")
# Возвращаемся в исходную директорию
os.chdir(original_directory)
else:
print("\033[93mСекрет уже существует.\033[0m")
check_existing_release_command = "helm list --all-namespaces --short"
existing_releases = subprocess.check_output(
check_existing_release_command, shell=True, text=True)
if "database-backup" not in existing_releases:
original_directory = os.getcwd()
os.chdir("database_backup_helm_charts/database-backup-chart/")
run_command(
f"helm upgrade --install database-backup ./database-backup-chart --namespace {namespace_backup}")
print("\033[92mCronJob успешно cоздан\033[0m")
# Возвращаемся в исходную директорию
os.chdir(original_directory)
sys.exit()
else:
print("\033[93mCronJob уже существует.\033[0m")
Данная функция предназначена для установки системы резервного копирования для СУБД PostgreSQL. Она взаимодействует с пользователем, собирая необходимую информацию, скачивает Helm charts, обрабатывает значения файлов YAML, устанавливает Helm, создает и обновляет Kubernetes ресурсы (Secret и CronJob) для обеспечения резервного копирования базы данных.
Шаги, выполняемые функцией:
- Получение параметров от пользователя:
- Запрашивает у пользователя информацию о параметрах установки, таких как хост PostgreSQL, пароль, имя пользователя, база данных, ключи доступа к S3-системе хранения, URL и порт S3, регион, namespace и другие.
- Выбор частоты запуска кронджоба:
- Вызывает функцию
get_cron_schedule()
, которая предоставляет пользователю выбор частоты запуска CronJob (регулярного задания).
- Вызывает функцию
- Проверка доступности кластера Kubernetes:
- Вызывает функцию
configure_kubernetes()
, которая проверяет доступность кластера Kubernetes, используя указанный путь к файлу конфигурации.
- Вызывает функцию
- Установка Helm:
- Загружает и устанавливает Helm.
- Скачивание и распаковка Helm charts:
- Вызывает функцию
download_and_unpack_helm_charts()
, которая скачивает и распаковывает Helm charts из указанного реестра и с указанными именами чартов.
- Вызывает функцию
- Обработка values файла для секрета:
- Вызывает функцию
insert_values_yaml_secrets()
, которая изменяет значения в файлеvalues.yaml
для создания секрета.
- Вызывает функцию
- Обработка values файла для CronJob:
- Вызывает функцию
insert_values_yaml_cj()
, которая изменяет значения в файлеvalues.yaml
для создания CronJob.
- Вызывает функцию
- Проверка существующих релизов:
- Проверяет существование релизов с помощью Helm.
- Установка секрета:
- Если секрета еще нет, устанавливает его в Kubernetes.
- Установка CronJob:
- Если CronJob еще не установлен, устанавливает его в Kubernetes.
- Вывод результатов:
- Выводит информацию об успешном создании или об уже существующих ресурсах.
- Завершение программы:
- Завершает выполнение программы.
- Если секрета еще нет, устанавливает его в Kubernetes.
Функция удаления системы резервного копирования.
def remove_backup_system():
print("Удаление системы резервного копирования...")
namespace_backup = input(
"Введите namespace в котором развернуто решение PostgreSQL. По умолчанию [backup]: ").strip() or "backup"
k8s_config_path = input("Введите путь к файлу конфигурации Kubernetes: ")
configure_kubernetes(k8s_config_path)
print("Удаление CronJob...")
check_existing_release_command = f"helm list --namespace {namespace_backup} --short"
existing_releases = subprocess.check_output(
check_existing_release_command, shell=True, text=True)
if "database-backup" in existing_releases:
run_command(
f"helm uninstall database-backup --namespace {namespace_backup}")
print("\033[92mCronJob успешно удален.\033[0m")
else:
print(
"\033[91mCronJob не может быть удален, так как не был найден.\033[0m")
print("Удаление системы резервного копирования...")
print("Удаление Секрета...")
check_existing_release_command = f"helm list --namespace {namespace_backup} --short"
existing_releases = subprocess.check_output(
check_existing_release_command, shell=True, text=True)
if "dockerconfigjson" in existing_releases:
run_command(
f"helm uninstall dockerconfigjson --namespace {namespace_backup}")
print("\033[92mСекрет успешно удален.\033[0m")
else:
print(
"\033[91mСекрет не может быть удален, так как не был найден.\033[0m")
sys.exit()
Данная функция предназначена для удаления системы резервного копирования для СУБД PostgreSQL. Она взаимодействует с пользователем, собирая необходимую информацию, удаляет Helm charts, удаляет Helm релизы, связанные с CronJob и Secret.
Шаги, выполняемые функцией:
- Получение параметров от пользователя:
- Запрашивает у пользователя информацию о параметрах удаления, таких как namespace, путь к файлу конфигурации Kubernetes и другие.
- Проверка доступности кластера Kubernetes:
- Вызывает функцию
configure_kubernetes()
, которая проверяет доступность кластера Kubernetes, используя указанный путь к файлу конфигурации.
- Вызывает функцию
- Удаление CronJob:
- Проверяет существование Helm релиза CronJob и, если он существует, удаляет его с помощью команды Helm. Выводит соответствующее сообщение.
- Удаление Secret:
- Проверяет существование Helm релиза Secret и, если он существует, удаляет его с помощью команды Helm. Выводит соответствующее сообщение.
- Вывод результатов:
- Выводит информацию об успешном удалении или об отсутствии найденных ресурсах.
- Завершение программы:
- Завершает выполнение программы.
Функция вывода информации.
def view_backup_system_info():
print("Просмотр информации о системе резервного копирования...")
print("Резервное копирование PostgreSQL базы данных реализовано следующим образом:")
print("- Внутри Kubernetes-кластера поднимается контейнер, в котором настроены все необходимые параметры для создания резервной копии.")
print("- Заданный при установке скрипта расписание крон-джоба определяет, как часто выполнять процесс резервного копирования.")
print("- Контейнер подключается к PostgreSQL базе данных, выполняет процедуру бэкапа и сохраняет его в хранилище S3.")
print("- Параметры для подключения к PostgreSQL и настройки хранения бэкапов задаются при установке скрипта.")
Данная функция предназначена для просмотра информации о системе резервного копирования для базы данных PostgreSQL. Функция предоставляет пользователю описание того, как реализован процесс резервного копирования после установки скрипта.
Функция запуска скрипта.
def main():
while True:
display_menu()
choice = input("Введите номер действия (0-3): ")
if choice == "1":
install_backup_system()
elif choice == "2":
remove_backup_system()
elif choice == "3":
view_backup_system_info()
elif choice == "0":
print("Выход из программы.")
break
else:
print("Некорректный ввод. Пожалуйста, выберите действие снова.")
if __name__ == "__main__":
main()
Функция main()
представляет собой основной исполняемый блок программы, который обеспечивает взаимодействие пользователя с основными функциональными частями программы. Включает в себя бесконечный цикл, который предлагает пользователю выбор действия из меню и вызывает соответствующую функцию в зависимости от выбора.
Шаги выполнения:
- Цикл выбора:
- Запускается бесконечный цикл, который предоставляет пользователю выбор из меню.
- Отображение меню:
- В каждой итерации цикла вызывается функция
display_menu()
, которая отображает пользователю меню с возможными действиями.
- В каждой итерации цикла вызывается функция
- Выбор пользователя:
- Пользователю предлагается ввести номер действия из меню (
0
— выход,1
— установка системы резервного копирования,2
— удаление системы,3
— просмотр информации). - Происходит считывание ввода пользователя (
input()
).
- Пользователю предлагается ввести номер действия из меню (
- Обработка выбора:
- В зависимости от выбора пользователя выполняется соответствующая ветвь условия (
if-elif-else
). - Если выбрано
1
, вызывается функцияinstall_backup_system()
. - Если выбрано
2
, вызывается функцияremove_backup_system()
. - Если выбрано
3
, вызывается функцияview_backup_system_info()
. - Если выбрано
0
, выводится сообщение о выходе, и цикл завершается (break
).
- В зависимости от выбора пользователя выполняется соответствующая ветвь условия (
- Некорректный ввод:
- Если введенный номер действия не соответствует ни одному из вариантов, выводится сообщение о некорректном вводе, и цикл продолжается.
Завершение программы:
- Если программа запущена напрямую (не импортирована как модуль), то выполнится последняя строка кода:
if __name__ == "__main__": main()
, что приведет к вызову функцииmain()
.
Окончание
Реализованное решение позволяет делать резервную копию СУБД в кластере Kubernetes. То что я здесь написал в целом достаточно спорно с какой стороны не посмотри. Но я искал подобное решение для проекта и несмотря на все минусы, оно справляется с поставленной задачей. Можно бесконечно улучшать его, например добавив в код проверки для каждой функции, нормально объединив Helm Charts и тд. Но как собранное в кратчайшие сроки, это решение имеет право на жизнь.