Entonces, como todos saben con certeza, más recientemente DevOpsConfRussia2018 se celebró en Moscú en "Infraspace" el 1 y 2 de octubre. Para aquellos que no se sienten cómodos , DevOpsConf es una conferencia profesional sobre la integración de procesos de desarrollo, prueba y operación.
Nuestra empresa también participó en esta conferencia. Éramos sus socios, representando a la compañía en nuestro stand, y también celebramos una pequeña reunión. Por cierto, esta fue nuestra primera participación en este tipo de actividad. La primera conferencia, el primer mitap, la primera experiencia.
¿De qué hablamos? Mitap fue sobre el tema "Copias de seguridad en Kubernetes".
Lo más probable es que escuchen este nombre, muchos dirán: “¿Por qué respaldar a Kubernetes? No necesita ser respaldado, es apátrida ".

Introducción ...
Comencemos con un poco de historia. ¿Por qué se hizo necesario cubrir este tema y por qué es necesario?
En 2016, nos familiarizamos con una tecnología como Kubernetes y comenzamos a aplicarla activamente a nuestros proyectos. Por supuesto, estos son principalmente proyectos con arquitectura de microservicio, y esto a su vez implica el uso de una gran cantidad de software diverso.
Con el primer proyecto en el que utilizamos Kubernetes, teníamos una pregunta sobre cómo hacer una copia de seguridad de los servicios de Stateful ubicados allí, que a veces por alguna razón u otra caen en k8.
Comenzamos a estudiar y buscar prácticas existentes para resolver este problema. Comuníquese con nuestros colegas y camaradas: "¿Y cómo llevan a cabo y construyen este proceso?"
Después de hablar, nos dimos cuenta de que todo esto sucede por diferentes métodos, medios y con una gran cantidad de muletas. Sin embargo, no seguimos ningún enfoque unificado, incluso dentro del marco de un proyecto .
¿Por qué es esto tan importante? Dado que nuestra empresa presta servicios a proyectos basados en k8, solo necesitábamos desarrollar una metodología estructurada para resolver este problema.
Imagine que está trabajando con un proyecto específico en Coober. Contiene algunos servicios con estado y necesita hacer una copia de seguridad de sus datos. En principio, aquí puedes hacer un par de muletas y olvidarte de eso. Pero, ¿y si ya tienes dos proyectos en k8s? Y el segundo proyecto utiliza servicios completamente diferentes en su trabajo. ¿Y si ya hay cinco proyectos? Diez? ¿O más de veinte?
Por supuesto, ponerse muletas ya es difícil e inconveniente. Necesitamos algún tipo de enfoque unificado que pueda usarse al trabajar con muchos proyectos en Cuba, y al mismo tiempo, para que el equipo de ingenieros pueda hacer los cambios necesarios en las copias de respaldo de estos proyectos de manera fácil y literal en cuestión de minutos.
En el marco de este artículo, solo hablaremos sobre qué herramienta y qué práctica usamos para resolver este problema dentro de nuestra empresa.
¿Cómo lo estamos haciendo?
Nxs-backup, ¿qué es?
Para las copias de seguridad, utilizamos nuestra propia herramienta de código abierto: nxs-backup. No entraremos en detalles de lo que pueda. Más detalles se pueden encontrar en el siguiente enlace .
Ahora pasemos a la implementación de copias de seguridad en k8s. Cómo y qué fue exactamente lo que hicimos nosotros.
¿Qué es el respaldo?
Veamos un ejemplo de copia de seguridad de nuestra propia Redmine. En él, haremos una copia de seguridad de la base de datos MySQL y los archivos de proyecto del usuario.
¿Cómo hacemos esto?
1 CronJob == 1 Servicio
En servidores comunes y clústeres en hardware, casi todas las herramientas de respaldo se lanzan principalmente a través de cron regular. En k8s, para este propósito usamos CronJob's, es decir, creamos 1 CronJob para 1 servicio, del cual realizaremos una copia de seguridad. Todos estos CronJob s se encuentran en el mismo espacio de nombres que el servicio en sí.
Comencemos con la base de datos MySQL. Para respaldar MySQL, necesitamos 4 elementos, como para casi cualquier otro servicio:
- ConfigMap (nxs-backup.conf)
- ConfigMap (mysql.conf para nxs-backup)
- Secreto (los accesos al servicio se almacenan aquí, en este caso MySQL). Por lo general, este elemento ya está definido para que el servicio funcione y se puede reutilizar.
- CronJob (para cada servicio propio)
Vamos en orden.
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
Aquí establecemos los parámetros básicos que se pasan a nuestra herramienta, que son necesarios para su funcionamiento. Este es el nombre del servidor, correo electrónico para notificaciones, restricción en el consumo de recursos y otros parámetros.
Las configuraciones se pueden especificar en formato j2, lo que permite el uso de variables de entorno.
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 archivo describe la lógica de respaldo para el servicio correspondiente, en nuestro caso es MySQL.
Aquí puedes especificar:
- ¿Cuál es el nombre del trabajo (campo: trabajo)
- Tipo de trabajo (campo: tipo)
- El directorio temporal necesario para recopilar copias de seguridad (campo: tmp_dir)
- Opciones de conexión de MySQL (campo: conectar)
- Base de datos a respaldar (campo: destino)
- Necesita detener Slave antes de recolectar (campo: is_slave)
- Teclas adicionales para mysqldump (campo: extra_keys)
- Almacenamiento almacenamiento, es decir, en qué almacenamiento almacenaremos una copia (campo: almacenamiento)
- El directorio donde almacenaremos nuestras copias (campo: backup_dir)
- Esquema de almacenamiento (campo: tienda)
En nuestro ejemplo, el tipo de almacenamiento se establece en local, es decir, recopilamos y almacenamos copias de seguridad localmente en un directorio específico del pod lanzado.
Aquí, por analogía con este archivo de configuración, puede especificar los mismos archivos de configuración para Redis, PostgreSQL o cualquier otro servicio necesario, si es compatible con nuestra herramienta. El hecho de que es compatible se puede encontrar en el enlace proporcionado 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: ""
Mantenemos acceso secreto para conectarse a MySQL y al servidor de correo. Se pueden almacenar en un secreto separado o aprovechar el existente, por supuesto, si es así. Nada interesante aquí Nuestro secreto también contiene el secret_token, que es necesario para el funcionamiento de nuestra 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
Quizás este elemento sea el más interesante. En primer lugar, para compilar el CronJob correcto, debe determinar dónde se almacenarán las copias de seguridad recopiladas.
Para esto tenemos un servidor separado con la cantidad necesaria de recursos. En el ejemplo, se asigna un nodo de clúster separado, nxs-node5, para recopilar copias de seguridad. La restricción para iniciar CronJob en los nodos que necesitamos está establecida por la directiva nodeAffinity.
Cuando se inicia CronJob, el directorio correspondiente se conecta a él a través de hostPath desde el sistema host, que se utiliza para almacenar copias de seguridad.
A continuación, ConfigMaps se conecta al CronJob específico, que contiene la configuración para nxs-backup, es decir, los archivos nxs-backup.conf y mysql.conf de los que acabamos de hablar.
Luego, se establecen todas las variables de entorno necesarias, que se definen directamente en el manifiesto o extraídas de Secret'ov.
Entonces, las variables se transfieren al contenedor y, a través de docker-entrypoint.sh, se reemplazan en ConfigMaps en los lugares que necesitamos con los valores necesarios. Para MySQL, esto es db_host, db_user, db_password. En este caso, pasamos el puerto simplemente como un valor en el manifiesto de CronJob, porque no contiene ninguna información valiosa.
Bueno, con MySQL todo parece estar claro. Ahora veamos qué se necesita para hacer una copia de seguridad de los archivos de la aplicación 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 es un archivo de configuración que describe la lógica de respaldo para los archivos. Aquí tampoco hay nada inusual, se establecen los mismos parámetros que para MySQL, con la excepción de los datos de autorización, porque simplemente no lo son. Aunque pueden serlo, si están involucrados protocolos para la transferencia de datos: ssh, ftp, webdav, s3 y otros. Consideraremos esta opción un poco más 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 nuevo, tampoco, con respecto a MySQL. Pero aquí se monta un PV adicional (target-dir), que respaldaremos - / var / www / files. De lo contrario, todo es igual, almacenamos copias localmente en el nodo que necesitamos, para lo cual se asigna CronJob.
Resumen
Para cada servicio que queremos respaldar, creamos un CronJob separado con todos los elementos relacionados necesarios: ConfigMaps y Secrets. Por analogía con los ejemplos considerados, podemos respaldar cualquier servicio similar en el clúster.
Creo que, en base a estos dos ejemplos, todos tienen alguna idea de cómo respaldamos los servicios estatales en Cuba. Creo que no tiene sentido analizar en detalle los mismos ejemplos para otros servicios, porque básicamente son todos iguales y tienen ligeras diferencias.
En realidad, esto es lo que queríamos lograr, es decir, algún tipo de enfoque unificado para construir el proceso de copia de seguridad. Y para que este enfoque pueda aplicarse a una gran cantidad de proyectos diferentes basados en k8.
¿Dónde almacenamos?
En todos los ejemplos discutidos anteriormente, almacenamos copias en el directorio local del nodo en el que se ejecuta el contenedor. Pero nadie se molesta en conectar el volumen persistente como un almacenamiento externo en funcionamiento y recopilar copias allí. O solo puede sincronizarlos con una tienda remota utilizando el protocolo deseado sin guardar localmente. Es decir, hay muchas variaciones. Primero recolecta localmente, luego sincroniza. O para recolectar y almacenar solo en un almacenamiento remoto, etc. La configuración es bastante flexible.
mysql.conf + s3
El siguiente es un ejemplo del archivo de configuración de respaldo de MySQL, donde las copias se almacenan localmente en el nodo donde se ejecuta CronJob, y también se sincronizan en 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
Es decir, si no es suficiente almacenar copias localmente, puede sincronizarlas con cualquier almacenamiento remoto utilizando el protocolo apropiado. El número de almacenamiento para almacenamiento puede ser cualquiera.
Pero en este caso, aún necesita hacer algunos cambios adicionales, a saber:
- Conecte el ConfigMap apropiado con el contenido necesario para la autorización con AWS S3, en formato j2
- Crear un secreto apropiado para almacenar accesos de autorización
- Establezca las variables de entorno necesarias tomadas del secreto anterior
- Ajuste docker-entrypoint.sh para reemplazar las variables correspondientes en ConfigMap
- Reconstruya la imagen de Docker, agregando utilidades para trabajar con AWS S3
Si bien este proceso está lejos de ser perfecto, estamos trabajando en ello. Por lo tanto, en el futuro cercano agregaremos a nxs-backup la capacidad de definir parámetros en el archivo de configuración utilizando variables de entorno, lo que simplificará enormemente el trabajo con el archivo de punto de entrada y minimizará el tiempo dedicado a agregar soporte para la copia de seguridad de nuevos servicios.
Conclusión
Eso es probablemente todo.
El uso del enfoque del que acabamos de hablar, en primer lugar, nos permite estructurar y hacer una copia de seguridad de la plantilla de los servicios de proyectos con estado en k8. Es decir, esta es una solución lista para usar, y lo más importante, la práctica que puede aplicar en sus proyectos, sin perder tiempo y esfuerzo buscando y refinando soluciones de código abierto existentes.