Skip to content

Ansible Fundamentals

Siddharth Rawat edited this page Sep 22, 2023 · 16 revisions

Ansible

The very basics on how to get started with Ansible

Install Ansible on your workstation

🔐 Configure SSH keys

  • Install ssh key pair on your workstation and servers
    • Generate ssh key-pair
    # create a private key and a .pub public key
    ssh-keygen -t ed25519 -C "description"
    # follow the on-screen instructions to generate the ssh-key
    • Add the generated ssh-keys to known_hosts
    ssh-add <keyname>
    • Dump/ copy the public key to all the other servers to which the main ansible machine is going to talk to.
    # comand to copy ssh public key from the main workstation to the server in order for the main server to talk to the target server
    ssh-copy-id -i ~/.ssh/<keyname>.pub <server-ip-or-hostname> 
    • List active keys:
    ssh-add -L
    • Remove an ssh-key:
    ssh-add -D <keyname>
  • Keep the private key on the workstation that the Ansible will use to ssh to other servers

💾 Inventory file

  • Add all the IP or Hostnames of the server you want ansible to connect to and work with
  • Dynamic Inventory is also supported in Ansible
  • For Dynamic just get the server info from the cloud provider.

⏱️ Connection check

  • In order to check the connection to all the servers from the main where ansible is installed
# create a file `inventory`
# this contains server hostnames and ips
10.0.0.136
pihole
# this will create a connection and check if we can connect to our servers
# here the path till ssh private is given and the key name is `ansible` (private key)
# inventory contains all the server ips to which using this key the ansible workstation is going to connect.
# -i is for the inventory file
# -m is for the module
ansible all --key-file ~/.ssh/<keyname> -i inventory -m ping
# To list all the hosts
ansible all --list-hosts
# Lists all the data/information of all the servers
# m is the module flag
ansible all -m gather_facts
# Lists all the data of a specific server
ansible all -m gather_facts --limit <server-ip-or-hostname>

Ansible config file

The ansible.cfg file can be used to setup some default values when running the ansible command.

NOTE: There is a default ansible.cfg file in /etc/ansible/, but the one we create in our working dir will take priority over the one defined in /etc/ansible.

# this file is read by ansible when we run it
[defaults]
inventory = <filename>
private_key_file = ~/.ssh/<filename>

This will help us shorten our ansible commands and avoid passing long command line options.

# previous command:
# ansible all --key-file ~/.ssh/<filename> -i inventory -m ping
# is shortened to:
ansible all -m ping

Output:

pihole | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

⌨️ AdHoc Commands

  • Some commands which require sudo privileges to work with.
  • Elevated privileges can be achieved using the below syntax.
# Tell ansible to use sudo (become) -> asks for password!
ansible all -m apt -a update_cache=true --become --ask-become-pass
# -m apt is for ubuntu/debian distros

Output:

BECOME password:
pihole | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "cache_update_time": 1695323800,
    "cache_updated": true,
    "changed": true
}
# Install a package named tmux via the apt module
# Equivalent to sudo apt-get install tmux 
ansible all -m apt -a name=tmux --become --ask-become-pass

To check what command was run by ansible on all your servers, login to any one server, navigate to /var/log/apt/history.log file.

# Install a package via the apt module, and also make sure it’s the latest version available
ansible all -m apt -a "name=snapd state=latest" --become --ask-become-pass
# Upgrade all the package updates that are available
ansible all -m apt -a upgrade=dist --become --ask-become-pass

Learn more about apt modules here: ansible.builtin.apt module

📖 Playbooks

  • Before Ansible runs any playbooks or before running any commands on servers it will First Gather Facts and do the further things.
  • To disable fact gathering for a play, set the gather_facts key to no
  • Follow a proper directory structure for defining an ansible playbook. Ref
# create the playbook
---

- hosts: all
  become: true
  tasks:
    - name: "install the apache2 package"
      apt:
        name: apache2
# Run the playbook
 ansible-playbook --ask-become-pass install_apache.yml

🎯 Targeting specific nodes

  • Lets say you 3 servers one for front end, second one contains some business logic as in REST API, and last one is a DB server
  • So, we would have to target specific servers for specific configurations
  • Need to create groups in inventory file, ex: one for db_servers, file_servers, web_servers
# inventory_group file 
[web_servers]
172.16.250.132
172.16.250.248
 
[db_servers]
172.16.250.133

[file_servers]
172.16.250.134

[workstations]
172.16.250.135
---
# target specific nodes
# ansible-playbook --ask-become-pass site.yml

# runs on all servers
- hosts: all
  become: true
  tasks:
    # `pre_tasks:` can be used with roles when we want to mandate some things to run before others
    - name: install updates (CentOS)
      dnf:
        update_only: true
        update_cache: true
      when: ansible_distribution = "CentOS"

    - name: install updates (Ubuntu)
      apt:
        upgrade: dist
        update_cache: true
      when: ansible_distribution == "Ubuntu"

# runs only on web_servers defined in the `inventory` file
- hosts: web_servers
  become: true
  tasks:
    - name: "install the apache2 and php package for Ubuntu"
      apt:
        name:
          - apache2
          - libapache2-mod-php
        state: latest
      when: ansible_distribution == "Ubuntu"

    - name: "install the apache and php package for CentOS"
      dnf:
        name:
          - httpd
          - php
        state: latest
      when: ansible_distribution == "CentOS"

# runs only on db_servers defined in the `inventory` file
- hosts: db_servers
  become: true
  tasks:
    - name: install mariadb package (CentOS)
      dnf:
        name: mariadb
        state: latest
      when: ansible_distribution == "CentOS"

    - name: install mariadb package (Ubuntu)
      apt:
        name: mariadb-server
        state: latest
      when: ansible_distribution == "Ubuntu"

# runs only on file_servers defined in the `inventory` file
- hosts: file_servers
  become: true
  tasks:
    - name: install samba package
      package:
        name: samba
        state: latest
  • To include multiple distros in a single task:
---
tasks:
    - name: "update repository index"
      apt:
        update_cache: true
      when: ansible_distribution in ["Debian", "Ubuntu"]

📑 Consolidated playbooks (w/ variables & conditionals)

  • We can make the above playbook even more modular and use variables in places for an easier configuration.
  1. Variables in the inventory
# inventory
172.16.250.132 apache_package=apache2 php_package=libapache2-mod-php
172.16.250.248 apache_package=apache2 php_package=libapache2-mod-php
172.16.250.133 apache_package=apache2 php_package=libapache2-mod-php
172.16.250.134 apache_package=httpd php_package=php
---
# ansible-playbook --ask-become-pass install_apache.yml
- hosts: all
  become: true
  tasks:
    # Ubuntu server setup
    - name: "install the apache2 and php package for Ubuntu"
      apt:
        name:
          - "{{ apache_package }}"
          - "{{ php_package }}"
        state: latest
        update_cache: true
      when: ansible_distribution == "Ubuntu"

    # CentOS server setup
    - name: "install the apache and php package for CentOS"
      dnf:
        name:
          - "{{ apache_package }}"
          - "{{ php_package }}"
        state: latest
        update_cache: true
      when: ansible_distribution == "CentOS"

We can further shorten our yaml file to a single play by defining a single package instead of 2 different plays for apt and dnf, for different distros.

  1. Using package

Package is a module in ansible that is a generic package manager and it automatically uses the underlying package manager for the hosts or servers.

---
# ansible-playbook --ask-become-pass install_apache.yml
- hosts: all
  become: true
  tasks:
    # Ubuntu server setup
    - name: "install the apache and php"
      package:
        name:
          - "{{ apache_package }}"
          - "{{ php_package }}"
        state: latest
        update_cache: true

NOTE: It can be argued that package may or may not be the best way to handle multi-distribution linux shop since we are hard-coding the package names. But this again is scenario and admin based.

🏷️ Tags

  • Used to run SPECIFIC TASK in the playbook instead of running all the tasks for testing just one change in one task.
  • So, we dont need to run all the tasks just for one change in other task.
  • We Tag all the tasks and then based on that tag we can run that particular task and skip all the other tasks.
---
# target specific nodes
# ansible-playbook --ask-become-pass site.yml

# runs on all servers
- hosts: all
  become: true
  tasks:
    # `pre_tasks:` can be used with roles when we want to mandate some things to run before others
    - name: install updates (CentOS)
      tags: always
      # dnf:
      #   update_only: true
      #   update_cache: true
      # when: ansible_distribution = "CentOS"

    - name: install updates (Ubuntu)
      tags: always
      # apt:
      #   upgrade: dist
      #   update_cache: true
      # when: ansible_distribution == "Ubuntu"

# runs only on web_servers defined in the `inventory` file
- hosts: web_servers
  become: true
  tasks:
    - name: "install the apache2 and php package for Ubuntu"
      tags: apache,apache2, ubuntu
      # apt:
      #   name:
      #     - apache2
      #     - libapache2-mod-php
      #   state: latest
      # when: ansible_distribution == "Ubuntu"

    - name: "install the apache and php package for CentOS"
      tags: apache,centos,https
      # dnf:
      #   name:
      #     - httpd
      #     - php
      #   state: latest
      # when: ansible_distribution == "CentOS"

# runs only on db_servers defined in the `inventory` file
- hosts: db_servers
  become: true
  tasks:
    - name: install mariadb package (CentOS)
      tags: centos,mariadb
      # dnf:
      #   name: mariadb
      #   state: latest
      # when: ansible_distribution == "CentOS"

    - name: install mariadb package (Ubuntu)
      tags: ubuntu,db,mariadb
      # apt:
      #   name: mariadb-server
      #   state: latest
      # when: ansible_distribution == "Ubuntu"

# runs only on file_servers defined in the `inventory` file
- hosts: file_servers
  become: true
  tasks:
    - name: install samba package
      tags: samba
      # package:
      #   name: samba
      #   state: latest

1. List all tags in a playbook

# List the available tags in a playbook
ansible-playbook --list-tags site_with_tags.yml

2. Run a playbook with a specific tag

# Examples of running a playbook but targeting specific tags
ansible-playbook --tags db --ask-become-pass site_with_tags.yml
ansible-playbook --tags centos --ask-become-pass site_with_tags.yml
ansible-playbook --tags apache --ask-become-pass site_with_tags.yml

3. Run a playbook with multiple tags

# target multiple tags
ansible-playbook --tags "apache,db" --ask-become-pass site_with_tags.yml

🗂️ File management & downloading binaries

  • You can copy files from Local System to all the other servers
  • We can also download binaries like Terraform, Unzip, etc on a workstation locally or on other servers.

1. Example of copying file from local to all the web servers

# Example of copying file from local to all the web servers 
- hosts: web_servers
   become: true
   tasks:   
    - name: copy html file for site
        tags: apache,apache,apache2,httpd
        copy:
          src: default_site.html
          dest: /var/www/html/index.html
          owner: root
          group: root
          mode: 0644

2. Example of downloading binaries in local workstation

# Example of downloading binaries in local workstation
- hosts: workstations
   become: true
   tasks:

   - name: install unzip
     package:
       name: unzip
 
   - name: install terraform
     unarchive:
      src: https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip # download link address
      dest: /usr/local/bin # binary will be downloaded here
      remote_src: yes # notify ansible that this package is from the internet and not a package manager
      mode: 0755
      owner: root
      group: root

3. Run the playbook

ansible-playbook --ask-become-pass file_management.yml

⚙️ Services

  • It is a module that can be used to start, stop, restart, or reload services on remote hosts. This is useful for managing the state of services, especially when deploying updates or changes.
# changes related to start httpd automatically and 
# enabling it in case the server goes down to restart httpd automatically
- name: start and enable httpd (CentOS)
     tags: apache,centos,httpd
     service: # module to start/stop/restart/enable a service
       name: httpd
       state: started
       enabled: yes
     when: ansible_distribution == "CentOS"
# If the configuration file is changed and needs a restart so this is how it can be done 
- name: change e-mail address for admin
     tags: apache,centos,httpd
     lineinfile: #module to be used to change a line 
       path: /etc/httpd/conf/httpd.conf # path to the file which is to be changed 
       regexp: '^ServerAdmin' # line begin with ServerAdmin
       line: ServerAdmin [email protected] #replace that with this line 
     when: ansible_distribution == "CentOS"
     register: httpd # variable declare that stores the state of httpd whether its changed or not
 
- name: restart httpd (CentOS)
     tags: apache,centos,httpd
     service:
       name: httpd
       state: restarted 
     when: httpd.changed  # this play check if changed then run this which will restart the server 

🧾 Variables

  • Registered Variables -> One can capture the output of a command by using the register statement.
  • The output is saved into a variable that could be used later for either debugging purposes or in order to achieve something else, such as a particular configuration based on a command's output.

🔁 Task Iteration with Loops

  • [WIP]

🪄 Ansible Handlers

  • They are executed only once.
  • Executed at the very end.

🥷 Anisble Roles

  • Create Roles basically an Ansible PlayBook only
  • Add all the plays(tasks) in that particular role
  • For example, you can add some Bootstrapping scripts as Plays like an apt-get update
  • These types of roles can be used to set up a server which has nothing on it.
  • So, this can actually download all the latest packages required by the system.

✅ Running playbooks in check mode

  • Ansible’s check mode allows you to execute a playbook without applying ANY ALTERATIONS to your systems.
  • You can use check mode to test playbooks before implementing them in a production environment.
  • Check mode offers a safe and practical approach to examine the functionality of your playbooks without risking unintended changes to your systems. Moreover, it is a valuable tool for troubleshooting playbooks that are not functioning as expected.
ansible-playbook --check playbook.yaml
Clone this wiki locally