Apr 14, 2015 Ansible admin

Rough draft notes and cheatsheet on Ansible. I’ve been using Ansible, a devop automation/configuration management tool, to handle the provisioning of the backend servers. It is a good alternative to Chef-Zero (Chef-Solo), and Ansible seems to be a lot easier to work with.

References

Simple usage

Test using ping

ansible <Server_Group_Name> -i <host_file> -m ping

ansible testserver -i my_hosts.txt -m ping

# for more detail (verbose)
ansible testserver -i hosts -m ping -vvvv

my_hosts.txt (Ansible - Up and Running)

testserver ansible_ssh_host=127.0.0.1 ansible_ssh_port=2222 \
ansible_ssh_user=vagrant \
ansible_ssh_private_key_file=.vagrant/machines/default/virtualbox/private_key

All this is not needed. Instead, use ansible.cfg.

my_hosts.txt becomes

testserver ansible_ssh_host=127.0.0.1 ansible_ssh_port=2222 

and

ansible testserver -m ping  # no need for `-i host_file`

longer command. -m is not needed, since it is default -a: argument. By default (or -m), it is the actual command to run

ansible testserver -a "tail /var/log/dmesg"

sudo command (use -s)

ansible testserver -s -a "tail /var/log/syslog"

ansible testserver -s -m apt -a name=nginx

see ad-hoc commands below

view current state

ansible all -m setup

Playbooks

Run scripts and is recommended way of running plays

ansible-playbook <MY_PLAYBOOK>.yml 

ansible-playbook <MY_PLAYBOOK>.yml -f 10

ansible-playbook <MY_PLAYBOOK>.yml  --verbose

# hosts in this file instead of 
ansible-playbook -i <hosts> <MY_PLAYBOOK>.yml

Before running, see what hosts will run

ansible-playbook playbook.yml --list-hosts

Dry run

ansible-playbook foo.yml --check

Run scripts from client/nodes via pull

ansible-pull ....

Example (simple ping)

---
- hosts: vagrant
  tasks:
    - name: test connection
      ping:

Example from doc

---
- hosts: webservers
  remote_user: root

  vars:
      http_port: 80
      max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
      yum: name=httpd state=latest
  - name: write the apache config file
      template: src=/srv/httpd.j2 dest=/etc/httpd.conf
      notify:
      - restart apache
  - name: ensure apache is running (and enable it at boot)
      service: name=httpd state=started enabled=yes
  handlers:
      - name: restart apache
      service: name=httpd state=restarted

Example

---
- hosts: all
  tasks:
    - name: ensure ntpd is at the latest version
      yum: pkg=ntp state=latest
      notify:
      - restart ntpd
  handlers:
    - name: restart ntpd
      service: name=ntpd state=restarted

Usage

  1. hosts
  2. tasks
  3. handlers

hosts

- hosts: <names of machines that it should run on>
- hosts: all  # 'special var'?, runs on all machines

Multiple hosts can be defined in a single yml

- hosts: webservers
  ...
  ...
- hosts: databases
  ...
  ...

remote_user: <username to run as>
remote_user: root

In vagrant, use “vagrant” as the user and set sudo: true

remote_user: vagrant
sudo: true

tasks

### service (run something)

tasks:
- name: make sure apache is running
    service: name=httpd state=running

- name: Ensure nginx is running
    service: >
    name=nginx
    state=started
    enabled=yes   

enabled=yes : allow service to start when system starts/reboots

command

more secure than shell, but cannot use <,>, |, & operation

tasks:
- name: disable selinux
    command: /sbin/setenforce 0

shell

run command in shell context (sim to command)

tasks:
- name: run this command and ignore the result
    shell: /usr/bin/somecommand || /bin/true

Or

tasks:
- name: run this command and ignore the result
    shell: /usr/bin/somecommand
    ignore_errors: True

break into multiple line

tasks:
- name: Copy ansible inventory file to client
    copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
            owner=root group=root mode=0644

Variables in template

tasks:
- name: create a virtual host file for 
    template: src=somefile.j2 dest=/etc/httpd/conf.d/

apt (debian/ubuntu) Ansible doc

Add apt repository

- name: Ensure the passenger apt repository is added
    apt_repository: >
    state=present
    repo='deb https://oss-binaries.phusionpassenger.com/apt/passenger raring main'

apt example

# Install the package "foo"
- apt: name=foo state=present

Install multiple packages

- name: Install list of packages
  apt: pkg= state=present update_cache=yes cache_valid_time=3600
  with_items:
        - package1
        - package2
        - package3

debug

# Example that prints the loopback address and gateway for each host
- debug: msg="System  has uuid "

- debug: var=myVar

Example (from Ansible - Up and Running):

hosts: server1
tasks:
    - name: capture output of id command
      command: id -un
      register: login
    - debug: var=login  

using sed

lineinfile

Example (from doc)

- lineinfile: dest=/etc/selinux/config regexp=^SELINUX= line=SELINUX=enforcing
- lineinfile: dest=/etc/sudoers state=absent regexp="^%wheel"
- lineinfile: dest=/etc/hosts regexp='^127\.0\.0\.1' line='127.0.0.1 localhost' owner=root group=root mode=0644
- lineinfile: dest=/etc/httpd/conf/httpd.conf regexp="^Listen " insertafter="^#Listen " line="Listen 8080"
- lineinfile: dest=/etc/services regexp="^# port for http" insertbefore="^www.*80/tcp" line="# port for http by default"

# Add a line to a file if it does not exist, without passing regexp
- lineinfile: dest=/tmp/testfile line="192.168.1.99 foo.lab.net foo"

# Fully quoted because of the ': ' on the line. See the Gotchas in the YAML docs.
- lineinfile: "dest=/etc/sudoers state=present regexp='^%wheel' line='%wheel ALL=(ALL) NOPASSWD: ALL'"

- lineinfile: dest=/opt/jboss-as/bin/standalone.conf regexp='^(.*)Xms(\d+)m(.*)$' line='\1Xms${xms}m\3' backrefs=yes

# Validate the sudoers file before saving
- lineinfile: dest=/etc/sudoers state=present regexp='^%ADMIN ALL\=' line='%ADMIN ALL=(ALL) NOPASSWD:ALL' validate='visudo -cf %s'

pip

# Install specified python requirements in indicated (virtualenv).
- pip: requirements=/my_app/requirements.txt virtualenv=/my_app/venv

# Install (Bottle) python package.
- pip: name=bottle

django

Django: it is a core module

add admin user with password

# Create an initial superuser.
- django_manage: command="createsuperuser --noinput --username=admin --email=admin@example.com" app_path=

However, adding password requires user interaction. Workaround - based on tip, I created it to match the one in the comment. It works!

- hosts: all
vars_files:
    - secret.yml
tasks:
    - name: Django add admin user and password
    shell: /bin/echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('', '', '') " | /usr/bin/python /vagrant/django/manage.py shell	

handlers

Example:

... 
- name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf
    notify:
    - restart apache
...

handlers:
    - name: restart apache
    service: name=apache state=restarted

Variables

Set variable

foo:
    field1: one
    field2: two

Read variable

foo['field1']  #recommended, since it doesn't interfere with python attrib
foo.field1    #warning: could interfere with existing python attributes. 
                # OReilly recommends this instead

{{ ansible_eth0["ipv4"]["address"] }} is same as {{ ansible_eth0.ipv4.address }}

{{ foo[0] }} Access the first element of an array:

Variables can be defined in playbooks

- hosts: webservers
  vars:
    http_port: 80
    username: john

Make sure to quote inside YAML:

app_path: "{{ base_path }}/app"

In Jinja2 templates, all variables are accessible:

Hi {{username}}! Welcome!

To see all currently available vars (especially the built-in vars)

ansible hostname -m setup

Registered Var: save output of a task to these variables

# doc
tasks:

 - shell: /usr/bin/foo
   register: foo_result   # save to foo_result
   ignore_errors: True

 - shell: /usr/bin/bar
   when: foo_result.rc == 5

Example (Ex 4.4 Using the output of a command in a task Ansible - Up and Running)

- name: capture output of id command
  command: id -un
  register: login
- debug: msg="Logged in as user {{ login.stdout }}"

Example (Ex 4-5 Ignoring when a module returns an error Ansible - Up and Running)

- name: Run myprog
  command: /opt/myprog
  register: result
  ignore_errors: True
- debug: var=result

Magic Variables (see doc)

Access var from another node

hostvars['HOSTNAME']['VAR_NAME']

Enumerate within certain groups

# from doc.  enumerate in app_servers and get all IP address
{% for host in groups['app_servers'] %}
    {{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}

Load external var file * useful for loading configuration, secrets, etc

- hosts: all
  remote_user: root
  vars:
    favcolor: blue
  vars_files:
    - /vars/secret.yml
  ....

# in secret.yml
---
somevar: somevalue
password: magic

Pass var on command line --extra-vars or -e

ansible-playbook something.yml -e myName=Dan

ansible-playbook something.yml -e 'myTextVar="hello world"'

ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo"

# in JSON
--extra-vars '{"pacman":"mrs","ghosts":["inky","pinky","clyde","sue"]}'

# in JSON file
--extra-vars "@some_file.json"

Access shell /env var * use “lookup” or “ansible_env.VAR”

---
# ...
vars:
    local_home: "{{ lookup('env','HOME') }}"
    local_home: " {{ansible_env.SOME_VARIABLE}} "

Built-in variables

(See Chapter 4 from Ansible - Up and Running)

Loops and iterations

Loops/iterations use item variable.

<module>: ... {{ item }}
...
with_items: 
    -item1
    -item2

Other variables

gather_facts: True

Playbook Galaxy

Ansible doc - Galaxy

Install 3rd party roles

ansible-galaxy install username.rolename

On Mac, error message “error: you do not have permission to modify files in /etc/…” Solution

ansible-galaxy install -p <ROLES_PATH>

Or

$ vim ansible.cfg
roles_path = /path/to/all/roles:/second/path/to/roles:/third/path/roles

Roles are first searched in playbook dir.

http://cmify.com - more ansible playbooks.

Concise ansible playbooks (https://github.com/MWGriffin/ansible-playbooks)

Mac ansible playbooks installs homebrew, handbrake, nvalt, pckeyboardhack, skype sublime, vlc, virtualbox, .osx dotfile

requirements.yml

Other commands

ansible-galaxy list

ansible-galaxy remove username.rolename

Playbook example detail

postgresql example

##
# Example Ansible playbook that uses the PostgreSQL module.
#
# This installs PostgreSQL on an Ubuntu system, creates a database called
# "myapp" and a user called "django" with password "mysupersecretpassword"
# with access to the "myapp" database.
#
---
- hosts: webservers
sudo: yes
gather_facts: no

tasks:
- name: ensure apt cache is up to date
    apt: update_cache=yes
- name: ensure packages are installed
    apt: name=
    with_items:
        - postgresql
        - libpq-dev
        - python-psycopg2

- hosts: webservers
sudo: yes
sudo_user: postgres
gather_facts: no

vars:
    dbname: myapp
    dbuser: django
    dbpassword: mysupersecretpassword

tasks:
- name: ensure database is created
    postgresql_db: name=

- name: ensure user has access to database
    postgresql_user: db= name= password= priv=ALL

- name: ensure user does not have unnecessary privilege
    postgresql_user: name= role_attr_flags=NOSUPERUSER,NOCREATEDB

Install

Control machine

Control machine is where Ansible is installed, opposite of remote.

pip install ansible     # generic, all system, Mac OSX, latest v1.9

sudo apt-get install ansible   #debian/ubuntu (may require repository)
# Debian jessie at v1.7.2, latest version v2.0.0,

Mac OSX

pip install ansible

If it shows error, use this:

sudo CFLAGS=-Qunused-arguments CPPFLAGS=-Qunused-arguments pip install ansible

Or Brew - currently at older version1.8

brew install ansible 

While it runs, OSX limits to 15 forks. May need to raise this limit:

sudo launchctl limit maxfiles 1024 2048

Config

Config is read in the following order:

  1. ANSIBLE_CONFIG (an environment variable)
  2. ansible.cfg (file in the current directory, recommended for common)
  3. ~/.ansible.cfg (file in the home directory)
  4. /etc/ansible/ansible.cfg (global setting file)

Latest default configuration file (Ansible Github)

forks: probably the most important config value

forks=5  # should be changed to highest value possible. 
# see Mac OSX install section 

Example for usage in Vagrant:

[defaults]
hostfile = hosts
remote_user = vagrant
private_key_file = .vagrant/machines/default/virtualbox/private_key
host_key_checking = False

SSH forwarding

ad-hoc commands

non-shell command

ansible <group> -a "cmd" -f 10
# -m "aka command" is not needed, as it is default, if -m is not set
# -f 10 = fork 10 process.  Default=5. Higher the better, parallel 
# -a: arguments

by default, username is same as local system. To change,

ansible ...... -u username

ansible .... -u vagrant

sudo

ansible <group> -a "/sbin/fsck" -u username --sudo [--ask-sudo-pass]

shell-based command

ansible <group> -m shell -a 'echo $TERM'

File op

scp

ansible <group> -m copy -a "src=~/scripts dest=/tmp/scripts"

change permission/owner

ansible <group> -m file -a "dest=/tmp/scripts mode=600"
ansible <group> -m file -a "dest=/tmp/scripts mode=600 owner=foo group=print"

mkdir

ansible <group> -m file -a "dest=/tmp/newdir mode=755 state=directory"

rm -r

ansible <group> -m file -a "dest=/tmp/todelete state=absent"

package (apt/yum)

install

ansible <group> -m apt -a "name=htop state=present"

uninstall

ansible <group> -m apt -a "name=htop state=absent"

user admin

$ ansible all -m user -a “name=foo password="

$ ansible all -m user -a “name=foo state=absent”

git

ansible webservers -m git -a "repo=git://foo.example.org/repo.git dest=/srv/myapp version=HEAD"

service

ansible webservers -m service -a "name=httpd state=started"

ansible webservers -m service -a "name=httpd state=restarted"

ansible webservers -m service -a "name=httpd state=stopped"

Misc

Nginx

There’s no built-in module for Nginx.

Example of Nginx, but does not set config

- name: NGINX | Adding NGINX signing key
  apt_key: url=http://nginx.org/keys/nginx_signing.key state=present
      
- name: NGINX | Adding sources.list deb url for NGINX
  lineinfile: dest=/etc/apt/sources.list line="deb http://nginx.org/packages/mainline/debian/ jessie nginx"
  lineinfile: dest=/etc/apt/sources.list line="deb-src http://nginx.org/packages/mainline/debian/ jessie nginx"

- name: update NGINX apt cache
  apt: update_cache=yes

- name: NGINX | Installing NGINX
  apt: pkg=nginx state=latest

- name: NGINX | Starting NGINX
  service: name=nginx state=started

Nginx playbooks

geerlingguy nginx

ansible-galaxy install geerlingguy.nginx

Debops nginx

https://galaxy.ansible.com/detail#/role/466

ansible-galaxy install jdauphant.nginx

ANXS Nginx

Other Playbooks

RVM - official ———–

$ ansible-galaxy install rvm_io.rvm1-ruby

Example .yml - Ruby V2.2.1

---

- name: Configure servers with ruby support
  hosts: all
  vars:
    rvm1_rubies:
        - 'ruby-2.2.1'

  roles:
    - { role: rvm_io.rvm1-ruby, tags: ruby, sudo: True }

Gem

Bundler is an Extra Module

Examples from doc

# Installs version 1.0 of vagrant.
- gem: name=vagrant version=1.0 state=present

# Installs latest available version of rake.
- gem: name=rake state=latest

# Installs rake version 1.0 from a local gem on disk.
- gem: name=rake gem_source=/path/to/gems/rake-1.0.gem state=present

Bundler

Bundler is an Extra Module

Examples from doc

# Installs gems from a Gemfile in the current directory
- bundler: state=present executable=~/.rvm/gems/2.1.5/bin/bundle

# Excludes the production group from installing
- bundler: state=present exclude_groups=production

# Only install gems from the default and production groups
- bundler: state=present deployment=yes

# Installs gems using a Gemfile in another directory
- bundler: state=present gemfile=../rails_project/Gemfile

# Updates Gemfile in another directory
- bundler: state=latest chdir=~/rails_project