Ansible: inizial server setup

Ansible initial server setup

Vedi L'articolo in Italiano

With this article we see how to simplify the initial setup of a new physical and virtual server with the use of Ansible.

Ansible environment installation

In order to continue it is necessary to have a linux computer where Ansible is present.

To install Ansible you can use your own distribution package or use the pip command and create a virtualenv where you can install everything at the latest version or the desired version without changing the system libraries.

prerequisites installation

First we install, if not present, python3 and pip:

  • on RedHat systems >=8: sudo dnf install -y python3 python3-pip
  • while on RedHat systems <8:  sudo yum install -y python3 python3-pip (it needs the epel repository)
  • finally on Debian/Ubuntu systems: sudo install -y python3 python3-pip python3-venv

Virtual environment venv for Python

Now as a user we create our python virtual environment:

cd $HOME && mkdir venv && python3 -m venv venv/ansible

We activate our python virtual environment with:

source venv/ansible/bin/activate

At this point with this shell we have a new python environment where we can install whatever we want without interfering with the system one.

From now on we consider to be in virtualenv or on a python environment where we can install libraries with pip.

Ansible, lint and molecule installation

We update to the latest pip version with:

pip install setuptools && pip install --upgrade pip

Than install ansible with:

pip install ansible

So we can test it with:

ansible -v

and check the ansible_facts in localhost with:

ansible localhost -m setup

To check the yaml syntax and the Ansible specific one we install the parsers:

pip install yamllint ansible-lint

To test Ansible rules automatically via Docker we install molecule:

pip install molecule docker molecule[docker]

Ansible Role: initial server setup

We then move on to the main project, which is the creation of an Ansible role for the initial configuration.

cd $HOME && mkdir roles
cd roles
ansible-galaxy role init eniocarboni.test
cd eniocarboni.test
molecule init scenario -r eniocarboni.test -d docker

The first thing to do is edit the metadata in the meta/main.yml file by adding or editing inside the galaxy_info tag:

galaxy_info:
  role_name: test
  namespace: eniocarboni
  author: Your Name
  company: Yours
  ...

In addition, at least the license and the compatibility of the role with the various distributions must be changed:

license: GPL-3.0
platforms:
  - name: Ubuntu
    versions:
      - all

At this point we can already test our role with molecule that will automatically use Docker with its default image (quay.io/centos/centos:stream8):

molecule test

since we have both yamllint and ansible-lint installed let’s configure them for use with molecules:

cat <<EOF >>molecule/default/molecule.yml
lint: |
  set -e
  yamllint .
  ansible-lint
EOF

cat <<EOF >.ansible-lint
---
exclude_paths:
  - molecule
EOF

Re-running the test we will see that molecule also uses yamllint and ansible-lint:

molecule test

Of course we can also launch commands directly without molecules:

yamllint .
ansible-lint

test on a real vm

To test on a physical or virtual server we need to have access via ssh with a user who has access to root via sudo or to root directly.

So what we will need is:

  • the name of the server or its ip: testserver;
  • the user to login: user;
  • the playbook file: playbook.yml;
  • the inventory file: inventory_hosts

So just do:

mkdir -p tests/mytest
echo "testserver" >tests/mytest/inventory_hosts
cat <<EOF >tests/mytest/playbook.yml
---
- hosts: all
  become: true
  roles:
    - eniocarboni.test
EOF

To test directly in the testserver server just run:

ansible-playbook -u root -i tests/mytest/inventory_hosts tests/mytest/playbook.yml

Task to perform

At this point we need to add the tasks to be done on the server and put them in the tasks/main.yml file such as:

  • installing new packages;
  • host name change;
  • time zone change;
  • setting of the main language;

Let’s briefly see how to do it:

cat <<EOF >tasks/main.yml
---
- import_tasks: update_packages.yml
- import_tasks: hostname.yml
- import_tasks: update_timezone.yml
- import_tasks: update_locale.yml
EOF
for f in update_packages hostname update_timezone update_locale
do
  echo "---" >tasks/$f.yml
done

We then complete the first update_packages.yml file with:

cat <<EOF >>tasks/update_packages.yml
- name: update apt cache
  ansible.builtin.apt:
    update_cache: yes
    cache_valid_time: 3600
  when: ansible_pkg_mgr == "apt"
- name: update yum cache
  ansible.builtin.yum:
    update_cache: yes
  when: ansible_pkg_mgr == "yum"
- name: update dnf cache
  ansible.builtin.dnf:
    update_cache: yes
  when: ansible_pkg_mgr == "dnf"
- name: Install new packages
  ansible.builtin.package: "name={{ item }} state=latest" # noqa package-latest
  with_items: "{{ packages | default([]) }}"
EOF

So just put the default variables such as packages in the defaults/main.yml file:

cat <<EOF >>defaults/main.yml
packages:
  - wget
  - openssh-server
EOF

Finally we can test the first tasks with molecule via docker or on our test server and verify that everything is working correctly:

molecule test
INFO     default scenario test matrix: dependency, lint, cleanup, destroy, syntax, create, prepare, converge, idempotence, side_effect, verify, cleanup, destroy
INFO     Performing prerun...
...
INFO     Running default > dependency
WARNING  Skipping, missing the requirements file.
WARNING  Skipping, missing the requirements file.
INFO     Running default > lint
WARNING  Loading custom .yamllint config file, this extends our internal yamllint config.
WARNING  Failed to discover lintable files using git: fatal: non è un repository Git (né lo è alcun genitore fino al punto di mount /)
Mi fermo al limite del filesystem (l'opzione GIT_DISCOVERY_ACROSS_FILESYSTEM non è impostata).
INFO     Running default > cleanup
WARNING  Skipping, cleanup playbook not configured.
INFO     Running default > destroy
INFO     Sanity checks: 'docker'

PLAY [Destroy] *****************************************************************

TASK [Destroy molecule instance(s)] ********************************************
changed: [localhost] => (item=instance)

TASK [Wait for instance(s) deletion to complete] *******************************
FAILED - RETRYING: [localhost]: Wait for instance(s) deletion to complete (300 retries left).
ok: [localhost] => (item=instance)

TASK [Delete docker networks(s)] ***********************************************

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

INFO     Running default > syntax

playbook: /home/enio/Progetti/ansible/eniocarboni.test/molecule/default/converge.yml
INFO     Running default > create
PLAY [Create] ******************************************************************

TASK [Log into a Docker registry] **********************************************
skipping: [localhost] => (item=None) 
skipping: [localhost]

TASK [Check presence of custom Dockerfiles] ************************************
ok: [localhost] => (item={'image': 'quay.io/centos/centos:stream8', 'name': 'instance', 'pre_build_image': True})
...
...
TASK [eniocarboni.test : update apt cache] *************************************
skipping: [instance]

TASK [eniocarboni.test : update yum cache] *************************************
skipping: [instance]

TASK [eniocarboni.test : update dnf cache] *************************************
ok: [instance]

TASK [eniocarboni.test : Install new packages] *********************************
changed: [instance] => (item=wget)
changed: [instance] => (item=openssh-server)

PLAY RECAP *********************************************************************
instance                   : ok=3    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

INFO     Running default > idempotence
...
...
PLAY RECAP *********************************************************************
instance                   : ok=3    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

INFO     Idempotence completed successfully.
INFO     Running default > side_effect
WARNING  Skipping, side effect playbook not configured.
INFO     Running default > verify
INFO     Running Ansible Verifier

PLAY [Verify] ******************************************************************

TASK [Example assertion] *******************************************************
ok: [instance] => {
    "changed": false,
    "msg": "All assertions passed"
}

PLAY RECAP *********************************************************************
instance                   : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

INFO     Verifier completed successfully.
INFO     Running default > cleanup
WARNING  Skipping, cleanup playbook not configured.
INFO     Running default > destroy

PLAY [Destroy] *****************************************************************

TASK [Destroy molecule instance(s)] ********************************************
changed: [localhost] => (item=instance)

TASK [Wait for instance(s) deletion to complete] *******************************
FAILED - RETRYING: [localhost]: Wait for instance(s) deletion to complete (300 retries left).
changed: [localhost] => (item=instance)

TASK [Delete docker networks(s)] ***********************************************

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

INFO     Pruning extra files from scenario ephemeral directory
ansible-playbook -u root -i tests/mytest/inventory_hosts tests/mytest/playbook.yml

At this point we should continue with all the other tasks that we have left empty and possibly add others, an operation that I will not do in this article so as not to dwell too much.

Ansible role: eniocarboni.server_initial_setup

For those interested can use my role on Ansible Galaxy eniocarboni.server_initial_setup by installing it with:

ansible-galaxy install eniocarboni.server_initial_setup

and checking or cloning the code on GitHub where you can also find all the documentation on how to use it.

This role is tested in various versions of Ubuntu and RedHat systems or equivalent.

The tasks it performs right now are (and are all configurable):

  • update package cache;
  • updating packages to the latest version;
  • installing new packages;
  • package for automatic update via cron based on the distribution;
  • screen or tmux installation;
  • hostname change;
  • timezone change;
  • locales change;
  • adding or deleting ssh public keys;
  • added sysctl parameters;
  • firewall setup (ufw on Debian/Ubuntu, Firewalld on RedHat systems or iptables)

References

Leave a Reply

Your email address will not be published. Required fields are marked *

8 × = 8