
En este artículo, me gustaría hablar sobre mi proyecto de pasatiempo de búsqueda y clasificación de anuncios para alquilar apartamentos de la red social VKontakte y la experiencia de trasladarlo a k8s.
Tabla de contenidos
- Un poco sobre el proyecto.
- Introduciendo k8s
- Preparándose para la mudanza
- Desarrollo de configuraciones K8s
- Despliegue de clúster K8s
Un poco sobre el proyecto.

En marzo de 2017, lancé un servicio de análisis y clasificación de anuncios para alquilar apartamentos desde la red social VKontakte.
Aquí puede leer más sobre cómo intenté clasificar los anuncios de diferentes maneras y, finalmente, me decidí por el analizador léxico Yandex Tomita Parser .
Aquí puede leer sobre la arquitectura del proyecto al comienzo de su existencia y qué tecnologías se utilizaron y por qué.
El desarrollo de la primera versión del servicio tomó aproximadamente un año. Para implementar cada componente del servicio, escribí scripts en Ansible . De vez en cuando, el servicio no funcionaba debido a errores en el código rediseñado o la configuración incorrecta de los componentes.
Alrededor de junio de 2019, se detectó un error en el código del analizador debido a que no se recopilaron nuevos anuncios. En lugar de otra corrección, se decidió desactivarlo temporalmente.
El motivo de la restauración del servicio fue el estudio de k8.
Introduciendo k8s
k8s es un software de código abierto para automatizar la implementación, el escalado y la administración de aplicaciones en contenedores.
Toda la infraestructura de servicio se describe mediante archivos de configuración en formato yaml (con mayor frecuencia).
No hablaré sobre la estructura interna de los k8, sino que solo daré información sobre algunos de sus componentes.
Componentes K8s
- Pod es la unidad más pequeña. Puede contener varios contenedores que se lanzarán en el mismo nodo.
Contenedores dentro de la cápsula:
- tienen una red común y pueden acceder entre sí a través de 127.0.0.1:$containerPort;
- no tiene un sistema de archivos común, por lo que no puede escribir directamente archivos de un contenedor a otro.
- Implementación: supervisa el trabajo de Pod. Puede aumentar el número requerido de instancias de Pod, reiniciarlas si caen y realizar la implementación de nuevos Pods.
- PersistentVolumeClaim - almacén de datos. Por defecto, funciona con el sistema de archivos del nodo local. Por lo tanto, si desea que dos Pods diferentes en nodos diferentes tengan un sistema de archivos común, debe usar un sistema de archivos de red como Ceph .
- Servicio: solicitudes de representación hacia y desde Pod.
Tipos de servicio:
- LoadBalancer: para la interacción con una red externa con equilibrio de carga entre varios pods;
- NodePort (solo 30000-32767 puertos): para la interacción con una red externa sin equilibrio de carga;
- ClusterIp: para la interacción en la red local del clúster;
- ExternalName: para la interacción entre Pod y servicios externos.
- ConfigMap: almacenamiento de configuraciones.
Para que k8s reinicie Pod con nuevas configuraciones cuando cambia ConfigMap, debe indicar la versión en el nombre de su ConfigMap y cambiarla cada vez que ConfigMap cambie.
Lo mismo vale para Secret.
Ejemplo de configuración con ConfigMapcontainers: - name: collect-consumer image: mrsuh/rent-collector:1.3.1 envFrom: - configMapRef: name: collector-configmap-1.1.0 - secretRef: name: collector-secrets-1.0.0
- Secreto: almacenamiento de configuraciones secretas (contraseñas, claves, tokens).
- Etiqueta: pares clave / valor que se asignan a los componentes de k8s, por ejemplo, Pod.
Al comienzo del conocimiento de k8s, puede que no esté completamente claro cómo usar las etiquetas. Aquí está la configuración que explica los principios básicos de trabajar con etiquetas:
Ejemplo de configuración con etiquetas apiVersion: apps/v1 kind: Deployment # Deployment metadata: name: deployment-name # Deployment labels: app: deployment-label-app # Label Deployment spec: selector: matchLabels: app: pod-label-app # Label, Deployment Pods template: metadata: name: pod-name labels: app: pod-label-app # Label Pod spec: containers: - name: container-name image: mrsuh/rent-parser:1.0.0 ports: - containerPort: 9080 --- apiVersion: v1 kind: Service # Service metadata: name: service-name # Service labels: app: service-label-app # Label Service spec: selector: # Service matchLabels, Deployment, Labels app: pod-label-app # Label, Service , Pod ports: - protocol: TCP port: 9080 type: NodePort
Preparándose para la mudanza
Recorte de funcionalidad
Para hacer que el servicio sea más estable y predecible, tuve que eliminar todos los componentes adicionales que funcionaban mal y reescribir los principales un poco.
Entonces, decidí rechazar:
- código de análisis para sitios que no sean VK;
- solicitar componente proxy;
- componente de notificaciones de nuevos anuncios en VKontakte y Telegram.
Componentes de servicio
Después de todos los cambios, el servicio desde el interior comenzó a verse así:

- ver: buscar y mostrar anuncios en el sitio (NodeJS);
- analizador: clasificador de anuncios (Ir);
- recopilador: recopilación, procesamiento y eliminación de anuncios (PHP):
- cron-explore: un equipo de consola que busca grupos en VKontakte para alquilar viviendas;
- cron-collect: un comando de consola que va a grupos compilados por cron-explore y recopila los anuncios ellos mismos;
- cron-delete: un comando de consola que elimina anuncios caducados;
- Consumer-parse: el manejador de colas, que recibe trabajos de cron-collect. Clasifica los anuncios utilizando el componente analizador;
- consumer-collect: el manejador de colas que obtiene trabajos del análisis de consumidores. Filtra anuncios malos y duplicados.
Crear imágenes de Docker
Para administrar los componentes y monitorearlos en un solo estilo, decidí:
- poner la configuración del componente en variables env,
- escribir registros en stdout.
No hay nada específico en las imágenes mismas.
Desarrollo de configuraciones K8s
Así que obtuve los componentes en las imágenes de Docker y comencé a desarrollar la configuración de k8s.
Todos los componentes que funcionan como demonios se resaltan en Implementación. Cada daemon debe ser accesible dentro del clúster, para que todos tengan un Servicio. Todas las tareas que deben realizarse periódicamente funcionan en CronJob.
Todas las estadísticas (imágenes, js, css) se almacenan en el contenedor de vista, y el contenedor de Nginx debe distribuirlo. Ambos contenedores están en una vaina. El sistema de archivos en Pod no está alterado, pero al comienzo de Pod puede copiar todas las estadísticas en la carpeta emptyDir común a ambos contenedores. Esta carpeta se compartirá para diferentes contenedores, pero solo dentro de un Pod.
Ejemplo de configuración con emptyDir apiVersion: apps/v1 kind: Deployment metadata: name: view spec: selector: matchLabels: app: view replicas: 1 template: metadata: labels: app: view spec: volumes: - name: view-static emptyDir: {} containers: - name: nginx image: mrsuh/rent-nginx:1.0.0 - name: view image: mrsuh/rent-view:1.1.0 volumeMounts: - name: view-static mountPath: /var/www/html lifecycle: postStart: exec: command: ["/bin/sh", "-c", "cp -r /app/web/. /var/www/html"]
El componente de recopilador se utiliza en Implementación y CronJob.
Todos estos componentes acceden a la API de VKontakte y deben almacenar el token de acceso compartido en algún lugar.
Para esto, utilicé PersistentVolumeClaim, que conecté a cada Pod. Dicha carpeta se compartirá para diferentes Pods, pero solo dentro de un nodo.
Ejemplo de configuración con PersistentVolumeClaim apiVersion: apps/v1 kind: Deployment metadata: name: collector spec: selector: matchLabels: app: collector replicas: 1 template: metadata: labels: app: collector spec: volumes: - name: collector-persistent-storage persistentVolumeClaim: claimName: collector-pv-claim containers: - name: collect-consumer image: mrsuh/rent-collector:1.3.1 volumeMounts: - name: collector-persistent-storage mountPath: /tokenStorage command: ["php"] args: ["bin/console", "app:consume", "--channel=collect"] - name: parse-consumer image: mrsuh/rent-collector:1.3.1 volumeMounts: - name: collector-persistent-storage mountPath: /tokenStorage command: ["php"] args: ["bin/console", "app:consume", "--channel=parse"]
PersistentVolumeClaim también se usa para almacenar datos de la base de datos. Como resultado, obtuvimos dicho esquema (las vainas de un componente se recopilan en bloques):

Despliegue de clúster K8s
Para comenzar, implementé el clúster localmente usando Minikube .
Por supuesto, hubo algunos errores, por lo que los equipos me ayudaron mucho.
kubectl logs -f pod-name kubectl describe pod pod-name
Después de que aprendí a implementar un clúster en Minikube, no fue difícil para mí implementarlo en DigitalOcean.
En conclusión, puedo decir que el servicio ha sido estable durante 2 meses. La configuración completa se puede encontrar aquí .