Se você já esteve no mundo dos contêineres, sabe que é bastante desafiador adotar uma automação completa do Kubernetes para um sistema de banco de dados em cluster, que geralmente adiciona um nível de complexidade ao sistema baseado em contêiner arquitetura para esses aplicativos com estado. É aí que um Operador do Kubernetes pode nos ajudar a resolver esse problema. Um operador do Kubernetes é um tipo especial de controlador introduzido para simplificar implantações complexas que basicamente estendem a API do Kubernetes com recursos personalizados. Ele se baseia nos conceitos básicos de recursos e controladores do Kubernetes, mas inclui conhecimento específico de domínio ou aplicativo para automatizar todo o ciclo de vida do software que ele gerencia.
O Percona XtraDB Cluster Operator é uma maneira elegante de automatizar as tarefas específicas do Percona XtraDB Cluster, como implantação, dimensionamento, backups e atualizações no Kubernetes, criadas e mantidas pela Percona. Ele implanta o cluster em um StatefulSet com um Volume Persistente, o que nos permite manter uma identidade consistente para cada Pod no cluster e nossos dados a serem mantidos.
Nesta postagem do blog, testaremos a implantação do Percona XtraDB Cluster 8.0 em um ambiente em contêiner, orquestrado pelo Percona XtraDB Cluster Kubernetes Operator no Google Cloud Platform.
Criando um cluster Kubernetes no Google Cloud
Neste passo a passo, usaremos o cluster Kubernetes no Google Cloud porque é relativamente simples e fácil colocar o Kubernetes em funcionamento. Faça login no painel do Google Cloud Platform -> Computação -> Kubernetes Engine -> Criar cluster e você verá a seguinte caixa de diálogo:
Basta digitar o nome do cluster Kubernetes, escolher sua zona preferida e clicar em "CRIAR " (no final da página). Em 5 minutos, um cluster Kubernetes de 3 nós estará pronto. Agora, em sua estação de trabalho, instale o SDK gcloud conforme mostrado neste guia e extraia a configuração do Kubernetes em sua estação de trabalho:
$ 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.
Você deve conseguir se conectar ao cluster Kubernetes neste momento. Execute o seguinte 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
A saída acima significa que podemos nos conectar ao mestre do Kubernetes e recuperar os nós do cluster do Kubernetes. Agora, estamos prontos para executar as cargas de trabalho do Kubernetes.
Implantando um cluster Percona XtraDB no Kubernetes
Para implantação de carga de trabalho, seguiremos as instruções conforme indicado na documentação do Operador de cluster Percona XtraDB. Basicamente, executamos o seguinte comando em nossa estação de trabalho para criar os recursos personalizados, namespace, controle de acesso baseado em função e também o próprio operador do 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
Em seguida, temos que preparar nossas senhas (chamadas Secrets no termo Kubernetes) atualizando os valores dentro de deploy/secrets.yaml em um formato codificado em base64. Você pode usar ferramentas online como https://www.base64encode.org/ para criar uma ou usar uma ferramenta de linha de comando como a seguinte:
$ echo -n 'mypassword' | base64
bXlwYXNzd29yZA==
Em seguida, atualize o deploy/secrets.yaml, conforme mostrado abaixo:
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==
O acima é uma super simplificação do gerenciamento de segredos, onde definimos todas as senhas para serem as mesmas para todos os usuários. Na produção, use uma senha mais complexa e especifique uma senha diferente para cada usuário.
Agora, podemos enviar a configuração secreta para o Kubernetes:
$ kubectl apply -f deploy/secrets.yaml
Antes de avançarmos para implantar um Percona XtraDB Cluster, precisamos revisitar a definição de implantação padrão dentro de deploy/cr.yaml para o cluster. Existem muitos objetos do Kubernetes definidos aqui, mas a maioria deles é comentada. Para nossa carga de trabalho, faríamos a modificação conforme abaixo:
$ 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
Fizemos algumas modificações no cr.yaml fornecido para que funcione com nosso aplicativo, conforme mostrado acima. Antes de tudo, temos que comentar (ou remover) todas as linhas relacionadas à CPU, por exemplo [*].resources.requests.cpu:600m, para garantir que o Kubernetes seja capaz de agendar a criação de pod corretamente em nós com CPU limitada. Em seguida, precisamos adicionar algumas opções de compatibilidade para o Percona XtraDB Cluster 8.0 que é baseado no MySQL 8.0, para funcionar sem problemas com nosso aplicativo WordPress que vamos implantar posteriormente, conforme mostrado no trecho a seguir:
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
O acima corresponderá ao conjunto de caracteres padrão do servidor MySQL com o driver MySQLi PHP em nosso contêiner WordPress. A próxima seção é a implementação do HAProxy em que ele é definido como "ativado:verdadeiro". Há também uma seção ProxySQL com "enabled:false" - normalmente alguém escolheria um dos proxies reversos para cada cluster. A última seção é a configuração de backup, onde gostaríamos de ter um backup diário agendado às 00:00 todos os dias e manter os últimos 5 backups.
Agora podemos começar a implantar nosso cluster Percona XtraDB de 3 nós:
$ kubectl apply -f deploy/cr.yaml
O processo de criação levará algum tempo. O operador implantará os pods do Percona XtraDB Cluster como um Stateful Set, o que significa uma criação de pod por vez e cada Pod no StatefulSet receberá um ordinal inteiro, de 0 a N-1, que é exclusivo no conjunto. O processo termina quando o operador e os pods atingem o status Running:
$ 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
Como esse operador é um recurso personalizado, podemos manipular o recurso perconaxtradbcluster para gostar do recurso padrão do Kubernetes:
$ kubectl get perconaxtradbcluster
NAME ENDPOINT STATUS PXC PROXYSQL HAPROXY AGE
cluster1 cluster1-haproxy.pxc ready 3 3 27h
Você também pode usar o nome de recurso mais curto, "pxc", e tentar com os seguintes comandos:
$ kubectl describe pxc
$ kubectl edit pxc
Ao olhar para o conjunto de carga de trabalho, podemos dizer que o operador criou dois 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
O operador também criará os serviços correspondentes que farão conexões com balanceamento de carga para os respectivos pods:
$ 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
A saída acima mostra que o operador criou 4 serviços:
- cluster1-haproxy - O serviço para um único mestre MySQL com balanceamento de carga (3306), protocolo Proxy (3309) e MySQL Admin (33062) - Uma nova porta administrativa introduzida no MySQL 8.0.14 e posterior. Este é o nome do serviço ou o endereço IP do cluster que os aplicativos precisam conectar para ter uma conexão de mestre único com o cluster Galera.
- cluster1-haproxy-replicas - O serviço para um multimestre MySQL com balanceamento de carga (3306). Este é o nome do serviço ou o endereço IP do cluster que os aplicativos precisam conectar para ter uma conexão multimestre com o cluster Galera com algoritmo de balanceamento round-robin.
- cluster1-pxc - O serviço para pods PXC com balanceamento de carga, ignorando o HAProxy. Ao se conectar diretamente a esse serviço, o Kubernetes roteará a conexão de forma round-robin para todos os pods PXC, semelhante ao que o cluster-haproxy-replicase fornece. O serviço não tem endereço IP público atribuído e não está disponível fora do cluster.
- cluster1-pxc-unready - O serviço 'unready' é necessário para a descoberta do endereço do pod durante a inicialização do aplicativo, independentemente do estado do pod. Os pods Proxysql e pxc devem se conhecer antes que o banco de dados fique totalmente operacional. O serviço não pronto não tem endereço IP público atribuído e não está disponível fora do cluster.
Para se conectar através de um cliente MySQL, basta executar o seguinte comando:
$ kubectl run -i --rm --tty percona-client --image=percona:8.0 --restart=Never -- bash -il
Isso criará um pod temporário e entrará imediatamente no ambiente do contêiner. Em seguida, execute o comando padrão do cliente mysql com uma credencial adequada:
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 |
+----------------+
Quando analisamos o posicionamento do pod, todos os pods do Percona XtraDB Cluster estão localizados em um host 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>
Isso definitivamente melhorará a disponibilidade do serviço, caso um dos hosts do Kubernetes fique inativo.
Para escalar até 5 pods, precisamos preparar outros 2 novos nós do Kubernetes com antecedência para respeitar a configuração de afinidade do pod (padrão para identity.antiAffinityTopologyKey.topologyKey="kubernetes.io/hostname"). Em seguida, execute o seguinte comando de patch para dimensionar o Percona XtraDB Cluster para 5 nós:
$ kubectl patch pxc cluster1 \
--type='json' -p='[{"op": "replace", "path": "/spec/pxc/size", "value": 5 }]'
Monitore a criação do pod usando o 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>
Outros 2 novos pods (cluster1-pxc-3 e cluster1-pxc-4) foram criados em outros 2 novos nós do Kubernetes (gke-my-k8s-cluster-pool-1-ab14a45e-h1pf e gke-my-k8s-cluster-pool-1-ab14a45e-01qn). Para reduzir, basta alterar o valor de volta para 3 no comando patch acima. Observe que o Percona XtraDB Cluster deve ser executado com um número ímpar de nós para evitar a divisão do cérebro.
Implantando um aplicativo (WordPress)
Neste exemplo, vamos implantar um aplicativo WordPress em cima do nosso Percona XtraDB Cluster e HAProxy. Vamos primeiro preparar o arquivo de definição YAML da seguinte forma:
$ 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 atenção às variáveis de ambiente WORDPRESS_DB_HOST e WORDPRESS_DB_PASSWORD. A primeira variável onde definimos "cluster1-haproxy" como o host do banco de dados, em vez de um nó de banco de dados individual e, para o último, especificamos a senha do root instruindo o Kubernetes a lê-la do objeto my-cluster-secrets na chave "root", que é equivalente a "mypassword" (depois que o valor base64 foi decodificado). Ignoramos a definição da variável de ambiente WORDPRESS_DB_USER, pois o valor padrão é "root".
Agora podemos criar nossa aplicação:
$ kubectl apply -f wordpress-deployment.yaml
Verifique o serviço:
$ 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
Neste ponto, podemos nos conectar ao nosso aplicativo WordPress em http://35.200.78.195/ (o endereço IP externo) e começar a configurar o aplicativo WordPress. Neste ponto, nosso aplicativo WordPress está conectado a um dos Percona XtraDB Cluster (conexão de mestre único) por meio de um dos pods HAProxy.
É isso por enquanto. Para obter mais informações, confira a documentação do Percona Kubernetes Operator for Percona XtraDB Cluster. Feliz conteinerização!