Install Apache Web Server and Serve a Custom Webpage Using Ansible
Tags: linux, apache, devops, automation
Published: 2024-04-29 13:21:20

As a cloud engineer, automation should be at the center of everything you do. Imagine you had to setup two servers, install some webservers and add some programs for a new staff, easy peasy to do on the two servers. Just log into them one at a time and install what needs to be installed and hand over to the new staff.

Now flip it and you have to setup 100 or 200 servers for 3 departments, it would take you weeks to do this manually and you are prone to making mistakes cos the repetition would become very tiring. However when you automate the process it will take you less than an hour to complete.

# :zap: What is Ansible

Ansible is an open-source platform used for automation and for various operations such as configuration management, application deployment, task automation, and IT orchestration. Ansible is easy to set up, and it is efficient, reliable, and powerful.

# :zap: Ansible Architecture

Ansible uses the concepts of control and managed nodes. It connects from the control node, any machine with Ansible installed, to the managed nodes sending commands and instructions to them.

### :zap: Control node requirements

For your control node (the machine that runs Ansible), you can use nearly any UNIX-like machine with Python installed. This includes Red Hat, Debian, Ubuntu, macOS, BSDs, and Windows under a Windows Subsystem for Linux (WSL) distribution. Windows without WSL is not natively supported as a control node.

### :zap: Managed node requirements

The managed node (the machine that Ansible is managing) does not require Ansible to be installed, but requires Python to run Ansible-generated Python code. The managed node also needs a user account that can connect through SSH to the node with an interactive POSIX shell.

# :zap: Install Ansible on Ubuntu

To install Ansible on Ubuntu you need to add the PPA to you apt repository and also install the necessary dependencies. Use the below commands:

```sh
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible
```

# :zap: Create a Common User

Ansible requires a common user across all the nodes, if the user you are working with is called ansible ensure you have an ansible user on all your nodes.

The user on my control node is named vagrant and so I will create a vagrant user (or any other user you want) with sudo privilege on my other nodes and ensure that the user can run sudo commands without password.

(I already have a vagrant user on all my servers and so I will be using the vagrant user for this walkthrough).

This is useful and necessary so that when we run the commands and SSH into our nodes, we won't be stuck because any of the nodes requires a password.

```sh
sudo adduser vagrant
```

To give this user sudo privilege and ensure that the user can run sudo commands without password, I will edit the `/etc/sudoers` file using the command below:

```sh
sudo visudo
```

Enter the below into the file

```sh
vagrant ALL=(ALL) NOPASSWD:ALL
```

Do this for all the servers.

# :zap: Generate SSH Keys on the Control Node

Before you begin this step switch to the Vagrant user.

Generate an SSH key on the control node. The private key will be on the master and we will copy the public key to the managed nodes (hosts).

Use the command below:

```sh
sudo su vagrant
ssh-keygen
```

Now we will copy the public key to the managed nodes, retrieve the public key and then navigate to the `.ssh/authorized_keys`/ file and add the copied key into the file:

#### On the control node

```sh
cat .ssh/id_rsa.pub
```
Copy the outputted public key 

#### On the managed node

```sh
vi authorized_keys
```

# :zap: Create Your Inventory

Ansible inventory is a file containing a list of your managed host, these are the servers on which your tasks will be carried out.

You can make use of the default hosts files in the /etc/ansible directory but it's better to create yours and use that so that in a case where you make a mistake you can use the default host file provided by ansible as a reference for the correct configuration syntax.

To that effect I will make an ansible directory and this is here i will be saving my inventory and playbooks.

```sh
mkdir ansible
sudo vi myhosts
```

In the inventory file add the IP addresses of your managed nodes. You can use the `IP a` command to get the IP address of the nodes.

![Inventory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vfcboi2rib3e0kq16v1x.png)

# :zap: Configure your Ansible Configuration file

The default ansible configuration file is in the `ansible.cfg` file however when you open that file you will find it almost empty with instructions on how to populate it.

When you run the `ansible --version` you will see where your current config file is located.

![Ansible config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3udzw47qgbu24lj1xkg2.png)

For what we want to do in this post though we want our configuration very basic and so I will create a new `ansible.cfg` file in the ansible directory I created earlier and populate it with the basic configuration I want to use for this post.

```sh
vi ansible.cfg
```

Enter the below into the file:

```sh
[defaults]
inventory = myhosts //(the config file is in the same place as the inventory, hence why I don't need to add the full path to the inventory)
remote_user = ansible //(or whatever user you are using)
host_key_checking = false

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ack_pass = false //(we don't want a password)
```

Save the file. now if you run the `ansible --version` command again it will show the new config file path.

![ansible.cfg](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ft707i8ga6v83wxey8w.png)

# :zap: Check Connectivity using adhoc Command

Using ad hoc commands is a quick way to run a single task on one or more managed nodes. 

Some examples of valid use cases are rebooting servers, copying files, checking connection status, managing packages, gathering facts, etc.

The pattern for ad hoc commands looks like this:

```sh
ansible [host-pattern] -m [module] -a “[module options]”
```

We will use the ping module to check the connectivity to our servers.

Run the below command:

```sh
ansible all -m ping
```

:bulb: **TIP**

Because we specified our inventory file in the `ansible.cfg` file we do not need to pass the inventory file path along with the `-i` flag with the adhoc command.

![Ping result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j78k7cnlc8t9p4q24bf2.png)

# :zap: Prepare Custom Website Files

Apache is a webserver and it serves an apache website by default, this is not the site we want our apache webserver to server and so we would need to retrieve the config file for our custom website.

The files are hosted in my GitHub account and so clone that into a folder on my control node.

First I need to install git on the control node, do that using the command below:

```sh
sudo apt update
sudo apt install git
```

I will create a new directory and it is in this directory that I would clone [this repo](https://github.com/ChigozieCO/assignment-03-WP-Pusher) into (you can use any repo of your choice). The repo contains the html and CSS code for a webpage I designed earlier on.

```sh
mkdir webpage
```

Now I'll navigate into the directory and clone the project there.

```sh
cd webpage
git clone https://github.com/ChigozieCO/assignment-03-WP-Pusher.git
```

This page will be used in our playbook.

# :zap: Create Playbook

Create a playbook directory and navigate into the directory

```sh
mkdir playbook
cd playbook
```

Now we will create our playbook, I will name it serverplay as the playbook will be contain tasks to install apache2 web server on my nodes.

```sh
sudo vi serverplay.yml
```

Copy the below and paste it into your playbook, this is the content of your playbook

```sh
---
- name: Setup webserver and copy custom webpage
  hosts: all
  become: yes
  tasks:
    - name: Update apt package cache (Ubuntu) / Update yum package cache (CentOS)
      when: ansible_os_family == 'Debian'
      apt:
        update_cache: yes

    - name: Update yum package cache (CentOS) / Update apt package cache (Ubuntu)
      when: ansible_os_family == 'RedHat'
      yum:
        update_cache: yes

    - name: Install Apache (Ubuntu) / Install Httpd (CentOS)
      package:
        name: "{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}"
        state: latest

    - name: Enable and start Apache/Httpd
      ansible.builtin.service:
        name: "{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}"
        enabled: yes
        state: started

    - name: Copy custom webpage files
      ansible.builtin.copy:
        src: "~/ansible/webpage/assignment-03-WP-Pusher/"
        dest: "/var/www/html/"
        mode: '0755'

    - name: Restart Apache2 (Ubuntu) / Httpd (CentOS)
      ansible.builtin.service:
        name: "{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}"
        state: restarted
```

# :zap: Run Playbook

Run the play book now with the below command:

```sh
ansible-playbook playbook/serverplay.yml
```

:bulb: Note

Because  I am running my playbook from the ansible directory we created earlier, I do not need to specify the location of my inventory as I have done that in my config file already.

My playbook ran successfully and we can see it in the screenshot below:

![output of executed playbook](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zc2mjk8upb8ehptgbrvc.png)

To see your webpage enter your node's IP address in a web browser, from the below screen shots you can see the pages displayed on my browser.

#### managed node 1

![centos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p6v4wqi4v0wgee0mfnko.png)

#### managed node 2

![ubuntu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nel5sbiarju6za2pzk79.png)