En esta guía, se proporcionan prácticas recomendadas para diseñar, implementar y probar un servicio de Knative serving. Para obtener más sugerencias, consulta Migra un servicio existente.
Escribe servicios eficaces
En esta sección, se describen las prácticas recomendadas generales para diseñar e implementar un servicio de Knative serving.
Evita las actividades en segundo plano
Cuando una aplicación que se ejecuta en Knative serving termina de administrar una solicitud, el acceso de la instancia del contenedor a la CPU se inhabilitará o se limitará de forma grave. Por lo tanto, no debes iniciar subprocesos o rutinas en segundo plano que se ejecuten fuera del alcance de los controladores de solicitudes.
La ejecución de subprocesos en segundo plano puede generar un comportamiento inesperado porque las solicitudes posteriores a la misma instancia de contenedor reanudan cualquier actividad en segundo plano suspendida.
La actividad en segundo plano es todo lo que sucede después de que se entrega la respuesta HTTP. Revisa el código para asegurarte de que todas las operaciones asíncronas finalicen antes de entregar la respuesta.
Si sospechas que puede haber actividad en segundo plano no evidente en el servicio, puedes revisar los registros: busca cualquier cosa registrada después de la entrada para la solicitud HTTP.
Borra archivos temporales
En el entorno de Cloud Run, el almacenamiento en disco es un sistema de archivos en la memoria. Los archivos escritos en el disco consumen memoria disponible para el servicio y pueden persistir entre invocaciones. Si no se borran, es posible que se produzca un error de memoria insuficiente y un inicio en frío posterior.
Optimiza el rendimiento
En esta sección, se describen las prácticas recomendadas para optimizar el rendimiento.
Inicia los servicios con rapidez
Debido a que las instancias de contenedor se escalan según sea necesario, inicializar el entorno de ejecución por completo es un método típico. Este tipo de inicialización se denomina “inicio en frío”. Si una solicitud del cliente activa un inicio en frío, el inicio de la instancia del contenedor genera latencia adicional.
La rutina de inicio consta de los siguientes pasos:
- Inicio del servicio
- Inicio del contenedor
- Ejecución del comando de entrypoint para iniciar el servidor
- Verificación del puerto de servicio abierto
La optimización de la velocidad de inicio del servicio minimiza la latencia que retrasa a una instancia de contenedor en la entrega de solicitudes.
Usa las dependencias de forma inteligente
Si usas un lenguaje dinámico con bibliotecas dependientes, como la importación de módulos en Node.js, el tiempo de carga de esos módulos agrega latencia durante un inicio en frío. Reduce la latencia de inicio de las siguientes maneras:
- Minimiza la cantidad y el tamaño de las dependencias para compilar un servicio optimizado.
- Carga de forma diferida el código que se usa con poca frecuencia, si tu lenguaje lo admite.
- Usa optimizaciones de carga de código como la optimización del cargador automático de composer de PHP.
Usa variables globales
En Knative serving, no puedes suponer que el estado del servicio se conserva entre las solicitudes. Sin embargo, Knative serving vuelve a usar las instancias de contenedores individuales para entregar tráfico continuo, por lo que puedes declarar una variable en el permiso global a fin de permitir que el valor se vuelva a usar en invocaciones posteriores. No se puede saber con anticipación si alguna solicitud individual recibe el beneficio de esta reutilización.
También puedes almacenar objetos en la memoria caché si son costosos de volver a crear en cada solicitud de servicio. Esta migración de la lógica de la solicitud al permiso global da como resultado un mejor rendimiento.
Node.js
Python
Go
Java
Realiza una inicialización diferida de variables globales
La inicialización de las variables globales siempre ocurre durante el inicio, lo que aumenta el tiempo de inicio en frío. Usa la inicialización diferida para los objetos usados con poca frecuencia a fin de diferir el costo del tiempo y disminuir los tiempos de inicio en frío.
Node.js
Python
Go
Java
Optimiza la simultaneidad
Las instancias de Knative serving pueden entregar varias solicitudes de manera simultánea hasta una simultaneidad máxima configurable.
Esto es diferente de Cloud Run Functions, que usa concurrency = 1
.
Debes mantener la configuración de simultaneidad máxima predeterminada, a menos que tu código tenga requisitos de simultaneidad específicos.
Ajusta la simultaneidad para tu servicio
La technology stack y el uso de recursos compartidos, como variables y conexiones de bases de datos, pueden limitar la cantidad de solicitudes simultáneas que puede entregar cada instancia de contenedor.
Para optimizar el servicio a fin de obtener la máxima simultaneidad estable, sigue estos pasos:
- Optimiza el rendimiento del servicio.
- Establece el nivel de compatibilidad de simultaneidad esperado en cualquier configuración de simultaneidad a nivel de código. No todas las technology stacks requieren esa configuración.
- Implementa el servicio.
- Configura la simultaneidad de Knative serving para tu servicio en igual o inferior a cualquier configuración a nivel de código. Si no hay una configuración a nivel de código, usa la simultaneidad esperada.
- Usa herramientas de prueba de carga que admitan una simultaneidad configurable. Debes confirmar que el servicio se mantiene estable con la carga y la simultaneidad esperadas.
- Si el servicio funciona mal, ve al paso 1 para mejorar el servicio o al paso 2 para reducir la simultaneidad. Si el servicio funciona bien, vuelve al paso 2 y aumenta la simultaneidad
Continúa iterando hasta encontrar la simultaneidad estable máxima.
Coincidencia entre memoria y simultaneidad
Cada solicitud que el servicio administra requiere cierta cantidad de memoria adicional. Por lo tanto, cuando aumentes o disminuyas la simultaneidad, asegúrate de ajustar también el límite de memoria.
Evita el estado global mutable
Si deseas aprovechar el estado global mutable en un contexto de simultaneidad, realiza pasos adicionales en el código para asegurarte de que esto se haga de forma segura. Para minimizar la contención, limita las variables globales a una inicialización única y vuelve a usarlas como se describió antes en Rendimiento.
Si usas variables globales mutables en un servicio que entrega varias solicitudes al mismo tiempo, asegúrate de usar bloqueos o exclusiones mutuas para evitar condiciones de carrera.
Seguridad de contenedores
Se aplican muchas prácticas de seguridad de software de uso general a las aplicaciones en contenedores. Hay algunas prácticas que son específicas de los contenedores o que se alinean con su filosofía y arquitectura.
Para mejorar la seguridad de los contenedores, haz lo siguiente:
Usa imágenes base seguras y mantenidas de forma activa, como las imágenes base de Google o las imágenes oficiales de Docker Hub.
Aplica actualizaciones de seguridad a los servicios Para hacerlo, vuelve a compilar imágenes de contenedor con regularidad y vuelve a implementar los servicios.
Incluye en el contenedor solo lo necesario para ejecutar el servicio. El código, los paquetes y las herramientas adicionales son vulnerabilidades de seguridad potenciales. Consulta más arriba el impacto en el rendimiento relacionado.
Implementa un proceso de compilación determinista que incluya versiones específicas de software y bibliotecas. Esto evita que se incluya código sin verificar en el contenedor.
Configura el contenedor para que se ejecute como un usuario que no sea
root
con la declaración deUSER
de Dockerfile. Es posible que algunas imágenes de contenedor ya tengan un usuario específico configurado.
Automatiza el análisis de seguridad
Habilita el análisis de vulnerabilidades para el análisis de seguridad de las imágenes de contenedor almacenadas en Artifact Registry.
También puedes usar la autorización binaria para garantizar que solo se implementen imágenes de contenedor seguras.
Compila imágenes de contenedor mínimo
Es probable que las imágenes de contenedor grandes aumenten las vulnerabilidades de seguridad porque contienen más de lo que necesita el código.
En Knative serving, el tamaño de la imagen de contenedor no afecta el tiempo de procesamiento de solicitud o el inicio en frío y no se considera en la memoria disponible del contenedor.
Para compilar un contenedor mínimo, considera trabajar con una imagen base eficiente, como las siguientes:
Ubuntu es más grande, pero es una imagen base de uso común con un entorno de servidor integrado más completo.
Si el servicio tiene un proceso de compilación con muchas herramientas, considera usar compilaciones de varias etapas para mantener el contenedor simple en el tiempo de ejecución.
Estos recursos proporcionan más información sobre la creación de imágenes de contenedores eficientes:
- Prácticas recomendadas de Kubernetes: Cómo y por qué crear imágenes de contenedor pequeñas
- 7 prácticas recomendadas para compilar contenedores