Registros en Kubernetes (y no solo) hoy: expectativas y realidad

Registros en Kubernetes (y no solo) hoy: expectativas y realidad

Estamos en 2019 y todavía no tenemos una solución estándar para la agregación de registros en Kubernetes. En este artículo nos gustaría, utilizando ejemplos de la práctica real, compartir nuestras búsquedas, los problemas encontrados y sus soluciones.

Sin embargo, primero, haré una reserva de que diferentes clientes entienden cosas muy diferentes al recopilar registros:

  • alguien quiere ver registros de seguridad y auditoría;
  • alguien: registro centralizado de toda la infraestructura;
  • y para algunos es suficiente recopilar solo registros de aplicaciones, excluyendo, por ejemplo, los balanceadores.

A continuación se muestra el corte sobre cómo implementamos varias "listas de deseos" y qué dificultades encontramos.

Teoría: sobre las herramientas de registro

Antecedentes sobre los componentes de un sistema de registro

El registro ha recorrido un largo camino, como resultado de lo cual se han desarrollado metodologías para recopilar y analizar registros, que es lo que utilizamos hoy. En la década de 1950, Fortran introdujo un análogo de los flujos de entrada/salida estándar, que ayudaba al programador a depurar su programa. Estos fueron los primeros registros informáticos que facilitaron la vida a los programadores de aquella época. Hoy vemos en ellos el primer componente del sistema de registro: fuente o “productor” de troncos.

La informática no se detuvo: aparecieron las redes de computadoras, los primeros clusters... Comenzaron a funcionar sistemas complejos que constan de varias computadoras. Ahora los administradores del sistema se vieron obligados a recopilar registros de varias máquinas y, en casos especiales, podían agregar mensajes del kernel del sistema operativo en caso de que necesitaran investigar una falla del sistema. Para describir los sistemas centralizados de recopilación de registros, a principios de la década de 2000 se publicó RFC 3164, que estandarizó remote_syslog. Así apareció otro componente importante: recolector de registros y su almacenamiento.

Con el aumento en el volumen de registros y la introducción generalizada de tecnologías web, surgió la pregunta de qué registros deben mostrarse convenientemente a los usuarios. Las herramientas de consola simples (awk/sed/grep) han sido reemplazadas por otras más avanzadas. registrar espectadores - tercer componente.

Debido al aumento en el volumen de registros, algo más quedó claro: se necesitan registros, pero no todos. Y diferentes troncos requieren diferentes niveles de conservación: algunos pueden perderse en un día, mientras que otros deben almacenarse durante 5 años. Entonces, se agregó al sistema de registro un componente para filtrar y enrutar flujos de datos, llamémoslo filtrar.

El almacenamiento también ha dado un salto importante: de archivos normales a bases de datos relacionales y luego al almacenamiento orientado a documentos (por ejemplo, Elasticsearch). Entonces el almacén se separó del recolector.

En última instancia, el concepto mismo de registro se ha expandido hasta convertirse en una especie de flujo abstracto de eventos que queremos preservar para la historia. O mejor dicho, en caso de que necesites realizar una investigación o elaborar un informe analítico...

Como resultado, en un período de tiempo relativamente corto, la recopilación de registros se ha convertido en un subsistema importante, que con razón se puede llamar una de las subsecciones de Big Data.

Registros en Kubernetes (y no solo) hoy: expectativas y realidad
Si alguna vez las impresiones ordinarias podían ser suficientes para un "sistema de registro", ahora la situación ha cambiado mucho.

Kubernetes y registros

Cuando Kubernetes llegó a la infraestructura, el problema ya existente de recopilar registros tampoco lo eludió. En cierto modo, se volvió aún más doloroso: la gestión de la plataforma de infraestructura no sólo se simplificó, sino que al mismo tiempo se complicó. Muchos servicios antiguos han comenzado a migrar a microservicios. En el contexto de los registros, esto se refleja en el creciente número de fuentes de registros, su ciclo de vida especial y la necesidad de rastrear las relaciones de todos los componentes del sistema a través de registros...

De cara al futuro, puedo afirmar que ahora, lamentablemente, no existe una opción de registro estandarizada para Kubernetes que se pueda comparar favorablemente con todas las demás. Los esquemas más populares en la comunidad son los siguientes:

  • alguien desenrolla la pila SFAO (Elasticsearch, Fluentd, Kibana);
  • alguien está probando el recién lanzado Loki o usos Operador de registro;
  • nosotros (¿y quizás no sólo nosotros?...) Estoy muy satisfecho con mi propio desarrollo. Casa de registro...

Como regla general, utilizamos los siguientes paquetes en los clústeres K8 (para soluciones autohospedadas):

Sin embargo, no me detendré en las instrucciones para su instalación y configuración. En cambio, me centraré en sus deficiencias y en conclusiones más globales sobre la situación de los registros en general.

Practica con troncos en K8

Registros en Kubernetes (y no solo) hoy: expectativas y realidad

“Registros cotidianos”, ¿cuántos sois?..

La recopilación centralizada de registros de una infraestructura bastante grande requiere recursos considerables, que se gastarán en recopilar, almacenar y procesar registros. Durante la operación de varios proyectos, nos enfrentamos a diversos requisitos y problemas operativos que surgen de ellos.

Probemos ClickHouse

Veamos un almacenamiento centralizado en un proyecto con una aplicación que genera registros de forma bastante activa: más de 5000 líneas por segundo. Comencemos a trabajar con sus registros, agregándolos a ClickHouse.

Tan pronto como se requiera el máximo tiempo real, el servidor de 4 núcleos con ClickHouse ya estará sobrecargado en el subsistema de disco:

Registros en Kubernetes (y no solo) hoy: expectativas y realidad

Este tipo de carga se debe a que intentamos escribir en ClickHouse lo más rápido posible. Y la base de datos reacciona a esto con una mayor carga en el disco, lo que puede provocar los siguientes errores:

DB::Exception: Too many parts (300). Merges are processing significantly slower than inserts

El hecho es que Combinar tablas de árbol en ClickHouse (contienen datos de registro) tienen sus propias dificultades durante las operaciones de escritura. Los datos insertados en ellos generan una partición temporal, que luego se fusiona con la tabla principal. Como resultado, la grabación resulta muy exigente en el disco, y también está sujeta a la limitación que recibimos antes: no se pueden fusionar más de 1 subparticiones en 300 segundo (de hecho, son 300 inserciones). por segundo).

Para evitar este comportamiento, debería escribir a ClickHouse en trozos lo más grandes posible y no más de 1 vez cada 2 segundos. Sin embargo, escribir en ráfagas grandes sugiere que deberíamos escribir con menos frecuencia en ClickHouse. Esto, a su vez, puede provocar un desbordamiento del búfer y la pérdida de registros. La solución es aumentar el buffer de Fluentd, pero luego el consumo de memoria también aumentará.

Nota: Otro aspecto problemático de nuestra solución con ClickHouse estaba relacionado con el hecho de que la partición en nuestro caso (loghouse) se implementa a través de tablas externas conectadas Fusionar tabla. Esto lleva al hecho de que cuando se muestrean grandes intervalos de tiempo, se requiere un exceso de RAM, ya que la metatabla recorre en iteración todas las particiones, incluso aquellas que obviamente no contienen los datos necesarios. Sin embargo, ahora este enfoque puede declararse obsoleto de forma segura para las versiones actuales de ClickHouse (c 18.16).

Como resultado, queda claro que no todos los proyectos tienen recursos suficientes para recopilar registros en tiempo real en ClickHouse (más precisamente, su distribución no será adecuada). Además, deberá utilizar acumulador, al que volveremos más adelante. El caso descrito anteriormente es real. Y en ese momento no podíamos ofrecer una solución confiable y estable que se adaptara al cliente y nos permitiera recopilar registros con un retraso mínimo...

¿Qué pasa con Elasticsearch?

Se sabe que Elasticsearch maneja cargas de trabajo pesadas. Probémoslo en el mismo proyecto. Ahora la carga se ve así:

Registros en Kubernetes (y no solo) hoy: expectativas y realidad

Elasticsearch pudo digerir el flujo de datos; sin embargo, escribir dichos volúmenes en él utiliza en gran medida la CPU. Esto se decide organizando un cluster. Técnicamente, esto no es un problema, pero resulta que sólo para operar el sistema de recolección de registros ya usamos alrededor de 8 núcleos y tenemos un componente adicional altamente cargado en el sistema...

En pocas palabras: esta opción puede justificarse, pero sólo si el proyecto es grande y su dirección está dispuesta a gastar importantes recursos en un sistema de registro centralizado.

Entonces surge una pregunta natural:

¿Qué registros son realmente necesarios?

Registros en Kubernetes (y no solo) hoy: expectativas y realidad Intentemos cambiar el enfoque en sí: los registros deben ser informativos al mismo tiempo y no cubrir cada evento en el sistema.

Digamos que tenemos una tienda online exitosa. ¿Qué registros son importantes? Recopilar la mayor cantidad de información posible, por ejemplo, de una pasarela de pago, es una gran idea. Pero no todos los registros del servicio de corte de imágenes del catálogo de productos son críticos para nosotros: basta con los errores y el seguimiento avanzado (por ejemplo, el porcentaje de 500 errores que genera este componente).

Entonces hemos llegado a la conclusión de que el registro centralizado no siempre está justificado. Muy a menudo, el cliente quiere recopilar todos los registros en un solo lugar, aunque de hecho, de todo el registro, solo se requiere un 5% condicional de los mensajes que son críticos para el negocio:

  • A veces basta con configurar, por ejemplo, sólo el tamaño del registro del contenedor y el recolector de errores (por ejemplo, Sentry).
  • Una notificación de error y un gran registro local a menudo pueden ser suficientes para investigar incidentes.
  • Tuvimos proyectos que se conformaron con pruebas exclusivamente funcionales y sistemas de recolección de errores. El desarrollador no necesitaba registros como tales: vieron todo, desde rastros de errores.

Ilustración de la vida

Otra historia puede servir como buen ejemplo. Recibimos una solicitud del equipo de seguridad de uno de nuestros clientes que ya estaba utilizando una solución comercial desarrollada mucho antes de la introducción de Kubernetes.

Era necesario "hacer amigos" del sistema centralizado de recopilación de registros con el sensor de detección de problemas corporativo: QRadar. Este sistema puede recibir registros a través del protocolo syslog y recuperarlos desde FTP. Sin embargo, no fue posible integrarlo de inmediato con el complemento remoto_syslog para fluentd (como se vio despues, no estamos solos). Los problemas con la configuración de QRadar resultaron estar del lado del equipo de seguridad del cliente.

Como resultado, parte de los registros críticos para el negocio se cargó en FTP QRadar y la otra parte se redirigió a través de syslog remoto directamente desde los nodos. Para esto incluso escribimos tabla sencilla - tal vez ayude a alguien a resolver un problema similar... Gracias al esquema resultante, el propio cliente recibió y analizó los registros críticos (usando sus herramientas favoritas), y pudimos reducir el costo del sistema de registro, ahorrando solo el el mes pasado.

Otro ejemplo es bastante indicativo de lo que no se debe hacer. Uno de nuestros clientes para el procesamiento. cada eventos provenientes del usuario, hechos multilínea salida no estructurada información en el registro. Como puede imaginar, dichos registros eran extremadamente incómodos de leer y almacenar.

Criterios para registros

Estos ejemplos llevan a la conclusión de que, además de elegir un sistema de recopilación de registros, es necesario también diseñar los propios registros! ¿Cuáles son los requisitos aquí?

  • Los registros deben estar en formato legible por máquina (por ejemplo, JSON).
  • Los registros deben ser compactos y con la capacidad de cambiar el grado de registro para depurar posibles problemas. Al mismo tiempo, en entornos de producción se deben ejecutar sistemas con un nivel de registro como advertencia o Error.
  • Los registros deben estar normalizados, es decir, en un objeto de registro, todas las líneas deben tener el mismo tipo de campo.

Los registros no estructurados pueden provocar problemas al cargar registros en el almacenamiento y detener completamente su procesamiento. A modo de ilustración, aquí hay un ejemplo con el error 400, que muchos seguramente han encontrado en los registros de fluentd:

2019-10-29 13:10:43 +0000 [warn]: dump an error event: error_class=Fluent::Plugin::ElasticsearchErrorHandler::ElasticsearchError error="400 - Rejected by Elasticsearch"

El error significa que está enviando un campo cuyo tipo es inestable al índice con una asignación ya preparada. El ejemplo más simple es un campo en el registro de nginx con una variable $upstream_status. Puede contener un número o una cadena. Por ejemplo:

{ "ip": "1.2.3.4", "http_user": "-", "request_id": "17ee8a579e833b5ab9843a0aca10b941", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staffs/265.png", "protocol": "HTTP/1.1", "status": "200", "body_size": "906", "referrer": "https://example.com/staff", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.001", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "127.0.0.1:9000", "upstream_status": "200", "upstream_response_length": "906", "location": "staff"}
{ "ip": "1.2.3.4", "http_user": "-", "request_id": "47fe42807f2a7d8d5467511d7d553a1b", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staff", "protocol": "HTTP/1.1", "status": "200", "body_size": "2984", "referrer": "-", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.010", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "10.100.0.10:9000, 10.100.0.11:9000", "upstream_status": "404, 200", "upstream_response_length": "0, 2984", "location": "staff"}

Los registros muestran que el servidor 10.100.0.10 respondió con un error 404 y la solicitud se envió a otro almacenamiento de contenido. Como resultado, el valor en los registros quedó así:

"upstream_response_time": "0.001, 0.007"

Esta situación es tan común que incluso merece un estudio aparte. referencias en la documentación.

¿Qué pasa con la confiabilidad?

Hay ocasiones en las que todos los registros sin excepción son vitales. Y con esto, los esquemas típicos de recolección de registros para K8 propuestos/discutidos anteriormente tienen problemas.

Por ejemplo, fluentd no puede recopilar registros de contenedores de corta duración. En uno de nuestros proyectos, el contenedor de migración de la base de datos duró menos de 4 segundos y luego se eliminó, según la anotación correspondiente:

"helm.sh/hook-delete-policy": hook-succeeded

Debido a esto, el registro de ejecución de la migración no se incluyó en el almacenamiento. La política puede ayudar en este caso. before-hook-creation.

Otro ejemplo es la rotación de registros de Docker. Digamos que hay una aplicación que escribe activamente en registros. En condiciones normales, logramos procesar todos los registros, pero tan pronto como aparece un problema, por ejemplo, como se describió anteriormente con un formato incorrecto, el procesamiento se detiene y Docker rota el archivo. El resultado es que se pueden perder registros críticos para el negocio.

Es precisamente eso es importante separar los flujos de registros, incorporando el envío de los más valiosos directamente a la aplicación para garantizar su seguridad. Además, no sería superfluo crear algunas “acumulador” de troncos, que puede sobrevivir a una breve indisponibilidad de almacenamiento y al mismo tiempo guardar mensajes críticos.

Por último, no debemos olvidar que Es importante monitorear cualquier subsistema adecuadamente.. De lo contrario, es fácil encontrarse con una situación en la que fluentd se encuentre en un estado CrashLoopBackOff y no envía nada, y esto promete la pérdida de información importante.

Hallazgos

En este artículo, no analizamos soluciones SaaS como Datadog. Muchos de los problemas descritos aquí ya han sido resueltos de una forma u otra por empresas comerciales especializadas en recopilar registros, pero no todo el mundo puede utilizar SaaS por diversas razones. (los principales son el costo y el cumplimiento de 152-FZ).

La recopilación centralizada de registros al principio parece una tarea sencilla, pero no lo es en absoluto. Es importante recordar que:

  • Solo es necesario registrar en detalle los componentes críticos, mientras que el monitoreo y la recopilación de errores se pueden configurar para otros sistemas.
  • Los registros en producción deben mantenerse al mínimo para no agregar carga innecesaria.
  • Los registros deben ser legibles por máquina, normalizados y tener un formato estricto.
  • Los registros realmente críticos deben enviarse en una secuencia separada, que debe estar separada de los principales.
  • Vale la pena considerar un acumulador de troncos, que puede salvarlo de ráfagas de carga elevada y hacer que la carga en el almacenamiento sea más uniforme.

Registros en Kubernetes (y no solo) hoy: expectativas y realidad
Estas sencillas reglas, si se aplicaran en todas partes, permitirían que los circuitos descritos anteriormente funcionaran, aunque les falten componentes importantes (la batería). Si no cumple con estos principios, la tarea lo llevará fácilmente a usted y a la infraestructura a otro componente del sistema altamente cargado (y al mismo tiempo ineficaz).

PS

Lea también en nuestro blog:

Fuente: habr.com

Añadir un comentario