Создание контейнера для базы данных отнюдь не является излишеством. На самом деле, это позволит вам привнести все преимущества контейнеров в вашу БД.

Мы рассмотрим, как создавать контейнеры Postgres с помощью Docker и перезапускать их без потери данных, а в конце статьи с помощью нестандартного метода (использующего ConfigMaps и StatefulSets) мы развернём внутри подов Kubernetes — Postgres.

Зачем использовать контейнерные базы данных?


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

Разделение данных и базы данных


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

Портирование


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

Создание контейнеризированной базы данных


Для начала нам нужен образ, который будет служить основой для нашего контейнера. Хотя мы и можем создать образ с нуля, но в большинстве случаев это будет излишним, поскольку такое привычное ПО, как Postgres, уже содержит официальные образы контейнеров с возможностью их настройки. Поэтому для создания контейнера базы данных мы будем использовать официальный образ Postgres в хабе Docker.


Что дальше? Пора создавать конфигурацию контейнера. Это можно легко сделать с помощью файла docker-compose.

version: '3.1'
services:
    postgres-db:
        container_name: postgres-db
        image: postgres:latest
        restart: always
        environment:
            POSTGRES_USER: testadmin
            POSTGRES_PASSWORD: test123
            POSTGRES_DB: testdb
            PGDATA: /var/lib/postgresql/data/pgdata
        volumes:
            - postgres-db-data:/var/lib/postgresql/data
        ports:
            - 5432:5432
volumes:
    postgres-db-data:
        name: postgres-db-data

Мы создаём том docker для хранения данных Postgres в приведённой выше конфигурации. Поскольку этот том является многоразовым, вы сможете восстановить базовые данные, даже если контейнер будет удалён. Для контейнера Postgres мы используем последний образ Postgres с переменными окружения, задающими пользователя, пароль, базу данных и расположение данных внутри контейнера. В разделе тома мы сопоставим внутреннее расположение данных контейнера с нашим томом и откроем порт 5432.

После создания файла мы можем выполнить команду docker-compose up для запуска контейнера.


Далее мы можем выполнить команду docker ps, чтобы проверить, успешно ли запущен контейнер.


Вот и всё. Мы успешно создали контейнерную базу данных Postgres. Работает ли она? Мы можем проверить это, попытавшись подключиться к базе данных через SQL-клиент. Поэтому давайте воспользуемся SQL-клиентом Arctype для инициализации тестового соединения. Сначала укажите данные подключения к базе данных. В данном случае мы будем использовать IP-адрес хоста Docker, порт и учётные данные, которые мы указали при создании контейнера. Затем, как видно на следующем изображении, мы можем успешно инициализировать соединение с базой данных.


Создание воспроизводимой конфигурации


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

Давайте рассмотрим этот сценарий на практике. Сначала создадим таблицу test_data_table и вставим в неё несколько записей с помощью клиента Arctype.



Теперь у нас есть некоторые данные в базе данных. Давайте удалим контейнер из среды docker с помощью команды docker-compose down.


Примечание: Когда контейнер будет удалён, SQL-клиент выдаст ошибку с сообщением об отказе в подключении.


Далее внесём небольшое изменение в файл compose, чтобы изменить имя контейнера.

version: '3.1'
services:
    postgres-db:
        # New Container
        container_name: postgres-db-new
        image: postgres:latest
        restart: always
        environment:
            POSTGRES_USER: testadmin
            POSTGRES_PASSWORD: test123
            POSTGRES_DB: testdb
            PGDATA: /var/lib/postgresql/data/pgdata
        volumes:
        - postgres-db-data:/var/lib/postgresql/data
        ports:
        - 5432:5432
volumes:
    postgres-db-data:
        name: postgres-db-data

Затем снова запустите контейнер и проверьте его работоспособность с помощью команды docker ps.


Обновите таблицы в клиенте Arctype, чтобы восстановить соединение. Затем выполните простую команду SELECT для запроса данных в test_data_table, как показано ниже.


Как видите, мы можем воспроизвести контейнер и получить доступ к данным, даже если контейнер был удалён.

Управление контейнерами с помощью Kubernetes


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

Лучший способ развёртывания контейнера Postgres в Kubernetes — это использование StatefulSet. Он позволяет пользователям предоставлять stateful-приложения и настраивать постоянное хранилище, уникальные сетевые идентификаторы, автоматические обновления, упорядоченное развёртывание и масштабирование. Все эти функции необходимы для обеспечения работы stateful-приложений, таких как база данных. В этом разделе мы рассмотрим, как развернуть контейнеры Postgres в кластере K8s.

Создание и развёртывание пода Postgres


Для этого развёртывания мы создадим configmap для хранения переменных окружения, сервис для демонстрации базы данных за пределами кластера и StatefulSet для пода Postgres.

# PostgreSQL StatefulSet ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
    name: postgres-db-config
    labels:
        app: postgresql-db
data:
    POSTGRES_PASSWORD: test123
    PGDATA: /data/pgdata
---
# PostgreSQL StatefulSet Service
apiVersion: v1
kind: Service
metadata:
    name: postgres-db-lb
spec:
    selector:
        app: postgresql-db
    type: LoadBalancer
    ports:
    - port: 5432
    targetPort: 5432
---
# PostgreSQL StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
    name: postgresql-db
spec:
    serviceName: postgresql-db-service
    selector:
        matchLabels:
            app: postgresql-db
    replicas: 2
    template:
        metadata:
            labels:
                app: postgresql-db
        spec:
            # Official Postgres Container
            containers:
            - name: postgresql-db
            image: postgres:10.4
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 5432
            # Resource Limits
            resources:
                requests:
                    memory: "265Mi"
                    cpu: "250m"
                limits:
                    memory: "512Mi"
                    cpu: "500m"
            # Data Volume
            volumeMounts:
            - name: postgresql-db-disk
            mountPath: /data
            # Point to ConfigMap
            env:
            - configMapRef:
            name: postgres-db-config
# Volume Claim
volumeClaimTemplates:
    - metadata:
        name: postgresql-db-disk
    spec:
        accessModes: ["ReadWriteOnce"]
        resources:
            requests:
                storage: 25Gi

Приведённую выше конфигурацию можно свести к следующим пунктам:

  • ConfigMap — postgres-db-config: ConfigMap определяет все переменные окружения, необходимые контейнеру Postgres.
  • Service — postgres-db-lb: Служба типа балансировщика нагрузки определена для того, чтобы вывести поды за пределы контейнера, используя порт 5432.
  • StatefulSet — postgresql-db: StatefulSet настроен с использованием двух дубликатов, использующих образ контейнера Postgres с данными, смонтированными на постоянный том. Дополнительные ограничения ресурсов настроены как для контейнера, так и для тома.

После создания этой конфигурации мы можем применить её и проверить StatefulSet с помощью следующих команд.

kubectl apply -f .\postgres-statefulset.yaml
kubectl get all
kubectl get pvc


Вот и всё! Вы успешно настроили базу данных Postgres на Kubernetes.

Получение доступа к базе данных


Поскольку теперь у нас есть запущенные поды, давайте обратимся к базе данных. Поскольку мы уже настроили службу, мы можем получить доступ к базе данных, используя внешний IP этой службы. Используя клиент Arctype, укажите данные сервера — пользователя по умолчанию и базу данных (Postgres), после чего протестируйте соединение.


Как и в примере с docker, давайте создадим таблицу test_data_table и добавим в неё несколько записей. Здесь мы удалим весь StatefulSet. Если мы все настроили правильно, данные останутся, а поды будут удалены.


Теперь давайте перезагрузим StatefulSet и попробуем получить доступ к базе данных. Поскольку это новое развёртывание, вы увидите новый связанный со службой внешний IP.


Измените строку конфигурации соединения в клиенте Arctype SQL и попытайтесь подключиться.


Затем вы увидите таблицу, которую мы создали ранее. Вы сможете открыть все данные в таблице, выполнив команду SELECT, что означает, что данные будут доступны независимо от состояния подов.


Лучшие методы работы с подами баз данных


Существует несколько классных методов, которым стоит следовать при развёртывании баз данных в Kubernetes, чтобы добиться максимальной надёжности и производительности.

  1. Используйте Kubernetes Secrets для хранения конфиденциальной информации, такой как пароли и т.д. Хотя мы и хранили пароль пользователя в открытом виде в нашей конфигурации для простоты, любая конфиденциальная информация в производственной среде должна храниться в секретах Kubernetes и использоваться только по мере необходимости.
  2. Внедрите ограничения на использование ресурсов процессора, оперативной памяти и хранилища. Это поможет управлять ресурсами в кластере и гарантирует, что поды не будут перерасходовать ресурсы.
  3. Всегда настраивайте резервное копирование томов. Даже если поды можно воссоздать, вся база данных станет непригодной для использования, если основные тома данных будут повреждены.
  4. Внедряйте сетевые политики и RBAC для контроля входа и пользователей, которые могут изменять эти ресурсы, для достижения наилучшей производительности и безопасности.
  5. Используйте отдельные пространства имён, чтобы изолировать базы данных от обычных приложений и управлять подключением через службы.

Заключение


Создание контейнерных баз данных даёт пользователям возможность задействовать все преимущества контейнеризации и применять их к базам данных. Такая контейнеризация подходит для любой базы данных, от лидеров рынка, таких как MySQL и Postgres, до более новых претендентов, разработанных с нуля специально для облачных приложений. Например, Yugabytedb — это новая база данных, которая может быть запущена в любой среде Kubernetes, например, Amazon EKS. Управление контейнерными базами данных позволяет пользователям создавать отказоустойчивые, высокодоступные и масштабируемые архитектуры баз данных, используя лишь малую часть ресурсов, необходимых для развёртывания традиционных баз данных.



НЛО прилетело и оставило здесь промокод для читателей нашего блога:

15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

Комментарии (10)


  1. alemiks
    02.05.2022 11:26
    +4

    индусские практики это хорошо, но please don't https://vsupalov.com/database-in-docker/


    1. Antra
      02.05.2022 16:57
      +2

      Думаю, что в контейнер/под базу данных засовывают только если она слабо нагружена. И на этой машине хватает мощности еще на десяток другой подов. Явно это не для терабайтов АБС и не mission critical. Например, в такую "карманную" БД выгружается маленький кусочек из огромной базы, и с этим данными аналитики и прочие (возможно даже внешние, для которых данные выгрузили деперсонифицированными) делают, что хотят. А вот пускать (особенно удаленных пользователей) в "самое сердце компании" не хочется.

      И вот тут начинаются муки выбора. Вроде бы правильнее поднять отдельную VM для такой базы. Но какие преимущества перед контейнером/подом? Как под может переехать на другую ноду, так и VM мигрирует на другой хост. По-любому данные надо держать где-то снаружи. Чем условный VMDK на внешней хранилке (возможно отданный через iSCSI, а не "родной vSAN") или подмонтированный NFS лучше Persistent Volume?

      Ну и если уж говорить о persistent volume, что лучше? iSCSI? NFS?

      Помню несколько лет назад нарекания на проблемы MySQL при расположении данных на NFS. Но вроде бы это про NFS3 было. С NFS4 про такие проблемы не попадалось.

      Разумеется, лучше быть богатым и здоровым, чем бедным, но больным. И в здравом уме при наличии огромной БД и возможности нарезать в ней инстанс, скорее так и сделают. Но если все-таки надо "что-то маленькое" (будь то докер или VM), чего действительно стоит избегать, а что более-менее норм для хранения данных?


      1. JuriM
        02.05.2022 18:52
        -1

        У нас терабайтные базы постгреса запущены в кубернетес кластерах, которые в свою очередь работают на проксмоксах, через csi-lvm. Особых проблем нет, кроме огромного обьема бэкапов wal-ов.

        Сейчас правда собираются переходить со своих in-house костылей на чоткий zalando operator


        1. Antra
          02.05.2022 20:53

          Терабайтные? Ничего себе!

          /metal-stack/csi-driver-lvm или что-то другое? Мне показалось, это продвинутая замена hostPath, т.е. под все-таки прибит гвоздями к ноде? С точки зрения производительности оно вполне логично, но интересны также варианты с внешними хранилками, скажем Synology, TrueNAS...


          1. JuriM
            02.05.2022 22:24

            /metal-stack/csi-driver-lvm да, и да - под прибит к выделеной ноде


      1. creker
        02.05.2022 21:10
        +3

        Нежелание ткать базу в кубер это уже больше устаревшие предрассудки. Особенно в свете наличия всяких разных HA cloud-native реализаций. Если у вас сетевой сторадж, то пофиг вообще где база запущена, в кубере или нет. Точно так же iscsi/ceph/gluster цепляете и вперед. Можно базу прибить к ноде, если там какой-то хитрый сторадж. Главный профит от контейнера - унификация окружения. Добавлять в этот микс виртуалки себе дороже. Если уж поднимать взрослый инстанс базы, то брать под это отдельную железку с четко подобранные компонентами, настроенным ядром и т.д.


        1. Antra
          03.05.2022 08:35

          Вот такой подход полностью соответсвует моим ощущениям! Спасибо!


  1. kiloper
    02.05.2022 13:22
    +1

    В случае субд контейнерной в swarm или кубернетес следует указывать конкретную ноду, на которой будет произведён запуск контейнера. Иначе тома не будет, в случае запуска на произвольной норде. Том в nfs я так понимаю для субд не делается?

    Верно я понимаю?

    почему вы реплику 2 тогда поставили? Как будет обеспечиваться синхронизация данных?


    1. creker
      02.05.2022 21:05

      В случае субд контейнерной в swarm или кубернетес следует указывать конкретную ноду, на которой будет произведён запуск контейнера. Иначе тома не будет, в случае запуска на произвольной норде.

      Все зависит от storage класса. Кубер просто не запустит под, если он не сможет подцепить том. Если том какой-нить localpath, который сам по себе всегда прибит к ноде, то пока это нода лежит, поды тоже будут лежать и ждать своего часа. А ежели это ceph и прочие облачные стораджи, то спокойно подцепит том с любой ноды, т.к. том сам по себе сетевой. Ваша ситуация может произойти в случае использования hostpath. Там да, под может оказаться на любой ноде и можно попасть в забавную ситуацию, когда внезапно все данные из базы пропали. Просто потому что под мигрировал на другую ноду и запустился с пустым хранилищем.

      Вот про две реплики действительно непонятно. В итоге получаются два постгре, которые друг про друга ничего не знают. У них свои независимые вольюмы.


  1. creker
    02.05.2022 21:15
    +4

    Статья какой-то лютый бред. Ванильный постгре, никаких кастомных конфигов и надстроек, statefulset, 2 реплики, раздельный сторадж. Это что вообще и как? В итоге запустилось два постгре совершенно независимых и никак не связанных. Зачем это нужно? Statefulset не превратит постгре магическим образом в HA базу.