Important stuff like bathrooms/emergency exits and meeting points for anyone that hasn't visited before.
Stores the state of infrastructure as code.
Ansible vs. other CM tools
Positives:
Negatives:
pip install ansible
sudo apt-get update
sudo apt-get install software-properties-common
sudo apt-add-repository --yes --update ppa:ansible/ansible
sudo apt-get install ansible -y
ansible --version # Should be > 2.9
ssh-keygen -f $HOME/.ssh/id_rsa -t rsa -b 4096 -N ""
ssh-copy-id $REMOTE_HOST
ssh $REMOTE_HOST
ansible all -b -K -m raw -a "apt-get install -y python-simplejson"
Ansible's configuration lives in "/etc/ansible".
sudo chown -R training:training /etc/ansible
In order to change files in this directory, we need to have permissions to do so
ansible --list-hosts all
[loadbalancer]
john-proxy01
[webserver]
john-web01
john-web02
[database]
john-database01
[control]
john-control01
[demo_app:children]
loadbalancer
webserver
database
[control]
john-control01 ansible_connection=local
[loadbalancer]
johns-proxy01 # Is the same as
johns-proxy01.$DOMAIN # Is also the same as
192.168.0.10 # Is also the same as
some_stupid_hostname ansible_host=johns-proxy01
ansible --list-hosts all
ansible --list-hosts "*"
ansible --list-hosts control
ansible --list-hosts john-proxy01
ansible --list-hosts control,webserver
ansible --list-hosts webserver[0]
ansible --list-hosts \!control
ansible all -m ping
ansible all -m command -a "hostname" # Is the same as
ansible all -a "hostname"
ansible all -a "/bin/false"
ansible $HOSTS -m $MODULE -a $MODULE_ARGUMENTS
ansible control -b -K -m copy -a \
"dest=/etc/sudoers.d/sudo_nopasswd mode=0660 \
content='%sudo ALL=(ALL) NOPASSWD: ALL' validate='visudo -cf %s'"
ansible all -b -m ping
ansible control -m copy -a "dest=/tmp/foo content=''" -C
ansible control -m copy -a "dest=/tmp/foo content=''"
ansible control -m copy -a "dest=/tmp/foo content=''"
ansible control -m ping -b
Lists with '-'
Dictionaries are denoted with ':'
---
Fruits:
- Apple
- Banana
- Pear
---
Key: Value
---
foo: "{{ variable }}"
Variables like '"{{ }}"'
mkdir playbooks
vim playbooks/hostname.yml
ansible-playbook playbooks/hostname.yml
---
- hosts: all
tasks:
- name: Print server hostname
command: hostname
ansible all -a "hostname"
ansible-playbook playbooks/hostname.yml --syntax-check
As we write a playbook to provision each application, we'll approach it in 4 steps:
And at each step, we'll approach it in the same way:
Lets take a look at the 'apt' module as an example:
---
- hosts: loadbalancer
tasks:
- name: Install nginx
apt:
name: nginx
state: present
playbooks/loadbalancer.yml
---
- hosts: database
tasks:
- name: Install mysql-server
apt:
name: mysql-server
state: present
playbooks/database.yml
ansible-playbook playbooks/loadbalancer.yml
playbooks/loadbalancer.yml
playbooks/database.yml
ansible-playbook playbooks/loadbalancer.yml
ansible-playbook playbooks/database.yml
---
- hosts: loadbalancer
become: true
tasks:
- name: Install nginx
apt:
name: nginx
state: present
---
- hosts: database
become: true
tasks:
- name: Install mysql-server
apt:
name: mysql-server
state: present
playbooks/webserver.yml
ansible-playbook playbooks/webserver.yml
---
- hosts: webserver
become: true
tasks:
- name: Install apache2
apt:
name: apache2
state: present
---
- hosts: webserver
become: true
tasks:
- name: Install apache2
apt:
name: apache2
state: present
- name: Install libapache2-mod-wsgi
apt:
name: libapache2-mod-wsgi
state: present
- name: Install python-pip
apt:
name: python-pip
state: present
- name: Install python-virtualenv
apt:
name: python-virtualenv
state: present
- name: Install python-mysqldb
apt:
name: python-mysqldb
state: present
---
- hosts: webserver
become: true
tasks:
- name: Install web server packages
apt:
name: "{{ item }}"
state: present
loop:
- apache2
- libapache2-mod-wsgi
- python-pip
- python-virtualenv
- python-mysqldb
- virtualenv
playbooks/loadbalancer.yml
---
- hosts: loadbalancer
become: true
tasks:
- name: Install nginx
apt:
name: nginx
state: present
---
- hosts: loadbalancer
become: true
tasks:
- name: Install nginx
apt:
name: nginx
state: present
- name: Ensure nginx service is started
service:
name: nginx
state: started
enabled: yes
ansible-playbook playbooks/loadbalancer.yml
wget -qO- http://johns-proxy01
playbooks/control.yml
---
- hosts: control
become: true
tasks:
- name: Install curl
apt:
name: curl
state: present
ansible-playbook playbooks/control.yml
curl http://johns-proxy01
Give this a go:
playbooks/webserver.yml
ansible-playbook playbooks/database.yml
ansible-playbook playbooks/webserver.yml
---
- hosts: webserver
become: true
tasks:
- name: Install web server packages
apt:
name: "{{ item }}"
state: present
loop:
- apache2
- libapache2-mod-wsgi
- python-pip
- python-virtualenv
- name: Ensure apache2 service is started
service:
name: apache2
state: started
enabled: yes
---
- hosts: database
become: true
tasks:
- name: Install mysql-server
apt:
name: mysql-server
state: present
- name: Ensure mysql service is started
service:
name: mysql
state: started
enabled: yes
playbooks/database.yml
curl http://johns-web01
curl http://johns-web02
playbooks/webserver.yml
---
- hosts: webserver
become: true
tasks:
- name: Install web server packages
apt:
name: "{{ item }}"
state: present
loop:
- apache2
- libapache2-mod-wsgi
- python-pip
- python-virtualenv
- name: Ensure apache2 service is started
service:
name: apache2
state: started
enabled: yes
- name: Ensure mod-wsgi module is enabled
apache2_module:
name: wsgi
state: present
playbooks/webserver.yml
tasks:
- name: Ensure mod-wsgi module is enabled
apache2_module:
name: wsgi
state: present
handlers:
- name: Restart apache2
service:
name: apache2
state: restarted
tasks:
- name: Ensure mod-wsgi module is enabled
apache2_module:
name: wsgi
state: present
notify: Restart apache2
handlers:
- name: Restart apache2
service:
name: apache2
state: restarted
ansible-playbook playbooks/webserver.yml
mkdir -p demo/app
demo/app/demo.wsgi
activate_this = '/var/www/demo/.venv/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))
import os
os.environ['DATABASE_URI'] = 'mysql://demo:demo@$IP_ADDRESS/demo'
import sys
sys.path.insert(0, '/var/www/demo')
from demo import app as application
demo/demo.conf
<VirtualHost *>
WSGIDaemonProcess demo threads=5
WSGIScriptAlias / /var/www/demo/demo.wsgi
<Directory /var/www/demo>
WSGIProcessGroup demo
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
</VirtualHost>
demo/app/demo.py
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
import os, socket
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URI']
db = SQLAlchemy(app)
hostname = socket.gethostname()
@app.route('/')
def index():
return 'Hello, from sunny %s!\n' % hostname
@app.route('/db')
def dbtest():
try:
db.create_all()
except Exception as e:
return e.message + '\n'
return 'Database Connected from %s!\n' % hostname
if __name__ == '__main__':
app.run()
demo/app/requirements.txt
Flask==0.10.1
Flask-SQLAlchemy==2.0
Change the $IP_ADDRESS
playbooks/webserver.yml
tasks:
- name: Copy demo application source
copy:
src: ../demo/app/
dest: /var/www/demo
mode: 0755
notify: Restart apache2
ansible-playbook playbooks/webserver.yml
tasks:
- name: Copy demo application source
copy:
src: ../demo/app/
dest: /var/www/demo
mode: 0755
notify: Restart apache2
- name: Copy apache2 virtualhost configuration
copy:
src: ../demo/demo.conf
dest: /etc/apache2/sites-available
mode: 0755
notify: Restart apache2
playbooks/webserver.yml
tasks:
- name: Install python-pip dependencies
pip:
requirements: /var/www/demo/requirements.txt
virtualenv: /var/www/demo/.venv
notify: Restart apache2
ansible-playbook playbooks/webserver.yml
playbooks/webserver.yml
tasks:
- name: De-activate default apache site
file:
path: /etc/apache2/sites-enabled/000-default.conf
state: absent
notify: Restart apache2
- name: Activate demo apache site
file:
src: /etc/apache2/sites-available/demo.conf
dest: /etc/apache2/sites-enabled/demo.conf
state: link
notify: Restart apache2
ansible-playbook playbooks/webserver.yml
curl http://johns-web01
curl http://johns-web02
curl http://johns-proxy01
templates/nginx.conf.j2
mkdir templates
vim templates/nginx.conf.j2
upstream demo {
server johns-web01;
server johns-web02;
}
server {
listen 80;
location / {
proxy_pass http://demo;
}
}
upstream demo {
{# Enter a server line for each host in the webservers group in Ansible #}
{% for server in groups.webserver %}
server {{ server }};
{% endfor %}
}
server {
listen 80;
location / {
proxy_pass http://demo;
}
}
playbooks/loadbalancer.yml
---
tasks:
- name: Configure nginx site
template:
src: ../templates/nginx.conf.j2
dest: /etc/nginx/sites-available/demo
mode: 0644
notify: Restart nginx
- name: De-activate default nginx site
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Restart nginx
- name: Activate demo nginx site
file:
src: /etc/nginx/sites-available/demo
dest: /etc/nginx/sites-enabled/demo
state: link
notify: Restart nginx
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
ansible-playbook playbooks/loadbalancer.yml
curl http://johns-proxy01
#Run it a couple times!
---
tasks:
- name: Configure nginx site
template:
src: ../templates/nginx.conf.j2
dest: /etc/nginx/sites-available/demo
mode: 0644
notify: Restart nginx
- name: De-activate default nginx site
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Restart nginx
- name: Activate demo nginx site
file:
src: /etc/nginx/sites-available/demo
dest: /etc/nginx/sites-enabled/demo
state: link
notify: Restart nginx
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
playbooks/database.yml
ansible-playbook playbooks/database.yml
tasks:
- name: Ensure mysql is listening on all addresses
lineinfile:
dest: /etc/mysql/mysql.conf.d/mysqld.cnf
regexp: "^bind-address"
line: "bind-address = 0.0.0.0"
notify: Restart mysql
handlers:
- name: Restart mysql
service:
name: mysql
state: restarted
ssh -t johns-database01 "grep -R bind-address /etc/mysql"
curl http://johns-app01/db
curl http://johns-proxy01/db
playbooks/database.yml
ansible-playbook playbooks/database.yml
---
- hosts: database
become: true
tasks:
- name: Install mysql-server
apt:
name: mysql-server
state: present
update_cache: yes
---
- hosts: database
become: true
tasks:
- name: Install packages
apt:
name: "{{ item }}"
state: present
update_cache: yes
loop:
- mysql-server
- python-mysqldb
Give this a go:
- name: Create demo database
mysql_db:
name: demo
state: present
- name: Create demo user
mysql_user:
name: demo
password: demo
priv: demo.*:ALL
host: '%'
state: present
curl http://johns-proxy01/db
ansible-galaxy init roles/control
ansible-galaxy init roles/nginx
Give this a go:
ansible-galaxy init roles/control
ansible-galaxy init roles/nginx
ansible-galaxy init roles/apache2
ansible-galaxy init roles/mysql
ansible-galaxy init roles/demo_app
roles/control/tasks/main.yml
---
- name: Install curl
apt:
name: curl
state: present
playbooks/control.yml
---
- hosts: control
become: true
tasks:
- name: Install curl
apt:
name: curl
state: present
---
- hosts: control
become: true
roles:
- control
roles/mysql/tasks/main.yml
playbooks/database.yml
---
- hosts: database
become: true
roles:
- mysql
---
- hosts: database
become: true
tasks:
- name: Install packages
...
handlers:
- name: Restart mysql
service:
name: mysql
state: restarted
roles/mysql/handlers/main.yml
---
- name: Restart mysql
service:
name: mysql
state: restarted
---
- name: Install packages
apt:
name: "{{ item }}"
state: present
loop:
- mysql-server
- python-mysqldb
- name: Ensure mysql is listening on all addresses
lineinfile:
dest: /etc/mysql/mysql.conf.d/mysqld.cnf
regexp: "^bind-address"
line: "bind-address = 0.0.0.0"
notify: Restart mysql
- name: Ensure mysql service is started
service:
name: mysql
state: started
enabled: yes
- name: Create demo database
mysql_db:
name: demo
state: present
- name: Create demo user
mysql_user:
name: demo
password: demo
priv: demo.*:ALL
host: '%'
state: present
roles/nginx/tasks/main.yml
---
- name: Install nginx
apt:
name: nginx
state: present
- name: Configure nginx site
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/demo
mode: 0644
notify: Restart nginx
- name: De-activate default nginx site
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Restart nginx
- name: Activate demo nginx site
file:
src: /etc/nginx/sites-available/demo
dest: /etc/nginx/sites-enabled/demo
state: link
notify: Restart nginx
- name: Ensure nginx service is started
service:
name: nginx
state: started
enabled: yes
playbooks/loadbalancer.yml
---
- hosts: loadbalancer
become: true
roles:
- nginx
roles/nginx/handlers/main.yml
---
- name: Restart nginx
service:
name: nginx
state: restarted
Give this a go:
roles/nginx/tasks/main.yml
- name: Configure nginx site
template:
src: ../templates/nginx.conf.j2
dest: /etc/nginx/sites-available/demo
mode: 0644
notify: Restart nginx
- name: Configure nginx site
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/demo
mode: 0644
notify: Restart nginx
mv templates/nginx.conf.j2 roles/nginx/templates
roles/apache2/tasks/main.yml
roles/demo_app/tasks/main.yml
- name: Install web server packages
apt:
name: "{{ item }}"
state: present
loop:
- libapache2-mod-wsgi
- python-pip
- python-virtualenv
- name: Install web server packages
apt:
name: "{{ item }}"
state: present
loop:
- apache2
- name: Ensure apache2 service is started
service:
name: apache2
state: started
enabled: yes
- name: Ensure mod-wsgi module is enabled
apache2_module:
name: wsgi
state: present
notify: Restart apache2
- name: Copy demo application source
copy:
src: demo/app/
dest: /var/www/demo
mode: 0755
notify: Restart apache2
- name: Copy apache2 virtualhost configuration
copy:
src: demo/demo.conf
dest: /etc/apache2/sites-available
mode: 0755
notify: Restart apache2
- name: Install python-pip dependencies
pip:
requirements: /var/www/demo/requirements.txt
virtualenv: /var/www/demo/.venv
notify: Restart apache2
- name: Activate demo apache site
file:
src: /etc/apache2/sites-available/demo.conf
dest: /etc/apache2/sites-enabled/demo.conf
state: link
notify: Restart apache2
- name: De-activate default apache site
file:
path: /etc/apache2/sites-enabled/000-default.conf
state: absent
notify: Restart apache2
playbooks/webserver.yml
---
- hosts: webserver
become: true
roles:
- apache2
- demo_app
roles/apache2/handlers/main.yml
---
- name: Restart apache2
service:
name: apache2
state: restarted
roles/demo_app/handlers/main.yml
---
- name: Restart apache2
service:
name: apache2
state: restarted
mv demo/* roles/demo_app/files
roles/demo_app/tasks/main.yml
- name: Copy demo application source
copy:
src: app/
dest: /var/www/demo
mode: 0755
notify: Restart apache2
- name: Copy apache2 virtualhost configuration
copy:
src: demo.conf
dest: /etc/apache2/sites-available
mode: 0755
notify: Restart apache2
playbooks/site.yml
---
- hosts: control
become: true
roles:
- control
- hosts: database
become: true
roles:
- mysql
- hosts: loadbalancer
become: true
roles:
- nginx
- hosts: webserver
become: true
roles:
- apache2
- demo_app
ansible-playbook playbooks/site.yml
---
- import_playbook: database.yml
- import_playbook: webserver.yml
- import_playbook: loadbalancer.yml
roles/mysql/tasks/main.yml
- name: Ensure mysql is listening on all addresses
lineinfile:
dest: /etc/mysql/my.cnf
regexp: "^bind-address"
line: "bind-address = 0.0.0.0"
notify: Restart mysql
ansible -m setup john-database01
ansible-playbook playbooks/database.yml
- name: Ensure mysql is listening on all addresses
lineinfile:
dest: /etc/mysql/my.cnf
regexp: "^bind-address"
line: "bind-address = {{ ansible_default_ipv4.address }}"
notify: Restart mysql
roles/mysql/tasks/main.yml
- name: Create demo database
mysql_db:
name: demo
state: present
- name: Create demo user
mysql_user:
name: demo
password: demo
priv: demo.*:ALL host='%'
state: present
- name: Create database
mysql_db:
name: demo
state: present
- name: Create user
mysql_user:
name: demo
password: demo
priv: demo.*:ALL host='%'
state: present
- name: Create database
mysql_db:
name: "{{ db_name }}"
state: present
- name: Create user
mysql_user:
name: demo
password: demo
priv: "{{ db_name }}.*:ALL host='%'"
state: present
- name: Create database
mysql_db:
name: "{{ db_name }}"
state: present
- name: Create user
mysql_user:
name: "{{ db_user }}"
password: "{{ db_password }}"
priv: "{{ db_name }}.*:ALL"
host: "{{ db_host }}"
state: present
roles/mysql/defaults/main.yml
---
db_name: mydb
db_user: mydbuser
db_password: mydbpass
db_host: mydbhost
ansible-playbook playbooks/database.yml
group_vars/all/vars.yml
---
db_name: demo
db_user: demo
db_password: demo
db_host: "%"
mkdir -p group_vars/all
vim group_vars/all/vars.yml
group_vars/all/vars.yml
---
db_name: demo
db_user: demo
db_password: demo
db_host: "%"
site_name: demo
Give this a go:
upstream {{ site_name }} {
{% for server in groups.webserver %}
server {{ server }};
{% endfor %}
}
server {
listen 80;
location / {
proxy_pass http://{{ site_name }};
}
}
- name: Configure demo nginx site
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/demo
mode: 0644
notify: Restart nginx
- name: De-activate default nginx site
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Restart nginx
- name: Activate demo nginx site
file:
src: /etc/nginx/sites-available/demo
dest: /etc/nginx/sites-enabled/demo
state: link
notify: Restart nginx
roles/nginx/tasks/main.yml
- name: Configure nginx site
template:
src: nginx.conf.j2
dest: "/etc/nginx/sites-available/{{ site_name }}"
mode: 0644
notify: Restart nginx
- name: De-activate default nginx site
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Restart nginx
- name: Activate nginx site
file:
src: "/etc/nginx/sites-available/{{ site_name }}"
dest: "/etc/nginx/sites-enabled/{{ site_name }}"
state: link
notify: Restart nginx
roles/nginx/templates/nginx.conf.j2
upstream demo {
{% for server in groups.webserver %}
server {{ server }};
{% endfor %}
}
server {
listen 80;
location / {
proxy_pass http://demo;
}
}
ansible-vault create group_vars/all/vault.yml
group_vars/all/vault.yml
...
vault_password_file = ~/.ansible/.vault_password_file
...
~/.ansible_vault_password
really_super_secure_password_here
ansible.cfg
---
vault_db_password: demo
group_vars/all/vars.yml
---
db_name: demo
db_user: demo
db_password: "{{ vault_db_password }}"
db_host: "%"
ansible-playbook playbooks/control.yml -vvvv