Tutorial

How to Use Ansible to Automate Initial Server Setup on Rocky Linux 9

Published on December 1, 2022

Senior DevOps Technical Writer

How to Use Ansible to Automate Initial Server Setup on Rocky Linux 9
Not using Rocky Linux 9?Choose a different version or distribution.
Rocky Linux 9

Introduction

Server automation now plays an essential role in systems administration, due to the disposable nature of modern application environments. Configuration management tools such as Ansible are typically used to streamline the process of automating server setup by establishing standard procedures for new servers while also reducing human error associated with manual setups.

Ansible offers a simple architecture that doesn’t require special software to be installed on nodes. It also provides a robust set of features and built-in modules which facilitate writing automation scripts.

This guide explains how to use Ansible to automate the steps contained in our Initial Server Setup Guide for Rocky Linux 9 servers.

Prerequisites

To follow this tutorial, you’ll need:

  • One Ansible control node: a Rocky Linux 9 machine with Ansible installed and configured to connect to your Ansible hosts using SSH keys. Make sure the control node has a regular user with sudo permissions and a firewall enabled, as explained in our Initial Server Setup guide. To set up Ansible, see Step 1 of our guide on How to Install and Configure Ansible on Rocky Linux 9. You don’t normally need to install from the exact same Linux version onto the same target version (for example, from Rocky Linux 9 to Rocky Linux 9 in this case), but it keeps everything consistent for demonstration purposes.
  • One remote server with a clean install of Rocky Linux 9: no prior setup is required on this server, but you must have SSH access to this server from the Ansible control node mentioned above. If you do not already have SSH access to the remote server from the Ansible control node, refer to our tutorial on How to Set Up SSH Keys. This server will become an Ansible host remote server, which is targeted for automated provisioning by the Ansible control node.

What Does this Playbook Do?

This Ansible playbook provides an alternative to manually running through the procedure outlined in the Rocky Linux 9 initial server setup guide and the guide on setting up SSH keys on Rocky Linux 9 every time you boot up a server. Set up your playbook once, and use it for every server after.

Running this playbook will perform the following actions on your Ansible hosts:

  1. Create a new sudo user and set up passwordless sudo.
  2. Copy a local SSH public key and include it in the authorized_keys file for the new administrative user on the remote host (if you were previously using a password for SSH).
  3. Disable password-based authentication for the root user.
  4. Install system packages.

Once the playbook has finished running, you’ll have a new user which you can use to log in to the server.

To begin, log into a sudo enabled user on your Ansible control node server.

Step 1 — Preparing your Ansible control node

On your Ansible control node server, add your Ansible host remote server’s IP to your Ansible inventory file. Using vi or your preferred text editor, open the Ansible inventory file:

  1. sudo vi /etc/ansible/hosts

This will open your Ansible inventory file. Add your Ansible host remote server’s IP to the [servers] block:

/etc/ansible/hosts
[servers]
server1 ansible_host=your_remote_server_ip

. . .

Save and close the file.

Now you’ll test and authenticate your SSH connection between this Ansible control node and your Ansible host remote server:

  1. ssh root@your_remote_server_ip

Accept the authentication request, and enter your password if prompted. Once you’ve verified the SSH connection, hit CTRL+D to close the connection and return to your control node.

Step 2 — Preparing your Playbook

The playbook.yml file is where all your tasks are defined. A task is the smallest unit of action you can automate using an Ansible playbook. Create your playbook file using vi or your preferred text editor:

  1. vi playbook.yml

This will open an empty YAML file. Before diving into adding tasks to your playbook, start by adding the following:

playbook.yml
---
- hosts: all
  become: true
  vars:
    created_username: sammy

Feel free to replace the username with one of your choosing.

Almost every playbook you come across will begin with declarations similar to this. hosts declares which servers the Ansible control node will target with this playbook. become states whether all commands will be done with escalated root privileges.

vars allows you to store data in variables. If you decide to change this username in the future, you will only have to edit this single line in your file.

Note: If you want to see the playbook file in its final finished state, jump to Step 6. YAML files can be particular with their indentation structure, so you may want to double-check your playbook once you’ve added all your tasks.

Step 3 — Adding Sudo User Setup Tasks to your Playbook

By default, tasks are executed synchronously by Ansible in order from top to bottom in your playbook. This means task ordering is important, and you can safely assume one task will finish executing before the next task begins.

All tasks in this playbook can stand alone and be re-used in your other playbooks.

It is good practice to avoid extensive use of the root user. You can automate the creation of a user that is granted sudo privileges by adding:

playbook.yml
  tasks:
    - name: Setup passwordless sudo
      lineinfile:
        path: /etc/sudoers
        state: present
        regexp: '^%sudo'
        line: '%sudo ALL=(ALL) NOPASSWD: ALL'
        validate: '/usr/sbin/visudo -cf %s' 

    - name: Create a new regular user with sudo privileges
      user:
        name: "{{ created_username }}"
        state: present
        groups: wheel
        append: true
        create_home: true

You’re using the lineinfile Ansible module to target and replace a specific line in a file. In this case, you’re using regex to target a specific line in the sudoers file then modifying it to allow passwordless use of sudo. You also use visudo to validate your changes to prevent anything from breaking.

In order to take advantage of this, you’re adding a new user with the user module. Ansible will ensure this user is created if not already in existence, that the user belongs to the wheel (admin) group while not being removed from other groups, and a home directory is created.

Note: Make sure you include the quotes around the curly braces that indicate a variable. Omission of these quotes is a very common Ansible syntax error.

Step 4 — Adding SSH Key Setup and Disabling Root Password Tasks to your Playbook

Ansible operates under the assumption that you’re using SSH keys. It is strongly recommended and generally good practice to pair SSH key usage with disabling root password authentication. To automate this, add:

playbook.yml
    - name: Set authorized key for remote user
      ansible.posix.authorized_key:
        user: "{{ created_username }}"
        state: present
        key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}"

    - name: Disable password authentication for root
      lineinfile:
        path: /etc/ssh/sshd_config
        state: present
        regexp: '^#?PermitRootLogin'
        line: 'PermitRootLogin prohibit-password'

The authorized_key module can be used if you supply the username and the location of the key. Here, the path towards your key is built using Ansible’s lookup function.

The lineinfile module is used to search and replace a line in sshd_config in order to disable password authentication for root, limiting access to its privileges for heightened security.

Step 5 — Adding a Package Installation Task to your Playbook

Ansible can ensure certain packages are always installed on your server. Rather than call dnf install on each individual package, or break it into multiple tasks, you can list out all your desired packages:

playbook.yml
    - name: Update and install required system packages
      dnf:
        pkg:
          - curl
          - vim
          - git
          - firewalld
        state: latest
        update_cache: true

You can add or remove packages to your liking. This will ensure all packages are not only present, but on the latest version, and done after an update with dnf is called.

Step 6 — Reviewing your Complete Playbook

Your playbook should look roughly like the following, with minor differences depending on your customizations:

playbook.yml
---
- hosts: all
  become: true
  vars:
    created_username: sammy

  tasks:
    - name: Setup passwordless sudo
      lineinfile:
        path: /etc/sudoers
        state: present
        regexp: '^%sudo'
        line: '%sudo ALL=(ALL) NOPASSWD: ALL'
        validate: '/usr/sbin/visudo -cf %s'

    - name: Create a new regular user with sudo privileges
      user:
        name: "{{ created_username }}"
        state: present
        groups: wheel
        append: true
        create_home: true

    - name: Set authorized key for remote user
      ansible.posix.authorized_key:
        user: "{{ created_username }}"
        state: present
        key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}"

    - name: Disable password authentication for root
      lineinfile:
        path: /etc/ssh/sshd_config
        state: present
        regexp: '^#?PermitRootLogin'
        line: 'PermitRootLogin prohibit-password'

    - name: Update and install required system packages
      dnf:
        pkg:
          - curl
          - vim
          - git
          - firewalld
        state: latest
        update_cache: true

Note: This is a gentle reminder to be mindful of your indentations. If you run into an error, this is very likely the culprit. YAML suggests using 2 spaces as an indent, as was done in this example.

Once you’re satisfied with your playbook, you can exit your text editor and save.

Step 7 — Running your Playbook for the First Time

You’re now ready to run this playbook on one or more servers. Most playbooks are configured to be executed on every server in your inventory by default, but you’ll specify your server this time.

To execute the playbook only on server1, connecting as root, you can use the following command:

  1. ansible-playbook playbook.yml -l server1 -u root -k

The -l flag specifies your server and the -u flag specifies which user to log into on the remote server. Since you’ve yet to setup your remote server, root is your only option. The -k flag is necessary if you are not using passwordless SSH: it will prompt you for an SSH password.

You will get output similar to this:

Output
. . . PLAY RECAP *************************************************************************************************************************************************************************************************************************************************** server1 : ok=6 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

This indicates your server setup is complete! Your output doesn’t have to be exactly the same, but it is important that you have zero failures.

Now that you’ve done the first setup for your playbook, all subsequent ansible calls can be done with user sammy (and without the -k flag, if you used a password in the first place):

  1. ansible-playbook playbook.yml -l server1 -u sammy

You’ll also be able to log in to the server with:

  1. ssh sammy@your_remote_server_ip

Remember to replace sammy with the user defined by the created_username variable, and server_host_or_IP with your server’s hostname or IP address.

Conclusion

Automating initial server setup can save you time, while also making sure your servers will follow a standard configuration that can be improved and customized to your needs. With the distributed nature of modern applications and the need for more consistency between different staging environments, automation like this becomes a necessity.

In this guide, you demonstrated how to use Ansible for automating the initial tasks that should be executed on a fresh server, such as creating a non-root user with sudo access, installing packages, and disabling remote password-based root login.

For more information on how to run Ansible playbooks, check out our Ansible Cheat Sheet Guide.

If you’d like to include new tasks in this playbook to further customize your initial server setup, please refer to our introductory Ansible guide Configuration Management 101: Writing Ansible Playbooks. You can also check out our guide on How to Use Ansible Roles to Abstract your Infrastructure Environment.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar

Senior DevOps Technical Writer

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Thank you for sharing. I think there is a small typo on Step 3

regexp: '^%sudo'
line: '%sudo ALL=(ALL) NOPASSWD: ALL'

the sudo group on rhel based distros is wheel

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more