Ficheiros locais ao migrar unha aplicación a Kubernetes

Ficheiros locais ao migrar unha aplicación a Kubernetes

Ao construír un proceso CI/CD usando Kubernetes, ás veces xorde o problema de incompatibilidade entre os requisitos da nova infraestrutura e a aplicación que se está a transferir a ela. En particular, na fase de compilación da aplicación é importante obter un imaxe que se utilizará en Todo contornas e clusters do proxecto. Este principio subxace no correcto segundo Google xestión de contedores (máis dunha vez sobre isto dito e o noso departamento técnico).

Non obstante, non verás a ninguén en situacións nas que o código do sitio usa un marco prefabricado, cuxo uso impón restricións ao seu uso posterior. E aínda que nun "ambiente normal" isto é fácil de tratar, en Kubernetes este comportamento pode converterse nun problema, especialmente cando o atopas por primeira vez. Aínda que unha mente inventiva pode chegar a solucións de infraestrutura que parecen obvias ou incluso boas a primeira vista... é importante lembrar que a maioría das situacións poden e deben. resolverse arquitectónicamente.

Vexamos as solucións alternativas populares para almacenar ficheiros que poden levar a consecuencias desagradables ao operar un clúster e tamén sinalamos un camiño máis correcto.

Almacenamento estático

Para ilustralo, considere unha aplicación web que utilice algún tipo de xerador estático para obter un conxunto de imaxes, estilos e outras cousas. Por exemplo, o marco PHP Yii ten un xestor de activos integrado que xera nomes de directorio únicos. En consecuencia, a saída é un conxunto de camiños para o sitio estático que obviamente non se cruzan entre si (isto fíxose por varias razóns, por exemplo, para eliminar duplicados cando varios compoñentes usan o mesmo recurso). Entón, fóra da caixa, a primeira vez que accede a un módulo de recursos web, os ficheiros estáticos (de feito, moitas veces enlaces simbólicos, pero máis sobre iso máis tarde) fórmanse e dispóñense cun directorio raíz común único para esta implementación:

  • webroot/assets/2072c2df/css/…
  • webroot/assets/2072c2df/images/…
  • webroot/assets/2072c2df/js/…

Que significa isto en termos de cluster?

O exemplo máis sinxelo

Vexamos un caso bastante común, cando PHP vai precedido por nginx para distribuír datos estáticos e procesar solicitudes sinxelas. O xeito máis sinxelo - desenvolvemento con dous recipientes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: site
spec:
  selector:
    matchLabels:
      component: backend
  template:
    metadata:
      labels:
        component: backend
    spec:
      volumes:
        - name: nginx-config
          configMap:
            name: nginx-configmap
      containers:
      - name: php
        image: own-image-with-php-backend:v1.0
        command: ["/usr/local/sbin/php-fpm","-F"]
        workingDir: /var/www
      - name: nginx
        image: nginx:1.16.0
        command: ["/usr/sbin/nginx", "-g", "daemon off;"]
        volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: nginx.conf

Nunha forma simplificada, a configuración de nginx redúcese ao seguinte:

apiVersion: v1
kind: ConfigMap
metadata:
  name: "nginx-configmap"
data:
  nginx.conf: |
    server {
        listen 80;
        server_name _;
        charset utf-8;
        root  /var/www;

        access_log /dev/stdout;
        error_log /dev/stderr;

        location / {
            index index.php;
            try_files $uri $uri/ /index.php?$args;
        }

        location ~ .php$ {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            include fastcgi_params;
        }
    }

Cando accede por primeira vez ao sitio, os recursos aparecen no contedor PHP. Pero no caso de dous contedores dentro dun pod, nginx non sabe nada sobre estes ficheiros estáticos, que (segundo a configuración) deberían darlles. Como resultado, o cliente verá un erro 404 para todas as solicitudes de ficheiros CSS e JS. A solución máis sinxela aquí sería organizar un directorio común para contedores. Opción primitiva - xeral emptyDir:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: site
spec:
  selector:
    matchLabels:
      component: backend
  template:
    metadata:
      labels:
        component: backend
    spec:
      volumes:
        - name: assets
          emptyDir: {}
        - name: nginx-config
          configMap:
            name: nginx-configmap
      containers:
      - name: php
        image: own-image-with-php-backend:v1.0
        command: ["/usr/local/sbin/php-fpm","-F"]
        workingDir: /var/www
        volumeMounts:
        - name: assets
          mountPath: /var/www/assets
      - name: nginx
        image: nginx:1.16.0
        command: ["/usr/sbin/nginx", "-g", "daemon off;"]
        volumeMounts:
        - name: assets
          mountPath: /var/www/assets
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: nginx.conf

Agora os ficheiros estáticos xerados no contedor son servidos por nginx correctamente. Pero permítanme lembrar que esta é unha solución primitiva, o que significa que está lonxe de ser ideal e ten os seus propios matices e deficiencias, que se comentan a continuación.

Almacenamento máis avanzado

Agora imaxina unha situación na que un usuario visitou o sitio, cargaba unha páxina cos estilos dispoñibles no contedor e, mentres estaba lendo esta páxina, volvemos a implantar o contedor. O catálogo de activos quedou baleiro e é necesaria unha solicitude a PHP para comezar a xerar outros novos. Non obstante, aínda despois disto, as ligazóns a estática antiga serán irrelevantes, o que provocará erros na visualización da estática.

Ademais, o máis probable é que teñamos un proxecto máis ou menos cargado, o que significa que unha copia da aplicación non será suficiente:

  • Imos escalalo desenvolvemento ata dúas réplicas.
  • Cando se accedeu por primeira vez ao sitio, os recursos creáronse nunha única réplica.
  • Nalgún momento, ingress decidiu (con fins de equilibrio de carga) enviar unha solicitude á segunda réplica, e estes activos aínda non estaban alí. Ou quizais xa non estean porque usamos RollingUpdate e nestes momentos estamos facendo despregamento.

En xeral, o resultado son de novo erros.

Para evitar perder activos antigos, pode cambiar emptyDir en hostPath, engadindo estática fisicamente a un nodo do clúster. Este enfoque é malo porque realmente temos que facelo vincularse a un nodo de clúster específico a súa aplicación, porque -en caso de moverse a outros nodos- o directorio non conterá os ficheiros necesarios. Ou é necesario algún tipo de sincronización de directorios en segundo plano entre os nós.

Cales son as solucións?

  1. Se o hardware e os recursos o permiten, pode usar cephs para organizar un directorio igualmente accesible para necesidades estáticas. Documentación oficial recomenda unidades SSD, polo menos unha replicación triple e unha conexión estable "grosa" entre os nodos do clúster.
  2. Unha opción menos esixente sería organizar un servidor NFS. Non obstante, entón cómpre ter en conta o posible aumento do tempo de resposta para o procesamento de solicitudes por parte do servidor web, e a tolerancia a fallos deixará moito que desexar. As consecuencias do fracaso son catastróficas: a perda do monte condena o cúmulo á morte baixo a presión da carga de LA que se precipita ao ceo.

Entre outras cousas, necesitarán todas as opcións para crear almacenamento persistente limpeza de fondo conxuntos desactualizados de ficheiros acumulados durante un período de tempo determinado. Diante dos contedores con PHP podes poñer DaemonSet desde a caché de nginx, que almacenará copias dos activos durante un tempo limitado. Este comportamento é facilmente configurable usando proxy_cache con profundidade de almacenamento en días ou gigabytes de espazo en disco.

A combinación deste método cos sistemas de ficheiros distribuídos mencionados anteriormente proporciona un campo enorme para a imaxinación, limitado só polo orzamento e o potencial técnico de quen o implementará e apoiará. Por experiencia, podemos dicir que canto máis sinxelo é o sistema, máis estable funciona. Cando se engaden tales capas, faise moito máis difícil manter a infraestrutura e, ao mesmo tempo, aumenta o tempo dedicado ao diagnóstico e á recuperación de calquera fallo.

Recomendación

Se a implementación das opcións de almacenamento propostas tamén che parece inxustificada (complicada, cara...), entón paga a pena mirar a situación desde o outro lado. É dicir, afondar na arquitectura do proxecto e solucionar o problema no código, ligado a algunha estrutura de datos estática na imaxe, unha definición inequívoca dos contidos ou procedemento para "quecer" e/ou precompilar recursos na fase de montaxe da imaxe. Deste xeito, obtemos un comportamento absolutamente previsible e o mesmo conxunto de ficheiros para todos os ambientes e réplicas da aplicación en execución.

Se volvemos ao exemplo concreto co marco Yii e non afondamos na súa estrutura (que non é o propósito do artigo), abonda con sinalar dous enfoques populares:

  1. Cambia o proceso de creación da imaxe para colocar os recursos nunha localización previsible. Isto suxírese/impleméntase en extensións como yii2-activos-estáticos.
  2. Defina hash específicos para directorios de activos, como se explica en, p. esta presentación (a partir da diapositiva no 35). Por certo, o autor do informe en última instancia (e non sen razón!) aconsella que despois de montar os activos no servidor de compilación, cargueos a un almacenamento central (como S3), diante do cal coloque un CDN.

Arquivos descargables

Outro caso que definitivamente entrará en xogo ao migrar unha aplicación a un clúster de Kubernetes é o almacenamento de ficheiros de usuario no sistema de ficheiros. Por exemplo, volvemos ter unha aplicación PHP que acepta ficheiros a través dun formulario de carga, fai algo con eles durante o funcionamento e os envía de volta.

En Kubernetes, a localización onde se deben colocar estes ficheiros debe ser común a todas as réplicas da aplicación. Dependendo da complexidade da aplicación e da necesidade de organizar a persistencia destes ficheiros, as opcións de dispositivos compartidos mencionadas anteriormente poden ser tal lugar, pero, como vemos, teñen os seus inconvenientes.

Recomendación

Unha solución é usando almacenamento compatible con S3 (aínda que sexa algún tipo de categoría autoaloxada como minio). Cambiar a S3 requirirá cambios a nivel de código, e como se entregará o contido no front end, xa o temos писали.

Sesións de usuarios

Por separado, cómpre destacar a organización do almacenamento das sesións dos usuarios. Moitas veces tamén se trata de ficheiros en disco, o que no contexto de Kubernetes levará a constantes solicitudes de autorización do usuario se a súa solicitude acaba noutro contedor.

O problema resólvese en parte ao acender stickySessions ao ingreso (a función é compatible con todos os controladores de entrada populares; para obter máis detalles, consulte a nosa revisión)para vincular o usuario a un pod específico coa aplicación:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: nginx-test
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "route"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"

spec:
  rules:
  - host: stickyingress.example.com
    http:
      paths:
      - backend:
          serviceName: http-svc
          servicePort: 80
        path: /

Pero isto non eliminará os problemas con implantacións repetidas.

Recomendación

Unha forma máis correcta sería transferir a aplicación a almacenando sesións en Memcached, Redis e solucións similares - en xeral, abandone completamente as opcións de ficheiros.

Conclusión

As solucións de infraestrutura discutidas no texto son dignas de usar só no formato de "muletas" temporais (que soa máis bonito en inglés como solución alternativa). Poden ser relevantes nas primeiras etapas da migración dunha aplicación a Kubernetes, pero non deberían enraizarse.

O camiño xeral recomendado é desfacerse deles en favor da modificación arquitectónica da solicitude de acordo co que xa é coñecido por moitos. Aplicación de 12 factores. Non obstante, isto - levar a aplicación a un formulario sen estado - significa inevitablemente que se requirirán cambios no código, e aquí é importante atopar un equilibrio entre as capacidades/requisitos da empresa e as perspectivas de implementación e mantemento do camiño escollido. .

PS

Lea tamén no noso blog:

Fonte: www.habr.com

Engadir un comentario