
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 Secret:
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.
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.