Saltar a contenido

Variables de entorno

Las variables de entorno son variables de texto manejadas por el sistema operativo. Se usan frecuentemente para configurar opciones y parámetros de los programas desde el sistema.

Introduccion

Los contenedores funcionan en entornos aislados por default. Al ser desplegados, sus programas internos son incapaces de acceder a estas variables de entorno por sí mismos. Por este motivo los gestores de contenedores dan opciones para realizar copias de las variables de entorno necesarias y asignarlas a los contenedores que las requieran.

Variables en BASH

Las variables de entorno se crean manualmente desde la shell Bash con el comando export:

Bash - crear variable de entorno
export NOMBRE_VARIABLE=VALOR    # variable con valor
export NOMBRE_VARIABLE          # variable vacía

y se eliminan con el comando unset:

Bash - eliminar variable de entorno
unset NOMBRE_VARIABLE
La consulta manual de los valores se hace con el comando echo y el signo $ delante del nombre de variable:

Bash - consultar valor de variable
echo $NOMBRE_VARIABLE
y además sus valores pueden ser modificados mediante asignaciones:

Bash - modificar valor de variable
NOMBRE_VARIABLE=nuevo_valor
Los valores de estas variables se transmiten como strings.

Lectura desde rutinas Python

Con el módulo os se pueden consultar las variables de entorno en las rutinas de Python. Estas son consultadas con la función getenv()

Python - leer variable de entorno
import os

valor_variable = os.getenv(
    "NOMBRE_VARIABLE",          # variable buscada
    default=valor_respaldo      # (opcional)
    )

Si la variable pedida existe entonces se lee su valor; en caso contrario se devuelve el valor indicado por el argumento default. Si este no fue definido entonces se devuelve None.

Borrado de variables

Si una variable de entorno tiene información sensible entonces ésta puede ser borrada desde la rutina de Python tras su lectura.

Python - borrar variable de entorno
# borrado de variable
os.environ["NOMBRE_VARIABLE"] = ""

Archivos .env

Una forma cómoda de definir las variables de entorno es usar un archivo de texto con nombre .env (archivo oculto). En este archivo se crean las variables de entorno necesarias, una por renglón:

Archivo .env - sintaxis
# comentarios (opcionales)
NOMBRE_VARIABLE_1=VALOR_1
NOMBRE_VARIABLE_2=VALOR_2

Durante el despliegue el gestor de contendores busca por este archivo en el directorio del proyecto. Este archivo es leído y los valores de sus variables internas son importadas automáticamente.

Jerarquía de valores

Si una misma variable de entorno es definida en terminal y en archivo entonces el valor leído por el gestor será el de terminal.

Control de versiones

Agregar los archivos .env a los repositorios de los proyectos es una mala práctica porque implica publicar información potencialmente sensible.

Variables predefinidas

En el caso de requerirse la creación de variables de entorno predefinidas para la imagen del contenedor se dispone de la cláusula ENV dentro del Dockerfile:

Dockerfile - Variables de entorno
# archivo Dockerfile 
ENV VARIABLE_INTERNA=VALOR

Se permite declarar varias variables con una única cláusula ENV:

Dockerfile - Variables de entorno múltiples
# archivo Dockerfile 
ENV VARIABLE_1=VALOR_1 VARIABLE_2=VALOR_2 VARIABLE_3=VALOR_3

Estas variables seguirán existiendo tras la creación de la imagen final y podrán ser modificadas durante el despliegue.

Asignación de variables

Cada contenedor del proyecto debe ser configurado deliberadamente para poder acceder a los valores de las variables de entorno exteriores al proyecto. En el archivo compose.yml se indican las variables de entorno necesarias para cada contenedor con ayuda del campo environment.

Mapeo de variables

Las variables de entorno experimentan un "mapeo": a izquierda se indica el nombre de variable que verá la rutina adentro del contenedor y a la derecha se indica el nombre de la variable definida en la terminal del sistema anfitrión:

compose.yml - mapeo de variables
services:

  programa_entorno:
    build: .
    environment:
      VARIABLE_PROGRAMA: "${VARIABLE_BASH}"

Valor default

Dentro de la lectura del valor de la variable se puede definir un valor default entre las llaves. Este valor es asignado solamente si la variable de entorno no fue definida. Este valor de resguardo se consigue con el signo -:

compose.yml - leer variable (con valor default)
services:

programa_entorno:
    build: .
    environment:
      VARIABLE_PROGRAMA: "${VARIABLE_BASH:-VALOR_DEFAULT}"

Si se agrega el signo : también se autocompleta ante variables existentes pero sin valor.

Valor de reemplazo

Existen casos donde se necesita sobreescribir el valor de entrada de la variable por un valor sustituto. Se marca con el signo +.

compose.yml - leer variable (con valor reemplazo)
services:

programa_entorno:
    build: .
    environment:
      VARIABLE_PROGRAMA: "${VARIABLE_BASH:+VALOR_SUSTITUTO}"

Agregando el signo : se sustituyen también las variables nulas.

Error ante faltante

Si la variable no está declarada o está vacía entonces se permite lanzar una excepción que interrumpe el despliegue y lanza un mensaje en consola. Esto se consigue con el signo ?:

compose.yml - leer variable (con error por faltante)
services:

programa_entorno:
    build: .
    environment:
      VARIABLE_PROGRAMA: "${VARIABLE_BASH:?'Error: valor faltante'}"

Nuevamente, colocando el signo : se dispara el error ante variables nulas.

Interpolación - resumen

A estas políticas para asignar valores de respaldo, errores, etc. se las llama interpolación.

Este es el resumen de secuencias posibles:

Secuencia Uso
- variable no definida
:- variable no definida o nula
? variable no definida
:? variable no definida o nula
+ variable no definida
:+ variable no definida o nula

Parámetros de despliegue

Con ayuda de las variables de entorno también se pueden modificar los parámetros de despliegue del proyecto sin tener que reescribir el archivo compose.yml. Por ejemplo, el puerto del host requerido por un contenedor se puede elegir mediante variables de entorno:

compose.yml - Puerto de host variable
services:

programa:
    build: .
    ports:
      - "${PUERTO_HOST:-5000}:8000"
En este ejemplo el contenedor recibe peticiones IP por el puerto 8000. Si la variable de entorno PUERTO_HOST está definida en la shell o en un archivo .env entonces se intentará asignarle su valor numérico. Si en cambio PUERTO_HOST no está definida se le intenta asignar el puerto 5000.

Ejemplo de uso

En este demo se crea una variable de entorno en un archivo .env y es leída por unar rutina llamada entorno.py desde adentro de un contenedor.

Demo entornos - Arbol de archivos
.
├── demo
│   └── main.py
├── compose.yml
├── Dockerfile
└── .env

Se crea una rutina sencilla en Python para leer una variable de entorno y reportar su valor en la ventana de logs:

Demo entornos -main.py
import os
import logging

logging.basicConfig(
    level=logging.INFO, # mínimo nivel de log a publicar
    format="%(message)s", #info incorporada
    )

# lectura de variable
valor_variable = os.getenv("VARIABLE_PYTHON")

# reporte de valor
logging.info(f"Valor de 'VARIABLE_PYTHON': '{valor_variable}'")

Al demo se le asigna el siguiente Dockerfile para construir la imagen:

Demo entornos - Dockerfile
# imagen de referencia
FROM python:alpine

# directorio de trabajo (se crea automáticamente)
WORKDIR /code

# copia de rutinas al directorio de trabajo
COPY demo/ ./

# variable de entorno local
ENV VARIABLE_PYTHON="Me definieron en el Dockerfile"

# comandos 
CMD ["python", "main.py"]

Se configura el despliegue en varios contenedores paralelos con distintas interpolaciones de variables:

Demo entornos - compose.yml
name: demo_variables_entorno

services:

  # crea una imagen común para todos los demos
  crear_imagen:
    build: .
    image: demo_entornos 

  # cada contenedor pone a prueba distintas interpolaciones
  entornos_interna:
    image: demo_entornos 
    depends_on:
      crear_imagen:
        condition: service_completed_successfully

  entornos_exterior:
    image: demo_entornos 
    environment:
      VARIABLE_PYTHON: "${VARIABLE}"
    depends_on:
      crear_imagen:
        condition: service_completed_successfully

  entornos_default:
    image: demo_entornos 
    environment:
      VARIABLE_PYTHON: "${VARIABLE:-'valor predefinido'}"
    depends_on:
      crear_imagen:
        condition: service_completed_successfully

  entornos_sustituir:
    image: demo_entornos 
    environment:
      VARIABLE_PYTHON: "${VARIABLE:+Sustituido}"
    depends_on:
      crear_imagen:
        condition: service_completed_successfully

Nótese que sólo uno de los contenedores construye la imagen y los demás la reutilizan.

Finalmente se crea un archivo .env aledaño al archivo compose.yml y se define dentro la variable de entorno:

Demo entornos - variable en archivo
VARIABLE="Vengo del archivo '.env'"

o se declara en la shell:

Demo entornos - variable en Bash
export VARIABLE="Me definieron en BASH"

Por último se realiza el despliegue:

Demo entornos - despliegue
podman compose up