En esta guía se incluyen prácticas recomendadas para diseñar, implementar, probar y desplegar un servicio de Cloud Run. 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 Cloud Run.
Actividad en segundo plano
La actividad en segundo plano es cualquier acción que se produce después de que se haya enviado la respuesta HTTP. Para determinar si hay actividad en segundo plano en tu servicio que no sea evidente, consulta los registros para ver si hay algo registrado después de la entrada de la solicitud HTTP.
Configurar la facturación basada en instancias para usar actividades en segundo plano
Si quieres admitir actividades en segundo plano en tu servicio de Cloud Run, configura tu servicio de Cloud Run para que use la facturación basada en instancias. De esta forma, podrás ejecutar actividades en segundo plano fuera de las solicitudes y seguir teniendo acceso a la CPU.
Evita las actividades en segundo plano si usas la facturación basada en solicitudes
Si necesitas configurar tu servicio para que use la facturación basada en solicitudes, cuando el servicio de Cloud Run termine de gestionar una solicitud, se inhabilitará o se limitará considerablemente el acceso de la instancia a la CPU. No debes iniciar subprocesos ni rutinas en segundo plano que se ejecuten fuera del ámbito de los controladores de solicitudes si usas este tipo de facturación.
Revisa tu código para asegurarte de que todas las operaciones asíncronas finalicen antes de enviar la respuesta.
Si ejecutas subprocesos en segundo plano con la facturación basada en solicitudes habilitada, puede producirse un comportamiento inesperado, ya que cualquier solicitud posterior a la misma instancia de contenedor reanuda cualquier actividad en segundo plano suspendida.
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, posteriormente, el contenedor tardará en iniciarse.
Informar de errores
Gestiona todas las excepciones y no permitas que tu servicio falle debido a errores. Un fallo provoca que el contenedor tarde en iniciarse mientras el tráfico se pone en cola para una instancia de sustitución.
Consulta la guía de informes de errores para obtener información sobre cómo informar de errores correctamente.
Optimización del rendimiento
En esta sección se describen las prácticas recomendadas para optimizar el rendimiento.
Iniciar contenedores rápidamente
Como las instancias se escalan según sea necesario, el tiempo de inicio influye en la latencia de tu servicio. Cloud Run desacopla el inicio de las instancias y el procesamiento de las solicitudes, por lo que, en algunos casos, una solicitud debe esperar a que se inicie una nueva instancia antes de procesarse. Esto suele ocurrir cuando un servicio se escala desde cero.
La rutina de inicio consta de lo siguiente:
- Descargar la imagen de contenedor (con la tecnología de streaming de imágenes de contenedor de Cloud Run)
- Inicia el contenedor ejecutando el comando entrypoint.
- Esperando a que el contenedor empiece a escuchar en el puerto configurado.
Si optimizas la velocidad de inicio de los contenedores, se minimiza la latencia de procesamiento de las solicitudes.
Usar el aumento de CPU al inicio para reducir la latencia de inicio
Puedes habilitar el aumento de la CPU al inicio para aumentar temporalmente la asignación de CPU durante el inicio de la instancia y, de este modo, reducir la latencia de inicio.
Usar instancias mínimas para reducir el tiempo de inicio de los contenedores
Puedes configurar las instancias mínimas y la concurrencia para minimizar los tiempos de inicio de los contenedores. Por ejemplo, si usas un número mínimo de instancias de 1, significa que tu servicio está listo para recibir hasta el número de solicitudes simultáneas configurado para tu servicio sin necesidad de iniciar una nueva instancia.
Ten en cuenta que una solicitud que esté esperando a que se inicie una instancia se mantendrá pendiente en una cola de la siguiente manera:
Las solicitudes se quedarán pendientes hasta 3, 5 veces el tiempo medio de inicio de las instancias de contenedor de este servicio o 10 segundos, lo que sea mayor.
Usa 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 se añade a la latencia de inicio.
Para reducir la latencia de inicio, puedes hacer lo siguiente:
- 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 de carga automática de Composer de PHP.
Usar variables globales
En Cloud Run, no puedes dar por hecho que el estado del servicio se conserva entre solicitudes. Sin embargo, Cloud Run reutiliza instancias individuales para servir 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 individual 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 inicio del contenedor. Usa la inicialización diferida para los objetos que se usen con poca frecuencia para aplazar el coste de tiempo y reducir los tiempos de inicio de los contenedores.
Un inconveniente de la inicialización diferida es el aumento de la latencia de las primeras solicitudes a las nuevas instancias. Esto puede provocar un escalado excesivo y solicitudes descartadas cuando implementas una nueva revisión de un servicio que está gestionando muchas solicitudes.
Node.js
Python
Go
Java
Usar un entorno de ejecución diferente
Puedes disfrutar de tiempos de inicio más rápidos si usas un entorno de ejecución diferente.
Optimizar la simultaneidad
Las instancias de Cloud Run pueden atender varias solicitudes simultáneamente, hasta un máximo de simultaneidad configurable.
Cloud Run ajusta automáticamente la simultaneidad hasta el máximo configurado.
La simultaneidad máxima predeterminada de 80 es adecuada para muchas imágenes de contenedor. Sin embargo, debes hacer lo siguiente:
- Redúcelo si tu contenedor no puede procesar muchas solicitudes simultáneas.
- Aumente este valor si su contenedor puede gestionar un gran volumen de solicitudes.
Ajustar la simultaneidad de tu servicio
El número de solicitudes simultáneas que puede atender cada instancia 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 Cloud Run 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.
Evita 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.
Compensaciones entre el rendimiento, la latencia y el coste
Ajustar la configuración del número máximo de solicitudes simultáneas puede ayudarte a equilibrar el compromiso entre el rendimiento, la latencia y el coste de tu servicio.
Por lo general, si se establece un número máximo de solicitudes simultáneas más bajo, se reduce la latencia y el rendimiento por instancia. Si se reduce el número máximo de solicitudes simultáneas, habrá menos solicitudes que compitan por los recursos de cada instancia y cada solicitud tendrá un mejor rendimiento. Sin embargo, como cada instancia puede atender menos solicitudes a la vez, el rendimiento por instancia es menor y el servicio necesita más instancias para atender el mismo tráfico.
Por el contrario, si se configura un número máximo de solicitudes simultáneas más alto, generalmente se obtendrá una latencia y un rendimiento por instancia mayores. Es posible que las solicitudes tengan que esperar para acceder a recursos como la CPU, la GPU y el ancho de banda de la memoria dentro de la instancia, lo que provoca un aumento de la latencia. Sin embargo, cada instancia puede procesar más solicitudes a la vez, por lo que el servicio necesita menos instancias en general para procesar el mismo tráfico.
Consideraciones sobre el coste
Los precios de Cloud Run se calculan por tiempo de instancia. Si configuras la facturación basada en instancias, el tiempo de instancia es el tiempo total de vida útil de cada instancia. Si configuras la facturación basada en solicitudes, el tiempo de instancia es el tiempo que cada instancia dedica a procesar al menos una solicitud.
El impacto del número máximo de solicitudes simultáneas en la facturación depende de tu patrón de tráfico. Reducir el número máximo de solicitudes simultáneas puede dar lugar a una factura más baja si el ajuste inferior provoca lo siguiente:
- Latencia reducida
- Las instancias completan su trabajo más rápido
- Las instancias se cierran más rápido, aunque se necesiten más instancias en total
Sin embargo, también puede ocurrir lo contrario: reducir el número máximo de solicitudes simultáneas puede aumentar la facturación si el aumento del número de instancias no compensa la reducción del tiempo que se ejecuta cada instancia debido a la mejora de la latencia.
La mejor forma de optimizar la facturación es realizar pruebas de carga con diferentes ajustes de solicitudes simultáneas máximas para identificar el ajuste que dé como resultado el tiempo de instancia facturable más bajo, tal como se muestra en la container/billable_instance_time
métrica de monitorización.
Seguridad en contenedores
Muchas prácticas de seguridad de software de uso general se aplican a los servicios 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, sigue 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 suponer 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.Impide el uso de las funciones de vista previa mediante políticas de organización personalizadas.
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.
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.
Gracias a la tecnología de streaming de imágenes de contenedor de Cloud Run, el tamaño de la imagen de contenedor no afecta al tiempo de inicio del contenedor ni al tiempo de procesamiento de las solicitudes. El tamaño de la imagen del contenedor tampoco se tiene en cuenta en la memoria disponible de tu contenedor.
Para crear un contenedor mínimo, puedes usar 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 ofrece 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