
В статье рассмотрим MySQL-оператор Moco, его возможности и некоторые недостатки, а также особенности его развёртывания и сборки образов.
Содержание:
Несколько слов о проблеме сохранности данных
В ходе эксплуатации кластеров баз данных (БД), развёрнутых в K8s, многие сталкиваются с проблемой обеспечения согласованности данных на узлах кластера БД. Учитывая относительно короткий жизненный цикл каждого конкретного пода, периодический рестарт узлов кластера БД неизбежен. Предотвратить отказ в обслуживании в случае пропадания узла, обслуживающего запросы на чтение, несложно. Совсем другое дело — когда пропадает узел, обслуживающий запросы на запись.
Для поддержания доступности сервиса при исчезновении лидера производится switchover или failover роли лидера на другой доступный узел.
Если при этом не все данные, записанные на узел, служивший лидером до выполнения switchover/failover, успели отреплицироваться на другие узлы, неизбежно возникнет рассогласование данных в кластере. Кроме того, предыдущий лидер, вернувшись в строй, не сможет присоединиться к кластеру, пока рассогласование не будет устранено. В самом простом случае оно может быть устранено путём удаления хранящихся на бывшем лидере уникальных данных, что в большинстве случаев недопустимо в продуктовых средах.
Для решения проблемы нарушения согласованности и потери данных применяются различные механизмы синхронной или полусинхронной репликации. Эти механизмы обеспечивают успешное завершение транзакции только в том случае, когда изменяемые данные успешно переданы на другие узлы кластера. Разумеется, это замедляет выполнение транзакций, но такова необходимая плата за обеспечение согласованности и целостности данных.
В случае с PostgreSQL популярные операторы реализуют возможность полной или частичной синхронной репликации между узлами кластера средствами самого PostgreSQL. А в случае с MySQL точно такого же функционала в СУБД нет, и проблема решается различными способами. Популярные MySQL-операторы решают их так:
Оператор |
Механизм репликации |
Репозиторий |
Oracle MySQL Operator for Kubernetes |
Group Replication |
|
Oracle MySQL NDB Operator |
NDB Cluster |
|
Percona Operator for MySQL |
Group Replication |
|
Percona Operator for MySQL based on Percona XtraDB Cluster |
Percona XtraDB Cluster |
Перечисленные выше механизмы репликации имеют свои ограничения. Подробнее о них можно узнать, перейдя по ссылкам ниже:
Эти ограничения, а также некоторые другие особенности поведения данных кластеров играют существенную роль при планировании миграции существующей инфраструктуры БД в Kubernetes. На практике они иногда делают такую миграцию просто невозможной без внесения изменений в приложения, работающие с MySQL.
В качестве возможной альтернативы перечисленным решениям сегодня мы рассмотрим Moco MySQL Operator.
Возможности Moco
Репозиторий оператора: https://github.com/cybozu-go/moco
На текущий момент поддерживаются LTS-версии MySQL 8.0 и 8.4.
Поддерживается создание кластера из 1, 3 или 5 узлов.
Поддерживается масштабирование кластера в сторону увеличения количества узлов. Масштабирование в сторону уменьшения количества узлов, к сожалению, не поддерживается.
-
Для возможности балансировки нагрузки на чтение/запись между узлами кластера оператор автоматически создаёт два сервиса вида:
moco-<cluster-name>-primarymoco-<cluster-name>-replica
Поддерживаются использование пользовательских образов для контейнеров и их хранение в приватном container registry.
Возможно задание пользовательских настроек через подключаемый ConfigMap.
Поддерживаются failover и switchover для роли лидера.
Поддерживается In-Place-апгрейд MySQL, в том числе мажорной версии с 8.0 до 8.4.
Возможна настройка каскадной асинхронной репликации как от внешнего MySQL к кластеру под управлением Moco, так и от кластера Moco к внешнему MySQL.
Реализовано автоматическое резервное копирование, а также восстановление с поддержкой PITR.
Оператор позволяет ставить на паузу репликацию в конкретном кластере, а также приостанавливать RECONCILE конкретного MySQL-кластера, что порой весьма удобно.
Сам по себе deployment оператора может работать в несколько реплик с автоматическими выборами лидера среди экземпляров оператора.
Пример CR кластера:
---
apiVersion: moco.cybozu.com/v1beta2
kind: MySQLCluster
metadata:
name: myclaster
spec:
replicas: 3
replicationSourceSecretName: null
mysqlConfigMapName: mycnf
backupPolicyName: daily
maxDelaySeconds: 0
primaryServiceTemplate: {}
replicaServiceTemplate: {}
podTemplate:
spec:
imagePullSecrets:
- name: registrysecret
affinity: {}
containers:
- name: mysqld
image: registry.myregistry.com/my-moco-assembly:v1.47
resources:
requests:
cpu: 2
memory: 16Gi
limits:
memory: 16Gi
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
storageClassName: fast
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 50Gi
Репликация внутри кластера Moco
Репликация основана на стандартном механизме GTID.
В отличие от других операторов, в Moco реализована полусинхронная репликация за счёт штатного semisync-плагина MySQL. При такой репликации COMMIT на лидере завершается только после того, как не менее 50 % реплик подтвердили запись транзакции в binlog и сброс этих изменений в binlog на диск. При этом подтверждение о применении изменений в самой базе данных не требуется. Таким образом достигается определённый компромисс между обеспечением целостности данных и задержкой записи в кластере.
Значение в 50 % достигается за счёт автоматического конфигурирования оператором параметра rpl_semi_sync_master_wait_for_slave_count в значение (spec.replicas − 1)/2.
Конечно, есть опасность, что по истечении таймаута rpl_semi_sync_master_timeout реплика деградирует до асинхронного режима и COMMIT все-таки произойдет. Но Moco-оператор выставляет параметр rpl_semi_sync_master_timeout по умолчанию в 86400000 мс (24 часа) и на практике такое маловероятно.
В операторе возможно регулирование обслуживания запросов на слишком отстающих от лидера репликах. При отставании реплики на значение, превышающее spec.maxDelaySeconds, на работающем в sidecar-контейнере moco-agent перестанут проходить readinessProbe, и реплика будет недоступна через сервис moco-<cluster-name>-replica до тех пор, пока не догонит лидера.
В качестве дополнительной меры контроля целостности данных оператор при переподключении узла к кластеру контролирует наличие на узле Errant GTID и в случае их обнаружения не включает такой узел в кластер, а предоставляет администратору данных принять решение о пути исправления возникшей проблемы.
Репликация с внешними узлами
Кластер Moco —> внешний сервер
Для настройки репликации достаточно любым доступным образом предоставить доступ к кластеру Moco извне, а затем просто настроить стандартную репликацию на основе GTID.
Для упрощения предоставления доступа к кластеру извне в CR Moco есть разделы spec.primaryServiceTemplate и spec.replicaServiceTemplate, которые отвечают за формирование Service для работы с лидером и/или репликами.
Примеры публикации с использованием этого инструмента:
1. External IP:
primaryServiceTemplate:
spec:
externalIPs:
- 192.168.199.246
2. LoadBalancer (LB), если в кластере поддерживается автоматический заказ LB в облаке:
primaryServiceTemplate:
spec:
type: LoadBalancer
Внешний сервер —> кластер Moco
Данный способ позволяет настроить репликацию как между внешним сервером и Read-Only-лидером кластера Moco, так и между двумя Moco-кластерами, из которых ведомый кластер будет в режиме StandBy (Read-Only).
1. Включить на сервере — источнике данных partial_revokes:
partial_revokes = On
2. Установить на сервере-источнике MySQL Clone plugin:
INSTALL PLUGIN clone SONAME 'mysql_clone.so';
3. Создать на сервере-источнике двух служебных пользователей и выдать им необходимые права:
CREATE USER 'clone-donor'@'%' IDENTIFIED BY 'xxxxxxxxxxx';
GRANT BACKUP_ADMIN, REPLICATION SLAVE ON *.* TO 'clone-donor'@'%';
CREATE USER 'clone-init'@'localhost' IDENTIFIED BY 'yyyyyyyyyyy';
GRANT ALL ON *.* TO 'clone-init'@'localhost' WITH GRANT OPTION;
GRANT PROXY ON ''@'' TO 'clone-init'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
4. Создать в пространстве имён, в котором планируется задеплоить StandBy-кластер Moco, секрет с координатами сервера — источника данных и с учётными данными служебных пользователей. Пример:
---
apiVersion: v1
kind: Secret
metadata:
name: donor-secret
type: Opaque
data:
HOST: {{ printf "192.168.199.69" | b64enc | quote }}
PORT: {{ printf "3306" | b64enc | quote }}
INIT_USER: {{ printf "clone-init" | b64enc | quote }}
INIT_PASSWORD: {{ printf "yyyyyyyyyyy" | b64enc | quote }}
USER: {{ printf "clone-donor" | b64enc | quote }}
PASSWORD: {{ printf "xxxxxxxxxxx" | b64enc | quote }}
5. Задеплоить кластер Moco с указанием имени созданного секрета — spec.replicationSourceSecretName: donor-secret — и дождаться его инициализации.
Готово!
Чтобы разорвать репликацию с внешним сервером и вывести лидера кластера Moco из Read-Only, достаточно заменить имя секрета в CR на null: spec.replicationSourceSecretName: null.
Резервное копирование и восстановление
Резервное копирование
Автоматическое резервное копирование в Moco осуществляется с помощью Instance Dump Utility (функция MySQL Shell).
При снятии бэкапа сохраняются структура данных в виде .sql-файлов и содержимое таблиц в виде .tsv-файлов, сжатых zstd, а также все пользователи БД, кроме системных пользователей Moco. Всё это упаковывается в tar и загружается в объектное хранилище.
Резервное копирование с помощью Instance Dump Utility, конечно, уступает по производительности и эффективности Percona XtraBackup, но этот метод существенно лучше, чем mysqldump.
Для включения резервного копирования достаточно задать параметры подключения к объектному хранилищу (например, создав Secret с ними), затем задеплоить в K8s CR с BackupPolicy и указать соответствующую BackupPolicy в spec.backupPolicyName CR кластера Moco. Пример:
---
apiVersion: v1
kind: Secret
metadata:
name: mycluster-s3
type: Opaque
data:
AWS_ACCESS_KEY_ID: WUNBQ0VpOXE3UXFNY3hNeFg2T2N1c2t4LQ==
AWS_SECRET_ACCESS_KEY: WUNOU3Qyd2I0dDlGXzRCeWZYX0hKdDBCTzVReGs1bDdnZlJ3Y09tTw==
---
apiVersion: moco.cybozu.com/v1beta2
kind: BackupPolicy
metadata:
name: daily
spec:
schedule: "0 14 * * *"
failedJobsHistoryLimit: 1
successfulJobsHistoryLimit: 1
jobConfig:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
namespaceSelector: {}
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values: ["mysql-backup"]
- key: app.kubernetes.io/created-by
operator: In
values: ["moco"]
topologyKey: kubernetes.io/hostname
serviceAccountName: moco-mycluster
resources:
cpu: 100m
maxCpu: 1
memory: 1Gi
maxMemory: 1Gi
threads: 4
envFrom:
- secretRef:
name: mycluster-s3
bucketConfig:
bucketName: moco-backup
region: ru-central1
endpointURL: https://storage.yandexcloud.net/
workVolume:
ephemeral:
volumeClaimTemplate:
spec:
storageClassName: "ceph-ssd"
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 50Gi
Здесь стоит обратить внимание на то, что поду для резервного копирования требуется своё дисковое хранилище для сохранения резервной копии перед загрузкой её в объектное хранилище. В качестве workVolume можно, конечно, использовать и emptyDir: {}, но при сколько-нибудь заметном размере БД есть риск нарушить работу узла кластера K8s.
После того как созданный CR backupPolicyName будет указан в spec.backupPolicyName CR любого кластера Moco, оператор автоматически создаст ресурс с CronJob, на который и будет возложено выполнение резервного копирования.
К сожалению, при использовании пользовательских образов на текущий момент для шаблона CronJob нет возможности задать поле imagePullSecrets. Однако оператор не препятствует модифицировать CronJob самостоятельно, так что для добавления манифеста можно либо использовать дополнительный шаг в деплое вида:
kubectl -n mynamespace get cj -l=app.kubernetes.io/created-by=moco -o name | \
xargs -I {} kubectl -n mynamespace patch {} -p \
'{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"imagePullSecrets":null}}}}}}'
kubectl -n mynamespace get cj -l=app.kubernetes.io/created-by=moco -o name | \
xargs -I {} kubectl -n mynamespace patch {} -p \
'{"spec": {"jobTemplate": {"spec": {"template": {"spec": {"imagePullSecrets": [{"name": "myregistrysecret"}]}}}}}}'
…либо сделать простой patch с помощью shell-operator, либо использовать для резервного копирования внешний образ.
Для ручного вызова резервного копирования достаточно создать Job из предварительно созданного на базе BackupPolicy CronJob.
Восстановление
Для восстановления из резервной копии необходимо развернуть новый кластер, описав его в CR и заполнив блок spec.restore данными об источнике резервной копии, точке восстановления и шаблоне Kubernetes Job, который оператор сгенерирует при создании кластера. Формат шаблона spec.restore.jobConfig полностью аналогичен такому шаблону в BackupPolicy.
Пример CR:
---
apiVersion: moco.cybozu.com/v1beta2
kind: MySQLCluster
metadata:
name: myrestored
spec:
replicas: 3
mysqlConfigMapName: mycnf
podTemplate:
spec:
imagePullSecrets:
- name: registrysecret
affinity: {}
containers:
- name: mysqld
image: registry.myregistry.com/my-moco-assembly:v1.47
resources:
requests:
cpu: 2
memory: 16Gi
limits:
memory: 16Gi
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
storageClassName: fast
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 50Gi
restore:
sourceName: old-cluster
sourceNamespace: old-namespace
restorePoint: "2025-10-25T11:20:00Z"
jobConfig:
serviceAccountName: moco-sa
affinity: {}
cpu: 100m
maxCpu: 1
memory: 1Gi
maxMemory: 1Gi
threads: 4
envFrom:
- secretRef:
name: backup-s3
bucketConfig:
bucketName: moco-backup
region: ru-central1
endpointURL: https://storage.yandexcloud.net/
workVolume:
ephemeral:
volumeClaimTemplate:
spec:
storageClassName: "ceph-ssd"
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 50Gi
Здесь видно, что в блоке spec.restore указаны имя и пространство имён кластера, из резервной копии которого необходимо выполнить восстановление. Сам исходный кластер и его пространство имён при этом могут уже не существовать. Эти имена нужны исключительно для поиска пути к резервной копии в объектном хранилище.
После успешного восстановления блок spec.restore удалять не следует, он Immutable.
При восстановлении оператором будет автоматически создан K8s-ресурс Job, обладающий тем же недостатком, что и CronJob для резервного копирования, то есть будет отсутствовать поле imagePullSecrets. И модифицировать вручную Job уже не получится. В качестве решения можно либо заранее загрузить необходимый образ на узел K8s, на котором будет выполняться Job, либо использовать внешний образ.
PITR
У оператора нет стриминга binlog в S3, равно как и нет отдельного пода, который бы раз в минуту делал резервную копию binlog'ов, как это реализовано, например, в Percona Operator.
Резервная копия binlog'ов создаётся только по заданному BackupPolicy расписанию вместе с полной резервной копией БД. Архив binlog'ов при этом сжимается zstd и помещается в объектном хранилище в каталог с предыдущей полной резервной копией.
Таким образом, на администраторе кластера лежит ответственность за то, чтобы значение параметра binlog_expire_logs_seconds превышало период полного резервного копирования. Если же понадобится восстановление на точку во времени позднее времени снятия последней полной резервной копии, то необходимые бинлоги придётся уже самостоятельно извлечь из подов того кластера, резервная копия которого восстанавливается.
Failover и switchover
Failover роли лидера оператор выполняет автоматически.
Для выполнения ручного switchover необходимо установить Moco-плагин для kubectl.
Пример установки:
OS=linux
ARCH=amd64
VERSION=v0.29.0
curl -L -sS https://github.com/cybozu-go/moco/releases/download/${VERSION}/kubectl-moco_${VERSION}_${OS}_${ARCH}.tar.gz \
| tar xz -C /usr/local/bin kubectl-moco
kubectl moco -h
Пример выполнения switchover с помощью плагина:
kubectl moco –n mynamespace switchover CLUSTER_NAME
Switchover — это не единственная полезная функция плагина. С его помощью можно приостановить репликацию в кластере:
kubectl moco –n mynamespace stop clustering CLSUTER_NAME
Приостановить автоматический Reconcile кластера оператором:
kubectl moco –n mynamespace stop reconciliation CLSUTER_NAME
Выполнить запрос интерактивно на лидере кластера:
kubectl moco –n mynamespace mysql --mysql-user moco-admin CLSUTER_NAME -- -N -e 'SELECT VERSION()'
В том числе, например, загрузить дамп:
cat sample.sql | kubectl moco –n mynamespace mysql --mysql-user moco-admin -i CLSUTER_NAME
Или получить доступ к консоли mysql-cli на узле кластера с определённым номером:
kubectl moco –n mynamespace mysql --mysql-user moco-admin --index 2 -it CLSUTER_NAME
Управление конфигурацией MySQL
Для управления конфигурацией Moco-кластера можно создать ConfigMap с параметрами, заданными в виде key: value, и указать его в spec.mysqlConfigMapName CR кластера. Пример:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mycnf
data:
long_query_time: "1"
binlog_expire_logs_seconds: "172800"
innodb_buffer_pool_size: "512M"
_include: |
performance-schema-instrument='memory/%=ON'
performance-schema-instrument='wait/synch/%/innodb/%=ON'
performance-schema-instrument='wait/lock/table/sql/handler=OFF'
performance-schema-instrument='wait/lock/metadata/sql/mdl=OFF'
Если параметр innodb_buffer_pool_size не задан явно, то он будет вычислен оператором автоматически и установлен в 70 % от resources.requests.memory либо resources.limits.memory контейнера с MySQL. Если параметры ресурсов для контейнера не заданы, то для этого параметра будет действовать значение по умолчанию 128 МБ.
Если какие-то параметры невозможно указать в виде key: value, то их можно задать в ключе _include. Внимание! В данном случае оператор не валидирует заданные значения.
Важно! При работе с mysql-cli непосредственно внутри контейнера с MySQL следует иметь в виду, что по умолчанию оператор устанавливает настройку:
[mysql]
init_command = "SET autocommit=0"
Таким образом все выполненные вручную изменения должны завершаться явным вызовом COMMIT;.
Поддержка пользовательских образов
Moco предоставляет свои образы контейнеров MySQL. Актуальные версии можно увидеть по ссылке. Кроме этого, есть поддержка использования пользовательских образов для узлов кластера.
При создании собственных образов MySQL крайне желательно создавать для MySQL пользователя с UID:GID 10000:10000 и запускать процесс mysqld именно от этого пользователя.
Помимо образа контейнера с MySQL, можно также переопределить образы sidecar-контейнеров для подов MySQL-кластера. В отличие от образа MySQL, который задаётся в CR MySQLCluster, образы sidecar-контейнеров определяются на уровне деплоя оператора в аргументах к его контейнеру. Пример:
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/component: moco-controller
app.kubernetes.io/name: moco
name: moco-controller
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/component: moco-controller
app.kubernetes.io/name: moco
template:
metadata:
annotations:
kubectl.kubernetes.io/default-container: moco-controller
labels:
app.kubernetes.io/component: moco-controller
app.kubernetes.io/name: moco
spec:
imagePullSecrets:
- name: registrysecret
containers:
image: registry.myregistry.com/my-moco-controller:v1.47
args:
- --agent-image
- registry.myregistry.com/my-moco-agent:v1.47
- --fluent-bit-image
- registry.myregistry.com/my-moco-fluentbit:v1.47
- --mysqld-exporter-image
- registry.myregistry.com/my-moco-exporter:v1.47
- --backup-image
- registry.myregistry.com/my-moco-backup:v1.47
...
Observability
В качестве sidecar-контейнера у каждого узла MySQL присутствует mysqld_exporter, отдающий метрики MySQL на порту 9104.
Кроме того, kubectl get mysqlclusters покажет состояние репликации, текущий индекс лидер-узла и краткую информацию о последней резервной копии. Пример:
kubectl -n mynamespace get mysqlclusters
NAME AVAILABLE HEALTHY PRIMARY SYNCED REPLICAS ERRANT REPLICAS CLUSTERING ACTIVE RECONCILE ACTIVE LAST BACKUP
my131 False False 1 3 True True 2025-10-28T14:00:15Z
Более подробную статистику о последней резервной копии можно получить из .status кластера:
kubectl -n mynamespace get mysqlclusters my131 -o json | jq .status.backup
{
"binlogFilename": "binlog.000004",
"binlogSize": 261,
"dumpSize": 181954560,
"elapsed": "3m21.986677055s",
"gtidSet": "628b0b7e-a820-11f0-aa6a-fe8fa3bad8bc:1-12,\n9da81e1e-a406-11f0-923a-1608d20af34b:1-1540448,\nb3dd194a-acd2-11f0-8098-fa163e8befbb:1-24",
"sourceIndex": 0,
"sourceUUID": "4293e5ec-ad75-11f0-87c1-d64b01871166",
"time": "2025-10-28T14:00:15Z",
"uuidSet": {
"0": "4293e5ec-ad75-11f0-87c1-d64b01871166",
"1": "43d71b48-ad75-11f0-a380-9a630af9308c",
"2": "41efc914-ad75-11f0-97c0-22ccb2184219"
},
"warnings": null,
"workDirUsage": 182165504
}
Работа с логами удобно разделена. Просмотр основного лога mysqld:
kubectl -n mynamespace logs my131-1 -c mysqld
Просмотр slow logs:
kubectl -n mynamespace logs my131-1 -c slow-log
О недостатках
Количество узлов кластера ограничено значениями 1, 3 и 5. Полусинхронная репликация MySQL не требует наличия кворума при применении транзакции, поэтому это ограничение может выглядеть как избыточная перестраховка.
Невозможно масштабирование уже развёрнутого кластера в сторону уменьшения количества узлов.
Пока не реализовано управление пользователями и базами данных через CR.
Пока не реализовано управление паролями для системных учётных записей MySQL самого Moco-кластера (пользователи moco-admin, moco-agent, moco-backup и т. д.), это планируется исправить.
Отсутствует функция автоматической ротации бэкапов в Object Storage средствами самого оператора: необходимо дополнительно настроить Bucket Lifecycle Policy, что не всегда возможно, если бэкапы нескольких сущностей хранятся в одном бакете.
Резервное копирование binlog осуществляется не непрерывно, только вместе с созданием полной резервной копии кластера.
Backup CronJobs и Restore Job лишены поля imagePullSecrets. Планируется исправить. Пока проблема решается несложными дополнительными действиями со стороны DevOps-инженера (см. выше раздел, посвященный резервному копированию и восстановлению).
Не очень понятно, зачем по умолчанию для mysql-client внутри самого контейнера с MySQL оператор выставляет настройку SET autocommit=0. Это может привести к не вполне ожидаемому поведению системы при работе администратора данных непосредственно изнутри контейнера.
Есть недостаток в официальных манифестах для деплоя Moco-оператора. Будьте внимательны: там присутствует MutatingWebhookConfiguration, содержащий webhooks с admissionReviewVersions для всех statefulsets:
...
name: statefulset.kb.io
rules:
- apiGroups:
- apps
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- statefulsets
...
Получается, если оператор Moco по каким-либо причинам перестанет отвечать, то K8s API Manager не сможет обработать запросы на создание или модификацию любых statefulsets в кластере. Необходимо при первичном развёртывании Moco-оператора любым образом ограничить область применения этого Admission Webhook. Например, добавить проверку labels:
...
name: statefulset.kb.io
objectSelector:
matchLabels:
app.kubernetes.io/created-by: moco
...
Выводы
Moco-оператор обладает достаточным функционалом для его успешного использования. В то же время, по нашему мнению, ни один его недостаток не является блокирующим его применение фактором.
Кроме того, хотелось бы отметить, что разработчики оператора открыты к взаимодействию и контактируют с сообществом, что очень важно для Open Source-решений.
Мы, со своей стороны, будем с интересом следить за развитием этого проекта.

P. S.
Читайте также в нашем блоге: