Si ha estado en el mundo de los contenedores, sabrá que es bastante desafiante adoptar una automatización completa de Kubernetes para un sistema de base de datos en clúster, lo que comúnmente agrega un nivel de complejidad a la base de datos basada en contenedores. arquitectura para estas aplicaciones con estado. Ahí es donde un operador de Kubernetes puede ayudarnos a abordar este problema. Un operador de Kubernetes es un tipo especial de controlador introducido para simplificar implementaciones complejas que básicamente amplía la API de Kubernetes con recursos personalizados. Se basa en los conceptos básicos de recursos y controladores de Kubernetes, pero incluye conocimientos específicos de aplicaciones o dominios para automatizar todo el ciclo de vida del software que administra.
Percona XtraDB Cluster Operator es una forma ordenada de automatizar las tareas específicas de Percona XtraDB Cluster como implementación, escalado, copias de seguridad y actualizaciones dentro de Kubernetes, creado y mantenido por Percona. Implementa el clúster en un StatefulSet con un volumen persistente, lo que nos permite mantener una identidad constante para cada pod en el clúster y mantener nuestros datos.
En esta publicación de blog, probaremos la implementación de Percona XtraDB Cluster 8.0 en un entorno en contenedores, orquestado por Percona XtraDB Cluster Kubernetes Operator en Google Cloud Platform.
Creación de un clúster de Kubernetes en Google Cloud
En este tutorial, usaremos el clúster de Kubernetes en Google Cloud porque es relativamente simple y fácil poner en marcha Kubernetes. Inicie sesión en su panel de control de Google Cloud Platform -> Compute -> Kubernetes Engine -> Create Cluster, y verá el siguiente cuadro de diálogo:

Simplemente ingrese el nombre del clúster de Kubernetes, elija su zona preferida y haga clic en "CREAR " (al final de la página). En 5 minutos, estará listo un clúster de Kubernetes de 3 nodos. Ahora, en su estación de trabajo, instale el SDK de gcloud como se muestra en esta guía y luego introduzca la configuración de Kubernetes en su estación de trabajo:
$ gcloud container clusters get-credentials my-k8s-cluster --zone asia-northeast1-a --project s9s-qa
Fetching cluster endpoint and auth data.
kubeconfig entry generated for my-k8s-cluster.
Debería poder conectarse al clúster de Kubernetes en este punto. Ejecute el siguiente comando para verificar:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-my-k8s-cluster-default-pool-b80902cd-gp09 Ready <none> 139m v1.16.13-gke.401
gke-my-k8s-cluster-default-pool-b80902cd-jdc3 Ready <none> 139m v1.16.13-gke.401
gke-my-k8s-cluster-default-pool-b80902cd-rdv8 Ready <none> 139m v1.16.13-gke.401
El resultado anterior significa que podemos conectarnos al maestro de Kubernetes y recuperar los nodos del clúster de Kubernetes. Ahora, estamos listos para ejecutar las cargas de trabajo de Kubernetes.
Implementación de un clúster Percona XtraDB en Kubernetes
Para la implementación de la carga de trabajo, vamos a seguir las instrucciones que se indican en la documentación del Operador de clúster de Percona XtraDB. Básicamente, ejecutamos el siguiente comando en nuestra estación de trabajo para crear los recursos personalizados, el espacio de nombres, el control de acceso basado en roles y también el propio operador de Kubernetes:
$ git clone -b v1.6.0 https://github.com/percona/percona-xtradb-cluster-operator
$ cd percona-xtradb-cluster-operator/
$ kubectl apply -f deploy/crd.yaml
$ kubectl create namespace pxc
$ kubectl config set-context $(kubectl config current-context) --namespace=pxc
$ kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=$(gcloud config get-value core/account)
$ kubectl apply -f deploy/rbac.yaml
$ kubectl apply -f deploy/operator.yaml
A continuación, tenemos que preparar nuestras contraseñas (se llama Secretos en el término de Kubernetes) actualizando los valores dentro de deployment/secrets.yaml en un formato codificado en base64. Puede usar herramientas en línea como https://www.base64encode.org/ para crear uno o usar una herramienta de línea de comandos como la siguiente:
$ echo -n 'mypassword' | base64
bXlwYXNzd29yZA==
Luego, actualice deployment/secrets.yaml, como se muestra a continuación:
apiVersion: v1
kind: Secret
metadata:
name: my-cluster-secrets
type: Opaque
data:
root: bXlwYXNzd29yZA==
xtrabackup: bXlwYXNzd29yZA==
monitor: bXlwYXNzd29yZA==
clustercheck: bXlwYXNzd29yZA==
proxyadmin: bXlwYXNzd29yZA==
pmmserver: bXlwYXNzd29yZA==
operator: bXlwYXNzd29yZA==
Lo anterior es una súper simplificación de la administración de secretos, donde configuramos todas las contraseñas para que sean iguales para todos los usuarios. En producción, utilice una contraseña más compleja y especifique una contraseña diferente para cada usuario.
Ahora, podemos enviar la configuración secreta a Kubernetes:
$ kubectl apply -f deploy/secrets.yaml
Antes de continuar con la implementación de un clúster Percona XtraDB, debemos revisar la definición de implementación predeterminada dentro de deployment/cr.yaml para el clúster. Hay muchos objetos de Kubernetes que se definen aquí, pero la mayoría de ellos están comentados. Para nuestra carga de trabajo, haríamos la modificación de la siguiente manera:
$ cat deploy/cr.yaml
apiVersion: pxc.percona.com/v1-6-0
kind: PerconaXtraDBCluster
metadata:
name: cluster1
finalizers:
- delete-pxc-pods-in-order
spec:
crVersion: 1.6.0
secretsName: my-cluster-secrets
vaultSecretName: keyring-secret-vault
sslSecretName: my-cluster-ssl
sslInternalSecretName: my-cluster-ssl-internal
allowUnsafeConfigurations: false
updateStrategy: SmartUpdate
upgradeOptions:
versionServiceEndpoint: https://check.percona.com
apply: recommended
schedule: "0 4 * * *"
pxc:
size: 3
image: percona/percona-xtradb-cluster:8.0.20-11.1
configuration: |
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
collation-server = utf8_unicode_ci
character-set-server = utf8
default_authentication_plugin = mysql_native_password
resources:
requests:
memory: 1G
affinity:
antiAffinityTopologyKey: "kubernetes.io/hostname"
podDisruptionBudget:
maxUnavailable: 1
volumeSpec:
persistentVolumeClaim:
resources:
requests:
storage: 6Gi
gracePeriod: 600
haproxy:
enabled: true
size: 3
image: percona/percona-xtradb-cluster-operator:1.6.0-haproxy
resources:
requests:
memory: 1G
affinity:
antiAffinityTopologyKey: "kubernetes.io/hostname"
podDisruptionBudget:
maxUnavailable: 1
gracePeriod: 30
backup:
image: percona/percona-xtradb-cluster-operator:1.6.0-pxc8.0-backup
storages:
fs-pvc:
type: filesystem
volume:
persistentVolumeClaim:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 6Gi
schedule:
- name: "daily-backup"
schedule: "0 0 * * *"
keep: 5
storageName: fs-pvc
Hemos realizado algunas modificaciones al cr.yaml provisto para que funcione con nuestra aplicación, como se muestra arriba. En primer lugar, debemos comentar (o eliminar) todas las líneas relacionadas con la CPU, por ejemplo [*].resources.requests.cpu:600m, para asegurarnos de que Kubernetes pueda programar la creación del pod correctamente en nodos con CPU limitada. Luego, debemos agregar algunas opciones de compatibilidad para Percona XtraDB Cluster 8.0, que se basa en MySQL 8.0, para que funcione sin problemas con nuestra aplicación de WordPress que implementaremos más adelante, como se muestra en el siguiente extracto:
configuration: |
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
collation-server = utf8_unicode_ci
character-set-server = utf8
default_authentication_plugin = mysql_native_password
Lo anterior hará coincidir el juego de caracteres predeterminado del servidor MySQL con el controlador MySQLi PHP en nuestro contenedor de WordPress. La siguiente sección es la implementación de HAProxy donde se establece en "habilitado:verdadero". También hay una sección ProxySQL con "habilitado:falso"; por lo general, uno elegiría cualquiera de los proxies inversos para cada clúster. La última sección es la configuración de la copia de seguridad, donde nos gustaría tener una copia de seguridad diaria programada a las 12:00 a. m. todos los días y mantener las últimas 5 copias de seguridad.
Ahora podemos comenzar a implementar nuestro clúster Percona XtraDB de 3 nodos:
$ kubectl apply -f deploy/cr.yaml
El proceso de creación llevará algún tiempo. El operador implementará los pods de Percona XtraDB Cluster como un Stateful Set, lo que significa que se creará un pod a la vez y a cada Pod en el StatefulSet se le asignará un ordinal entero, desde 0 hasta N-1, que es único en el conjunto. El proceso finaliza cuando tanto el operador como los Pods alcanzan su estado de ejecución:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
cluster1-haproxy-0 2/2 Running 0 71m
cluster1-haproxy-1 2/2 Running 0 70m
cluster1-haproxy-2 2/2 Running 0 70m
cluster1-pxc-0 1/1 Running 0 71m
cluster1-pxc-1 1/1 Running 0 70m
cluster1-pxc-2 1/1 Running 0 69m
percona-xtradb-cluster-operator-79d786dcfb-6clld 1/1 Running 0 121m
Dado que este operador es un recurso personalizado, podemos manipular el recurso perconaxtradbcluster para que le guste el recurso estándar de Kubernetes:
$ kubectl get perconaxtradbcluster
NAME ENDPOINT STATUS PXC PROXYSQL HAPROXY AGE
cluster1 cluster1-haproxy.pxc ready 3 3 27h
También puede usar el nombre de recurso más corto, "pxc", e intentar con los siguientes comandos:
$ kubectl describe pxc
$ kubectl edit pxc
Al mirar el conjunto de cargas de trabajo, podemos decir que el operador ha creado dos StatefulSets:
$ kubectl get statefulsets -o wide
NAME READY AGE CONTAINERS IMAGES
cluster1-haproxy 3/3 26h haproxy,pxc-monit percona/percona-xtradb-cluster-operator:1.6.0-haproxy,percona/percona-xtradb-cluster-operator:1.6.0-haproxy
cluster1-pxc 3/3 26h pxc percona/percona-xtradb-cluster:8.0.20-11.2
El operador también creará los servicios correspondientes que equilibrarán la carga de las conexiones a los pods respectivos:
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cluster1-haproxy ClusterIP 10.40.9.177 <none> 3306/TCP,3309/TCP,33062/TCP 3h27m
cluster1-haproxy-replicas ClusterIP 10.40.0.236 <none> 3306/TCP 3h27m
cluster1-pxc ClusterIP None <none> 3306/TCP,33062/TCP 3h27m
cluster1-pxc-unready ClusterIP None <none> 3306/TCP,33062/TCP 3h27m
El resultado anterior muestra que el operador ha creado 4 servicios:
- clúster1-haproxy - El servicio para un maestro único de MySQL con carga balanceada (3306), protocolo Proxy (3309) y MySQL Admin (33062) - Un nuevo puerto administrativo introducido en MySQL 8.0.14 y versiones posteriores. Este es el nombre del servicio o la dirección IP del clúster que las aplicaciones necesitan para conectarse para tener una conexión maestra única al clúster de Galera.
- cluster1-haproxy-réplicas - El servicio para un multi-maestro MySQL con balance de carga (3306). Este es el nombre del servicio o la dirección IP del clúster que las aplicaciones necesitan para conectarse para tener una conexión multimaestro al clúster de Galera con el algoritmo de equilibrio de turno rotativo.
- clúster1-pxc - El servicio para pods PXC con equilibrio de carga, sin pasar por HAProxy. Al conectarse directamente a este servicio, Kubernetes enrutará la conexión por turnos a todos los pods de PXC, de forma similar a lo que proporciona cluster-haproxy-replicase. El servicio no tiene una dirección IP pública asignada y no está disponible fuera del clúster.
- cluster1-pxc-no preparado - El servicio 'no preparado' es necesario para la detección de direcciones de pod durante el inicio de la aplicación, independientemente del estado del Pod. Los pods de Proxysql y pxc deben conocerse antes de que la base de datos esté completamente operativa. El servicio no preparado no tiene una dirección IP pública asignada y no está disponible fuera del clúster.
Para conectarse a través de un cliente MySQL, simplemente ejecute el siguiente comando:
$ kubectl run -i --rm --tty percona-client --image=percona:8.0 --restart=Never -- bash -il
Esto creará un Pod transitorio e ingresará inmediatamente al entorno del contenedor. Luego, ejecute el comando de cliente mysql estándar con una credencial adecuada:
bash-4.2$ mysql -uroot -pmypassword -h cluster1-haproxy -P3306 -e 'SELECT @@hostname'
mysql: [Warning] Using a password on the command line interface can be insecure.
+----------------+
| @@hostname |
+----------------+
| cluster1-pxc-0 |
+----------------+
Cuando observamos la ubicación de los pods, todos los pods de Percona XtraDB Cluster están ubicados en un host de Kubernetes diferente:
$ kubectl get pods -o wide --selector=app.kubernetes.io/component=pxc
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
cluster1-pxc-0 1/1 Running 0 67m 10.36.2.5 gke-my-k8s-cluster-default-pool-b80902cd-gp09 <none> <none>
cluster1-pxc-1 1/1 Running 0 66m 10.36.1.10 gke-my-k8s-cluster-default-pool-b80902cd-rdv8 <none> <none>
cluster1-pxc-2 1/1 Running 0 65m 10.36.0.11 gke-my-k8s-cluster-default-pool-b80902cd-jdc3 <none> <none>
Esto definitivamente mejorará la disponibilidad del servicio, en caso de que uno de los hosts de Kubernetes se caiga.
Para escalar hasta 5 pods, debemos preparar otros 2 nuevos nodos de Kubernetes de antemano para respetar la configuración de afinidad de pods (predeterminada en affinity.antiAffinityTopologyKey.topologyKey="kubernetes.io/hostname"). Luego, ejecute el siguiente comando de parche para escalar Percona XtraDB Cluster a 5 nodos:
$ kubectl patch pxc cluster1 \
--type='json' -p='[{"op": "replace", "path": "/spec/pxc/size", "value": 5 }]'
Supervise la creación del pod mediante el comando kubectl get pods:
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
cluster1-pxc-0 1/1 Running 0 27h 10.36.2.5 gke-my-k8s-cluster-default-pool-b80902cd-gp09 <none> <none>
cluster1-pxc-1 1/1 Running 0 27h 10.36.1.10 gke-my-k8s-cluster-default-pool-b80902cd-rdv8 <none> <none>
cluster1-pxc-2 1/1 Running 0 27h 10.36.0.11 gke-my-k8s-cluster-default-pool-b80902cd-jdc3 <none> <none>
cluster1-pxc-3 1/1 Running 0 30m 10.36.7.2 gke-my-k8s-cluster-pool-1-ab14a45e-h1pf <none> <none>
cluster1-pxc-4 1/1 Running 0 13m 10.36.5.3 gke-my-k8s-cluster-pool-1-ab14a45e-01qn <none> <none>
Se han creado otros 2 nuevos pods (cluster1-pxc-3 y cluster1-pxc-4) en otros 2 nuevos nodos de Kubernetes (gke-my-k8s-cluster-pool-1-ab14a45e-h1pf y gke-my-k8s-cluster-pool-1-ab14a45e-01qn). Para reducir la escala, simplemente cambie el valor a 3 en el comando de parche anterior. Tenga en cuenta que Percona XtraDB Cluster debe ejecutarse con un número impar de nodos para evitar el cerebro dividido.
Implementación de una aplicación (WordPress)
En este ejemplo, implementaremos una aplicación de WordPress sobre nuestro Percona XtraDB Cluster y HAProxy. Primero preparemos el archivo de definición YAML como el siguiente:
$ cat wordpress-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
selector:
app: wordpress
tier: frontend
type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wp-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: frontend
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: frontend
spec:
containers:
- image: wordpress:4.8-apache
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: cluster1-haproxy
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-cluster-secrets
key: root
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wp-pv-claim
Preste atención a las variables de entorno WORDPRESS_DB_HOST y WORDPRESS_DB_PASSWORD. La primera variable en la que definimos "cluster1-haproxy" como el host de la base de datos, en lugar de un nodo de base de datos individual, y para la última especificamos la contraseña de root al indicarle a Kubernetes que la leyera desde el objeto my-cluster-secrets bajo la clave "root", que es equivalente a "mypassword" (después de decodificar el valor base64). Omitimos definir la variable de entorno WORDPRESS_DB_USER ya que el valor predeterminado es "root".
Ahora podemos crear nuestra aplicación:
$ kubectl apply -f wordpress-deployment.yaml
Consulte el servicio:
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cluster1-haproxy ClusterIP 10.40.9.177 <none> 3306/TCP,3309/TCP,33062/TCP 4h42m
cluster1-haproxy-replicas ClusterIP 10.40.0.236 <none> 3306/TCP 4h42m
cluster1-pxc ClusterIP None <none> 3306/TCP,33062/TCP 4h42m
cluster1-pxc-unready ClusterIP None <none> 3306/TCP,33062/TCP 4h42m
wordpress LoadBalancer 10.40.13.205 35.200.78.195 80:32087/TCP 4h39m
En este punto, podemos conectarnos a nuestra aplicación de WordPress en http://35.200.78.195/ (la dirección IP externa) y comenzar a configurar la aplicación de WordPress. En este punto, nuestra aplicación de WordPress está conectada a uno de los Percona XtraDB Cluster (conexión de maestro único) a través de uno de los pods HAProxy.
Eso es todo por ahora. Para obtener más información, consulte la documentación de Percona Kubernetes Operator for Percona XtraDB Cluster. ¡Feliz contenedorización!