Backups com estado em Kubernetes

Então, como todo mundo sabe ao certo, mais recentemente o DevOpsConfRussia2018 foi realizado em Moscou, no “Infraspace”, de 2 a 2 de outubro. Para quem não se sente confortável , o DevOpsConf é uma conferência profissional sobre a integração de processos de desenvolvimento, teste e operação.


Nossa empresa também participou desta conferência. Nós éramos seus parceiros, representando a empresa em nosso estande, e também realizamos uma pequena reunião. A propósito, essa foi a nossa primeira participação nesse tipo de atividade. A primeira conferência, o primeiro mitap, a primeira experiência.


Sobre o que conversamos? Mitap estava no tópico "Backups no Kubernetes".


Muito provavelmente ouvindo esse nome, muitos dirão: “Por que voltar para o Kubernetes? Não precisa ser feito backup, é sem estado. ”



Introdução ...


Vamos começar com um pouco de fundo. Por que se tornou necessário cobrir este tópico e por que é necessário?


Em 2016, nos familiarizamos com uma tecnologia como a Kubernetes e começamos a aplicá-la ativamente em nossos projetos. Obviamente, esses são principalmente projetos com arquitetura de microsserviço, e isso, por sua vez, implica o uso de um grande número de softwares diversos.


Com o primeiro projeto em que usamos o Kubernetes, tínhamos uma pergunta sobre como fazer backup de serviços com estado localizados lá, que às vezes, por algum motivo ou outro, se enquadram nos k8s.


Começamos a estudar e procurar práticas existentes para resolver esse problema. Comunique-se com nossos colegas e camaradas: "E como esse processo é realizado e construído por eles?"


Depois de conversar, percebemos que tudo isso acontece por diferentes métodos, meios e com um grande número de muletas. No entanto, não seguimos nenhuma abordagem unificada, mesmo dentro da estrutura de um projeto .


Por que isso é tão importante? Como nossa empresa presta serviços de manutenção a projetos baseados no k8s, precisamos apenas desenvolver uma metodologia estruturada para resolver esse problema.


Imagine que você está trabalhando com um projeto específico no Coober. Ele contém alguns serviços com estado e você precisa fazer backup dos dados deles. Em princípio, aqui você pode fazer algumas muletas e esquecer. Mas e se você já tiver dois projetos no k8s? E o segundo projeto usa serviços completamente diferentes em seu trabalho. E se já existem cinco projetos? Dez? Ou mais de vinte?


Obviamente, colocar muletas já é difícil e inconveniente. Precisamos de algum tipo de abordagem unificada que possa ser usada ao trabalhar com muitos projetos em Cuba e, ao mesmo tempo, para que a equipe de engenheiros possa, fácil e literalmente, em questão de minutos, fazer as alterações necessárias nos backups desses projetos.


Na estrutura deste artigo, falaremos apenas sobre qual ferramenta e qual prática usamos para resolver esse problema em nossa empresa.


Como estamos fazendo isso?


Nxs-backup, o que é?

Para backups, usamos nossa própria ferramenta de código aberto - nxs-backup. Não entraremos em detalhes do que ele pode. Mais detalhes podem ser encontrados no seguinte link .


Agora vamos à implementação de backups no k8s. Como e o que exatamente foi feito por nós.


O que é backup?


Vejamos um exemplo de backup do nosso próprio Redmine. Nele, iremos fazer backup do banco de dados MySQL e dos arquivos do projeto do usuário.


Como fazemos isso?


1 CronJob == 1 Serviço

Em servidores e clusters comuns no hardware, quase todas as ferramentas de backup são iniciadas principalmente por meio de cron regular. No k8s para esse fim, usamos o CronJob's, ou seja, criamos 1 CronJob para 1 serviço, que iremos fazer backup. Todos esses CronJob s estão localizados no mesmo espaço de nome que o próprio serviço.


Vamos começar com o banco de dados MySQL. Para fazer backup do MySQL, precisamos de 4 elementos, como em quase qualquer outro serviço:


  • ConfigMap (nxs-backup.conf)
  • ConfigMap (mysql.conf para nxs-backup)
  • Segredo (os acessos ao serviço são armazenados aqui, neste caso, MySQL). Normalmente, esse elemento já está definido para o serviço funcionar e pode ser reutilizado.
  • CronJob (para cada serviço próprio)

Vamos em ordem.


nxs-backup.conf

apiVersion: v1 kind: ConfigMap metadata: name: nxs-backup-conf data: nxs-backup.conf: |- main: server_name: Nixys k8s cluster admin_mail: admins@nixys.ru client_mail: - '' mail_from: backup@nixys.ru level_message: error block_io_read: '' block_io_write: '' blkio_weight: '' general_path_to_all_tmp_dir: /var/nxs-backup cpu_shares: '' log_file: /dev/stdout jobs: !include [conf.d/*.conf] 

Aqui, definimos os parâmetros básicos que são passados ​​para nossa ferramenta, necessários para sua operação. Este é o nome do servidor, e-mail para notificações, restrição no consumo de recursos e outros parâmetros.


As configurações podem ser especificadas no formato j2, que permite o uso de variáveis ​​de ambiente.


mysql.conf

 apiVersion: v1 kind: ConfigMap metadata: name: mysql-conf data: service.conf.j2: |- - job: mysql type: mysql tmp_dir: /var/nxs-backup/databases/mysql/dump_tmp sources: - connect: db_host: {{ db_host }} db_port: {{ db_port }} socket: '' db_user: {{ db_user }} db_password: {{ db_password }} target: - redmine_db gzip: yes is_slave: no extra_keys: '--opt --add-drop-database --routines --comments --create-options --quote-names --order-by-primary --hex-blob' storages: - storage: local enable: yes backup_dir: /var/nxs-backup/databases/mysql/dump store: days: 6 weeks: 4 month: 6 

Este arquivo descreve a lógica de backup para o serviço correspondente, no nosso caso, é MySQL.


Aqui você pode especificar:


  • Qual é o nome do trabalho (campo: trabalho)
  • Tipo de trabalho (campo: tipo)
  • O diretório temporário necessário para coletar backups (campo: tmp_dir)
  • Opções de conexão MySQL (campo: conectar)
  • Banco de dados a ser copiado (campo: target)
  • Precisa parar o Escravo antes de coletar (campo: is_slave)
  • Chaves adicionais para mysqldump (campo: extra_keys)
  • Armazenamento de armazenamento, ou seja, em qual armazenamento armazenaremos uma cópia (campo: armazenamento)
  • O diretório em que armazenaremos nossas cópias (campo: backup_dir)
  • Esquema de armazenamento (campo: loja)

Em nosso exemplo, o tipo de armazenamento é definido como local, ou seja, coletamos e armazenamos backups localmente em um diretório específico do pod iniciado.


Aqui, por analogia com este arquivo de configuração, você pode especificar os mesmos arquivos de configuração para Redis, PostgreSQL ou qualquer outro serviço necessário, se for suportado por nossa ferramenta. O fato de que ele suporta pode ser encontrado no link fornecido anteriormente.


MySQL secreto

 apiVersion: v1 kind: Secret metadata: name: app-config data: db_name: "" db_host: "" db_user: "" db_password: "" secret_token: "" smtp_address: "" smtp_domain: "" smtp_ssl: "" smtp_enable_starttls_auto: "" smtp_port: "" smtp_auth_type: "" smtp_login: "" smtp_password: "" 

Mantemos acesso secreto para conectar-se ao MySQL e ao servidor de email. Eles podem ser armazenados em um segredo separado ou, se for o caso, tirar proveito do existente. Nada interessante aqui. Nosso segredo também contém o secret_token, necessário para a operação do nosso Redmine.


CronJob MySQL

 apiVersion: batch/v1beta1 kind: CronJob metadata: name: mysql spec: schedule: "00 00 * * *" jobTemplate: spec: template: spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - nxs-node5 containers: - name: mysql-backup image: nixyslab/nxs-backup:latest env: - name: DB_HOST valueFrom: secretKeyRef: name: app-config key: db_host - name: DB_PORT value: '3306' - name: DB_USER valueFrom: secretKeyRef: name: app-config key: db_user - name: DB_PASSWORD valueFrom: secretKeyRef: name: app-config key: db_password - name: SMTP_MAILHUB_ADDR valueFrom: secretKeyRef: name: app-config key: smtp_address - name: SMTP_MAILHUB_PORT valueFrom: secretKeyRef: name: app-config key: smtp_port - name: SMTP_USE_TLS value: 'YES' - name: SMTP_AUTH_USER valueFrom: secretKeyRef: name: app-config key: smtp_login - name: SMTP_AUTH_PASS valueFrom: secretKeyRef: name: app-config key: smtp_password - name: SMTP_FROM_LINE_OVERRIDE value: 'NO' volumeMounts: - name: mysql-conf mountPath: /usr/share/nxs-backup/service.conf.j2 subPath: service.conf.j2 - name: nxs-backup-conf mountPath: /etc/nxs-backup/nxs-backup.conf subPath: nxs-backup.conf - name: backup-dir mountPath: /var/nxs-backup imagePullPolicy: Always volumes: - name: mysql-conf configMap: name: mysql-conf items: - key: service.conf.j2 path: service.conf.j2 - name: nxs-backup-conf configMap: name: nxs-backup-conf items: - key: nxs-backup.conf path: nxs-backup.conf - name: backup-dir hostPath: path: /var/backups/k8s type: Directory restartPolicy: OnFailure 

Talvez este elemento seja o mais interessante. Primeiro, para compilar o CronJob correto, você precisa determinar onde os backups coletados serão armazenados.


Para isso, temos um servidor separado com o número necessário de recursos. No exemplo, um nó de cluster separado, nxs-node5, é alocado para coletar backups. A restrição para iniciar o CronJob nos nós de que precisamos é definida pela diretiva nodeAffinity.


Quando o CronJob inicia, o diretório correspondente é conectado a ele via hostPath a partir do sistema host, usado para armazenar cópias de backup.


Em seguida, o ConfigMaps é conectado ao CronJob específico, que contém a configuração do nxs-backup, a saber, os arquivos nxs-backup.conf e mysql.conf dos quais acabamos de falar acima.


Em seguida, todas as variáveis ​​de ambiente necessárias são definidas, definidas diretamente no manifesto ou extraídas de Secret'ov.


Portanto, as variáveis ​​são transferidas para o contêiner e, através do docker-entrypoint.sh, são substituídas no ConfigMaps nos locais que precisamos com os valores necessários. Para o MySQL, este é db_host, db_user, db_password. Nesse caso, passamos a porta simplesmente como um valor no manifesto do CronJob, porque ela não carrega nenhuma informação valiosa.


Bem, com o MySQL tudo parece estar claro. Agora vamos ver o que é necessário para fazer backup dos arquivos do aplicativo Redmine.


desc_files.conf

 apiVersion: v1 kind: ConfigMap metadata: name: desc-files-conf data: service.conf.j2: |- - job: desc-files type: desc_files tmp_dir: /var/nxs-backup/files/desc/dump_tmp sources: - target: - /var/www/files gzip: yes storages: - storage: local enable: yes backup_dir: /var/nxs-backup/files/desc/dump store: days: 6 weeks: 4 month: 6 

Este é um arquivo de configuração que descreve a lógica de backup para arquivos. Também não há nada incomum aqui, todos os mesmos parâmetros são definidos como para o MySQL, com exceção dos dados de autorização, porque eles simplesmente não são. Embora possam ser, se houver protocolos para transferência de dados: ssh, ftp, webdav, s3 e outros. Vamos considerar esta opção um pouco mais tarde.


CronJob desc_files

 apiVersion: batch/v1beta1 kind: CronJob metadata: name: desc-files spec: schedule: "00 00 * * *" jobTemplate: spec: template: spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - nxs-node5 containers: - name: desc-files-backup image: nixyslab/nxs-backup:latest env: - name: SMTP_MAILHUB_ADDR valueFrom: secretKeyRef: name: app-config key: smtp_address - name: SMTP_MAILHUB_PORT valueFrom: secretKeyRef: name: app-config key: smtp_port - name: SMTP_USE_TLS value: 'YES' - name: SMTP_AUTH_USER valueFrom: secretKeyRef: name: app-config key: smtp_login - name: SMTP_AUTH_PASS valueFrom: secretKeyRef: name: app-config key: smtp_password - name: SMTP_FROM_LINE_OVERRIDE value: 'NO' volumeMounts: - name: desc-files-conf mountPath: /usr/share/nxs-backup/service.conf.j2 subPath: service.conf.j2 - name: nxs-backup-conf mountPath: /etc/nxs-backup/nxs-backup.conf subPath: nxs-backup.conf - name: target-dir mountPath: /var/www/files - name: backup-dir mountPath: /var/nxs-backup imagePullPolicy: Always volumes: - name: desc-files-conf configMap: name: desc-files-conf items: - key: service.conf.j2 path: service.conf.j2 - name: nxs-backup-conf configMap: name: nxs-backup-conf items: - key: nxs-backup.conf path: nxs-backup.conf - name: backup-dir hostPath: path: /var/backups/k8s type: Directory - name: target-dir persistentVolumeClaim: claimName: redmine-app-files restartPolicy: OnFailure 

Nada de novo no MySQL. Mas aqui é montado um PV adicional (target-dir), que iremos fazer backup - / var / www / files. Caso contrário, tudo será o mesmo, armazenamos cópias localmente no nó de que precisamos, para o qual o CronJob está designado.


Sumário

Para cada serviço que queremos fazer backup, criamos um CronJob separado com todos os elementos relacionados necessários: ConfigMaps e Secrets. Por analogia com os exemplos considerados, podemos fazer backup de qualquer serviço semelhante no cluster.


Penso que, com base nesses dois exemplos, todo mundo tem alguma idéia de como fazemos o backup dos serviços estatais em Cuba. Eu acho que não faz sentido analisar em detalhes os mesmos exemplos para outros serviços, porque basicamente eles são todos iguais e têm pequenas diferenças.


Na verdade, é isso que queríamos alcançar, a saber, algum tipo de abordagem unificada para criar o processo de backup. E para que essa abordagem possa ser aplicada a um grande número de projetos diferentes com base nos k8s.


Onde a armazenamos?


Em todos os exemplos discutidos acima, armazenamos cópias no diretório local do nó no qual o contêiner está sendo executado. Mas ninguém se preocupa em conectar o Persistent Volume como um armazenamento externo ativo e coletar cópias lá. Ou você só pode sincronizá-los com um armazenamento remoto usando o protocolo desejado sem salvar localmente. Ou seja, existem muitas variações. Primeiro colete localmente e depois sincronize. Ou para coletar e armazenar apenas em um armazenamento remoto, etc. A configuração é bastante flexível.


mysql.conf + s3

A seguir, é apresentado um exemplo do arquivo de configuração de backup do MySQL, onde as cópias são armazenadas localmente no nó em que o CronJob está sendo executado e também são sincronizadas no s3.


 apiVersion: v1 kind: ConfigMap metadata: name: mysql-conf data: service.conf.j2: |- - job: mysql type: mysql tmp_dir: /var/nxs-backup/databases/mysql/dump_tmp sources: - connect: db_host: {{ db_host }} db_port: {{ db_port }} socket: '' db_user: {{ db_user }} db_password: {{ db_password }} target: - redmine_db gzip: yes is_slave: no extra_keys: ' --opt --add-drop-database --routines --comments --create-options --quote-names --order-by-primary --hex-blob' storages: - storage: local enable: yes backup_dir: /var/nxs-backup/databases/mysql/dump store: days: 6 weeks: 4 month: 6 - storage: s3 enable: yes backup_dir: /nxs-backup/databases/mysql/dump bucket_name: {{ bucket_name }} access_key_id: {{ access_key_id }} secret_access_key: {{ secret_access_key }} s3fs_opts: {{ s3fs_opts }} store: days: 2 weeks: 1 month: 6 

Ou seja, se não for suficiente armazenar cópias localmente, você poderá sincronizá-las com qualquer armazenamento remoto usando o protocolo apropriado. O número de armazenamento para armazenamento pode ser qualquer.


Mas, neste caso, você ainda precisa fazer algumas alterações adicionais, a saber:


  • Conecte o ConfigMap apropriado ao conteúdo necessário para autorização com o AWS S3, no formato j2
  • Crie um segredo apropriado para armazenar acessos de autorização
  • Defina as variáveis ​​de ambiente necessárias extraídas do Segredo acima
  • Ajuste docker-entrypoint.sh para substituir as variáveis ​​correspondentes no ConfigMap
  • Recrie a imagem do Docker, adicionando utilitários para trabalhar com o AWS S3

Embora esse processo esteja longe de ser perfeito, estamos trabalhando nisso. Portanto, em um futuro próximo, adicionaremos ao nxs-backup a capacidade de definir parâmetros no arquivo de configuração usando variáveis ​​de ambiente, o que simplificará bastante o trabalho com o arquivo do ponto de entrada e minimizará o tempo necessário para adicionar suporte de backup para novos serviços.


Conclusão


Provavelmente é tudo.


O uso da abordagem sobre a qual acabamos de falar, em primeiro lugar, permite estruturar e modelar o backup de serviços de projetos com estado para os k8s. Ou seja, essa é uma solução pronta e, mais importante, a prática que você pode aplicar em seus projetos, sem perder tempo e esforço pesquisando e refinando as soluções de código aberto existentes.

Source: https://habr.com/ru/post/pt426543/


All Articles