Introducción
Las imágenes de contenedor deben poder reutilizarse en distintos entornos. Kubernetes separa las configuraciones no sensibles en ConfigMaps y las credenciales en Secrets, y después inyecta cualquiera de estos objetos como variables de entorno o archivos.
Configurarás una aplicación utilizando ambos mecanismos, inspeccionarás el resultado de forma segura, actualizarás la configuración y observarás por qué las variables de entorno y los volúmenes montados se actualizan de manera diferente.
El objetivo no es solo crear dos objetos de la API. Seguirás cada valor desde el almacenamiento de Kubernetes hasta el contenedor en ejecución; después lo modificarás para observar cómo el mecanismo de entrega afecta al comportamiento de actualización.
Crear la configuración de la aplicación
En este paso, crea un espacio de nombres y un ConfigMap a partir de valores literales. Un ConfigMap almacena claves de texto bajo data; no las cifra.
Primero, ve al espacio de trabajo del laboratorio. El espacio de nombres mantiene juntos todos los objetos de este ejemplo y evita que se seleccione accidentalmente una configuración con el mismo nombre perteneciente a otro laboratorio.
cd /home/labex/project/application-config
kubectl create namespace practice-config
kubectl create configmap app-settings -n practice-config --from-literal=APP_MODE=production --from-literal=LOG_LEVEL=info
kubectl get configmap app-settings -n practice-config -o yaml
Cada --from-literal=KEY=VALUE añade una entrada. La salida YAML muestra las claves almacenadas y resulta adecuada para configuraciones no sensibles.
Busca APP_MODE y LOG_LEVEL bajo data:. Kubernetes almacena los valores deseados, pero todavía no hay ningún consumidor; crear un ConfigMap por sí solo no modifica ningún Pod.
La parte relevante de la salida es deliberadamente texto sin formato:
data:
APP_MODE: production
LOG_LEVEL: info
kind: ConfigMap
Campos como uid, resourceVersion y creationTimestamp son generados por Kubernetes y serán diferentes. En este paso, lee únicamente data, ya que contiene la configuración que proporcionaste.
Crear un Secret de forma segura
En este paso, crea un Secret sin colocar su valor en un archivo YAML. Los datos de un Secret se codifican en base64 en la API; no quedan cifrados simplemente porque el tipo de objeto sea Secret. El control de acceso basado en roles (RBAC) y el cifrado en reposo siguen siendo importantes.
generic indica que se trata de un Secret común de clave y valor, en lugar de un Secret especializado para TLS o para extraer imágenes. Las comillas alrededor de la contraseña evitan que el intérprete de comandos procese los signos de puntuación si el valor contiene caracteres especiales.
kubectl create secret generic app-credentials -n practice-config --from-literal=username=appuser --from-literal=password='practice-pass'
kubectl get secret app-credentials -n practice-config
kubectl get secret app-credentials -n practice-config -o jsonpath='{.data.username}' | base64 --decode; echo
La última canalización decodifica únicamente el nombre de usuario como demostración. Evita mostrar contraseñas en las salidas operativas habituales.
El carácter de tubería envía el resultado codificado de JSONPath a base64 --decode. El punto y coma inicia un echo final, que añade un salto de línea después del valor decodificado para que el siguiente indicador del intérprete siga siendo legible. Ver appuser demuestra cómo funciona la codificación; también deja claro por qué base64 nunca debe considerarse un límite de seguridad.
El comando intermedio muestra metadatos sin revelar ninguno de los valores:
NAME TYPE DATA AGE
app-credentials Opaque 2 ...
appuser
DATA=2 significa que el Secret contiene dos claves; no indica un número de bytes. Opaque es el tipo de API utilizado para un Secret común de clave y valor.
Inyectar la configuración en un Pod
En este paso, utiliza los valores del ConfigMap como variables de entorno y monta el Secret como archivos de solo lectura.
Antes de crear el Pod, separa las dos vías de entrega en el manifiesto:
envFromconvierte cada clave del ConfigMap en una variable de entorno del contenedor.volumesconecta el Secret con el Pod, mientras quevolumeMountsdetermina dónde aparece ese volumen dentro de este contenedor concreto.
El bucle del Pod no realiza ninguna tarea de aplicación; simplemente mantiene el contenedor en ejecución para que puedas inspeccionar ambas vías.
cd /home/labex/project/application-config
cat <<'EOF' > configured-app.yaml
apiVersion: v1
kind: Pod
metadata:
name: configured-app
namespace: practice-config
spec:
containers:
- name: app
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "while true; do sleep 30; done"]
envFrom:
- configMapRef:
name: app-settings
volumeMounts:
- name: credentials
mountPath: /etc/app-credentials
readOnly: true
volumes:
- name: credentials
secret:
secretName: app-credentials
EOF
Aplica el manifiesto y espera a que el Pod esté listo:
kubectl apply -f configured-app.yaml
kubectl wait --for=condition=Ready pod/configured-app -n practice-config --timeout=60s
Ahora inspecciona cada vía de entrega desde dentro del contenedor. printenv lee el entorno del proceso, mientras que ls muestra los archivos proyectados por el volumen del Secret:
kubectl exec configured-app -n practice-config -- printenv APP_MODE LOG_LEVEL
kubectl exec configured-app -n practice-config -- ls -l /etc/app-credentials
envFrom importa todas las claves del ConfigMap. El volumen del Secret crea un archivo por cada clave y se marca como de solo lectura dentro del contenedor.
La salida esperada del entorno es production, seguida de info. El directorio debe contener archivos llamados username y password; los nombres de archivo corresponden a las claves del Secret.
En el contenedor real, el listado será similar a este:
production
info
total 0
lrwxrwxrwx ... password -> ..data/password
lrwxrwxrwx ... username -> ..data/username
Las entradas son enlaces simbólicos administrados por Kubernetes. Esta indirección permite que kubelet cambie los datos montados a una versión nueva sin reescribir un archivo mientras la aplicación lo está leyendo. La aplicación puede seguir abriendo las rutas conocidas /etc/app-credentials/username y /etc/app-credentials/password.
Actualizar los datos montados del Secret
En este paso, actualiza el Secret y observa el archivo montado. kubectl create ... --dry-run=client -o yaml | kubectl apply -f - genera localmente el YAML deseado y lo envía directamente a apply; - significa entrada estándar.
Este patrón resulta útil para actualizar un objeto generado de forma imperativa sin eliminarlo. El lado izquierdo imprime un manifiesto completo del Secret y el derecho envía ese manifiesto al servidor de la API.
kubectl create secret generic app-credentials -n practice-config --from-literal=username=serviceuser --from-literal=password='rotated-pass' --dry-run=client -o yaml | kubectl apply -f -
Como el Secret se creó originalmente con kubectl create, la primera actualización puede incluir esta advertencia antes de la línea de éxito:
Warning: resource secrets/app-credentials is missing the kubectl.kubernetes.io/last-applied-configuration annotation ...
secret/app-credentials configured
La advertencia indica que apply no dispone de una instantánea declarativa anterior con la que comparar. Kubernetes añade automáticamente la anotación durante esta actualización, por lo que configured confirma que la operación se realizó correctamente; no es necesario eliminar y volver a crear el Secret.
Kubelet actualiza de forma asíncrona el contenido proyectado del volumen del Secret. Repite la comprobación hasta que aparezca el nuevo nombre de usuario:
current_username=""
for attempt in $(seq 1 24); do
current_username=$(kubectl exec configured-app -n practice-config -- cat /etc/app-credentials/username)
if [ "$current_username" = "serviceuser" ]; then
break
fi
sleep 5
done
echo "$current_username"
test "$current_username" = "serviceuser"
El bucle reintenta durante un máximo de dos minutos porque la proyección del volumen es eventualmente consistente. En el entorno probado, el objeto de la API cambió de inmediato, pero el archivo montado tardó más de un minuto en actualizarse.
$(...) es una sustitución de comandos: el intérprete ejecuta seq 1 24 y utiliza sus números como valores del bucle. En cada iteración, guarda el contenido actual del archivo en current_username; break detiene el bucle en la primera coincidencia y sleep 5 evita consultar continuamente la API. El test final devuelve un error si el valor nunca cambia, de modo que el comando no puede continuar silenciosamente con datos obsoletos. La salida correcta es serviceuser.
Actualizar la configuración del entorno
En este paso, actualiza LOG_LEVEL y compáralo con el valor del Pod en ejecución. Las variables de entorno quedan fijadas cuando se inicia un contenedor, por lo que cambiar el ConfigMap no modifica el entorno del proceso actual.
El parche de combinación cambia únicamente una entrada dentro de data. Leer de inmediato el proceso en ejecución demuestra una diferencia importante con respecto a un volumen montado:
kubectl patch configmap app-settings -n practice-config --type=merge -p '{"data":{"LOG_LEVEL":"debug"}}'
kubectl exec configured-app -n practice-config -- printenv LOG_LEVEL
El Pod todavía muestra info. Vuelve a crearlo a partir del manifiesto guardado para que consuma el nuevo valor:
configmap/app-settings patched
info
Este resultado es esperado y no indica que el parche haya fallado: el ConfigMap contiene debug, pero el proceso que ya estaba en ejecución conserva el entorno creado durante el arranque.
cd /home/labex/project/application-config
kubectl delete pod configured-app -n practice-config --wait=true
kubectl apply -f configured-app.yaml
kubectl wait --for=condition=Ready pod/configured-app -n practice-config --timeout=60s
kubectl exec configured-app -n practice-config -- printenv LOG_LEVEL
El Pod reemplazado muestra debug. Esta diferencia explica por qué las cargas de trabajo de producción suelen activar un nuevo despliegue después de cambiar una configuración basada en variables de entorno.
pod "configured-app" deleted
pod/configured-app created
pod/configured-app condition met
debug
El manifiesto sigue haciendo referencia al ConfigMap por su nombre; no se copió ningún valor en configured-app.yaml. Al volver a crear el Pod, kubelet resuelve de nuevo esa referencia e inyecta los datos más recientes.
Resumen
Separaste la configuración de la aplicación y las credenciales de la imagen, consumiste los datos del ConfigMap como variables de entorno, montaste los datos del Secret como archivos, rotaste un Secret proyectado y volviste a crear un Pod para actualizar la configuración basada en variables de entorno.


