Ansible: Good Things Come to Those Who Wait

I have been a windows admin for many years. This tenure has much to do with local job opportunities and less so about a staunch love of one side. As a person who attempts to stay current in both worlds, I am currently dabbling with Configuration Management or Desired State Configuration (MS Speak). Because my place of employment doesn't have a decent sized GNU/Linux deployment I am really only able to go it alone in lab environments, but hey, I still enjoy it!

CM is not new, the Chef and Puppet camps have been around for quite some time. I simply never started the journey with either product, thus I won't comment on them, other than to say it was probably because I was too lazy. :)

Fast forward to today and there are several in the CM space. Salt, Ansible, Puppet, Chef, etc.
I did some basic research and landed on Ansible. There isn't a ton of thought that went into this conclusion, but as a long time windows admin with a disdain for agents... well you get my point. To be fair to myself, that was not the only criteria. I love the design choices made by the Ansible team, Jinja2, YAML, SSH, few dependencies, and to top it off the documentation is first rate.

I am less than a week into this adventure and have found Ansible to be a breeze to configure. Did I mention there are almost zero dependencies on the client and master machines? The first step is to indoctrinate new machines, ie creating a user account for Ansible, fixing sudoers, sshd.conf, and transporting ssh keys

---
- hosts: all
  remote_user: root  
  tasks:
  - name: add user ansi
    user:
      name=ansi
      groups=wheel

  - name: transport ssh keys
    authorized_key:
      user=ansi
      key='{{ lookup('file','~/.ssh/id_rsa.pub') }}'

  - name: set nopasswd for wheel
    lineinfile:
      dest=/etc/sudoers
      regexp='^%wheel'
      line='%wheel ALL=(ALL) NOPASSWD{{':'}} ALL'
      state=present
      validate='visudo -cf %s'
    when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int >= 6

  - name: disable ssh root access
    lineinfile:
      dest=/etc/ssh/sshd_config
      regexp="^#PermitRootLogin"
      line="PermitRootLogin no"
      state=present
    when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int >= 6
    notify:
      - restart sshd

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

I have decided to leave all the upfront work in a single YAML playbook (run as root) and setup a more elegant scheme once the machines are commissioned.

[root@main ~]# su - ansi
[ansi@main ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ansi/.ssh/id_rsa):
Created directory '/home/ansi/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/ansi/.ssh/id_rsa.
Your public key has been saved in /home/ansi/.ssh/id_rsa.pub.
[ansi@main ~]$ ansible-playbook indoctrinate.yml --ask-pass
SSH password:

PLAY [all] ********************************************************************

GATHERING FACTS ***************************************************************
ok: [server4.tui.lan]
ok: [server3.tui.lan]
ok: [server2.tui.lan]
ok: [server1.tui.lan]

TASK: [Add user ansi] *********************************************************
changed: [server4.tui.lan]
changed: [server2.tui.lan]
changed: [server1.tui.lan]
changed: [server3.tui.lan]

TASK: [transport ssh keys] *************************************
changed: [server1.tui.lan]
changed: [server4.tui.lan]
changed: [server3.tui.lan]
changed: [server2.tui.lan]

TASK: [set nopasswd for wheel] ************************************************
changed: [server3.tui.lan]
changed: [server2.tui.lan]
changed: [server4.tui.lan]
changed: [server1.tui.lan]

TASK: [disable ssh root access] ****************************************************
changed: [server2.tui.lan]
changed: [server3.tui.lan]
changed: [server1.tui.lan]
changed: [server4.tui.lan]

NOTIFIED: [restart sshd] ******************************************************
changed: [server4.tui.lan]
changed: [server2.tui.lan]
changed: [server1.tui.lan]
changed: [server3.tui.lan]

PLAY RECAP ********************************************************************
server1.tui.lan            : ok=6    changed=5    unreachable=0    failed=0
server2.tui.lan            : ok=6    changed=5    unreachable=0    failed=0
server3.tui.lan            : ok=6    changed=5    unreachable=0    failed=0
server4.tui.lan            : ok=6    changed=5    unreachable=0    failed=0

Once the machines are ready to rock, I retrieve keys to the known_hosts file on the master node.

[ansi@main ~]$ ssh-keyscan server{1..4}.tui.lan >> ~/.ssh/known_hosts

The so called indoctrinating is done. I will now create a role that can be assigned to a group of servers. There is a nice tool to generate the appropriate folder structure for a role. The tool is called ansible-galaxy, which additionally can also be used to deploy community YAML playbooks. Cool! For simplicity sake I am strictly using it to create the role based directory structure.

[ansi@main roles]$ ansible-galaxy init ntp
ntp was created successfully
[ansi@main roles]$ ls -R ntp
ntp:
defaults  files  handlers  meta  README.md  tasks  templates  vars

ntp/defaults:
main.yml

ntp/files:

ntp/handlers:
main.yml

ntp/meta:
main.yml

ntp/tasks:
main.yml

ntp/templates:

ntp/vars:
main.yml

Now that the structure is created we can populate the skeleton with relevant YAML. For example ntp/vars/main.yml

---
# vars file for ntp
ntp_servers:
  - 0.pool.ntp.org
  - 1.pool.ntp.org
  - 2.pool.ntp.org
  - 3.pool.ntp.org

Create some tasks to do actual work. Example ntp/tasks/main.yml

---
# tasks file for ntp
- name: install ntp
  sudo: true
  yum: 
     pkg=ntp 
     state=installed
  when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int >= 6

- name: configure ntp
  sudo: true
  template: 
     src=ntp.conf.j2 
     dest=/etc/ntp.conf
  notify:
    - restart ntpd
  when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int >= 6

- name: start and enable ntpd service
  sudo: true
  service: 
     name=ntpd 
     state=running 
     enabled=yes
  when: ansible_os_family == "RedHat" and ansible_distribution_major_version|int >= 6

Now I simply need to create a playbook that includes the ntp role.

---
# file: servers.yml
- hosts: servers
  roles:
    - ntp

The following command will deploy and enable ntpd on all my servers!

[ansi@main playbooks]$ ansible-playbook servers.yml

PLAY [all] ********************************************************************

GATHERING FACTS ***************************************************************
ok: [server1.tui.lan]
ok: [server3.tui.lan]
ok: [server2.tui.lan]
ok: [server4.tui.lan]

TASK: [ntp | be sure ntp is installed] ****************************************
ok: [server1.tui.lan]
ok: [server2.tui.lan]
ok: [server3.tui.lan]
ok: [server4.tui.lan]

TASK: [ntp | be sure ntp is configured] ***************************************
ok: [server1.tui.lan]
ok: [server2.tui.lan]
changed: [server3.tui.lan]
changed: [server4.tui.lan]

TASK: [ntp | be sure ntpd is running and enabled] *****************************
ok: [server2.tui.lan]
ok: [server4.tui.lan]
ok: [server3.tui.lan]
ok: [server1.tui.lan]

NOTIFIED: [ntp | restart ntpd] ************************************************
changed: [server3.tui.lan]
changed: [server4.tui.lan]

PLAY RECAP ********************************************************************
server1.tui.lan            : ok=4    changed=0    unreachable=0    failed=0
server2.tui.lan            : ok=4    changed=0    unreachable=0    failed=0
server3.tui.lan            : ok=5    changed=2    unreachable=0    failed=0
server4.tui.lan            : ok=5    changed=2    unreachable=0    failed=0

Comments

Popular posts from this blog

Cisco VRF-Lite Guest Network and OpenDNS

Work Folders, Folder Redirection, Symbolic Links, Oh My!