MariaDB
 sql >> Base de Dados >  >> RDS >> MariaDB

Clustering nativo do ProxySQL com Kubernetes


O ProxySQL oferece suporte ao clustering nativo desde a v1.4.2. Isso significa que várias instâncias do ProxySQL reconhecem clusters; eles estão cientes do estado um do outro e são capazes de lidar com as alterações de configuração automaticamente, sincronizando com a configuração mais atualizada com base na versão da configuração, carimbo de data/hora e valor da soma de verificação. Confira esta postagem de blog que demonstra como configurar o suporte de cluster para ProxySQL e como você pode esperar que ele se comporte.

ProxySQL é um proxy descentralizado, recomendado para ser implantado mais próximo da aplicação. Essa abordagem se adapta muito bem até centenas de nós, pois foi projetada para ser facilmente reconfigurável em tempo de execução. Para gerenciar com eficiência vários nós ProxySQL, é preciso garantir que as alterações realizadas em um dos nós sejam aplicadas em todos os nós do farm. Sem clustering nativo, é preciso exportar manualmente as configurações e importá-las para os outros nós (embora você possa automatizar isso sozinho).

Na postagem anterior do blog, abordamos o clustering ProxySQL por meio do Kubernetes ConfigMap. Essa abordagem é mais ou menos bastante eficiente com a abordagem de configuração centralizada no ConfigMap. O que quer que seja carregado no ConfigMap será montado em pods. A atualização da configuração pode ser feita por meio de controle de versão (modifique o conteúdo do proxysql.cnf e carregue-o no ConfigMap com outro nome) e, em seguida, envie para os pods, dependendo do agendamento do método de implantação e da estratégia de atualização.

No entanto, em um ambiente em rápida mudança, essa abordagem ConfigMap provavelmente não é o melhor método porque, para carregar a nova configuração, é necessário reprogramar o pod para remontar o volume ConfigMap e isso pode comprometer o serviço ProxySQL como um todo. Por exemplo, digamos que em nosso ambiente, nossa política de senha estrita exige forçar a expiração da senha do usuário MySQL a cada 7 dias, o que teríamos que manter atualizando o ConfigMap ProxySQL para a nova senha semanalmente. Como observação lateral, o usuário do MySQL dentro do ProxySQL requer que o usuário e a senha correspondam aos dos servidores MySQL de back-end. É aí que devemos começar a usar o suporte de cluster nativo do ProxySQL no Kubernetes, para aplicar automaticamente as alterações de configuração sem o incômodo de versionamento do ConfigMap e reprogramação de pod.

Nesta postagem do blog, mostraremos como executar o clustering nativo do ProxySQL com serviço headless no Kubernetes. Nossa arquitetura de alto nível pode ser ilustrada como abaixo:

Temos 3 nós Galera rodando em infraestrutura bare-metal implantada e gerenciada pelo ClusterControl:
  • 192.168.0.21
  • 192.168.0.22
  • 192.168.0.23

Nossos aplicativos estão todos sendo executados como pods no Kubernetes. A ideia é introduzir duas instâncias de ProxySQL entre o aplicativo e nosso cluster de banco de dados para servir como um proxy reverso. Os aplicativos se conectarão aos pods do ProxySQL por meio do serviço Kubernetes, que terá balanceamento de carga e failover em várias réplicas do ProxySQL.

Veja a seguir um resumo de nossa configuração do Kubernetes:
[email protected]:~# kubectl get nodes -o wide
NAME    STATUS   ROLES    AGE     VERSION   INTERNAL-IP       EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
kube1   Ready    master   5m      v1.15.1   192.168.100.201   <none>        Ubuntu 18.04.1 LTS   4.15.0-39-generic   docker://18.9.7
kube2   Ready    <none>   4m1s    v1.15.1   192.168.100.202   <none>        Ubuntu 18.04.1 LTS   4.15.0-39-generic   docker://18.9.7
kube3   Ready    <none>   3m42s   v1.15.1   192.168.100.203   <none>        Ubuntu 18.04.1 LTS   4.15.0-39-generic   docker://18.9.7

Configuração do ProxySQL via ConfigMap


Vamos primeiro preparar nossa configuração base que será carregada no ConfigMap. Crie um arquivo chamado proxysql.cnf e adicione as seguintes linhas:
datadir="/var/lib/proxysql"

admin_variables=
{
    admin_credentials="proxysql-admin:adminpassw0rd;cluster1:secret1pass"
    mysql_ifaces="0.0.0.0:6032"
    refresh_interval=2000
    cluster_username="cluster1"
    cluster_password="secret1pass"
    cluster_check_interval_ms=200
    cluster_check_status_frequency=100
    cluster_mysql_query_rules_save_to_disk=true
    cluster_mysql_servers_save_to_disk=true
    cluster_mysql_users_save_to_disk=true
    cluster_proxysql_servers_save_to_disk=true
    cluster_mysql_query_rules_diffs_before_sync=3
    cluster_mysql_servers_diffs_before_sync=3
    cluster_mysql_users_diffs_before_sync=3
    cluster_proxysql_servers_diffs_before_sync=3
}

mysql_variables=
{
    threads=4
    max_connections=2048
    default_query_delay=0
    default_query_timeout=36000000
    have_compress=true
    poll_timeout=2000
    interfaces="0.0.0.0:6033;/tmp/proxysql.sock"
    default_schema="information_schema"
    stacksize=1048576
    server_version="5.1.30"
    connect_timeout_server=10000
    monitor_history=60000
    monitor_connect_interval=200000
    monitor_ping_interval=200000
    ping_interval_server_msec=10000
    ping_timeout_server=200
    commands_stats=true
    sessions_sort=true
    monitor_username="proxysql"
    monitor_password="proxysqlpassw0rd"
    monitor_galera_healthcheck_interval=2000
    monitor_galera_healthcheck_timeout=800
}

mysql_galera_hostgroups =
(
    {
        writer_hostgroup=10
        backup_writer_hostgroup=20
        reader_hostgroup=30
        offline_hostgroup=9999
        max_writers=1
        writer_is_also_reader=1
        max_transactions_behind=30
        active=1
    }
)

mysql_servers =
(
    { address="192.168.0.21" , port=3306 , hostgroup=10, max_connections=100 },
    { address="192.168.0.22" , port=3306 , hostgroup=10, max_connections=100 },
    { address="192.168.0.23" , port=3306 , hostgroup=10, max_connections=100 }
)

mysql_query_rules =
(
    {
        rule_id=100
        active=1
        match_pattern="^SELECT .* FOR UPDATE"
        destination_hostgroup=10
        apply=1
    },
    {
        rule_id=200
        active=1
        match_pattern="^SELECT .*"
        destination_hostgroup=20
        apply=1
    },
    {
        rule_id=300
        active=1
        match_pattern=".*"
        destination_hostgroup=10
        apply=1
    }
)

mysql_users =
(
    { username = "wordpress", password = "passw0rd", default_hostgroup = 10, transaction_persistent = 0, active = 1 },
    { username = "sbtest", password = "passw0rd", default_hostgroup = 10, transaction_persistent = 0, active = 1 }
)

proxysql_servers =
(
    { hostname = "proxysql-0.proxysqlcluster", port = 6032, weight = 1 },
    { hostname = "proxysql-1.proxysqlcluster", port = 6032, weight = 1 }
)

Algumas das linhas de configuração acima são explicadas por seção abaixo:

admin_variables


Preste atenção às admin_credentials variável onde usamos usuário não padrão que é "proxysql-admin". O ProxySQL reserva o usuário "admin" padrão apenas para conexão local via localhost. Portanto, temos que usar outros usuários para acessar a instância ProxySQL remotamente. Caso contrário, você obteria o seguinte erro:
ERROR 1040 (42000): User 'admin' can only connect locally

Também anexamos o cluster_username e cluster_password valor em admin_credentials linha, separada por um ponto e vírgula para permitir a sincronização automática. Todas as variáveis ​​prefixadas com cluster_* estão relacionados ao clustering nativo do ProxySQL e são autoexplicativos.

mysql_galera_hostgroups


Esta é uma nova diretiva introduzida para ProxySQL 2.x (nossa imagem ProxySQL está sendo executada em 2.0.5). Se você deseja executar no ProxySQL 1.x, remova esta parte e use a tabela do agendador. Já explicamos os detalhes de configuração nesta postagem do blog, Como executar e configurar o ProxySQL 2.0 para MySQL Galera Cluster no Docker em "ProxySQL 2.x Support for Galera Cluster".

mysql_servers


Todas as linhas são autoexplicativas, baseadas em três servidores de banco de dados executados no MySQL Galera Cluster, conforme resumido na captura de tela de Topologia a seguir retirada do ClusterControl:

servidores_proxysql


Aqui definimos uma lista de pares ProxySQL:
  • nome do host - nome do host/endereço IP do par
  • porta - porta de administração do par
  • peso - não utilizado atualmente, mas no roteiro para melhorias futuras
  • comentário - Campo de comentário de forma livre

No ambiente Docker/Kubernetes, existem várias maneiras de descobrir e vincular nomes de host ou endereços IP de contêiner e inseri-los nesta tabela, seja usando ConfigMap, inserção manual, via script entrypoint.sh, variáveis ​​de ambiente ou outros meios. No Kubernetes, dependendo do método ReplicationController ou Deployment usado, adivinhar antecipadamente o nome do host resolvível do pod é um pouco complicado, a menos que você esteja executando no StatefulSet.

Confira este tutorial sobre o índice ordinal do pod StatefulState, que fornece um nome de host resolvível estável para os pods criados. Combine isso com o serviço headless (explicado mais abaixo), o formato do nome do host resolvível seria:

{app_name}-{index_number}.{service}

Onde {service} é um serviço headless, que explica de onde vêm "proxysql-0.proxysqlcluster" e "proxysql-1.proxysqlcluster". Se você quiser ter mais de 2 réplicas, adicione mais entradas de acordo, acrescentando um número de índice crescente relativo ao nome do aplicativo StatefulSet.

Agora estamos prontos para enviar o arquivo de configuração para o ConfigMap, que será montado em cada pod ProxySQL durante a implantação:
$ kubectl create configmap proxysql-configmap --from-file=proxysql.cnf

Verifique se nosso ConfigMap está carregado corretamente:
$ kubectl get configmap
NAME                 DATA   AGE
proxysql-configmap   1      7h57m

Criando usuário de monitoramento ProxySQL


A próxima etapa antes de iniciarmos a implantação é criar um usuário de monitoramento ProxySQL em nosso cluster de banco de dados. Como estamos executando no cluster do Galera, execute as seguintes instruções em um dos nós do Galera:
mysql> CREATE USER 'proxysql'@'%' IDENTIFIED BY 'proxysqlpassw0rd';
mysql> GRANT USAGE ON *.* TO 'proxysql'@'%';

Se você não criou os usuários do MySQL (conforme especificado na seção mysql_users acima), temos que criá-los também:
mysql> CREATE USER 'wordpress'@'%' IDENTIFIED BY 'passw0rd';
mysql> GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'%';
mysql> CREATE USER 'sbtest'@'%' IDENTIFIED BY 'passw0rd';
mysql> GRANT ALL PRIVILEGES ON sbtest.* TO 'proxysql'@'%';

É isso. Agora estamos prontos para iniciar a implantação.

Implantando um StatefulSet


Começaremos criando duas instâncias do ProxySQL ou réplicas para fins de redundância usando StatefulSet.

Vamos começar criando um arquivo de texto chamado proxysql-ss-svc.yml e adicionar as seguintes linhas:
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: proxysql
  labels:
    app: proxysql
spec:
  replicas: 2
  serviceName: proxysqlcluster
  selector:
    matchLabels:
      app: proxysql
      tier: frontend
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: proxysql
        tier: frontend
    spec:
      restartPolicy: Always
      containers:
      - image: severalnines/proxysql:2.0.4
        name: proxysql
        volumeMounts:
        - name: proxysql-config
          mountPath: /etc/proxysql.cnf
          subPath: proxysql.cnf
        ports:
        - containerPort: 6033
          name: proxysql-mysql
        - containerPort: 6032
          name: proxysql-admin
      volumes:
      - name: proxysql-config
        configMap:
          name: proxysql-configmap
---
apiVersion: v1
kind: Service
metadata:
  annotations:
  labels:
    app: proxysql
    tier: frontend
  name: proxysql
spec:
  ports:
  - name: proxysql-mysql
    nodePort: 30033
    port: 6033
    protocol: TCP
    targetPort: 6033
  - name: proxysql-admin
    nodePort: 30032
    port: 6032
    protocol: TCP
    targetPort: 6032
  selector:
    app: proxysql
    tier: frontend
  type: NodePort

Existem duas seções da definição acima - StatefulSet e Service. O StatefulSet é a definição de nossos pods ou réplicas e o ponto de montagem para nosso volume ConfigMap, carregado de proxysql-configmap. A próxima seção é a definição do serviço, onde definimos como os pods devem ser expostos e roteados para a rede interna ou externa.

Crie o conjunto e o serviço com estado do ProxySQL:
$ kubectl create -f proxysql-ss-svc.yml

Verifique os estados do pod e do serviço:
$ kubectl get pods,svc
NAME             READY   STATUS    RESTARTS   AGE
pod/proxysql-0   1/1     Running   0          4m46s
pod/proxysql-1   1/1     Running   0          2m59s

NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                         AGE
service/kubernetes        ClusterIP   10.96.0.1        <none>        443/TCP                         10h
service/proxysql          NodePort    10.111.240.193   <none>        6033:30033/TCP,6032:30032/TCP   5m28s

Se você observar o log do pod, notará que fomos inundados com este aviso:
$ kubectl logs -f proxysql-0
...
2019-08-01 19:06:18 ProxySQL_Cluster.cpp:215:ProxySQL_Cluster_Monitor_thread(): [WARNING] Cluster: unable to connect to peer proxysql-1.proxysqlcluster:6032 . Error: Unknown MySQL server host 'proxysql-1.proxysqlcluster' (0)

O acima simplesmente significa que o proxysql-0 não conseguiu resolver "proxysql-1.proxysqlcluster" e se conectar a ele, o que é esperado, pois não criamos nosso serviço headless para registros DNS que serão necessários para comunicação entre ProxySQL.

Serviço sem cabeça do Kubernetes


Para que os pods do ProxySQL possam resolver o FQDN previsto e se conectar a ele diretamente, o processo de resolução deve poder pesquisar o endereço IP do pod de destino atribuído e não o endereço IP virtual. É aqui que o serviço sem cabeça entra em cena. Ao criar um serviço headless definindo "clusterIP=None", nenhum balanceamento de carga é configurado e nenhum IP de cluster (IP virtual) é alocado para este serviço. Somente o DNS é configurado automaticamente. Ao executar uma consulta DNS para serviço sem periféricos, você obterá a lista dos endereços IP dos pods.

Aqui está o que parece se procurarmos os registros DNS do serviço headless para "proxysqlcluster" (neste exemplo, tivemos 3 instâncias do ProxySQL):
$ host proxysqlcluster
proxysqlcluster.default.svc.cluster.local has address 10.40.0.2
proxysqlcluster.default.svc.cluster.local has address 10.40.0.3
proxysqlcluster.default.svc.cluster.local has address 10.32.0.2

Enquanto a saída a seguir mostra o registro DNS para o serviço padrão chamado "proxysql" que resolve para o clusterIP:
$ host proxysql
proxysql.default.svc.cluster.local has address 10.110.38.154

Para criar um serviço headless e anexá-lo aos pods, é preciso definir o ServiceName dentro da declaração StatefulSet, e a definição do serviço deve ter "clusterIP=None" conforme mostrado abaixo. Crie um arquivo de texto chamado proxysql-headless-svc.yml e adicione as seguintes linhas:
apiVersion: v1
kind: Service
metadata:
  name: proxysqlcluster
  labels:
    app: proxysql
spec:
  clusterIP: None
  ports:
  - port: 6032
    name: proxysql-admin
  selector:
    app: proxysql

Crie o serviço headless:
$ kubectl create -f proxysql-headless-svc.yml

Apenas para verificação, neste momento, temos os seguintes serviços em execução:
$ kubectl get svc
NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
kubernetes        ClusterIP   10.96.0.1       <none>        443/TCP                         8h
proxysql          NodePort    10.110.38.154   <none>        6033:30033/TCP,6032:30032/TCP   23m
proxysqlcluster   ClusterIP   None            <none>        6032/TCP                        4s

Agora, confira um dos logs do nosso pod:
$ kubectl logs -f proxysql-0
...
2019-08-01 19:06:19 ProxySQL_Cluster.cpp:215:ProxySQL_Cluster_Monitor_thread(): [WARNING] Cluster: unable to connect to peer proxysql-1.proxysqlcluster:6032 . Error: Unknown MySQL server host 'proxysql-1.proxysqlcluster' (0)
2019-08-01 19:06:19 [INFO] Cluster: detected a new checksum for mysql_query_rules from peer proxysql-1.proxysqlcluster:6032, version 1, epoch 1564686376, checksum 0x3FEC69A5C9D96848 . Not syncing yet ...
2019-08-01 19:06:19 [INFO] Cluster: checksum for mysql_query_rules from peer proxysql-1.proxysqlcluster:6032 matches with local checksum 0x3FEC69A5C9D96848 , we won't sync.

Você notaria que o componente Cluster é capaz de resolver, conectar e detectar uma nova soma de verificação do outro peer, proxysql-1.proxysqlcluster na porta 6032 por meio do serviço headless chamado "proxysqlcluster". Observe que esse serviço expõe a porta 6032 apenas na rede Kubernetes, portanto, é inacessível externamente.

Neste ponto, nossa implantação está concluída.

Conectando ao ProxySQL


Existem várias maneiras de se conectar aos serviços ProxySQL. As conexões MySQL com balanceamento de carga devem ser enviadas para a porta 6033 de dentro da rede Kubernetes e usar a porta 30033 se o cliente estiver se conectando de uma rede externa.

Para se conectar à interface de administração do ProxySQL de uma rede externa, podemos conectar à porta definida na seção NodePort, 30032 (192.168.100.203 é o endereço IP primário do host kube3.local):
$ mysql -uproxysql-admin -padminpassw0rd -h192.168.100.203 -P30032

Use o clusterIP 10.110.38.154 (definido no serviço "proxysql") na porta 6032 se quiser acessá-lo de outros pods na rede Kubernetes.

Em seguida, execute as alterações de configuração do ProxySQL conforme desejar e carregue-as no tempo de execução:
mysql> INSERT INTO mysql_users (username,password,default_hostgroup) VALUES ('newuser','passw0rd',10);
mysql> LOAD MYSQL USERS TO RUNTIME;

Você notará as seguintes linhas em um dos pods indicando que a sincronização da configuração foi concluída:
$ kubectl logs -f proxysql-0
...
2019-08-02 03:53:48 [INFO] Cluster: detected a peer proxysql-1.proxysqlcluster:6032 with mysql_users version 2, epoch 1564718027, diff_check 4. Own version: 1, epoch: 1564714803. Proceeding with remote sync
2019-08-02 03:53:48 [INFO] Cluster: detected peer proxysql-1.proxysqlcluster:6032 with mysql_users version 2, epoch 1564718027
2019-08-02 03:53:48 [INFO] Cluster: Fetching MySQL Users from peer proxysql-1.proxysqlcluster:6032 started
2019-08-02 03:53:48 [INFO] Cluster: Fetching MySQL Users from peer proxysql-1.proxysqlcluster:6032 completed

Tenha em mente que a sincronização automática só acontece se houver uma alteração de configuração no tempo de execução do ProxySQL. Portanto, é vital executar a instrução "LOAD ... TO RUNTIME" antes de poder ver a ação. Não se esqueça de salvar as alterações do ProxySQL no disco para persistência:
mysql> SAVE MYSQL USERS TO DISK;

Limitação


Observe que há uma limitação para essa configuração porque o ProxySQL não suporta salvar/exportar a configuração ativa em um arquivo de configuração de texto que poderíamos usar posteriormente para carregar no ConfigMap para persistência. Há uma solicitação de recurso para isso. Enquanto isso, você pode enviar as modificações para o ConfigMap manualmente. Caso contrário, se os pods forem excluídos acidentalmente, você perderá sua configuração atual porque os novos pods serão inicializados por qualquer definição definida no ConfigMap.

Agradecimentos especiais a Sampath Kamineni, que despertou a ideia desta postagem no blog e forneceu informações sobre os casos de uso e implementação.