Ansible
https://docs.ansible.com/ansible/latest/modules/modules_by_category.html
Intalación
sudo apt install ansible
ansible --version
Configuración
configurar ssh hacia todos los nodos
ssh-copy-id -i user@host
Por defecto ansible utiliza python2, si en los host no esta instalado se reproducira un fallo que no encuentra /usr/lib/python
se resuelve crando un link en todos los hosts remotos.
ln -s /usr/lib/python3 /usr/lib/python
windows
https://docs.ansible.com/ansible/latest/user_guide/windows_setup.html
Para poder controlar remotamente nodos windows con ansible es necesario tener los siguientes modulos de python en el servidor python3-winrm o pywinrm
y en el nodo remoto tener powershell 3.0 o superior y habilitiar control remoto con puerto 5986.
Una vez habilitada la conexión se tendra que crear un certificado para la conexión o ignorar mediante la siguiente valiable, pasada desde ad-hoc o desde variable de configuración ansible_winrm_server_cert_validation=ignore
Inventory
https://docs.ansible.com/ansible/2.3/intro_inventory.html
El inventorio es donde se definen los servidores a controlar, estos servidores se pueden agrupar por grupos y especificar argumentos a cada uno de ellos.
Inventori /etc/ansible/hosts
#green.example.com
#blue.example.com
#192.168.100.1
#192.168.100.10
# GRUPO de servers --------------------------------
#[dbservers]
#
#db01.intranet.mydomain.net
#db02.intranet.mydomain.net
#10.25.1.56
#10.25.1.57
# Here's another example of host ranges, this time there are no
# leading 0s:
#db-[99:101]-node.example.com
localhost ansible_connection=local
[vhosts]
192.168.122.200 ansible_user=root
# ALIAS -----------------------------------
# Alias a un server
# vhost1 ansible_host=192.168.122.200 ansible_user=root
# VARIABLES --------------------------------
# Variables a un grupo de servidores
[vhosts:vars]
ansible_python_interpreter=/usr/bin/python3
# SUBGRUPOS -----------------------------------
[debian]
192.168.122.20
[ubuntu]
192.168.122.21
[apt:children]
ubuntu
debian
Se pueden separar las variables en ficheros externos al inventario, estos ficheros se escribe en formato yaml
.
/etc/ansible/group_vars/grupo
/etc/ansible/host_vars/servidor
/etc/ansible/group_vars/apt
ansible_become: True
/etc/ansible/host_vars/pc10
ansible_python_interpreter: /usr/bin/python3
Modulos
Ansible trabaja con modulos y realizan las tareas que requrimos, tiene una lista muy extensa de módulos donde podemos encrnttrarlos organizados por: cloud, command,clustering, crypto, database, files, monitoring, ... Lista completa aquí
ad-hoc
sintaxis. ansible [opciones] servidores,grupos,all [-m modulo] [-a argumentos]
si no se especifica ningún módulo por defecto utiliza el módulo command
Argumentos pas utilizados en ansible:
-C # comprobar si puede ser realizada una tarea
-u, --user # especificar usuario
-i --inventory # especificar inventario
-b --become # utilizar sudo
-f --fork # numero de nodos controlados simultaniamente (default 5)
-k # introducir password
--list-hosts # listar gosts, grupos del inventario
-m # modulo
-a # argumentos
Ejemplos:
# listartodos los hosts del inventario
➜ ansible ansible -i hosts all --list-hosts
hosts (2):
localhost
node
# ejecutar un comando directo
➜ ~ ansible localhost -a "hostname"
localhost | CHANGED | rc=0 >>
pc02
# utilizar modulo ping
➜ ~ ansible 192.168.122.200 -m ping
192.168.122.200 | SUCCESS => {
"changed": false,
"ping": "pong"
}
# usuario especifico
➜ ~ ansible vhosts -u root -m ping
192.168.122.200 | SUCCESS => {
"changed": false,
"ping": "pong"
}
# filtrar un nodo y comprobar conexión con usario root
➜ ansible ansible --limit node -i hosts all -m ping -u root
node | SUCCESS => {
"changed": false,
"ping": "pong"
}
# instalaciones
# modulo dnf, argumentos intalar vim
➜ ~ ansible 192.168.122.200 -u root -m dnf -a 'name=vim state=present'
192.168.122.200 | SUCCESS => {
"changed": false,
"msg": "Nothing to do",
"rc": 0,
"results": [
"Installed: vim"
]
}
# -K preguntar por contraseña
# --become utilizar sudo
➜ ~ ansible 192.168.122.200 -m dnf -a 'name=vim state=present' -b -K
SUDO password:
192.168.122.200 | SUCCESS => {
"changed": false,
"msg": "Nothing to do",
"rc": 0,
"results": [
"Installed: vim"
]
}
# inventorio especifico -i --inventory
➜ ~ ansible 192.168.122.200 --inventory my-inventory -m ping
192.168.122.200 | SUCCESS => {
"changed": false,
"ping": "pong"
}
Playbook
Playbook es una lista de tareas a realizar en uno o mas nodos remotos, a continuación se muestra dos ejemplos simples.
➜ ansible cat playbooks/copiarHost.yml
---
- name: copiar archivo /etc/hosts
hosts: all
remote_user: jorge
tasks:
- name: Copiar /etc/hosts
become: true
copy: src=/etc/hosts dest=/etc/hosts
➜ ansible cat playbook.yml
---
- name: instalar vim
hosts: vhosts
tasks:
- name: instalando vim
dnf:
name: vim
state: present
become: true
- name: saludar
shell: echo "Hola Mundo"
Para ejecutar un playbook se utiliza la sintaxis ansible-playbook [opciones] playbook.yml
➜ ansible-playbook playbook.yml -K
SUDO password:
PLAY [vhosts] *******************************************************************************************
TASK [Gathering Facts] **********************************************************************************
ok: [192.168.122.200]
TASK [instalar vim] *************************************************************************************
ok: [192.168.122.200]
TASK [saludar] ******************************************************************************************
changed: [192.168.122.200]
PLAY RECAP **********************************************************************************************
192.168.122.200 : ok=3 changed=1 unreachable=0 failed=0
Opciones útiles para playbooks, también se pueden utilizar las opciones de ad-hoc:
-i # inventario
--syntax-check # verificar sintaxis de playbook
--list-task # listar tareas del playbook
--step # preguntar en cada tarea si ejecutarla
--start-at-task=nombre de la tarea # empezar desde una tarea especifica
--forks= / -f # numero de conexiones simultaneas, por defecto 5
Parametros básicos
Con hosts
especificamos que grupos o servidores ejecutaran la siguientes tareas, el separador es :
y es posible utilizar los simbolos & y !
para ambos o negación.
hosts: serverweb # grupo serverweb
hosts: serverweb:dbweb # grupo serverweb y dbweb
hosts: serverweb:&madrid # nodos que pertenezcan a serverweb y madrid
hosts: serverweb:!madrid # nodos del serverweb que no pertenezcan a madrid
remote_user: jorge
become: true/false ( 1/0 ) # utilizar sudo o no
become_user: (usuario) postgres # cambiar de usuario
become_method: sudo/su # metodo de cambio
check_mode: true/false # mode de prueba
Variables
# plantilla donde se mostraran variables
➜ ansible cat plantillas/host.j2
{{ miip }} {{ ansible_hostname }} {{ ansible_fqdn }}
# playbook donde se especifica una variable y se asigna a una plantilla
➜ ansible cat playbooks/variables.yml
---
- name: crear fichero usando variables
hosts: localhost
connection: local
vars:
- miip: "1.2.3.4"
tasks:
- name: crear fichero hosts
template: src=host.j2 dest=/tmp/hosts
# ejeución
➜ ansible-playbook -i hosts playbooks/variables.yml
...
# de la plantilla se a generado este archivo
➜ ansible cat /tmp/hosts
1.2.3.4 pc02 pc02
# las variables predefinidas de ansible se pueden ver con el modulo setup
➜ ansible localhost -m setup
Prioridades de variables definidas de menor prioridad a mayor prioridad.
- defaults definidas en un rol
- variables de grupo (
inventario, group_vars/all, group_vars/grupo
) - variables de servidor (
inventario, host_vars/servidor
) - "Facts" del servidor
- variabes de playbook (
vars_prompt, vars_files
) - variable de rol definidas en
/roles/rol/vars/main.yml
- variables de bloque, definidas en tareas
- parametros de rol,
include_params, include_vars
set_facts/ registered_vars
- extra vars, definidas al lanzar la orden de ansible ( siempre gana )
Handlers
handlers son tareas que se ejecutan si un trabajo a ido bien y solicita dicha tarea, es decir si se instala nginx reinicia el servicio, si ya esta intalado no se ejecutara el handler.
➜ ansible cat handlers.yml
---
- hosts: vhosts
become: true
tasks:
- name: instalar nginx
dnf:
name: nginx
state: present
update_cache: true
notify:
- "Reiniciar nginx"
handlers:
- name: Reiniciar nginx
service:
name: nginx
state: restarted
➜ ansible ansible-playbook handlers.yml -K
SUDO password:
PLAY [vhosts] *******************************************************************************************
TASK [Gathering Facts] **********************************************************************************
ok: [192.168.122.200]
TASK [instalar nginx] ***********************************************************************************
changed: [192.168.122.200]
RUNNING HANDLER [Reiniciar nginx] ***********************************************************************
changed: [192.168.122.200]
PLAY RECAP **********************************************************************************************
192.168.122.200 : ok=3 changed=2 unreachable=0 failed=0
Include y Role
Estos son metodos para desglosar un playbook que es demasiado grande para ser leeible y facilitar la tarea de modificaciones etc..
Include es simplemente el incluir un playbook dentro de otro.
Role es una estructura de directorios donde se ordenaran adecuadamente los diferentes archivos yaml para las tareas.
site.yml
webservers.yml
fooservers.yml
roles/
common/
tasks/
handlers/
files/
templates/
vars/
defaults/
meta/
webservers/
tasks/
defaults/
meta/
tasks
- contiene la lista principal de tareas que debe ejecutar el rol.handlers
- contiene handlers, que pueden ser utilizados por este rol o incluso en cualquier lugar fuera de este rol.defaults
- variables predeterminadas para el rol (consulta Using Variables para mas información).vars
- otras variables para el rol (see Using Variables for more information).files
- contiene archivos que se pueden implementar a través de este rol.templates
- contiene plantillas que se pueden implementar a través de este rol.meta
- define algunos metadatos para este rol.
Include
En el siguiente ejemplo es una instalación simple de el editor vim y un servicio nginx, con el metodo include separando los pasos de nginx en un archivo yaml diferente.
---
- name: primer Play
hosts: vhosts
remote_user: jorge
become: true
tasks:
- name: instalar nginx
dnf:
name: vim
state: present
- include: instalar_nginx.yml
instalar_nginx.yml
---
- name: instalar nginx
dnf: name=nginx state=latest
- name: instalar servicio
service: name=nginx state=started
- name: habilitar servicio
service: name=nginx enabled=true
Role
En este ejemplo se muestra una instalación simple con la estructura de un role
➜ web tree
├── instalar_con_rol.yml
└── roles
└── apache2
├── files
│ └── apache2.conf
├── handlers
│ └── main.yml
├── tasks
│ └── main.yml
└── templates
└── index.html.j2
instalar_con_rol.yml
- name: instalar y configurar apache2
hosts: vhosts
remote_user: jorge
become: true
roles:
- apache2
roles/apache2/tasks/main.yml
- name: instalar apache2
apt: name=apache2 state=latest
- name: Iniciar y habilitar servicio
service: name=apache2 state=started enabled=true
- name: Copiar fichero de configuracion
copy: src=apache2.conf dest=/etc/apache2/apache2.conf
notify: restart_httpd
- name: Copiar fichero index.html
template: src=index.html.j2 dest=/var/www/html/index.html
roles/apache2/handlers/main.yml
- name: restart_httpd
service: name=apache2 state=restarted
roles/apache2/templates/index.html.j2
Hola al servidor {{ ansible_fqdn }}
Templates
dentro de una plantilla se pueden especificar las siguientes instrucciones:
Expresiones: una {{ variable }}
propia o del sistema como {{ ansible_fqdn }}
Control: código con {% ... %}
comentarios: {# ... #}
{% if ansible_distribution == "Debian" %}
Muestra sólo en sistemas Debian
{% endif %}
{% for usuario in lista_usuarios %}
{{ usuario }}
{% endfor %}
Condiciones
Las condiciones son para ejecutar tareas si dicha condición es cierta, en los siguientes ejemplos se ejecutaran las tareas si las distribuciones remotas son Debian o Ubuntu, esta información de las variables de ansible que podemos consultar en ansible hostname -m setup
.
# condicion para tarea
- name: instalar apache2
include: instalar_apache2.yml
when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
# condicion para rol
- name: instalar y configurar apache2
hosts: vhosts
remote_user: jorge
become: true
roles:
- { role: apache2, when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu" }
bucles
los bucles evitan repetir código y asi reducir el tamaño del playbook por ejemplo en la instalación de diversos paquetes.
# bucle de tres paquetes a instalar
- name: Instalar paquetes
apt: name={{ item }} state=latest
with_items:
- mysql-server
- phpmyadmin
- php5
# bucle de diccionario creando usuarios
- name: Crear usuarios necesarios
user: name={{ item.nombre }} groups={{ item.grupo }}
with_items:
- { nombre: usuario1, grupo: www-data }
- { nombre: usuario2, grupo: www-data }
# bucle utilizando una variable que es una lista externa
- name: Instalar paquetes
apt: name={{ item }} state=latest
with_items: "{{ lista_paquetes }}"
defaults/main.yml
lista_paquetes: ["php5", "phpmyadmin", "mysql-server"]
Register
El register permite guardar en una variable el resultado de una acción generada por un modulo de una tarea.
- name: playbook para la clase register
hosts: localhost
tasks:
- name: Ejecutar comando uptime
command: uptime
register: salida_uptime
- name: Visualizar variable
debug: var=salida_uptime
Los valores de retorno son: changed, failed, skipped, rc
rc (retorn command) efecto de salida stdout, stderr, stdout_lines, stderr_lines
ejemplo: register: salida_uptime.stdout
...
TASK [Visualizar variable] ******************************************************************************
ok: [localhost] => {
"salida_uptime": {
"changed": true,
"cmd": [
"uptime"
],
"delta": "0:00:00.003743",
"end": "2020-11-24 18:50:34.459941",
"failed": false,
"rc": 0,
"start": "2020-11-24 18:50:34.456198",
"stderr": "",
"stderr_lines": [],
"stdout": " 18:50:34 up 15 days, 10:36, 1 user, load average: 0,17, 0,46, 0,53",
"stdout_lines": [
" 18:50:34 up 15 days, 10:36, 1 user, load average: 0,17, 0,46, 0,53"
]
}
}
En este ejemplo al modificar el index.html
se registra la acción en copiado y si esta es changed
se registra la salida del comando cat para mostrarla por stdout
- name: instalar apache
include: instalar-apache2.yml
- name: Copiar fichero index.html
template: src=index.html.j2 dest=/var/www/html/index.html
register: copiado
- name: Mostrar contenido
command: cat /var/www/html/index.html
register: salida
when: copiado|changed
- debug: var=salida.stdout
when: salida|changed
Ignore errors
Esta opción sirve para que en el caso de que una tarea de error, este se ignore y siga el playbook ejecutandose con la siguiente tarea.
- name: playbook para la clase register
hosts: localhost
tasks:
- name: Visualizar hosts
command: cat /etc/hosts2
ignore_errors: True
Es una buena practica relacionar esta opció con tareas siguientes, es decir si da error haz una tarea o otra.
Por ejemplo, en el siguiente playbook si no existe el fichero hosts2
se ignora el error, ademas lo registramos en una variable que con una condición when
hace una tarea u otra.
- name: playbook para la clase register
hosts: localhost
tasks:
- name: Visualizar hosts
command: cat /etc/hosts2
register: salida
ignore_errors: True
- name: Visualizar variable
debug: var=salida.stdout_lines
when: not salida is failed # salida!=failed
- name: Visualizar variable
debug: var=salida.stderr_lines
when: salida is failed
Failed when
failed_when
nos sirve para probocar un fallo en el caso de no cumplir la condición que indicamos.
Por ejempo en el siguiente caso si no tenemos en localhost la interfaz eth0
el playbook registrara un fallo.
- name: playbook ejemplo failed_when
hosts: localhost
tasks:
- name: Optener interfaces de red
command: ip a
register: salida
failed_when: "'eth0' not in salida.stdout"
También esta su versión inversa con changed_when
, que en este caso daria por buena una tarea según la condición indicada.
- name: playbook ejemplo failed_when
hosts: localhost
tasks:
- name: Optener interfaces de red
command: ip a
register: salida
failed_when: "'inet6' not in salida.stdout"
changed_when: True
Tags
Los tags sirven para especificar selectivamente que tareas ejecutar o omitir.
- name: Ejemplo etiqueas
hosts: localhost
tasks:
- debug: msg="Tarea con tag PROD"
tags: prod
- debug: msg="Tarea con tag DEV"
tags: dev
- debug: msg="Tarea con varios tags"
tags: [qa, prod]
- debug: msg="Esta tare se ejecuta siempre"
tags: always
Si lanzamos el playbook sin el argumento tags se ejecutaran todas las tareas del playbook, en cambio si añadimos el argumento tag seleccionamos cuales vejecutar.
# ejecutar todas las tareas
ansible-playbook etiquetas.yml
# realizar tares con tag prod
ansible-playbook --tags prod etiquetas.yml
# omitir tareas con tag prod
ansible-playbook --skip-tags prod etiquetas.yml
Galaxy
Ansible galaxy es un repositorio de ansible donde puedes descargar roles de otros usuarios o alojar los tuyos propios a traves de github.
sintaxis: ansible-galaxy [accion] [opciones] argumentos
Acciones:
delete
Eliminar rol dentro de la web de galaxy-
import
Importar un rol desde github a galaxy -
info
muestra información detallaa del rol init
iniciar estructura de diretorioinstall
Descargar rollist
Listar rolers instaladoslogin
Auntentifica en la web de galaxyremove
Elimina un rol del servidor localsearch
Busqueda dentro del repositoriosetup
Crea una integracion conTravis CI
Opciones:
-f/--force
sobreescribir rol-i/--ignore-errors
-n/--no-deps
no instalar dependencias-p/--roles-path
directorio de roles-r/--roles-file
ficherpo con lista de roles a instalar
Busqueda
Los roles de galaxy se pueden buscar desde linea de comandos o desde la web de galaxy, es recomendable la web por ser una vista mas agradable.
Una vez escojido el rol deseado lo podemos instalar en nuestro servidor y lo tendremos disponible para las modificaciones necesarias.
# buscar rol
➜ ansible-galaxy search "Nginx"
# inspeccionar e instalar
➜ ansible-galaxy info nginxinc.nginx
➜ ansible-galaxy install nginxinc.nginx
- downloading role 'nginx', owned by nginxinc
- downloading role from https://github.com/nginxinc/ansible-role-nginx/archive/0.18.1.tar.gz
- extracting nginxinc.nginx to /home/debian/.ansible/roles/nginxinc.nginx
- nginxinc.nginx (0.18.1) was installed successfully
# los roles instalados se alojan en:
➜ ls ~/.ansible/roles
nginxinc.nginx
➜ ls ~/.ansible/roles/nginxinc.nginx
CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md defaults files handlers LICENSE meta molecule README.md tasks templates vars
Nuevo rol
Galaxy permite crear una estructura de rol automaticamente para crear nuestros roles.
Recordar añadir en meta: autor, descripcion y modificar el readme
➜ playbooks ansible-galaxy init pruebarol
- pruebarol was created successfully
➜ playbooks tree pruebarol
pruebarol
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
Subir rol a galaxy
Una vez el rol listo podemos subirlo a galaxy para añadirlo al repositorio galaxy, para hacer esto primero tendremos que subirlo a github.
primero subimos nuestro rol a Github
# crear uevpo repo en github y subir rol
git init
Inicializado repositorio Git vacío en /home/debian/Documentos/ansible/playbooks/pruebarol/.git/
git add .
git commit -m "first commit"
[master (commit-raíz) a8bf75f] first commit
9 files changed, 142 insertions(+)
...
git remote add origin git@github.com:Jorgepastorr/pruebarol.git
git push -u origin master
Sincronizar repo de github con galaxy, este paso se puede hacer también desde la web de galaxy.
# logueamos en galaxy y subimos nuestro rol
ansible-galaxy login
ansible-galaxy import jorgepastorr pruebarol
Ya podemos encontrar nuestro rol en el repositorio de galaxy e instalarlo donde queramos
ansible-galaxy search pruebarol
ansible-galaxy install jorgepastorr.pruebarol
- downloading role 'pruebarol', owned by jorgepastorr
- downloading role from https://github.com/Jorgepastorr/pruebarol/archive/master.tar.gz
- extracting jorgepastorr.pruebarol to /home/debian/.ansible/roles/jorgepastorr.pruebarol
- jorgepastorr.pruebarol (master) was installed successfully
ansible-galaxy list
- jorgepastorr.pruebarol, master
- nginxinc.nginx, 0.18.1
ansible-galaxy delete jorgepastorr pruebarol
Vault
El comando ansible-vault
permite cifrar ficheros con una contraseña para proteger datos sensibles.
ansible-vault create fichero.yml # crear fichero encriptado
ansible-vault edit fichero.yml # editar fichero encriptado
ansible-vault encrypt fichero.yml # encriptar un fichero
ansible-vault decrypt fichero.yml # desencriptar fichero
ansible-vault view fichero.yml # ver fichero encriptado
# preguntar contraseña de fichero
ansible-playbook --ask-vault-pass playbook.yml
# coge contraseña de fichero
ansible-playbook --vault-password-file=file playbook.yml
# crear archivo encryptado
➜ ansible-vault create secreto.yml
New Vault password:
Confirm New Vault password:
➜ cat secreto.yml
$ANSIBLE_VAULT;1.1;AES256
65613139323466646538383064626463393435656134353662613730333764313339633237313935
3363653534306566373430386631333432633566393963650a333839633137396632383865373962
37653161366234386463313362623939313062643432626530616438343664303631343861333063
3838356238306265360a323631613637313762663732636339323961366639333633393565373062
30383061643634336266376633383334666363383739363562653133386663656131
# crear archivo normal y luego encryptarlo
➜ vim secreto2.yml
➜ cat secreto2.yml
clave: miSecreto
➜ ansible-vault encrypt secreto2.yml
New Vault password:
Confirm New Vault password:
Encryption successful
➜ cat secreto2.yml
$ANSIBLE_VAULT;1.1;AES256
30306137646336316235336264396364346666343364613363353961653632343536646561663332
3935396661623165663764663738386338353263393137310a643330326331333533323961326231
64333938313130613335633064346639313465666136356132313132353663313136643730306562
6238663564646264350a336566373939316231646333386661626531373962373234363361643637
37633861376132393265363566663236656337623864656666373133393562613631
# desencriptar archivo
➜ ansible-vault decrypt secreto2.yml
Vault password:
Decryption successful
➜ cat secreto2.yml
clave: miSecreto
bien, ahora creo un playbpook muy simple donde la variable clave es data sensible.
yml
- name: Prueba Vault
hosts: localhost
tasks:
- debug: var=clave
```bash
esta es una simple prueba del playbook
ansible-playbook -e "clave=prueba" vault.yml ... TASK [debug] *** ok: [localhost] => { "clave": "prueba" } ...
añado la variable en el archivo encriptado y me pregunta clave para desencriptar
ansible-playbook -e "@secreto.yml" --ask-vault-pass vault.yml Vault password: ... TASK [debug] *** ok: [localhost] => { "clave": "superSecreto" } ...
añado la variable en el archivo encriptado y coje la clave para desencriptar del archivo .pass
➜ ansible vim .pass ➜ ansible ansible-playbook -e "@secreto.yml" --vault-password-file=.pass playbooks/vault.yml ... TASK [debug] *** ok: [localhost] => { "clave": "superSecreto" } ... ```