Commit 4039e57793c8817bcd0a395e614f278abd44099f

Authored by Lars Tiede
0 parents

first commit

.gitignore 0 → 100755
  1 +++ a/.gitignore
  1 +*.pyc
  2 +*.o
  3 +*.so
  4 +*.a
  5 +*~
  6 +*.swp
  7 +*tags
  8 +*DS_Store
  9 +*_pycache__
README.markdown 0 → 100644
  1 +++ a/README.markdown
  1 +Utviklerlunsj: Ansible
  2 +======================
  3 +
  4 +How to install the demo environment
  5 +-----------------------------------
  6 +
  7 +First, install [Vagrant](https://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/). Both installs are easy and fairly quick.
  8 +
  9 +Then, clone this repository somewhere and, in a terminal, cd into the directory you cloned the repo into. There:
  10 +
  11 + $ vagrant plugin install vagrant-hosts
  12 +
  13 +You need to do that step only once. On my mac, it failed at first because it didn't find some Ruby gem, but when I just tried again a few minutes later, it worked. No idea whether that was just bad luck.
  14 +
  15 +Then, to build and start all the demo VMs:
  16 +
  17 + $ vagrant up
  18 +
  19 +That will take a bit of time, certainly the first time you run this. When everything is up and running, you have 3 VirtualBox VMs running on your machine: "control", "target1", and "target2". You can ssh to either of them like this:
  20 +
  21 + $ vagrant ssh NAME
  22 +
  23 +The directory you've been in on the host OS (the one with the Vagrantfile) is mapped into the guest VM at `/vagrant`.
  24 +
  25 +When you're done playing and want to ditch the VMs, do this:
  26 +
  27 + $ vagrant destroy
  28 +
  29 +For anything beyond this, consult the Vagrant [documentation](https://docs.vagrantup.com/v2/).
  30 +
  31 +
  32 +Demo
  33 +----
  34 +
  35 +These are the things I demonstrate in the talk.
  36 +
  37 +
  38 +### 1. Install ansible on the control machine
  39 +
  40 + $ vagrant ssh control
  41 + vagrant@demo-control:~$ sudo apt-get update
  42 + vagrant@demo-control:~$ sudo apt-get install -y ansible
  43 +
  44 +Ubuntu 14.04's Ansible version is a bit outdated, but it's recent enough for us.
  45 +
  46 +
  47 +### 2. Run a few innocent ad-hoc commands ("SSH is all you need")
  48 +
  49 +First, cd to /vagrant, which is the directory containing the cloned repository, mapped into the VM. Then
  50 +
  51 + vagrant@demo-control:/vagrant$ ansible all -i inventory/demo -m ping
  52 +
  53 +You need to add the host keys now. This happens only the first time you connect to the hosts, as usual with SSH.
  54 +
  55 +Run a few other commands to get a feel for what's happening.
  56 +
  57 + vagrant@demo-control:/vagrant$ ansible all -i inventory/demo -m shell -a "free -h"
  58 + vagrant@demo-control:/vagrant$ ansible target1 -i inventory/demo -m shell -a "free -h"
  59 +
  60 +Ignore the -m and -i things for now, we'll get to them later.
  61 +
  62 +Here's one that spews out a lot of info about target1:
  63 +
  64 + vagrant@demo-control:/vagrant$ ansible target1 -i inventory/demo -m setup
  65 +
  66 +Since Ansible s using SSH under the hood and you will very likely require root privieleges on your target systems, it is important to be able to get root privileges in one way or another. If you don't specify anything to that effect, Ansible will just try to log in as "the current user":
  67 +
  68 + vagrant@demo-control:/vagrant$ ansible target1 -i inventory/demo -m shell -a "whoami"
  69 +
  70 +There are two principle ways to become root with Ansible: Either SSH as root to the target (`-u root` will do this), or log in as whichever user and use sudo on the target (`--sudo`). In both cases, you have to be able to do the respective thing without password. There are options you can specify that lets Ansible ask you for a password, but that is really not recommended.
  71 +
  72 +On our Vagrant boxes, passwordless SSH for the root user is not set up, but the 'vagrant' user can sudo without a password. So let's use sudo:
  73 +
  74 + vagrant@demo-control:/vagrant$ ansible target1 -i inventory/demo -m shell -a "whoami" --sudo
  75 +
  76 +
  77 +### 3. Describe the state you want a target to be in, not how to get there
  78 +
  79 +Let's *do* something.
  80 +
  81 +Say we want pip (a Python package manager) on one of the target machines. We tell Ansible that we want the apt package named "python-pip" present:
  82 +
  83 + vagrant@demo-control:/vagrant$ ansible target1 --sudo -i inventory/demo -m apt -a "pkg=python-pip state=present"
  84 +
  85 +Note that we didn't say "install python-pip", but rather "have python-pip installed". It is up to Ansible, more specififcally the 'apt' module, to figure out whether anything has to be done or not. Go on, just do the same thing again. All you get back now is "changed: false". So Ansible only does something if something needs to be done. That's why you specify what state you want in the end, not how to get there. That last bit is an implementation detail you usually don't concern yourself with!
  86 +
  87 +If you want to break the rules, however, nothing stops you from running "apt-get install -y python-pip" with the "shell" module.
  88 +
  89 +
  90 +### 4. On second "concepts" slide: re-usable descriptions of small aspects of the system that you can combine in endless ways
  91 +
  92 +We have a minimal web app in the [webapp](webapp/) directory. We want to deploy this to two app servers (target1 and target2), and put them behind a simple load balancer on target1.
  93 +
  94 +The playbook in [deploy\_and\_run\_web\_app.yml](deploy_and_run_web_app.yml) does all of this.
  95 +
  96 +The playbook contains two 'plays', i.e., sequences of tasks and role invocations that are applied to a set of hosts. In the first play, the playbook sets up the web app instances on all hosts in the 'app\_servers' group (as defined in the [inventory file](inventory/demo)). For this, we have a role '[runs\_web\_app](roles/runs_web_app)'. In it, its sequence of tasks is (per convenion) in [tasks/main.yml](roles/runs_web_app/tasks/main.yml). Files, templates and other role specific things can also be found in the role's directory. For this role, we just have one file in its 'files' subdirectory. Check out the Ansible documentation on [roles](http://docs.ansible.com/playbooks_roles.html#roles) for more details about what you can do with and inside roles.
  97 +
  98 +The second play of the playbook sets up the load balancer on all hosts in the 'load\_balancer' group. In our inventory, that group contains only one host. For running a simple load balancer, we have the role '[runs\_simple\_load\_balancer](roles/runs_simple_load_balancer)'. This role can be configured a bit through variables that are passed into it on invocation: on which hosts and ports does the app run that should be load-balanced. There is a template in the role ([the nginx config file snippet](roles/runs_simple_load_balancer/templates/sites-enabled/webapp.j2)) that uses these variables. Variables can also be used nearly anywhere else outside of templates; think of most of the playbook text content as template-able content. [Nearly everything is passed through the jinja2 templating engine that comes bundled with Ansible](http://docs.ansible.com/playbooks_variables.html#using-variables-about-jinja2).
Vagrantfile 0 → 100644
  1 +++ a/Vagrantfile
  1 +# -*- mode: ruby -*-
  2 +# vi: set ft=ruby :
  3 +
  4 +VAGRANTFILE_API_VERSION = "2"
  5 +
  6 +
  7 +# I don't know anything about Ruby! I am sorry!
  8 +
  9 +
  10 +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  11 +
  12 + config.vm.box = "ubuntu/trusty64"
  13 + config.vm.provision :hosts # plugin: magically fills /etc/hosts
  14 +
  15 + config.vm.define "target2" do |node|
  16 + node.vm.hostname = "demo-target2"
  17 + node.vm.network :private_network, :ip => '10.20.1.4'
  18 + end
  19 +
  20 + config.vm.define "target1" do |node|
  21 + node.vm.hostname = "demo-target1"
  22 + node.vm.network :private_network, :ip => '10.20.1.3'
  23 + end
  24 +
  25 + config.vm.define "control" do |node|
  26 + node.vm.hostname = "demo-control"
  27 + node.vm.network :private_network, :ip => '10.20.1.2'
  28 + node.vm.provision "file", source: "~/.vagrant.d/insecure_private_key",
  29 + destination: "~/.ssh/id_rsa"
  30 + #node.ssh.forward_agent = true
  31 + end
  32 +
  33 +end
deploy_and_run_web_app.yml 0 → 100644
  1 +++ a/deploy_and_run_web_app.yml
  1 +---
  2 +- hosts: app_servers
  3 + roles:
  4 + - role: runs_web_app
  5 +
  6 +- hosts: load_balancer
  7 + roles:
  8 + - role: runs_simple_load_balancer
  9 + app_hosts: "{{ groups.app_servers }}"
  10 + app_port: 8000
  11 + tasks:
  12 + - debug: msg="Point your browser to the load balancer at {{ ansible_eth1.ipv4.address }}"
inventory/demo 0 → 100644
  1 +++ a/inventory/demo
  1 +target1
  2 +target2
  3 +
  4 +[load_balancer]
  5 +target1
  6 +
  7 +[app_servers]
  8 +target1
  9 +target2
presentation.key 0 → 100644
No preview for this file type
roles/runs_simple_load_balancer/tasks/main.yml 0 → 100644
  1 +++ a/roles/runs_simple_load_balancer/tasks/main.yml
  1 +---
  2 +- name: have nginx installed
  3 + sudo: yes
  4 + apt: pkg=nginx
  5 +
  6 +- name: don't have nginx default site enabled
  7 + sudo: yes
  8 + file: path=/etc/nginx/sites-enabled/default state=absent
  9 +
  10 +- name: have config for our web app
  11 + sudo: yes
  12 + template: src=sites-enabled/webapp.j2 dest=/etc/nginx/sites-enabled/webapp.conf
  13 + register: config_file
  14 +
  15 +- name: restart nginx if our config has changed
  16 + sudo: yes
  17 + service: name=nginx state=restarted
  18 + when: config_file.changed
roles/runs_simple_load_balancer/templates/sites-enabled/webapp.j2 0 → 100644
  1 +++ a/roles/runs_simple_load_balancer/templates/sites-enabled/webapp.j2
  1 +# {{ ansible_managed }}
  2 +
  3 +upstream demo_web_app {
  4 + {% for app_host in app_hosts %}
  5 + server {{ app_host }}:{{ app_port }};
  6 + {% endfor %}
  7 +}
  8 +
  9 +server {
  10 + server_name {{ ansible_fqdn }};
  11 + listen 80;
  12 +
  13 + location / {
  14 + proxy_pass http://demo_web_app;
  15 + }
  16 +}
roles/runs_web_app/files/webapp.conf 0 → 100644
  1 +++ a/roles/runs_web_app/files/webapp.conf
  1 +description "minimal demo webapp for utviklerlunsj."
  2 +author "Lars Tiede"
  3 +
  4 +start on runlevel [2345]
  5 +
  6 +stop on runlevel [06]
  7 +
  8 +respawn
  9 +
  10 +exec /bin/su root -c "python /webapp/backend.py >> /var/log/webapp.log"
roles/runs_web_app/tasks/main.yml 0 → 100644
  1 +++ a/roles/runs_web_app/tasks/main.yml
  1 +---
  2 +- name: have /webapp directory
  3 + sudo: yes
  4 + file: dest="/webapp" state=directory
  5 +
  6 +- name: synchronize webapp directory to /webapp on target hosts
  7 + sudo: yes
  8 + synchronize: src="webapp/" dest="/webapp/" owner=no
  9 +
  10 +- name: have python-pip
  11 + sudo: yes
  12 + apt: pkg=python-pip state=present
  13 +
  14 +- name: have pip dependencies
  15 + sudo: yes
  16 + pip: requirements="/webapp/pip-req.txt"
  17 +
  18 +- name: have upstart job description
  19 + sudo: yes
  20 + copy: src=webapp.conf dest=/etc/init/webapp.conf
  21 +
  22 +- name: have webapp running
  23 + sudo: yes
  24 + service: name=webapp state=started
webapp/backend.py 0 → 100644
  1 +++ a/webapp/backend.py
  1 +import socket
  2 +from bottle import get, run
  3 +
  4 +@get('/')
  5 +def main_page() :
  6 + return "Hello from %s\n" % socket.gethostname()
  7 +
  8 +if __name__ == "__main__" :
  9 + run(host='0.0.0.0', port=8000)
webapp/pip-req.txt 0 → 100644
  1 +++ a/webapp/pip-req.txt
  1 +bottle