OpenShift: ConfigMaps & Secrets

Históricamente cuando una aplicación requería de datos de configuración, como por ejemplo, la cadena de conexión a una base de datos o el nivel de granularidad de los logs, no quedaba más remedio que empaquetarlos dentro de la propia aplicación, pasarlos como argumentos de linea de comandos o a través de variables del sistema. La primera de las opciones requería construir y desplegar una nueva versión de la aplicación, mientras que en el resto no existía un versionado o histórico en un SCM.

En los últimos años, con la irrupción de los contenedores (Docker en su gran mayoría) y a los sistemas de orquestación como Kubernetes, la manera en la que se gestionan los datos de configuración ha cambiado. El nuevo paradigma busca desacoplar la configuración del contenido de la imagen, manteniendo así la portabilidad de los contenedores. Es decir, la aplicación y la configuración se gestionan por separado.

OpenShift, mas bien Kubernetes, proporciona dos utilidades para ello.

ConfigMap

Un ConfigMap es un objecto de OpenShift/Kubernetes que proporciona un mecanismo para la inyección de datos de configuración en los contenedores. En su interior puede albergar información en formato clave-valor o estructuras complejas como ficheros properties, YAML o binarios.

Este objeto puede crearse tanto desde la consola de administración de OpenShift como desde linea de comando con el OpenShift Container Platform CLI y a modo de ejemplo, este sería aspecto de un ConfigMap con varios ficheros de configuración en su interior:

apiVersion: v1
metadata:
  name: application-config
data: 
  server.port: 8080
  application.properties: |-
    spring.application.name = "Demo application"
  application.yml: |-    
    spring:
      application:
        admin:
          enabled: false
binaryData:
  bar: L3Jvb3QvMTAw

En resumen, es posible detallar los datos configuración de una aplicación en un objeto JSON|YAML, que después pueden ser cargados en OpenShift y añadidos al contenedor en fase de despliegue, manteniendo ambas partes desacopladas.

Una buena forma de hacerlo es almacenar la definición de un ConfigMap como código en un SCM, lo que brinda opciones como el versionado o auditoria. El SCM a su vez, puede notificar a Jenkins con cada nuevo cambio y actualizar el ConfigMap de OpenShift vía job.

pipeline {
	agent any
	stages { 
		stage('Checkout') {
			steps {
				git url: "$BITBUCKET_URL", credentialsId: 'bbCredentials', branch: 'master'
			}
		}    
		stage('Update ConfigMap') {
			steps {
				script {
					// ConfigMap is updated if file exists but is never deleted in Openshift
					if (fileExists('./configmap.yml')) {
						sh '''
							oc login $OPENSHIFT_URL --token=$OPENSHIFT_TOKEN
							sh 'oc apply -f ./configmap.yml -n $OPENSHIFT_PROJECT'
						'''
					}
				}
			}
		}
	}
    post {
		always {   
			cleanWs()
		}  
	}  
}

 Una vez que el ConfigMap esta creado en OpenShift, es el momento de decidir de que manera se quiere inyectar la información en el contenedor.

Environment Variables

Una opción es añadir las propiedades clave-valor definidas en el ConfigMap como variables de entorno del contenedor.

Partiendo del siguiente ConfigMap:

kind: ConfigMap
apiVersion: v1
metadata:
  name: application-config
data:
  server.port: 8080
  spring.application.name = "Demo application"

Es posible especificar una a una las propiedades especificas a cargar como variables de entorno en la configuración del despliegue (DeploymentConfig).

spec:
  containers:
  - name: demo-application 
    image: registry/demo-application 
    env:  
    - name: SERVER_PORT
      valueFrom:
        configMapKeyRef:
          name: application-config 
          key: server.port

O cargar directamente todos los valores existentes en el ConfigMap.

spec:
  containers:
  - name: demo-application 
    image: registry/demo-application
    envFrom: 
      configMapRef:
        name: application-config

Volumes

La otra opción es consumir el ConfigMap como un volumen, es decir, inyectando físicamente el fichero properties|YAML|binary en una ruta del contenedor.

Partiendo del siguiente ConfigMap:

kind: ConfigMap
apiVersion: v1
metadata:
  name: application-config
  data: 
    application.yml: |-
      spring:
        application:
          admin:
            enabled: false
          name: "Demo application"

La configuración del DeploymentConfig sería la siguiente:

spec:
  containers:
  - name: demo-application 
    image: registry/demo-application 
    volumeMounts: 
    - name: application-config
      mountPath: /deployments/config/application
  volumes:
  - name: application-config
    configMap:
      name: application-config 

De esta manera, se consigue inyectar físicamente en la fase de despliegue, el fichero “application.yml” definido en el ConfigMap, en la ruta “/deployments/config/application” del contenedor.

Secret

Un Secret es un objecto de OpenShift/Kubernetes que proporciona un mecanismo para la inyección de datos de configuración sensibles en los contenedores como pueden ser credenciales, ficheros dockercfg, certificados etc. Es decir, se trata de un ConfigMap especialmente diseñado para información sensible.

Este objeto puede crearse tanto desde la consola de administración de OpenShift como desde linea de comando con el OpenShift Container Platform CLI y a modo de ejemplo, este sería aspecto de un Secret 
con distintos ficheros de configuración en su interior:

kind: Secret
metadata:
  name: application-sensitive-config
  type: Opaque
  data:
    ORACLE_URL: amRiYzpvcmFjbGU6dGhpbjpALy9iZC5vcmFjbGUuY29tOjUwNzE1L0VYQU1QTEU=
    ORACLE_USERNAME: dXNlcm5hbWU=
    ORACLE_PASSWORD: cGFzc3dvcmQ=
  stringData:
    secret.properties: |-
      property1=valueA
      property2=valueB

Del mismo modo que un ConfigMap, esto permite detallar los datos configuración de una aplicación en un objeto JSON|YAML, que después pueden ser cargados en OpenShift y añadidos al contenedor en fase de despliegue, manteniendo ambas partes desacopladas.

En consecuencia, también es posible almacenarlo como código en un SCM (versionado, auditoria) y que este a su vez notifique a Jenkins con cada nuevo cambio para actualizarlo en OpenShift vía job.

pipeline {
	agent any
	stages { 
		stage('Checkout') {
			steps {
				git url: "$BITBUCKET_URL", credentialsId: 'bbCredentials', branch: 'master'
			}
		}    
		stage('Update Secret') {
			steps {
				script {
					// Secret is updated if file exists but is never deleted in Openshift
					if (fileExists('./secret.yml')) {
						sh '''
							oc login $OPENSHIFT_URL --token=$OPENSHIFT_TOKEN
							sh 'oc apply -f ./secret.yml -n $OPENSHIFT_PROJECT'
						'''
					}
				}
			}
		}
	}
    post {
		always {   
			cleanWs()
		}  
	}  
}

Una vez que el Secret esta creado en OpenShift, es el momento de decidir de que manera se quiere inyectar la información en el contenedor y la posibilidades vuelven a ser las mismas.

Environment Variables

Una opción es añadir las propiedades clave-valor definidas en el Secret como variables de entorno del contenedor.

Partiendo del siguiente ConfigMap:

apiVersion: v1
kind: Secret
metadata:
  name: application-sensitive-config
  type: Opaque
  data:
    ORACLE_URL: amRiYzpvcmFjbGU6dGhpbjpALy9iZC5vcmFjbGUuY29tOjUwNzE1L0VYQU1QTEU=
    ORACLE_USERNAME: dXNlcm5hbWU=
    ORACLE_PASSWORD: cGFzc3dvcmQ=

Es posible especificar una a una las propiedades especificas a cargar como variables de entorno en la configuración del despliegue  (DeploymentConfig).

spec:
  containers:
  - name: demo-application
    image: registry/demo-application 
    env:
    - name: ORACLE_URL
        valueFrom:
          secretKeyRef:
            name: application-sensitive-config
            key: ORACLE_URL 

O cargar directamente todos los valores existentes en el Secret.

spec:
  containers:
  - name: demo-application
    image: registry/demo-application
    envFrom: 
      secretRef:
       name: application-sensitive-config 

Volumes

La otra opción es consumir el Secret como un volumen, es decir, inyectando físicamente el fichero properties|YAML|binary en una ruta del contenedor.

Partiendo del siguiente Secret:

apiVersion: v1
  kind: Secret
  metadata:
    name: application-sensitive-config
  type: Opaque
  stringData:
    secret.properties: |-     
      property1=valueA
      property2=valueB

La configuración del DeploymentConfig sería la siguiente:

spec:
  containers:
  - name: demo-application
    image: registry/demo-application
    volumeMounts: 
    - name: application-sensitive-config
      mountPath: /deployments/config/application-sensitive-config
      readOnly: true
  volumes:
  - name: application-sensitive-config
    secret:
      secretName: application-sensitive-config 

De esta manera, se consigue inyectar físicamente en la fase de despliegue, el fichero “secret.properties” definido en el Secret, en la ruta “/deployments/config/application-sensitive-config” del contenedor.

Conclusiones

En conclusión, con los ConfigMaps y Secrets se logra desacoplar la configuración de la aplicación del contenido de la imagen, manteniendo así la portabilidad de los contenedores. Finalmente en la fase de despliegue se unen ambas partes.

Fuente

Ademas, gracias a su naturaleza (JSON|YAML) es posible almacenarlos como código en un SCM (de nuevo, histórico y auditoria) e integrar su gestión en los ciclos de integración continua.

Para acabar, comentar que aunque los cambios en un ConfigMap/Secret se reflejen en tiempo real en un contenedor, la aplicación debe estar preparada para volver a leer dichos valores en caliente.

Advertisements