En esta guía se proporcionan prácticas recomendadas para diseñar, implementar, probar y desplegar un servicio de Knative Serving. Para ver más consejos, consulta Migrar un servicio actual.
Escribir servicios eficaces
En esta sección se describen las prácticas recomendadas generales para diseñar e implementar un servicio de Knative Serving.
Evitar actividades en segundo plano
Cuando una aplicación que se ejecuta en Knative Serving termina de gestionar una solicitud, se inhabilita o se limita considerablemente el acceso de la instancia de contenedor a la CPU. Por lo tanto, no debes iniciar subprocesos ni rutinas en segundo plano que se ejecuten fuera del ámbito de los controladores de solicitudes.
La ejecución de subprocesos en segundo plano puede provocar un comportamiento inesperado, ya que cualquier solicitud posterior a la misma instancia de contenedor reanuda cualquier actividad en segundo plano suspendida.
La actividad en segundo plano es cualquier acción que se produce después de que se haya enviado la respuesta HTTP. Revisa el código para asegurarte de que todas las operaciones asíncronas finalizan antes de enviar la respuesta.
Si sospechas que puede haber actividad en segundo plano en tu servicio que no sea evidente, puedes consultar tus registros: busca cualquier elemento que se haya registrado después de la entrada de la solicitud HTTP.
Eliminar archivos temporales
En el entorno de Cloud Run, el almacenamiento en disco es un sistema de archivos en memoria. Los archivos escritos en el disco consumen memoria que, de lo contrario, estaría disponible para tu servicio y pueden conservarse entre invocaciones. Si no se eliminan estos archivos, se puede producir un error de falta de memoria y un arranque en frío posterior.
Optimizar el rendimiento
En esta sección se describen las prácticas recomendadas para optimizar el rendimiento.
Iniciar servicios rápidamente
Como las instancias de contenedor se escalan según sea necesario, un método habitual consiste en inicializar el entorno de ejecución por completo. Este tipo de inicialización se denomina "inicio en frío". Si una solicitud de cliente activa un inicio en frío, el inicio de la instancia de contenedor provoca una latencia adicional.
La rutina de inicio consta de lo siguiente:
- Iniciar el servicio
- Iniciar el contenedor
- Ejecuta el comando entrypoint para iniciar el servidor.
- Comprobando el puerto de servicio abierto.
Si optimizas la velocidad de inicio del servicio, se minimiza la latencia que retrasa el servicio de solicitudes de una instancia de contenedor.
Usar las dependencias con cabeza
Si usas un lenguaje dinámico con bibliotecas dependientes, como importar módulos en Node.js, el tiempo de carga de esos módulos añade latencia durante un inicio en frío. Reduce la latencia de inicio de las siguientes formas:
- Minimiza el número y el tamaño de las dependencias para crear un servicio ligero.
- Carga en diferido 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.
Usar variables globales
En Knative Serving, no puedes dar por hecho que el estado del servicio se conserva entre solicitudes. Sin embargo, Knative Serving reutiliza instancias de contenedores individuales para atender el tráfico continuo, por lo que puedes declarar una variable en el ámbito global para permitir que su valor se reutilice en invocaciones posteriores. No se puede saber de antemano si una solicitud concreta se beneficiará de esta reutilización.
También puedes almacenar objetos en caché en la memoria si es caro recrearlos en cada solicitud de servicio. Si se mueve de la lógica de la solicitud al ámbito global, el rendimiento será mejor.
Node.js
Python
Go
Java
Realizar la inicialización diferida de variables globales
La inicialización de las variables globales siempre se produce durante el inicio, lo que aumenta el tiempo de arranque en frío. Usa la inicialización diferida para los objetos que no se usan con frecuencia para aplazar el coste temporal y reducir los tiempos de arranque en frío.
Node.js
Python
Go
Java
Optimizar la simultaneidad
Las instancias de servicio de Knative pueden atender varias solicitudes simultáneamente, hasta un máximo de simultaneidad configurable.
Es diferente de Cloud Run Functions, que usa concurrency = 1
.
Debes mantener el ajuste de simultaneidad máxima predeterminado, a menos que tu código tenga requisitos de simultaneidad específicos.
Ajustar la simultaneidad de tu servicio
El número de solicitudes simultáneas que puede atender cada instancia de contenedor puede estar limitado por la pila tecnológica y el uso de recursos compartidos, como variables y conexiones de bases de datos.
Para optimizar tu servicio y conseguir la máxima simultaneidad estable, haz lo siguiente:
- Optimiza el rendimiento de tu servicio.
- Define el nivel de simultaneidad esperado en cualquier configuración de simultaneidad a nivel de código. No todas las pilas tecnológicas requieren este ajuste.
- Despliega tu servicio.
- Define la simultaneidad de Knative Serving de tu servicio para que sea igual o inferior a cualquier configuración a nivel de código. Si no hay ninguna configuración a nivel de código, usa la simultaneidad esperada.
- Usa herramientas de pruebas de carga que admitan una simultaneidad configurable. Debes confirmar que tu servicio sigue siendo estable con la carga y la simultaneidad esperadas.
- Si el servicio no funciona bien, ve al paso 1 para mejorarlo o al paso 2 para reducir la simultaneidad. Si el servicio funciona bien, vuelve al paso 2 y aumenta la simultaneidad.
Sigue iterando hasta que encuentres la simultaneidad estable máxima.
Asignar memoria a la simultaneidad
Cada solicitud que gestiona tu servicio requiere una cantidad de memoria adicional. Por lo tanto, cuando aumentes o disminuyas la simultaneidad, asegúrate de ajustar también el límite de memoria.
Evitar el estado global mutable
Si quieres usar un estado global mutable en un contexto simultáneo, toma medidas adicionales en tu código para asegurarte de que se hace de forma segura. Minimiza la contención limitando las variables globales a la inicialización única y reutilizándolas, tal como se ha descrito anteriormente en la sección Rendimiento.
Si usas variables globales mutables en un servicio que atiende varias solicitudes al mismo tiempo, asegúrate de usar bloqueos o mutex para evitar las condiciones de carrera.
Seguridad en contenedores
Muchas prácticas de seguridad de software de uso general se aplican a las aplicaciones en contenedores. Hay algunas prácticas que son específicas de los contenedores o que se ajustan a la filosofía y la arquitectura de los contenedores.
Para mejorar la seguridad del contenedor, siga estos pasos:
Usa imágenes base seguras y que se mantengan activamente, como las imágenes base de Google o las imágenes oficiales de Docker Hub.
Aplica actualizaciones de seguridad a tus servicios. Para ello, vuelve a compilar imágenes de contenedor y vuelve a implementar tus servicios con regularidad.
Incluye en el contenedor solo lo necesario para ejecutar tu servicio. El código, los paquetes y las herramientas adicionales pueden tener vulnerabilidades de seguridad. Consulta la sección anterior para ver el impacto en el rendimiento relacionado.
Implementa un proceso de compilación determinista que incluya versiones específicas de software y bibliotecas. De esta forma, se evita que se incluya código no verificado en tu contenedor.
Configura el contenedor para que se ejecute como un usuario distinto de
root
con la instrucción DockerfileUSER
. Es posible que algunas imágenes de contenedor ya tengan configurado un usuario específico.
Automatizar el análisis de seguridad
Habilita el análisis de vulnerabilidades para analizar la seguridad de las imágenes de contenedor almacenadas en Artifact Registry.
También puedes usar la autorización binaria para asegurarte de que solo se desplieguen imágenes de contenedor seguras.
Crear imágenes de contenedor mínimas
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 tu contenedor no afecta al tiempo de arranque en frío ni al tiempo de procesamiento de las solicitudes, y no se tiene en cuenta en la memoria disponible de tu contenedor.
Para crear un contenedor mínimo, te recomendamos que utilices una imagen base ligera, como las siguientes:
Ubuntu tiene un tamaño mayor, pero es una imagen base que se usa con frecuencia y que incluye un entorno de servidor más completo.
Si tu servicio tiene un proceso de compilación que requiere muchas herramientas, te recomendamos que utilices compilaciones multietapa para que tu contenedor sea ligero en el tiempo de ejecución.
En estos recursos encontrarás más información sobre cómo crear imágenes de contenedor ligeras:
- Prácticas recomendadas de Kubernetes: cómo y por qué crear imágenes de contenedor pequeñas
- 7 prácticas recomendadas para crear contenedores