ansible
Updated: April 30, 2024
Ansible is a configuration tool, that can setup and administer multiple machines on a network.
Note: Ansible only needs to be installed on a controller node.
Table of Contents
- SSH Keys
- Install
- Create User
- Config
- Vault
- Inventory
- ADHOC Commands
- Commands
- Includes
- Galaxy
- Roles
- Playbooks
- Copying Files
- Webserver
- Handlers
- Secrets
- Setup Database
SSH KEYS
For ansible to work ssh access is required for the machines you want to config and admin.
INSTALL
For ansible to work it needs to be installed on a control machine. This is the only machine that will have anisible related software on it. As ansible is used on other machines they will not have any software on them.
Ubuntu
sudo apt install ansible
Mac
brew install ansible
For any others check here
Create User
Create an ansible user for carrying out ansible playbooks. This user helps to distinguish if it was ansible that did something in the logs from all other users – even root.
- user will need to be created on every node and controller.
- needs to be sudoer and use without password on all managed nodes.
- must be able to ssh from control to managed node without a password.
useradd -m ansible # create ansible user with home directory
usermod -s /bin/bash ansible # set users shell
su - ansible # switch to ansible user
Edit visudo and search for with /wheel First set editor for visudo
EDITOR=vim visudo
## Samthing without a password
# %wheel ALL=(ALL) NOPASSWD: ALL
ansible ALL=(ALL) NOPASSWD: ALL
CONFIG
Ansible will install a basic configuration in /etc/ansible but you should copy the config to another location for working directory so that multiple platforms can be managed without effecting the others:
- /etc/ansible.cfg # default
- /tmp/ansible.cfg # as an environment variable
- /home/vagrant/ansible.cfg # for a vagrant project
- /path/to/project/ansible.cfg # alongside a playbook
Configs will override any config location above them in this list.
nocows = 0
forks = 7
inventory = /home/ansible/inventory
remote_user = ansible
remote_port = 2222
private_key_file = ~/.ssh/key-pair.pem
transport = smart #paramiko
VAULT
Ansible Vault is used to keep sensitive data safe.
- ansible-vault will use a password for use
# encrypt data in a file
ansible-vault encrypt private.yml
# to change the data that is already encrypted or view it
ansible-vault edit private.yml
# for use in play book
ansible-playbook playbook.yml --ask-vault-pass
INVENTORY FILES
Also known as hosts files. They work much the same as cfg for precedence.
- Default location is ‘/etc/ansible/hosts’ and can override with -i
- In order to not need to supply a path to inventory, each play can have it’s own host file alongside
- hosts files are formatted .ini
ADHOC Commands
Adhoc commands are not so much setup, as more just poking around checking on things. Testing things to work.
# check that all hosts can be reached
ansible -m ping all
# check hostname of every server
ansible -a 'hostname' all
# find how much disk space
ansible -a 'df -h' all
# check time and date
ansible -a "date" multi
# dry run any achoc or playbook command.
--check
All the commands above will be executed as user. To execute as root use the -e
option. Will work if root has no password. To be able to use a password use -K
# add a user to the servers
ansible -b -K -m user -a 'name=testuser' all
# check if user was created on machine login to one of them
getent passwd | egrep testuser
# here is why ansible is great, lets check them all at once!
ansible -m shell -a 'getent passwd | egrep testuser' all
# remove testuser from the servers
ansible -b -K -m user -a 'name=testuser state=absent' all
COMMANDS
# show all variable of target host
ansible <IP or hostname> -m setup
-a # use adhoc command
-f # tell ansible how many forks to use
-m # use a module
-b # become root
-K # ask for password
--limit # limit an adhoc command to a single server
Includes
Includes are other ansible playbooks that usually are a specific role. They can be used in the same level as the playbook including them or can be from some other place. This will require a bit more of the path if it is somewhere else and not at the playbooks level. They are run in the order they are placed. You can put them at beginning or end of a task or block. Or you can place them at start or end of the file.
tasks:
- include: selinux.yml
- block:
- name: some task
- include: yml on some other level to run last
Galaxy
Community released roles.
ansible-galaxy collection list # lists collections already installed
Roles
- Roles will execute a list of commands in order on a specific machine
- Create a roles folder
- inside roles create a role using ansible-galaxy init
ansible-galaxy init mariadb
Inside the roles folder we need to make 2 directories for a main.yml to live.
cd roles
mkdir -p basic/tasks
cd !:2
Create the required yml
vim main.yml
- name: "Installing VIM" # optional: prints our during process so good to use them
apt: pkg=vim state=present
If installing many items we can list them in short form.
- name: "Installing additional software"
apt: pkg={{ item }} state=present
with_items:
- dnsutils
- git
- vim
- ntp
- at
- tree
- lvm2
PLAYBOOK
Playbooks determine which role should be applied to which target machine. Playbooks should be created in the working directory.
vim playbook.yml
- hosts: all
become: true # become root
roles:
- basic # roles to use
Because we selected to become root when we run the playbook we will use -K again to ask for password.
ansible-playbook -K playbook.yml
Delegate
- used for when we need a task performed on another machine than the one the playbook is running on.
---
- name: shutdown delegage
hosts: worker1
become: yes
become_user: megacron
tasks:
- name: restarting machine...
shell: sleep 2 && shutdown -r now "rastarting to apply changes"
async: 1
poll: 0
ignore_errors: true
- name: waiting for server to come back...
wait_for: host={{inventory_hostname}} state=started delay=30 timeout=300
become: no
delegate_to: 127.0.0.1
- In the above if you are delegating to localhost you can replace wait_for with local_action and delete the delegate_to line.
- We can also delegate facts!
- name: shutdown delegage
hosts: worker1
become: yes
become_user: megacron
tasks:
- name: gathering local facts
setup:
delegate_to: 127.0.0.1
delegate_facts: true
COPYING FILES
- name: "Adding bashrc"
copy: src=../files/bash.bashrc dest=/etc/bash.bashrc owner=root group=root mode=0644
# src is where to find the file on the control machine relative to main.yml
For this to work we have to place a copy of the .bashrc file by making a folder to place the file.
mkdir roles/basic/files
While we did add our bashrc the old file is still there. To remove this safely we use shell with the creates command, the means ansible will check if the file is there but wont execute command if it exists.
- name: "Adding bashrc"
copy: src=../files/bash.bashrc dest=/etc/bash.bashrc owner=root group=root mode=0644
- name: "Removing megacron's bashrc..."
shell: creates=/home/megacron/.bashrc_backup mv /home/megacron/.bashrc /home/megacron/.bashrc_backup
- name: "Removing roots's bashrc..."
shell: creates=/root/.bashrc_backup mv /root/.bashrc /root/.bashrc_backup
MAKING WEBSERVER WITH REVERSE PROXY
mkdir -p roles/webserver/tasks
vim roles/webserver/tasks/main.yml
main.yml
- name: "Installing Webserver Software"
apt: name={{ item }}
with_items:
- apache2
- name: "Enabling Proxy Module"
apache2-module: name=proxy_http
- name: "Adding Proxy Configuration"
template: src=../files/proxy.conf dest=/etc/apache2/conf-available owner=root group=root mode=0644
notify:
- enable-proxy-config # use by handler
We can add configuration files related to the webserver.
mkdir -p roles/webserver/files
vun roles/webserver/files/proxy.conf
proxy.conf
ProxyPass / http://{{hostvars['appserver']['ansible_eth0']['ipv4']['address']}}:8000/
ProxyPassReverse / http://{{hostvars['appserver']['ansible_eth0']['ipv4']['address']}}:8000/
HANDLERS
Handlers are best used when you do not want to restart a service every time but only when a change has been made requiring a reboot of the service. These also should be placed in their corresponding folder.
mkdir roles/webserver/handlers
vim roles/webserver/handlers/main.yml
main.yml
- name: enable-proxy-config # must match name given in notify list
shell: a2enconf proxy.conf
notify:
- restart-apache # handler notifying another handler.
- name: restart-apache
service: name=apache2 state=restarted
Update the playbook so role matches correct machine
- hosts: all
become: true # become root
roles:
- basic # roles to use
- hosts: webserver
become: true
roles:
- webserver
Now we can move to setup the appserver now that the proxy is up.
mkdir -p roles/appserver/{taks,handlers,files}
vim roles/appserver/tasks/main.yml
main.yml
- name: "Installing Required Software"
apt: pkg={{ item }} state=present
with_items:
- gunicorn # small server for small python applications
- supervisor # python management tool to run apps like gunicorn
- python-mysqldb
- python-falcon
- name: "Making sure supervisor is enabled and started..."
service: name=supervisor state=started enabled=yes
# makes sure it is started now, enabled so starts after reboots
- name: "Creating the base folder of the application"
file: path=/opt/testapp state=directory owner=nobody group=nogroup mode=0755
- name: "Copying the application..."
copy: src=../files/testapp.py dest=/opt/testapp/testapp.py owner=nobody group=nogroup mode=0755
notify: # create notify to restart app if any changes are made
- restart-app
- name: "Copying the supervisor config file..."
template: src=../files/testapp.conf dest=/etc/supervisor/conf.d/testapp.conf owner=nobody group=nogroup mode=0644
notify:
- reread-config
- restart-app
- name: "Adding IP of dbserver to /etc/hosts..."
lineinfile: name=/etc/hosts line=" hostvars['dbserver']['ansible_eth0']['ipv4']['address'] }} dbserver"
SECRETS (PASSWORDS)
vim roles/appserver/files/testapp.conf
testapp.conf
[program:testapp]
command=/usr/bin/gunicorn --bind 0.0.0.0:8000 testapp:app
directory=/opt/testapp
user=nobody
autostart=true
autorestart=true
redirect_stderr=true
environment=databasepassword="{{ lookup('password', 'credentials/dbpassword.txt') }}" # by creating this file we can control the passoword, lookup is used with ansible actions and templates.
create the handlers that were mentioned before (notify)
vim roles/appserver/handlers/main.yml
main.yml
- name: reread-config
shell: supervisorctl reread
- name: restart-app
supervisorctl: name=testapp state=restarted
Add role to ansible playbook
- hosts: all
become: true # become root
roles:
- basic # roles to use
- hosts: webserver
become: true
roles:
- webserver
- hosts: appserver
become: true
roles:
- appserver
SETUP DATABASE
Again create standard file structure for ansible
mkdir -p roles/dbserver/{tasks,handlers,files}
vim roles/dbserver/tasks/main.yml
main.yml
- name: "Installing database packages..."
apt: pkg={{ item }} state=present
with_items:
- mysql-server
- python-mysqldb
- name: "Adding database backup cronjob"
cron: hour=10 minute=22 user=root job="/usr/bin/mysqldump --all-databases > /root/dbbackup.dump" name="mySQL Backup"
# keep database from being overwritten by changes
- name: "Adding database..."
mysql_db: name=ansibletutorial state=present
notify:
- import-db
- name: "Copying database dump..."
copy: src=../files/dbdump.sql dest=/var/tmp/dbdump.sql owner=root group=root mode=0600
- name: "Granting Access"
mysql_user: name=ansibletutorial host=% priv=ansibletutorial.*:ALL password={{ lookup('password', 'credentials/dbpassword.txt') }}
- name: "Allowing remote access"
copy: src=../files/n-mysqld-bind.cnf dest=/etc/mysql/mysql.conf.d/n-mysqld-bind.cnf owner=root group=root mode=0644
notify:
- restart-mysql
Now we have to create the files we listed in the tasks.
vim roles/dbserver/files/n-mysqld-bind.cnf
n-mysqld-bind.cnf
[mysqld]
bind-address = 0.0.0.0
cp /var/tmp/dbdump.sql roles/dbserver/files/
vim roles/dbserver/handlers/main.yml # next create the handlers
main.yml
- name: import-db
mysql_db: name=ansibletutorial state=import target=/var/tmp/dbdump.sql
- name: restart-mysql
service: name=mysql state=restarted
Finally add the new role to the playbook
- hosts: all
become: true # become root
roles:
- basic # roles to use
- hosts: webserver
become: true
roles:
- webserver
- hosts: appserver
become: true
roles:
- appserver
- hosts: dbserver
become: true
roles:
- dbserver
WUT I DUN DID #TODO
sudo apt update && sudo apt upgrade -y
sudo apt nfs-common git zsh rpi-eeprom
sudo mkdir -p nfs/euryale
sudo mount -t nfs -O proto=tcp,port=2049,rw,all_squash,anonuid=1001,anongid=1001 192.168.1.87:/volume1/tftp/pi-pxe/euryale /nfs/euryale -vvv
sudo rsync -xa --progress --exclude /nfs / /nfs/euryale
mkdir bootmnt
sudo mount -t nfs -O proto=tcp,port=2049,rw,all_squash,anonuid=1001,anongid=1001 192.168.1.87:/volume1/tftp/bootmnt /nfs/bootmnt -vvv
vcgencmd otp_dump | grep 28: | sed s/.*://g
sudo mkdir -p bootmnt/686f3d0c
sudo cp -r /boot/* /nfs/bootmnt/686f3d0c/
sudo vi /nfs/euryale/etc/fstab
sudo vi /nfs/pi-tftpboot/686f3d0c/cmdline.txt