Configure Load Balancer and multiple dynamic web servers using Ansible

Hi all, in this blog post we are going to see one more use case of ansible to configure a load balancer and multiple web servers dynamically using AWS EC2 instances.

So before we start our practical, let's understand some technical terms first.

Load Balancer:- It is a server running to distribute or pass the client requests/load to all the available web servers so that there is lagging or downfall of services. 

AWS EC2:- It is a service provided by Amazon Web Services to dynamically create virtual machines/OS with desired hardware, networking, security and other configurations. 

Ansible:- It is a configuration management tool used to configure almost all devices and softwares in any kind of environment, may it be UNIX, LINUX, Windows, cloud, containers, network devices etc. Ansible is built on top of Python and is an agent-less tool running on Control Node facilitating configurations on Managed Nodes. 

Web Server:- It is a kind of server where we usually run our websites/webpages. Some common examples are like, Apache WebServer, nginx, IIS web server etc.

So lets start our setup.

Prerequisites:-

  1. Need to have one controller node with Ansible installed. Check with ansible --version command
  2. Should have access to one aws cloud account with IAM configured.

Go to /etc/ansible/roles path and then we need to create 3 ansible roles, 

  • First to create aws ec2 instances.
  • Second to install and configure load balancer. Here we will use haproxy software.
  • Third to install and configure Apache web server. 

Ansible command to create roles is:

ansible-galaxy init <role-name>

 ansible-galaxy init ec2_instance  
 ansible-galaxy init lbserver  
 ansible-galaxy init webserver  

Contents of each role will be some predefined directories defaults, files, handlers, meta, tasks, templates, tests, vars.

Now we will write tasks to create our ec2 instances. Open ec2_instance/tasks/main.yml file and copy below contents to it.

 ########################################################  
 ##### Below file is required to login aws account #######  
 - include_vars: secure.yml 

In some time we will generate one ansible vault file called secure.yml file to store our secret aws credentials which is used to login to our aws account.

 #####################################################  
 ###### Creating a security group for ec2 instance ###  
 - name: create a security group  
  ec2_group:  
   name: "{{ security_group }}"  
   description: "An ec2 group for ansible webserver"  
   region: "{{ region }}"  
   vpc_id: "vpc-f735d19c"  
   state: present  
   aws_access_key: "{{ myuser }}"  
   aws_secret_key: "{{ mypass }}"  
   rules:  
    - proto: tcp  
     from_port: 22  
     to_port: 22  
     cidr_ip: 0.0.0.0/0  
     rule_desc: "Allow ssh on port 22"  
    - proto: tcp  
     ports:  
     - 80  
     - 8080  
     cidr_ip: 0.0.0.0/0  
     rule_desc: "ansible-grp-80"  
   rules_egress:  
    - proto: all  
     cidr_ip: 0.0.0.0/0  
  register: grp_result  
  tags: create_group  

All the words inside "{{ }}" are ansible variables written in jinja2 format which is a python template engine. We will define the values of these varibales below in some time.

 ################################################  
 #### Starting our ec2 instance on aws #########  
 - name: provision an ec2 instance  
  ec2:  
   key_name: "{{ keypair }}"  
   instance_type: "{{ instance_type }}"  
   image: "{{ image }}"  
   wait: yes  
   #wait_timeout: 600  
   instance_tags:  
    Name: "{{ instance_tag }}"  
   count: 1  
   vpc_subnet_id: "subnet-8b000de3"  
   assign_public_ip: yes  
   region: "{{ region }}"  
   state: present  
   # group_id: "sg-0416e7537feb95257"  
   group: "{{ security_group }}"  
   aws_access_key: "{{ myuser }}"  
   aws_secret_key: "{{ mypass }}"  
  register: ec22  
  when: grp_result.failed == false  
  tags: create_ec2  

The value of  "{{ myuser }}" and "{{ mypass }}" we will get from secure.yml that we have included in this file at top.

 #############################################  
 ##### Waiting to let ec2 instance comes up ###  
 - name: wait for SSH to come up  
  wait_for:  
   host: "{{ item.public_ip }}"  
   port: 22  
   state: started  
  with_items: "{{ ec22.instances }}"  
  when: ec22.failed == false  

Finally wait_for module is required to let the ec2 instance come up properly.

We will create one ansible vault named secure.yml under path /etc/ansible/roles/ec2_instance/vars/ which will contain my aws account AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. For security we will attach a password to it

 ansible-vault create --vault-id ec2@prompt secure.yml  
 New vault password (ec2):  
 Confirm new vault password (ec2):  
 myuser: "AWS_ACCESS_KEY_ID"  
 mypass: "AWS_SECRET_ACCESS_KEY"  

Now open ec2_instance/vars/main.yml file and paste below code in it.

 ---  
 # vars file for ec2_instance  
 security_group: ansible_web_sg   # This name can be any group name  
 keypair: "EC2 Tutorial"          # This name should match to your Key pair generated from aws console  
 image: "ami-0ebc1ac48dfd14136"   # This is the aws in-built image id. Can provide any valid image id  
 instance_type: "t2.micro"        # This instance type comes with aws free tier  
 region: "ap-south-1"             # Need to provide aws valid region code where we want to create out ec2 instance  
 instance_tag: "webserver"        # Can be any unique name but it is required later  

Now we will create tasks for load balancer server. Open /etc/ansible/roles/lbserver/tasks/main.yml file and copy below content

 ---  
 # tasks file for lbserver  
 ###########################################  
 ##### Insatlling load balancer software ###  
 - name: Install haproxy load balancer  
  package:  
   name: "haproxy"  
   state: present  
 #################################################  
 ##### Configure LB to register new webservers ###  
 - name: Configure load balancer for multiple web servers  
  template:  
   src: "haproxy.cfg"  
   dest: "/etc/haproxy/haproxy.cfg"  
  notify: lb restart  
 ##########################################  
 #### Start load balancer service ######  
 - name: start haproxy load balancer server  
  service:  
   name: "haproxy"  
   state: started  

Here haproxy.cfg is the conf file for haproxy server. We have defined this file at /etc/ansible/roles/lbserver/templates/haproxy.cfg. Open this file and save below content

 #---------------------------------------------------------------------  
 # Example configuration for a possible web application. See the  
 # full configuration options online.  
 #  
 #  https://www.haproxy.org/download/1.8/doc/configuration.txt  
 #  
 #---------------------------------------------------------------------  
 #---------------------------------------------------------------------  
 # Global settings  
 #---------------------------------------------------------------------  
 global  
   # to have these messages end up in /var/log/haproxy.log you will  
   # need to:  
   #  
   # 1) configure syslog to accept network log events. This is done  
   #  by adding the '-r' option to the SYSLOGD_OPTIONS in  
   #  /etc/sysconfig/syslog  
   #  
   # 2) configure local2 events to go to the /var/log/haproxy.log  
   #  file. A line like the following can be added to  
   #  /etc/sysconfig/syslog  
   #  
   #  local2.*            /var/log/haproxy.log  
   #  
   log     127.0.0.1 local2  
   chroot   /var/lib/haproxy  
   pidfile   /var/run/haproxy.pid  
   maxconn   4000  
   user    haproxy  
   group    haproxy  
   daemon  
   # turn on stats unix socket  
   stats socket /var/lib/haproxy/stats  
   # utilize system-wide crypto-policies  
   ssl-default-bind-ciphers PROFILE=SYSTEM  
   ssl-default-server-ciphers PROFILE=SYSTEM  
 #---------------------------------------------------------------------  
 # common defaults that all the 'listen' and 'backend' sections will  
 # use if not designated in their block  
 #---------------------------------------------------------------------  
 defaults  
   mode          http  
   log           global  
   option         httplog  
   option         dontlognull  
   option http-server-close  
   option forwardfor    except 127.0.0.0/8  
   option         redispatch  
   retries         3  
   timeout http-request  10s  
   timeout queue      1m  
   timeout connect     10s  
   timeout client     1m  
   timeout server     1m  
   timeout http-keep-alive 10s  
   timeout check      10s  
   maxconn         3000  
 #---------------------------------------------------------------------  
 # main frontend which proxys to the backends  
 #---------------------------------------------------------------------  
 frontend main  
   bind *:8080  
   acl url_static    path_beg    -i /static /images /javascript /stylesheets  
   acl url_static    path_end    -i .jpg .gif .png .css .js  
   use_backend static     if url_static  
   default_backend       app  
 #---------------------------------------------------------------------  
 # static backend for serving up images, stylesheets and such  
 #---------------------------------------------------------------------  
 backend static  
   balance   roundrobin  
   server   static 127.0.0.1:4331 check  
 #---------------------------------------------------------------------  
 # round robin balancing between the various backends  
 #---------------------------------------------------------------------  
 backend app  
   balance   roundrobin  
   {% for host in groups['tag_Name_webserver'] %}  
   server app1 {{ host }}:80 check  
   {% endfor %}  

In this file we have mainly modified the listening port of haproxy server to 8080 and

 frontend main  
   bind *:8080  

we have added jinja2 syntax to dynamically register websever IPs  as

 backend app  
   balance   roundrobin  
   {% for host in groups['tag_Name_webserver'] %}  
   server app1 {{ host }}:80 check  
   {% endfor %}  

In /etc/ansible/roles/lbserver/tasks/main.yml file, we have referenced one handler using notify directive and the implementation for that handler needs to be defined in /etc/ansible/roles/lbserver/handlers/main.yml file as

 ---  
 # handlers file for lbserver  
 ###################################################################  
 ## Whenever changes happen in haproxy conf file, restart server ####  
 - name: lb restart  
  service:  
   name: "haproxy"  
   state: restarted  

Its time to create ansible role for webserver.  Open /etc/ansible/roles/webserver/tasks/main.yml file and copy below content

 ###############################################  
 #### Installing apache webs server software ###  
 - name: Install httpd web server  
  package:  
   name: "httpd"  
   state: present  

To test our load balancer we will create a webpage which will print the host (in our case ec2 instance) private IP. So every time we refresh webpage we can see different IP will be printed in round robin fashion.

 ###############################################  
 ### Configure webserver to show new webpage ###  
 - name: Configure httpd web server  
  copy:  
   content: "Hi from {{ ansible_hostname }}"  
   dest: /var/www/html/index.html  

Then we will start our web server.

 ############################  
 ### Start the web server ###  
 - name: Start httpd web server  
  service:  
   name: "httpd"  
   state: started  
 ~  

So we have created all the required 3 roles. Now we can create our playbooks which actually use these roles to create new EC2 instances, install haproxy (Load Balancer) and webserver on them.

Create another directory named /root/dynamicinventory/ and created 2 files 

  • ec2_instance.yml -- to provision new EC2 instances 
  • lb_ec2_setup.yml -- to install and start load balancer & web servers.

We also need to copy two files ec2.py and ec2.ini files from github path

Download the files and make ec2.py executable using 

 chmod 755 ec2.py  

ec2.py will list out all the running ec2 instances. This script required three env variables to set.

 export AWS_ACCESS_KEY_ID='your-aws-key'  
 export AWS_SECRET_ACCESS_KEY='your-aws-secret-key'  
 export AWS_REGION='ap-south-1' # which region you want to start the ec2 instance  

Open ec2_instance.yml and write below yml code

- name: create one ec2 instance
  hosts: localhost
  connection: local
  gather_facts: false

  tasks:
   - include_role:
       name: "ec2_instance"
     vars:
       region: "ap-south-1"
       instance_tag: "lbserver" # valid values are'webserver' or 'lbserver'

   - name: Refresh the ec2.py cache
     command: python3 /root/ansibleWorks/mycode/dynamicInventory/inv/ec2.py --refresh-cache

   - name: Refresh inventory
     meta: refresh_inventory

Here we are asking ansible to run ec2_instance role in the localhost with two variables region & instance_tag. In next playbook, based on this instance_tag we will run different roles. The command module will refresh our dynamic inventory so that next playbook can access those dynaic IPs.

We then write lb_ec2_setup.yml playbook with below content

 # Setting up load balancers and web server  
 - name: Configure webserver  
  hosts: tag_Name_webserver  
  become: yes  
  remote_user: ec2-user  
  roles:  
   - role: webserver  
 - name: Configure load balancer  
  hosts: tag_Name_lbserver  
  become: yes  
  remote_user: ec2-user  
  roles:  
   - role: lbserver  

So first run ec2_instance.yml playbook to create a new EC2 instance which will be our load balancer using below command

 ansible-playbook --vault-id prod@prompt ec2_instance.yml  

Run the same playbook 3 times after modifying instance_tag: "webserver" to create 3 EC2 instances as webserver

 ansible-playbook --vault-id prod@prompt ec2_instance.yml  

We can verify in our aws console that two new ec2 instances are created with Name as lbserver & webserver.

We can also verify in localhost that all the EC2 instances are spawned with below command

Now its time to run our final playbook lb_ec2_setup.yml that will setup one load balancer and 3 webservers with below command.

 ansible-playbook lb_ec2_setup.yml  

So with this our setup is complete and now we can verify if our load balancer is working properly

Go to web browser and type the load balancer IP and port as 8080. In my case 

http://13.233.183.133:8080/

As we refresh our web page, we can see different private IP showing in round robin fashion. 


Hope you learnt something new and useful today. 

Comments