An Introduction to Server

Configuration

and

Provisioning

with Ansible

Daniel Oliver

The problems

  • Server setup documentation is wrong.

  • Environment configuration documentation is wrong.

  • We have a million servers in each environment.

  • Wait, whose server is this?!?!

Server setup documentation

How to create the server from scratch including all installed programs, security settings, configuration, etc.


For the most part, we do this.

But then we ask, is every server documented?

Environment configuration documentation

How the server should be setup for each environment: development, test, user-acceptance-testing, production, etc.


But then we ask, is every environment matching this documentation?

A lot of Servers

Each environment has a lot of servers to setup and maintain.


But then we ask, how do we setup each of the servers?

Tribal Knowledge

Tribal knowledge is any unwritten knowledge and is passed on by word of mouth & experience.


This could be a problem if you lose your vital employees.

How do we solve the age-old problem of managing servers efficiently, consistently, and repeatably?

Infrastructure AS Code

Because not everything is a container yet.

And even containers have to run on a server.

Benefits of Infrastructure as Code

  • Automation. We know how to automate deployments

  • Quality. We can code review infrastructure changes.

  • Documentation as Code.

  • Source control all code in Git/TFS/Mercurial.

  • Great for audits and compliance restrictions.

  • DevOps is cool.

Tool Options

  • Chef

  • Puppet

  • Ansible

  • Vagrant

  • and many others!

Ansible

For when we don't want 5000 line shell scripts.

All code samples is a demo.


Everything was tested with the following 3 VMs

PS C:\Users\Daniel> az network public-ip list --query '[].{IPAddress: ipAddress, FQDN: dnsSettings.fqdn, Name: name}' --output table
IPAddress      FQDN                                  Name
-------------  ------------------------------------  ------------------
13.90.245.94   ansiblevm1.eastus.cloudapp.azure.com  ansiblevm1PublicIP
52.168.111.32  ansiblevm2.eastus.cloudapp.azure.com  ansiblevm2PublicIP
13.90.251.252  ansiblevm3.eastus.cloudapp.azure.com  ansiblevm3PublicIP
PS C:\Users\Daniel>

Note: these VMs shouldn't be publicly accessible.

Ad-Hoc Command

ansible all -m ping

$ ansible all -m ping
ansiblevm1 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
ansiblevm2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
ansiblevm3 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Ansible provides wrappers to common functionality in the form of modules.

Ad-Hoc Command Explained - Part 1.

$ ansible all -m ping

Ansible configuration includes an "inventory" of the servers.

ansiblevm1   ansible_ssh_user=daniel   ansible_host=ansiblevm1.eastus.cloudapp.azure.com
ansiblevm2   ansible_ssh_user=daniel   ansible_host=ansiblevm2.eastus.cloudapp.azure.com
ansiblevm3   ansible_ssh_user=daniel   ansible_host=ansiblevm3.eastus.cloudapp.azure.com

Inventories can be dynamic too.

Ad-Hoc Command Explained - Part 2.

$ ansible all -m ping

Ansible connects using SSH, SCP, and SFTP.

An example from the above looks like this.

ssh -i /mnt/c/Users/Daniel/.ssh/id_rsa -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no \
    -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no \
    -o User=daniel -o ConnectTimeout=10 ansiblevm3.eastus.cloudapp.azure.com '/bin/sh -c '"'"'echo ~ && sleep 0'"'"''

Simplified

ssh -i /mnt/c/Users/Daniel/.ssh/id_rsa daniel@ansiblevm1.eastus.cloudapp.azure.com

Ad-Hoc Command Explained - Part 3.

$ ansible all -m ping

Ansible configuration is simple to start with.

[ssh_connection]
ssh_args = -i "/mnt/c/Users/Daniel/.ssh/id_rsa"

[defaults]
inventory = hosts
host_key_checking = False

Note: "host_key_checking = False" may not be a great idea.

Ad-Hoc Command Explained - Part 4.

$ ansible all -m ping
  • "ansible" = command
  • "all" = servers in inventory to run command against
  • "-m" = Ansible module flag
  • "ping" = Ansible module
ansiblevm1 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

When each command runs, it's going to tell you if it changed anything on the server.

Idempotent

Infrastructure as Code declares the state we want to achieve.
Idempotent actions allow us to run the code many times with no harm.

$ ansible all -s -m user -a "name=johndoe state=absent"
ansiblevm1 | SUCCESS => {
    "changed": true,
    "force": false,
    "name": "johndoe",
    "remove": false,
    "state": "absent",
    "stderr": "sent invalidate(passwd) request, exiting\nsent invalidate(group) request, exiting\nsent invalidate(passwd) request, exiting\nsent invalidate(group) request, exiting\n",
    "stderr_lines": [
        "sent invalidate(passwd) request, exiting",
        "sent invalidate(group) request, exiting",
        "sent invalidate(passwd) request, exiting",
        "sent invalidate(group) request, exiting"
    ]
}
ansiblevm3 | SUCCESS => {
    "changed": false,
    "name": "johndoe",
    "state": "absent"
}
...

Reason for Idempotent

Idempotent commands are as close to declaring the state of the server as we can get.

Servers are rather like databases, being transformed in-place.

CREATE OR ALTER PROCEDURE

Playbook

Ansible can combine commands into a playbook.

---
- hosts: all  # The servers from the inventory.
  tasks: 
    # Idempotent Task.
    - name: Make sure that apt-get dotnet-sdk-2.1.4 is present.
      apt:
        name: dotnet-sdk-2.1.4
        state: present

    # Non-Idempotent Task.
    - name: Runs apt-get update, if last run more than one hour ago.
      apt: 
        update_cache: yes
        cache_valid_time: 3600

Notice that not all commands are Idempotent.

Example Ansible Playbook Run

$ ansible-playbook -s dotnet-playbook.yml
PLAY [all] *************************************************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************************************
ok: [ansiblevm1]

TASK [Is Microsoft.gpg there?] *****************************************************************************************************************************************************
ok: [ansiblevm1]

TASK [Download Microsoft GPG] ******************************************************************************************************************************************************
skipping: [ansiblevm1]

TASK [Microsoft Packages is in source list.] ***************************************************************************************************************************************
ok: [ansiblevm1]

TASK [Runs apt-get update, if last run more than one hour ago.] ********************************************************************************************************************
ok: [ansiblevm1]

TASK [dotnet-sdk-2.1.4 is present.] ************************************************************************************************************************************************
ok: [ansiblevm1]

PLAY RECAP *************************************************************************************************************************************************************************
ansiblevm1                 : ok=5    changed=0    unreachable=0    failed=0

.NET Core Ubuntu Install playbook

---
- hosts: ansiblevm1
  tasks:
  - name: Is Microsoft.gpg there?
    stat: 
      path: /etc/apt/trusted.gpg.d/microsoft.gpg
    register: microsoft_gpg
  - name: Download Microsoft GPG
    command: bash -c "curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /etc/apt/trusted.gpg.d/microsoft.gpg"
    when: microsoft_gpg.stat.exists == False        
  - name: Microsoft Packages is in source list.
    lineinfile:
      path: /etc/apt/sources.list.d/dotnetdev.list
      line: "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main"
      state: present
      create: yes
  - name: Runs apt-get update, if last run more than one hour ago.
    apt: 
      update_cache: yes
      cache_valid_time: 3600
  - name: dotnet-sdk-2.1.4 is present.
    apt:
      name: dotnet-sdk-2.1.4
      state: present

Infrastructure As declarative Code

Ansible code can be declarative, but it can also be imperative when needed.

And there is an Ansible Module for almost anything you want.

Server Provisioning

Server setup documentation is how to create the server from scratch including all installed programs, security settings, configuration, etc.

So far, I've shown how to install programs with Ansible.
Checking dotnet version with ad-hoc commands shows this:

$ ansible all -m shell -a "dotnet --version"
ansiblevm1 | SUCCESS | rc=0 >>
2.1.4

ansiblevm3 | SUCCESS | rc=0 >>
2.1.4

ansiblevm2 | SUCCESS | rc=0 >>
2.1.4

Ad-hoc commands are useful for gathering information on existing servers.

Server Configuration

Environment configuration documentation is how the server should be setup for each environment:
development, test, user-acceptance-testing, production, etc.

Variables

Variables can come from:

  • Inventory
  • Playbooks
  • Included Files
  • Discovered system facts, such as hostname.

System Variables are implicit. User-defined variables must be explicitly defined.

Variables in template commands

Putting a line in a file with a template

$ ansible all -m lineinfile -a "path=~/test.txt state=present create=true line=\"{{ inventory_hostname }} is the 
name of this server in the inventory file\""
ansiblevm3 | SUCCESS => {
    "backup": "",
    "changed": true,
    "msg": "line added"
}
...

Reading the line from that file

$ ansible all -m shell -a "cat ~/test.txt"
ansiblevm1 | SUCCESS | rc=0 >>
ansiblevm1 is the name of this server in the inventory file

ansiblevm2 | SUCCESS | rc=0 >>
ansiblevm2 is the name of this server in the inventory file
...

Setting Variables

Variables such as a database connection string may be placed with these modules and more:

  • LineInFile - Ensures a line matching a regex pattern exists in a given file.
  • Template - Templates a file, and places it on the remote server.
  • ini_file - Sets INI settings

Environments

So far you've seen "all" or the "ansiblevm1" virtual machine name used to identify servers in ad-hoc commands and playbooks.

Inventory can also be grouped for conenvience.

[DBServer]
ansiblevm1   ansible_ssh_user=daniel   ansible_host=ansiblevm1.eastus.cloudapp.azure.com
[WebServers]
ansiblevm2   ansible_ssh_user=daniel   ansible_host=ansiblevm2.eastus.cloudapp.azure.com
ansiblevm3   ansible_ssh_user=daniel   ansible_host=ansiblevm3.eastus.cloudapp.azure.com

Environments can be groups in inventory.
Or, make separate inventory files for each environment.

ansible WebServers -m ping

Sensitive Variables

Configuration variables usually hold sensitive information: passwords, keys.
Ansible-Vault can help protect variables in source control.

Variables Summary

Variables are the basis for configuration and can be grouped by many ways, and come from many sources.


How variables are organized can be flexible, and will depend upon your organization.

Other Ansible

  • Ansible Galaxy - A galaxy of premade Ansible tasks ready to go.
  • Ansible Container - Build containers with Ansible.
  • Cloud - Also make servers in Azure and AWS.
  • And many more use-cases.

When does Ansible make sense?

Ansible makes sense Because

  • Agentless - No central server by default. Run Ansible from your laptop if you want.
  • Nothing to install on servers. SSH and Python is it, and most Linux distributions have that.
  • Minimal effort to get started.
  • Ansible can be gradually implemented.
  • Free

Ansible doesn't make sense Because

  • Agentless - No central server by default. A separate product Ansible-Tower provides central management.
  • You like 5000 line shell scripts
  • You like a different tool better

Why Infrastructure as Code?

Let's make our lives easier.

Daniel Oliver

Website: olivercoding.com

Twitter: @a_software_dev

Github: danieloliver

Slides: https://ansible.olivercoding.com/