Enfoque sistemático para variables en Ansible

estilo de codificación devops ansible

¡Hola! Me llamo Denis Kalyuzhny Trabajo como ingeniero en el departamento de automatización de procesos de desarrollo. Cada día se implementan nuevas versiones de aplicaciones en cientos de servidores de la empresa. En este artículo, comparto mi experiencia utilizando Ansible para estos fines.

Esta guía ofrece una forma de organizar variables en el despliegue. Está orientada a aquellos que ya utilizan roles en sus playbooks y han leído BestPractices, pero se enfrentan a problemas como los siguientes:

  • Al encontrar una variable en el código, no es fácil entender de inmediato para qué sirve;
  • Hay varios roles y las variables necesitan estar vinculadas a un solo valor, pero no se puede lograr;
  • Se plantean dificultades para explicar a otros cómo está organizada la lógica de las variables en sus playbooks.

Con estos problemas nos encontramos en los proyectos de nuestra empresa, lo que nos llevó a establecer reglas para la redacción de variables en nuestros playbooks, que en cierta medida han solucionado estos problemas.

Enfoque sistemático para variables en Ansible

Variables en roles

Un rol es un objeto independiente del sistema de despliegue. Como cualquier objeto del sistema, debe tener una interfaz de interacción con el resto del sistema. Esta interfaz son las variables del rol.

Tomemos, por ejemplo, un rol api, que instala una aplicación Java en el servidor. ¿Qué variables podría tener?

Enfoque sistemático para variables en Ansible

Las variables del rol se pueden dividir en dos tipos según su naturaleza:

1. Propiedades
    a) independientes del entorno
    b) dependientes del entorno
2. Relaciones
    a) escuchadores 
    b) solicitudes dentro del sistema
    c) solicitudes al entorno

Variables de propiedades son variables que determinan el comportamiento del rol.

Variables de solicitud son variables cuyo valor se utiliza para referirse a recursos externos en relación con el rol.

Variables escuchador son variables cuyo valor se usa para formar variables de solicitud.

Por otro lado, 1a, 2a, 2b son variables que no dependen del entorno (hardware, recursos externos, etc.) y pueden llenarse con valores predeterminados en los defaults del rol. Sin embargo, las variables de tipo 1b y 2c no se pueden llenar con valores distintos a 'example', ya que cambiarán de un entorno a otro dependiendo de la configuración.

Estilo de código

  • El nombre de la variable debe comenzar obligatoriamente con el nombre del rol. Esto permitirá en el futuro identificar fácilmente de qué rol proviene la variable y qué función cumple.
  • Al utilizar variables en roles, debes seguir estrictamente el principio de encapsulación y utilizar variables definidas ya sea en el propio rol o en los roles de los que depende el actual.
  • Intenta no utilizar diccionarios para variables. Ansible no permite sobrescribir fácilmente valores individuales en un diccionario.

    Ejemplo de una mala variable:

    myrole_user:
        login: admin
        password: admin

    Aquí, login es una variable independiente, mientras que password es dependiente. Sin embargo,
    dado que están combinadas en un diccionario, tendrás que especificarla completamente
    siempre. Lo cual es muy incómodo. Mejor así:

    myrole_user_login: admin
    myrole_user_password: admin

Variables en los playbooks de despliegue

Al crear un playbook de despliegue (en adelante playbook), seguimos la regla de que debe estar en un repositorio separado. Al igual que los roles: cada uno en su propio repositorio git. Esto permite entender que los roles y el playbook son objetos independientes del sistema de despliegue, y los cambios en uno no deben afectar al funcionamiento del otro. Esto se logra modificando los valores predeterminados de las variables.

Al crear un playbook, en general, existe la posibilidad de sobrescribir los valores predeterminados de las variables de rol en dos lugares: en las variables del playbook y en las variables del inventario.

mydeploy                        # Directorio de despliegue
├── deploy.yml                  # Playbook de despliegue
├── group_vars                  # Directorio de variables del playbook
│   ├── all.yml                 # Archivo para variables que conectan todo el sistema
│   └── myapi.yml               # Archivo de variables de propiedades del grupo myapi
└── inventories                 #
    └── prod                    # Directorio del entorno prod
        ├── prod.ini            # Archivo de inventario
        └── group_vars          # Directorio para variables del inventario
            └── myapi           #
                ├── vars.yml    # Variables independientes del grupo myapi
                └── vault.yml   # Secretos (siempre independientes) *

* — Variables y Vaults

La diferencia es que las variables del playbook se utilizan siempre al invocar playbooks que están al mismo nivel que él. Esto significa que estas variables son perfectas para modificar los valores predeterminados de las variables que no dependen del entorno. Por otro lado, las variables del inventario solo se utilizarán para un entorno específico, lo cual es ideal para las variables que dependen del entorno.

Es importante señalar que la prioridad de las variables no permitirá que redefinas las variables primero en las variables del playbook y luego por separado en un inventario.

Esto significa que ya en esta etapa debes decidir si la variable depende del entorno o no y colocarla en el lugar adecuado.

Por ejemplo, en un proyecto, la variable responsable de activar SSL fue durante mucho tiempo dependiente del entorno, ya que no podíamos activar SSL por razones ajenas a nosotros en uno de los entornos. Después de resolver este problema, se volvió independiente del entorno y se movió a las variables del playbook.

Variables de propiedades para grupos

Ampliemos nuestro modelo en la Figura 1, agregando 2 grupos de servidores con una aplicación Java diferente, pero con configuraciones distintas.

Enfoque sistemático para variables en Ansible

Veamos cómo se vería el playbook en este caso:

- hosts: myapi
  roles:
    - api

- hosts: bbauth
  roles:
    - auth

- hosts: ghauth
  roles:
    - auth

Tenemos tres grupos en el playbook, por lo que se recomienda crear tantos archivos de grupos en group_vars de las variables del inventario y del playbook. Un archivo de grupo en este caso es la descripción de un componente de la aplicación superior en el playbook. Al abrir el archivo de grupo en las variables del playbook, puedes ver inmediatamente todas las diferencias respecto al comportamiento predeterminado de los roles establecidos para el grupo. En las variables del inventario: las diferencias en el comportamiento del grupo de un entorno a otro.

Estilo de código

  • Intenta no utilizar variables host_vars, ya que no describen el sistema, sino solo un caso particular, lo que a largo plazo generará preguntas como: '¿Por qué este host es diferente de los demás?', a lo que no siempre es fácil encontrar respuesta.

Variables de relación

Sin embargo, esto se refiere a las variables de propiedades, pero ¿qué pasa con las variables de relación?
Su diferenciación radica en que deben tener el mismo valor en diferentes grupos.

Al principio fue idea utilizar una construcción monstruosa como:
hostvars[groups['bbauth'][0]]['auth_bind_port'], pero se descartó de inmediato
ya que tiene desventajas. En primer lugar, es engorrosa. En segundo lugar, depende de un host específico en el grupo. En tercer lugar, es necesario recopilar hechos de todos los hosts antes de comenzar el despliegue, si no queremos obtener un error de variable indefinida.

Como resultado, se decidió utilizar variables de relación.

Variables de relación — son variables que pertenecen al playbook y son necesarias para conectar los objetos del sistema.

Las variables de relación se llenan en las variables generales del sistema group_vars/all/vars y se forman extrayendo todas las variables de los oyentes de cada grupo, y agregando al inicio de la variable el nombre del grupo del que se extrajo el oyente.

De este modo se asegura la homogeneidad y la no intersección de los nombres.

Intentemos vincular las variables del ejemplo anterior:

Enfoque sistemático para variables en Ansible

Imaginemos que tenemos variables que dependen unas de otras:

# roles/api/defaults:
# Переменная запроса
api_auth1_address: "http://example.com:80"
api_auth2_address: "http://example2.com:80"

# roles/auth/defaults:
# Переменная слушатель
auth_bind_port: "20000"

Las extraemos a las variables generales group_vars/all/vars de todos los oyentes, y añadimos en el nombre el nombre del grupo:

# group_vars/all/vars
bbauth_auth_bind_port: "20000"
ghauth_auth_bind_port: "30000"

# group_vars/bbauth/vars
auth_bind_port: "{{ bbauth_auth_bind_port }}"

# group_vars/ghauth/vars
auth_bind_port: "{{ ghauth_auth_bind_port }}"

# group_vars/myapi/vars
api_auth1_address: "http://{{ bbauth_auth_service_name }}:{{ bbauth_auth_bind_port }}"
api_auth2_address: "http://{{ ghauth_auth_service_name }}:{{ ghauth_auth_bind_port }}"

Ahora, al cambiar el valor del conector, estaremos seguros de que la solicitud se dirigirá al mismo lugar donde se encuentra el puerto.

Estilo de código

  • Dado que los roles y los grupos son diferentes objetos en el sistema, es necesario que tengan nombres distintos, así las variables de relación mostrarán claramente que pertenecen a un grupo específico de servidores, y no a un rol en el sistema.

Archivos dependientes del entorno

En los roles pueden utilizarse archivos que difieren de un entorno a otro.

Un ejemplo de tales archivos son los certificados SSL. Almacenarlos en formato de texto
en una variable no es muy cómodo. Sin embargo, es conveniente guardar la ruta de acceso a ellos dentro de la variable.

Por ejemplo, usamos la variable api_ssl_key_file: "/path/to/file".

Dado que es evidente que el certificado de clave cambiará de un entorno a otro, esta es una variable dependiente del entorno, por lo que debe ubicarse en el archivo
group_vars/myapi/vars de inventario de variables y contener el valor 'para ejemplo'.

Lo más conveniente en este caso es colocar el archivo de clave en el repositorio del playbook en la ruta
files/prod/certs/myapi.key, entonces el valor de la variable será:
api_ssl_key_file: "prod/certs/myapi.key". La conveniencia radica en que las personas responsables del despliegue del sistema en un stand específico también tienen su lugar dedicado en el repositorio para almacenar sus archivos. Al mismo tiempo, permanece la opción de indicar la ruta absoluta al certificado en el servidor, en caso de que los certificados sean proporcionados por otro sistema.

Varios stands en un mismo entorno

A menudo surge la necesidad de desplegar varios entornos prácticamente idénticos en un mismo ambiente con mínimas diferencias. En este caso, dividimos las variables dependientes del entorno en aquellas que no cambian dentro de este entorno y las que sí cambian. Y llevamos estas últimas directamente a los archivos de inventario. Después de esta manipulación, es posible crear otro inventario directamente en el directorio del entorno.

Este reutilizará los group_vars del inventario, así como tendrá la capacidad de sobreescribir algunas variables directamente para sí mismo.

La estructura final de directorios para el proyecto de despliegue:

mydeploy                        # Directorio de despliegue
├── deploy.yml                  # Playbook de despliegue
├── files                       # Directorio para archivos de despliegue
│   ├── prod                    # Directorio para archivos dependientes del entorno del stand prod
│   │   └── certs               # 
│   │       └── myapi.key       #
│   └── test1                   # Directorio para archivos dependientes del entorno del stand test1
├── group_vars                  # Directorio de variables del playbook
│   ├── all.yml                 # Archivo para variables relacionadas con todo el sistema
│   ├── myapi.yml               # Archivo de variables de la propiedad del grupo myapi
│   ├── bbauth.yml              # 
│   └── ghauth.yml              #
└── inventories                 #
    ├── prod                    # Directorio del entorno prod
    │   ├── group_vars          # Directorio para variables de inventario
    │   │   ├── myapi           #
    │   │   │   ├── vars.yml    # Variables dependientes del grupo myapi
    │   │   │   └── vault.yml   # Secretos (siempre dependientes del entorno)
    │   │   ├── bbauth          # 
    │   │   │   ├── vars.yml    #
    │   │   │   └── vault.yml   #
    │   │   └── ghauth          #
    │   │       ├── vars.yml    #
    │   │       └── vault.yml   #
    │   └── prod.ini            # Inventario del stand prod
    └── test                    # Directorio del entorno test
        ├── group_vars          #
        │   ├── myapi           #
        │   │   ├── vars.yml    #
        │   │   └── vault.yml   #
        │   ├── bbauth          #
        │   │   ├── vars.yml    #
        │   │   └── vault.yml   #
        │   └── ghauth          #
        │       ├── vars.yml    #
        │       └── vault.yml   #
        ├── test1.ini           # Inventario del stand test1 en el entorno test
        └── test2.ini           # Inventario del stand test2 en el entorno test

Resumen

Después de organizar las variables de acuerdo con el artículo: cada archivo de variables es responsable de una tarea específica. Y dado que cada archivo tiene tareas determinadas, se ha vuelto posible asignar a alguien para garantizar la corrección de cada archivo. Por ejemplo, el desarrollador de la implementación del sistema es responsable de la corrección del llenado de las variables del playbook, mientras que el administrador, cuyo entorno está descrito en el inventario, es responsable del llenado de las variables de inventario.

Los roles se han convertido en una unidad de desarrollo independiente con su propia interfaz, lo que ha permitido al desarrollador del rol crear funcionalidades en lugar de ajustar el rol al sistema. Este problema afectaba especialmente a los roles comunes para todos los sistemas en la campaña.

Los administradores de sistemas ya no necesitan entender el código de implementación. Todo lo que se requiere de ellos para un despliegue exitoso es llenar los archivos de variables dependientes del entorno.

Literatura

  1. La documentación

Autor

Kalyuzhny Denis Alexandrovich

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster