Lección 16 de 25 · Lectura y práctica
ConfigMaps y Secrets: Gestión de parámetros de modelos
Aprende configmaps y secrets: gestión de parámetros de modelos con esta lectura y práctica de Containerización y Despliegue de Modelos ML con Docker y Kubernetes.
Del concepto a la práctica
Taller de esta lección
ConfigMaps y Secrets separan configuración del código, pero no deben tratarse como almacenamiento de modelos. Un umbral de decisión puede ser configuración no sensible; una contraseña de laboratorio debe mantenerse fuera de la imagen y limitarse al proceso que la necesita.
Ejemplo resuelto
Un ConfigMap define MODEL_THRESHOLD=0.70 y un Secret define STORE_PASSWORD para una prueba local. El contenedor lee ambos mediante variables de entorno. Cambiar el umbral no requiere reconstruir la imagen; cambiar el modelo sí requiere versionar el artefacto o su referencia.
Pruébalo
Clasifica tres valores como configuración, secreto o artefacto.
Crea el ConfigMap con el umbral explícito.
Inyecta el Secret sin imprimir su valor en logs.
Cambia el umbral y verifica la nueva respuesta.
Error frecuente: codificar un Secret en texto y considerarlo cifrado. Corrección: trata la codificación como representación, restringe acceso y no lo incluyas en repositorios ni imágenes.
Comprueba tu resultado
Con probabilidad de la clase urgente 0.68 y umbral 0.70, la decisión es normal; con probabilidad 0.75 y el mismo umbral, es urgente. Documenta la regla exacta. El manifiesto no debe contener el valor de la contraseña y los logs no deben mostrarla. Si la configuración supera el tamaño práctico, separa el archivo o artefacto en otro mecanismo.
Regla explícita y actualización real
Define p_urgente >= MODEL_THRESHOLD → urgente. Con umbral .70, p_urgente=.68 produce normal y p_urgente=.75 produce urgente. Es probabilidad de la clase urgente, no la confianza de cualquier clase elegida; localiza su índice en model.classes_. La lección 8 no lee este umbral: para aplicar esta variante debes añadir lectura del entorno y la regla explícita al servicio, probarla y reconstruir esa nueva versión.
El ConfigMap de laboratorio es:
apiVersion: v1
kind: ConfigMap
metadata: {name: clasificador-config}
data: {MODEL_THRESHOLD: "0.70"}En el campo env del contenedor usa valueFrom.configMapKeyRef con nombre clasificador-config y key MODEL_THRESHOLD; para STORE_PASSWORD usa secretKeyRef hacia un Secret creado separadamente a partir de un archivo local protegido. No publiques contraseña ni el YAML con sus datos. Base64 no cifra y un Secret no garantiza cifrado en reposo por defecto: requiere control de acceso y configuración del clúster.
Variables de entorno se fijan al iniciar el contenedor: cambiar el ConfigMap requiere reemplazar/reiniciar los Pods para ver el valor nuevo, no reconstruir la imagen si el código ya lo lee. Archivos montados pueden actualizarse eventualmente, pero la aplicación debe recargarlos y subPath no recibe esas actualizaciones. Una referencia obligatoria a objeto/clave inexistente bloquea el inicio; no produce automáticamente cadena vacía. ConfigMap y Secret tienen límites de tamaño y no sustituyen un almacén de modelos.

Mapa de prácticaEl código se empaqueta en una imagen y se prueba antes de publicarlo. Esa misma imagen se guarda en un registro y se usa en el despliegue. Comprobar la salud de las instancias permite detectar un fallo sin reconstruir el artefacto.
Ver la infografía en tamaño completo ↗Introducción: Separando Configuración del Código en el Mundo ML
En el desarrollo tradicional de modelos de Machine Learning, es común encontrar parámetros, rutas de archivos, credenciales y configuraciones de hiperparámetros incrustados directamente en el código fuente o en scripts auxiliares. Este enfoque, aunque funcional en un entorno local, se convierte en una pesadilla operativa al llevar el modelo a producción en un orquestador como Kubernetes. Imagina tener que reconstruir y redespelgar la imagen de tu contenedor Docker cada vez que necesitas cambiar el umbral de clasificación de tu modelo, la dirección de un servidor de características o una clave de API. Este proceso es lento, propenso a errores y rompe los principios de la entrega continua.
Aquí es donde entran en juego dos objetos fundamentales de Kubernetes: ConfigMaps y Secrets. Estos recursos nos permiten desacoplar la configuración y la información sensible de la definición de la aplicación (el Pod o Deployment), inyectándola de manera dinámica en los contenedores en tiempo de ejecución. Para un ingeniero de ML, esto significa poder gestionar el entorno del modelo (parámetros, endpoints de servicios, configuraciones de logging) y sus secretos (tokens, claves de acceso a bases de datos, credenciales de cloud) de forma centralizada, segura y declarativa, sin tocar una sola línea del código del modelo empaquetado en Docker.
Dominar el uso de ConfigMaps y Secrets es lo que separa un despliegue básico de uno profesional, resiliente y preparado para escalar. Permite crear una sola imagen de contenedor genérica para tu modelo que puede ser configurada para múltiples entornos (desarrollo, staging, producción) simplemente cambiando los objetos de configuración que se montan en ella, un patrón esencial para las prácticas modernas de MLOps.
Concepto Clave: ConfigMaps y Secrets Desmitificados
Piensa en un ConfigMap como el "archivo de configuración inteligente" de tu aplicación en Kubernetes. No es más que un diccionario clave-valor (o un conjunto de archivos de configuración) que Kubernetes almacena en su base de datos interna (etcd). Puedes llenar este diccionario con cualquier dato no sensible que tu aplicación necesite para configurarse: URLs de servicios externos, nombres de buckets de almacenamiento, banderas de funcionalidad (feature flags), hiperparámetros del modelo (como la tasa de aprendizaje o el número de estimadores), o incluso archivos de configuración completos en formato JSON, YAML o XML. La magia está en que este ConfigMap vive fuera de tu contenedor, y Kubernetes puede inyectar sus datos en el contenedor como variables de entorno o como archivos en un directorio específico del sistema de archivos del Pod.
Por otro lado, un Secret es conceptualmente idéntico a un ConfigMap: también es un objeto clave-valor. La diferencia crucial es que Kubernetes trata los Secrets con más cuidado. Están diseñados para contener información sensible como contraseñas, tokens OAuth, claves SSH o claves de API. Kubernetes se esfuerza por no escribir datos de Secrets en el disco (aunque puede hacerlo bajo petición) y, cuando lo hace, intenta mantenerlos encriptados. En la práctica, los valores dentro de un Secret están codificados en base64, lo que no es encriptación, pero añade una capa de ofuscación que evita que se lean de forma casual. Es fundamental entender que los Secrets están destinados a la *conveniencia*, no a la *seguridad absoluta* en clusters no configurados específicamente para ello. Su principal ventaja es separar la información sensible del código y de las definiciones de los Pods.
Tip Crítico: Nunca confíes en los Secrets de Kubernetes para almacenar datos altamente sensibles (como claves maestras de cifrado) sin habilitar el cifrado en reposo para etcd y gestionar los controles de acceso RBAC de forma estricta. Considera soluciones externas como Vault, AWS Secrets Manager o Azure Key Vault para esos casos, usando los Secrets de Kubernetes solo para almacenar las credenciales de acceso a esos servicios más seguros.
Cómo Funciona en la Práctica: Un Flujo Paso a Paso
El flujo de trabajo típico para integrar ConfigMaps y Secrets en el despliegue de un modelo de ML sigue una serie de pasos lógicos y repetibles. Primero, debes identificar qué partes de tu aplicación son configurables y cuáles son sensibles. Para un servicio de inferencia de un modelo, los parámetros configurables podrían incluir el puerto en el que escucha el servidor, la URL del endpoint de un servicio de monitoreo, el nombre del modelo a cargar desde un almacenamiento, o los hiperparámetros de post-procesamiento (como un umbral de confianza). La información sensible podría ser una clave de acceso a un bucket de S3 donde está guardado el modelo serializado, o un token de API para enviar métricas a una plataforma externa.
Una vez identificados, el siguiente paso es crear los objetos de Kubernetes. Esto se hace de forma declarativa, usualmente mediante archivos YAML. Creas un archivo config-modelo.yaml que define un ConfigMap con claves como MODEL_THRESHOLD y FEATURE_SERVICE_URL. De forma similar, creas un archivo secret-modelo.yaml que define un Secret, donde los valores (como tu clave de S3) deben ser previamente codificados en base64. Luego, aplicas estos archivos al cluster con kubectl apply -f config-modelo.yaml.
El paso final es modificar el Deployment o el Pod de tu modelo para consumir estos objetos. En el spec del contenedor, defines variables de entorno que obtienen su valor desde el ConfigMap o el Secret usando valueFrom. Alternativamente, puedes montar todo el ConfigMap o Secret como un volumen, creando un archivo en el sistema de archivos del contenedor por cada clave-valor. Cuando Kubernetes programa una nueva instancia (Pod) de tu modelo, leerá las definiciones, obtendrá los datos actuales del ConfigMap y del Secret, y los inyectará en el contenedor antes de que se inicie. Tu aplicación, al arrancar, leerá estas variables de entorno o archivos y se configurará dinámicamente.
Código en Acción: Despliegue de un Clasificador Configurable
Vamos a construir un ejemplo completo. Supongamos un servicio de inferencia para un modelo de clasificación de texto. El código de la aplicación (app.py) está diseñado para leer su configuración desde variables de entorno.
1. Código de la Aplicación Python (app.py)
import os
import pickle
import numpy as np
from flask import Flask, request, jsonify
import boto3
from botocore.exceptions import ClientError
import logging
app = Flask(__name__)
# Cargar configuración desde variables de entorno (inyectadas por Kubernetes)
MODEL_FILE_PATH = os.getenv('MODEL_FILE_PATH', 'model.pkl')
MODEL_CONFIDENCE_THRESHOLD = float(os.getenv('MODEL_THRESHOLD', '0.7'))
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
# Credenciales sensibles desde Secrets
AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY')
S3_BUCKET = os.getenv('S3_BUCKET')
# Configurar logging
logging.basicConfig(level=getattr(logging, LOG_LEVEL))
logger = logging.getLogger(__name__)
def load_model():
"""Carga el modelo desde S3 usando credenciales de Secrets."""
try:
s3_client = boto3.client(
's3',
aws_access_key_id=AWS_ACCESS_KEY_ID,
aws_secret_access_key=AWS_SECRET_ACCESS_KEY
)
# Descargar el modelo desde S3
s3_client.download_file(S3_BUCKET, MODEL_FILE_PATH, '/tmp/model.pkl')
with open('/tmp/model.pkl', 'rb') as f:
model = pickle.load(f)
logger.info(f"Modelo cargado desde s3://{S3_BUCKET}/{MODEL_FILE_PATH}")
return model
except ClientError as e:
logger.error(f"Error al cargar el modelo desde S3: {e}")
raise
model = load_model()
@app.route('/predict', methods=['POST'])
def predict():
data = request.json
features = np.array(data['features']).reshape(1, -1)
prediction_proba = model.predict_proba(features)[0]
# Aplicar umbral configurable
predicted_class = 1 if prediction_proba[1] >= MODEL_CONFIDENCE_THRESHOLD else 0
confidence = float(prediction_proba[predicted_class])
response = {
'prediction': predicted_class,
'confidence': confidence,
'threshold_used': MODEL_CONFIDENCE_THRESHOLD
}
logger.debug(f"Solicitud de predicción procesada: {response}")
return jsonify(response)
if __name__ == '__main__':
port = int(os.getenv('PORT', 8080))
app.run(host='0.0.0.0', port=port)
2. ConfigMap y Secret en YAML
Ahora definimos los objetos de Kubernetes que proporcionarán los valores a las variables de entorno. Primero, codificamos en base64 nuestra clave secreta de AWS (ejemplo: echo -n 'tu-clave-super-secreta' | base64).
# configmap-modelo-clasificador.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ml-model-config
data:
# Datos de configuración no sensibles
MODEL_THRESHOLD: "0.75"
LOG_LEVEL: "INFO"
PORT: "8080"
MODEL_FILE_PATH: "models/production/classifier_v2.pkl"
FEATURE_SERVICE_URL: "http://feature-service.prod.svc.cluster.local:8500"
---
# secret-modelo-clasificador.yaml
apiVersion: v1
kind: Secret
metadata:
name: ml-model-secrets
type: Opaque
data:
# Valores codificados en base64
AWS_ACCESS_KEY_ID: QUtJQUpLM0VaVFlST1VHQUZOT0c=
AWS_SECRET_ACCESS_KEY: dTdJei9FUmZQMmN6S3RrbHhWTEZSMUNyN2pWa2pVNXVGd2pJYw==
S3_BUCKET: bXktbWwtbW9kZWxzLXByb2Q=
3. Deployment que Consume ConfigMap y Secret
Finalmente, creamos un Deployment que monta estos objetos en el contenedor como variables de entorno.
# deployment-modelo-clasificador.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: text-classifier-deployment
labels:
app: text-classifier
spec:
replicas: 2
selector:
matchLabels:
app: text-classifier
template:
metadata:
labels:
app: text-classifier
spec:
containers:
- name: classifier-app
image: mi-registry/text-classifier:latest
ports:
- containerPort: 8080
env:
# Variables de entorno desde ConfigMap
- name: MODEL_THRESHOLD
valueFrom:
configMapKeyRef:
name: ml-model-config
key: MODEL_THRESHOLD
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: ml-model-config
key: LOG_LEVEL
- name: PORT
valueFrom:
configMapKeyRef:
name: ml-model-config
key: PORT
- name: MODEL_FILE_PATH
valueFrom:
configMapKeyRef:
name: ml-model-config
key: MODEL_FILE_PATH
# Variables de entorno desde Secret
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: ml-model-secrets
key: AWS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: ml-model-secrets
key: AWS_SECRET_ACCESS_KEY
- name: S3_BUCKET
valueFrom:
secretKeyRef:
name: ml-model-secrets
key: S3_BUCKET
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
Para desplegar todo el sistema, ejecutarías: kubectl apply -f configmap-modelo-clasificador.yaml -f secret-modelo-clasificador.yaml -f deployment-modelo-clasificador.yaml. Cada Pod creado tendrá sus variables de entorno pobladas con los valores actuales de los objetos ConfigMap y Secret. Si actualizas el ConfigMap (por ejemplo, cambias MODEL_THRESHOLD a 0.8), los Pods existentes NO se actualizarán automáticamente. Necesitarías reiniciarlos (por ejemplo, con un rollout restart del deployment) para que capten los nuevos valores, a menos que uses una herramienta como Reloader o hayas implementado un mecanismo de recarga en caliente en tu aplicación.
Errores Comunes y Cómo Evitarlos
Al trabajar con ConfigMaps y Secrets, varios errores recurrentes pueden causar dolores de cabeza en producción. Conocerlos de antemano te ahorrará horas de debugging.
1. Codificar/Decodificar manualmente en base64 de forma incorrecta: Un error clásico al crear Secrets es no codificar correctamente en base64, o hacerlo añadiendo un salto de línea final. Usa echo -n para evitar el salto de línea, o mejor aún, deja que kubectl lo haga por ti con kubectl create secret generic mi-secreto --from-literal=clave=valor y luego obtén el YAML con kubectl get secret mi-secreto -o yaml.
2. Asumir que los cambios en ConfigMaps/Secrets se propagan automáticamente a los Pods en ejecución: Kubernetes no reinicia automáticamente los Pods cuando se actualiza un ConfigMap o Secret al que hacen referencia. El contenedor seguirá viendo los valores antiguos. Para forzar una recarga, debes realizar un kubectl rollout restart deployment/nombre-deployment. Una alternativa es usar volúmenes montados, donde las actualizaciones eventualmente se propagan (aunque el timing no es instantáneo), o usar operadores como Stakater Reloader que monitorean cambios.
3. Almacenar datos verdaderamente sensibles en Secrets sin cifrado adicional: Como se mencionó, los Secrets por defecto no están encriptados en etcd. Cualquiera con acceso de lectura a etcd o a la API de Kubernetes podría recuperarlos. Para entornos de producción, siempre habilita el cifrado en reposo para los Secrets y aplica políticas RBAC estrictas que limiten el acceso a los objetos Secret.
4. Referencias incorrectas en el spec del Pod (nombres o claves que no existen): Si en tu Deployment especificas configMapKeyRef.name: config-inexistente o key: CLAVE_INEXISTENTE, el Pod NO fallará en el despliegue inmediatamente. El contenedor se creará, pero la variable de entorno no se establecerá (o se establecerá como vacía). Esto puede llevar a fallos silenciosos en tu aplicación. Siempre verifica que los objetos referenciados existen y tienen las claves correctas usando kubectl get configmap <nombre> -o yaml.
5. Exponer accidentalmente Secrets en logs o mensajes de error: Tu aplicación debe ser cuidadosa de no imprimir variables de entorno que contengan secretos en los logs, trazas o respuestas de error. Asegúrate de que tu configuración de logging (también manejable por ConfigMap) esté en un nivel apropiado y de filtrar datos sensibles en los manejadores de errores.
Checklist de Dominio
Para verificar que has comprendido y puedes aplicar efectivamente los conceptos de esta lección, asegúrate de poder realizar o comprender cada uno de los siguientes puntos:
- Diferenciar claramente el uso de un ConfigMap (datos no sensibles) de un Secret (datos sensibles) y justificar cuándo usar cada uno.
- Crear un ConfigMap y un Secret desde la línea de comandos usando
kubectl createy desde un archivo YAML declarativo. - Configurar un Deployment para inyectar:
- Una variable de entorno específica desde una clave de ConfigMap.
- Una variable de entorno específica desde una clave de Secret.
- Todo un ConfigMap como un volumen, creando múltiples archivos en el sistema del contenedor.
- Explicar el ciclo de vida de la actualización: saber que al modificar un ConfigMap/Secret, los Pods existentes no se actualizan automáticamente y conocer al menos una estrategia para forzar la recarga de la configuración (ej: restart del deployment).
- Listar y aplicar al menos dos medidas de seguridad básicas para trabajar con Secrets (cifrado en reposo, RBAC estricto, evitar codificación manual).
- Depurar un problema donde un Pod no está recibiendo la configuración esperada, verificando la existencia del objeto, las claves y las referencias en el spec del Pod.
- Diseñar la configuración para un modelo de ML típico, identificando correctamente qué parámetros irían a un ConfigMap (hiperparámetros, URLs) y cuáles a un Secret (claves de API, contraseñas de BD).
- Comprender la limitación de tamaño (1 MiB para ConfigMaps y Secrets) y planificar para configuraciones muy grandes (usar volúmenes persistentes o servicios de configuración externos).
Sigue con Containerización y Despliegue de Modelos ML con Docker y Kubernetes
Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.
Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.