How to Use Ansible to Deploy a Web Server, Log Server, and Database Server
Traducciones al EspañolEstamos traduciendo nuestros guías y tutoriales al Español. Es posible que usted esté viendo una traducción generada automáticamente. Estamos trabajando con traductores profesionales para verificar las traducciones de nuestro sitio web. Este proyecto es un trabajo en curso.
What is Ansible?
Ansible is an open-source, software provisioning tool that automates application and IT infrastructure deployment. It is lightweight and agent-less, meaning there is no client or server software to install. Ansible uses SSH and Python to accomplish its highly useful automation tasks.
In this guide you:
- Deploy and configure five Linodes. One is the Ansible control node and the others are worker nodes.
- Configure and run an Ansible playbook that configures the worker nodes.
- Test your running web servers and log server.
CautionThe example instructions in this guide create five, 1GB Linodes. These add billable resources to your Linode account. If you do not want to keep using the Linodes created, be sure to delete the resources once you have finished this how-to guide.
If you remove these resources afterward, you are only billed for the time the resources were present on your account.
Prerequisites
- Intermediate understanding of the Bash shell and its utilities.
- Install the
Linode CLI or you can use the
Linode Cloud Manager.
- Using the CLI allows you to save time creating, labeling, and tagging your Linodes.
- Create a new directory to work from. For example, you can name it, “Ansible_Infra”.
NoteThe steps in this guide are written for non-root users. Commands that require elevated privileges are prefixed withsudo
. If you’re not familiar with thesudo
command, see the Users and Groups guide.
Create Five Linodes Using the Linode CLI
Create One Ansible Control Node and Four Managed Nodes
NoteThis section requires that you have the Linode CLI installed and configured on your computer.
On your local machine, set up an environment variable to temporarily store a password. This environment variable will be sued in later steps in a for
loop to create five Linodes. Substitute yourrootpassword
for a secure password as this is used as the root password for all your newly created Linodes.
pass=yourrootpassword
Check if this password works by running echo $pass
. You should see your newly created password as the output.
echo $pass
yourrootpassword
Run the command below to create five Linodes.
for i in {1..5}; do linode-cli linodes create --root_pass $pass; done
Your output should resemble the following example:
┌──────────┬────────────────┬─────────┬─────────────┬────────────────────┬──────────────┬────────────────┐
│ id │ label │ region │ type │ image │ status │ ipv4 │
├──────────┼────────────────┼─────────┼─────────────┼────────────────────┼──────────────┼────────────────┤
│ 31202535 │ linode31202535 │ us-east │ g6-nanode-1 │ linode/ubuntu20.04 │ provisioning │ 192.0.2.0 │
└──────────┴────────────────┴─────────┴─────────────┴────────────────────┴──────────────┴────────────────┘
┌──────────┬────────────────┬─────────┬─────────────┬────────────────────┬──────────────┬────────────────┐
│ id │ label │ region │ type │ image │ status │ ipv4 │
├──────────┼────────────────┼─────────┼─────────────┼────────────────────┼──────────────┼────────────────┤
│ 31202548 │ linode31202548 │ us-east │ g6-nanode-1 │ linode/ubuntu20.04 │ provisioning │ 192.0.2.1 │
└──────────┴────────────────┴─────────┴─────────────┴────────────────────┴──────────────┴────────────────┘
┌──────────┬────────────────┬─────────┬─────────────┬────────────────────┬──────────────┬─────────────────┐
│ id │ label │ region │ type │ image │ status │ ipv4 │
├──────────┼────────────────┼─────────┼─────────────┼────────────────────┼──────────────┼─────────────────┤
│ 31202559 │ linode31202559 │ us-east │ g6-nanode-1 │ linode/ubuntu20.04 │ provisioning │ 192.0.2.2 │
└──────────┴────────────────┴─────────┴─────────────┴────────────────────┴──────────────┴─────────────────┘
┌──────────┬────────────────┬─────────┬─────────────┬────────────────────┬──────────────┬─────────────────┐
│ id │ label │ region │ type │ image │ status │ ipv4 │
├──────────┼────────────────┼─────────┼─────────────┼────────────────────┼──────────────┼─────────────────┤
│ 31202570 │ linode31202570 │ us-east │ g6-nanode-1 │ linode/ubuntu20.04 │ provisioning │ 192.0.2.3 │
└──────────┴────────────────┴─────────┴─────────────┴────────────────────┴──────────────┴─────────────────┘
┌──────────┬────────────────┬─────────┬─────────────┬────────────────────┬──────────────┬─────────────────┐
│ id │ label │ region │ type │ image │ status │ ipv4 │
├──────────┼────────────────┼─────────┼─────────────┼────────────────────┼──────────────┼─────────────────┤
│ 31202576 │ linode31202576 │ us-east │ g6-nanode-1 │ linode/ubuntu20.04 │ provisioning │ 192.0.2.4 │
└──────────┴────────────────┴─────────┴─────────────┴────────────────────┴──────────────┴─────────────────┘
Store Your Linode Ids In a Temporary File
Use the following command to print the last five Linode IDs and redirect the output to the tmp.txt
file. You can use these Linode IDs to tag and label your Linodes.
NoteTagging and labeling your Linodes helps to keep your Linode Cloud Manager organized.
linode-cli linodes list --text | tail -5 | awk '{print $1}' > tmp.txt
Tag Your Ansible Linodes
Use the following line of code to tag the Linodes with “Ansible”. Doing this groups the Linodes together in the Cloud Manager.
for i in $(cat tmp.txt); do linode-cli linodes update --tags Ansible $i; done
Label Your Ansible Linodes
Loop through the Linode IDs from the tmp.txt
file you created earlier. Then, assign each Linode a numbered label (vm1
through vm5
) using the following line of code.
i=1; for j in $(cat tmp.txt); do linode-cli linodes update --label vm$i $j; let "i++"; done
If you check the Cloud Manager GUI, you can see these five Linodes grouped under the tag “Ansible” and labeled vm1
through vm5
.
Create Setup Files to Configure the Ansible Control Node and Worker Nodes
Create three files named ansibleCN_setup.sh
, ansibleMN_setup.sh
, and myplaybook.yml
. Using a text editor, copy and paste the code in the example files below into each respective file. The setup files help to secure your Linode, install needed software, and create a limited user on your instance. The playbook file, myplaybook.yml
is what Ansible uses to configure the managed nodes.
NoteAs a shortcut, you can also
wget
the configuration files from the author’s GitHub repository and save them in your local, working folder,Ansible_Infra
that you created earlier.wget https://raw.githubusercontent.com/bennettnw2/Ansible_webserver_infra_files/main/ansibleCN_setup.sh wget https://raw.githubusercontent.com/bennettnw2/Ansible_webserver_infra_files/main/ansibleMN_setup.sh wget https://raw.githubusercontent.com/bennettnw2/Ansible_webserver_infra_files/main/myplaybook.yml
- File: ansibleCN_setup.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
#! /bin/bash # This is the script to run to setup an Ansible control node. echo "##########################################################" echo "# Update and Secure Linode Instance #" echo "##########################################################" apt update apt upgrade -y hostnamectl set-hostname CtlNode # Secure ssh a bit with no root login and no x11 forwarding # Need to remove host key checking for Ansible to run properly sed -in 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config sed -in 's/X11Forwarding yes/X11Forwarding no/' /etc/ssh/sshd_config sed -in 's/# StrictHostKeyChecking ask/StrictHostKeyChecking no/' /etc/ssh/ssh_config echo "##########################################################" echo "# Installing Software #" echo "##########################################################" # Install Software # ==================================================================================================== apt install sshpass -y apt install ansible -y apt install fail2ban -y apt install python3-pip -y pip3 install passlib # Configure Software # ==================================================================================================== # fail2ban # ======== systemctl enable fail2ban.service systemctl start fail2ban.service # ufw # ======== ufw allow openssh yes | ufw enable ufw status echo "##########################################################" echo "# Creating limited user #" echo "##########################################################" echo "" echo "Please enter preferred username: " read USERNAME # Create limited user and give sudo privileges. useradd -m -G sudo -s /bin/bash $USERNAME passwd $USERNAME mv ansibleMN_setup.sh myplaybook.yml /home/$USERNAME # Create passwordless sudo for user $USERNAME #+ and add file in /etc/sudoers.d/ echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/10-user-$USERNAME chmod 440 /etc/sudoers.d/10-user-$USERNAME visudo -c # Create an ssh key for the user. mkdir /home/$USERNAME/.ssh ssh-keygen -t rsa -b 2048 -f /home/$USERNAME/.ssh/id_rsa -q -N '' # Set file permissions for the user. chown -R $USERNAME:$USERNAME /home/$USERNAME/.ssh chown $USERNAME:$USERNAME /home/$USERNAME/myplaybook.yml echo "##########################################################" echo "# Dunzo. Poke around if you like. I recommend a reboot. #" echo "##########################################################"
- File: ansibleMN_setup.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
#!/bin/bash # This is the script to run in order to setup Ansible managed nodes. echo "##########################################################" echo "# Update and Secure Linode Instance #" echo "##########################################################" apt update apt upgrade -y # Secure ssh a bit with no root login and no x11 forwarding. sed -in 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config sed -in 's/X11Forwarding yes/X11Forwarding no/' /etc/ssh/sshd_config sed -in 's/#PubkeyAuthentication/PubkeyAuthentication/' /etc/ssh/sshd_config apt install fail2ban -y systemctl enable fail2ban.service systemctl start fail2ban.service echo "##########################################################" echo "# Creating limited user #" echo "##########################################################" echo "" echo "Please enter preferred username: " read USERNAME # Create limited user and give sudo privileges. useradd -m -G sudo -s /bin/bash $USERNAME passwd $USERNAME # Create passwordless sudo execution for user $USERNAME #+ and add file in /etc/sudoers.d/ echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/10-user-$USERNAME chmod 440 /etc/sudoers.d/10-user-$USERNAME visudo -c echo "##########################################################" echo "# Dunzo. Poke around if you like. I recommend a reboot. #" echo "##########################################################"
Ansible playbooks are what makes Ansible powerful software. The syntax of the tasks is very similar to plain language and therefore, makes the playbook file human readable. As a best practice, ensure your task names are descriptive and precise. Review the names of the tasks in myplaybook.yml
, to learn what each task is supposed to do accomplish.
- File: myplaybook.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
--- - hosts: webservers become: yes tasks: - name: "Install Apache Server" apt: name: apache2 state: present - name: "Enable and Start Apache Server" service: name: apache2 enabled: yes state: started - name: "Install Firewalld" apt: name: firewalld state: present - name: "Enable and start firewalld server" service: name: firewalld enabled: yes state: started - name: "Open Firewall Port" firewalld: service: http immediate: true permanent: true state: enabled - name: "Create web admin group" group: name: web state: present - name: "Create web admin user" user: name: webadm comment: "Web Admin" password: {{ HASHED_PASSWORD }} groups: web append: yes - name: "Set content directory group/permissions" file: path: /var/www/html owner: root group: web state: directory mode: u=rwx,g=rwx,o=rx,g+s - name: "Create default page content" copy: content: "Welcome to {{ ansible_fqdn }} on {{ ansible_default_ipv4.address }}" dest: /var/www/html/index.html owner: webadm group: web mode: u=rw,g=rw,o=r - hosts: dbservers become: yes tasks: - name: "Install MariaDB Server" apt: name: mariadb-server state: present - name: "Enable and start MariaDB server" service: name: mariadb enabled: yes state: started - hosts: logservers become: yes tasks: - name: "Configure rsyslog remote log reception over udp" lineinfile: path: /etc/rsyslog.conf line: "{{ item }}" state: present with_items: - '$ModLoad imudp' - '$UDPServerRun 514' notify: - restart rsyslogd - name: "Install Firewalld" apt: name: firewalld state: present - name: "Enable and start firewalld server" service: name: firewalld enabled: yes state: started - name: "Open firewall port" firewalld: port: 514/udp immediate: true permanent: true state: enabled handlers: - name: "restart rsyslogd" service: name: rsyslog state: restarted - hosts: lamp become: yes tasks: - name: configure rsyslog lineinfile: path: /etc/rsyslog.conf line: '*.* @{{ LOG_IP_ADDRESS }}' state: present notify: - restart rsyslogd handlers: - name: "restart rsyslogd" service: name: rsyslog state: restarted
Configure Your Control Node
Using scp
, the above files are sent to the Ansible control node. You can then log into the control node and execute the control node script, ansibleCN_setup.sh
.
NoteThroughout all the steps in this section, replaceVM1_IPADDRESS
with the IP address obtained from either the Linode CLI or Cloud Manager.
CautionDo not forget the colon “:” at the end of the command below.
scp ansibleCN_setup.sh ansibleMN_setup.sh myplaybook.yml root@VM1_IPADDRESS:
Update, Secure, and Install Package Dependencies on the Linode
SSH into
vm1
and runansibleCN_setup.sh
. This updates, secures, and installs only the needed dependencies on the Linode.ssh root@VM1_IPADDRESS
Change the permissions on
ansibleCN_setup.sh
andansibleMN_setup.sh
to be executable.chmod 744 ansibleCN_setup.sh ansibleMN_setup.sh
Execute
ansibleCN_setup.sh
script. It takes a few minutes for the script to complete../ansibleCN_setup.sh
Note
The script asks you to enter a username and password for the new user being created.
Reboot and Access your Ansible Linode
Use the command below to reboot the Linode either using the Linode CLI. You can also reboot the Linode using the Cloud Manager.
linode-cli linodes reboot LINODE_ID
Use the command below to SSH into vm1
.
CautionBe sure to use the new user you created because the setup script disables root logins.
ssh YOUR_USERNAME@VM1_IPADDRESS
You should see the hostname, CtlNode, configured on the command prompt along with your username.
example_user@CtlNode:~$
Configure your Control Node to Connect to the Worker Nodes
Set Up your Control Node’s Hosts File
This enables you to use hostnames when referring to different instances. Run the command below from your local machine.
linode-cli linodes list --text | grep vm | awk '{print $7,$2,$2".ansi.com"}' | column -t
Your output should resemble the following example:
192.0.2.0 vm1 vm1.ansi.com
192.0.2.1 vm2 vm2.ansi.com
192.0.2.2 vm3 vm3.ansi.com
192.0.2.3 vm4 vm4.ansi.com
192.0.2.4 vm5 vm5.ansi.com
Using the text editor of your choice, copy and paste this output to the end of the /etc/hosts/
file on vm1
.
NoteUsesudo
to edit/etc/hosts
using Vi(m).
Use the line of code below to ensure you can ping all the hostnames.
for i in {1..5}; do ping -c 2 vm$i; done
Create Ansible Configs on the Control Node
Using a text editor, create and copy the configuration file, ansible.cfg
, to your home directory.
- File: /home/YOUR_USERNAME/ansible.cfg
1 2
[defaults] inventory = $HOME/hosts
Create and copy the below Ansible hosts configuration file to your home directory.
- File: /home/YOUR_USERNAME/hosts
1 2 3 4 5 6 7 8 9 10 11 12 13
[webservers] vm2 vm3 [dbservers] vm4 [logservers] vm5 [lamp:children] webservers dbservers
Using the command below to ensure all hosts are accessible.
ansible all --list-hosts
Your output should resemble the following:
example_user@CtlNode:~$ ansible all --list-hosts
hosts (4):
vm5
vm2
vm3
vm4
Set Up Ansible Playbook to Configure Worker Nodes
Create and Add Hashed, Plain-Text Password to Ansible Playbook
Run the command below from your Ansible control node. The command prompts you for a password. This password is used to access the web servers.
python3 -c "from passlib.hash import sha512_crypt; import getpass; print(sha512_crypt.hash(getpass.getpass()))"
Be sure to copy the output from the dollar sign to the period. Paste the resulting hashed password into myplaybook.yml
, in the place holder, {{ HASHED_PASSWORD }}.
Add your Log Server IP Address to Playbook
Grab the IP address of logging sever (vm5
) and paste it into configure rsyslog
section of myplaybook.yml
. Paste the IP address into the place holder, {{ LOG_IP_ADDRESS }}.
Configure Ansible Managed Nodes
Send Setup Script to Each Managed Node
Create a password file to use in the next step. Use the same root password you used when creating these Linodes.
echo 'yourrootpassword' > ~/.ssh/file
Send the managed node setup script to each managed node.
for i in {2..5}; do sshpass -f ~/.ssh/file scp ansibleMN_setup.sh root@vm$i:/root/; done
Check to make sure each managed node contains the script by sending the ls
command via ssh.
for i in {2..5}; do sshpass -f ~/.ssh/file ssh root@vm$i 'ls'; done
The resulting output is the following:
example_user@CtlNode:~$ for i in {2..5}; do sshpass -f ~/.ssh/file ssh root@vm$i 'ls'; done
ansibleMN_setup.sh
ansibleMN_setup.sh
ansibleMN_setup.sh
ansibleMN_setup.sh
Log into Each Worker Node and Run the Setup Script
From the local computer, open four terminal sessions, and within each session, ssh into each managed node (vm2
- vm5
). Once logged in, execute the managed node setup script.
./ansibleMN_setup.sh
NoteThe script asks you to enter a username and password for the new user being created.
Once the setup script has been completed for each managed node, reboot all your Ansible infrastructure Linodes with the command below.
for i in $(cat tmp.txt); do linode-cli linodes reboot $i; done
Upload the SSH Key from the Control Node to the Managed Nodes
Log back into the control node and run the command below. This sends the control node’s limited user’s ssh key to each managed node. This allows easy, secure ssh communication from the control node to the managed nodes.
for i in {2..5}; do sshpass -f ~/.ssh/file ssh-copy-id $USER@vm$i; done
NoteIf the limited user’s password is different from the root user’s password, change the~/.ssh/file
contents to match the limited user’s password.
Confirm all hosts can be pinged using Ansible. A successful run of this command indicates the ssh communication is working.
ansible all -m ping
example_user@CtlNode:~$ ansible all -m ping
vm4 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
vm3 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
vm2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
vm5 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
Run the Ansible Playbook to Configure Managed Nodes
Run the playbook with the command below:
ansible-playbook myplaybook.yml
If all is successful, you should see the output included below. Note that unreachable
and failed
both show 0
instances.
Truncated output:
example_user@CtlNode:~$ ansible-playbook myplaybook.yml
PLAY [webservers] *************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************
ok: [vm3]
ok: [vm2]
TASK [Install Apache Server] **************************************************************************************
changed: [vm3]
changed: [vm2]
{ ... }
PLAY RECAP ********************************************************************************************************
vm2 : ok=13 changed=9 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
vm3 : ok=13 changed=9 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
vm4 : ok=6 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
vm5 : ok=6 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Check the Playbook Run for Success
cURL the IP addresses of the web servers. (vm2
and vm3
)
curl vm2_IPADDRESS
curl vm3_IPADDRESS
example_user@CtlNode:~$ curl 192.0.2.1
Welcome to li1924-155 on 192.0.2.1
example_user@CtlNode:~$ curl 192.0.2.2
Welcome to li1924-165 on 192.0.2.2
Send a logger
command to the LAMP stack defined in the hosts
file.
ansible lamp -m command -a 'logger hurray it works'
example_user@CtlNode:~$ ansible lamp -m command -a 'logger hurray it works'
vm4 | CHANGED | rc=0 >>
vm3 | CHANGED | rc=0 >>
vm2 | CHANGED | rc=0 >>
Search the log server for the entry you just sent.
ansible logservers -m command -a "grep 'hurray it works$' /var/log/syslog" -b
example_user@CtlNode:~$ ansible logservers -m command -a "grep 'hurray it works$' /var/log/syslog" -b
vm5 | CHANGED | rc=0 >>
Oct 25 21:22:00 li1924-200 example_user: hurray it works
Oct 25 21:22:00 li1924-165 example_user: hurray it works
Oct 25 21:22:00 li1924-155 example_user: hurray it works
You have now completed the deployment of your web server, log server, and database server using Ansible.
More Information
You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.
This page was originally published on