When the import module is used, all statements are pre-processed at the time playbooks are parsed. This also means that, during actual execution, if any statement changes, such statements will not be considered. Hence, it is static.
On the other hand, when include module is used, all statements are processed only during execution of the playbook. Meaning, after the statements are parsed, any changes to the statements encountered during execution will be used.
Including roles, tasks, or variables adds them to a playbook dynamically. Ansible processes included files and roles as they come up in a playbook, so included tasks can be affected by the results of earlier tasks within the top-level playbook. Included roles and tasks are similar to handlers - they may or may not run, depending on the results of other tasks in the top-level playbook.
The primary advantage of using include_* statements is looping. When a loop is used with an include, the included tasks or role will be executed once for each item in the loop.
In most cases however, it is recommended to use static assignments for playbooks, because it is more reliable. With dynamic ones, it is hard to debug playbook problems due to its dynamic nature.
-
In your https://github.com//ansible-config-mgt GitHub repository, start a new branch and call it "dynamic-assignments".
-
Therein, create a new folder, name it "dynamic-assignments" and create a file in the new folder "env-vars.yml"
Since we will be using the same Ansible to configure multiple environments, and each of these environments will have certain unique attributes, such as servername, ip-address etc., we will need a way to set values to variables per specific environment. Thus, we shall create a folder to keep each environment’s variables file.
- create a new folder "env-vars", and for each environment (dev,stage,uat and prod), create new YAML files which we will use to set variables.
The tree structure thus looks like the below:
- Update the env-vars.yml file (dynamic-assignments/env-vars.yml) with the code below:
---
- name: collate variables from env specific file, if it exists
hosts: all
tasks:
- name: looping through list of available files
include_vars: "{{ item }}"
with_first_found:
- files:
- dev.yml
- stage.yml
- prod.yml
- uat.yml
paths:
- "{{ playbook_dir }}/../env-vars"
tags:
- always
We made use of a special variables "{ playbook_dir }", this will help Ansible to determine the location of the running playbook, and from there navigate to other path on the filesystem. "{ inventory_file }" on the other hand will dynamically resolve to the name of the inventory file being used, then append .yml so that it picks up the required file within the 'env-vars' folder.
We are including the variables using a loop 'with_first_found' which implies that, looping through the list of files, the first one found is used. This is good so that we can always set default values in case an environment specific env file does not exist.
- Update site.yml file to make use of the dynamic assignment. Our site.yml file should now look like below:
---
- hosts: all
- name: Include dynamic variables
tasks:
import_playbook: ../static-assignments/common.yml
include: ../dynamic-assignments/env-vars.yml
tags:
- always
- hosts: webservers
- name: Webserver assignment
import_playbook: ../static-assignments/uat-webservers.yml
In order to sync our github with the jenkins server directly, we shall configure our VS code to work directly with github;
- On Jenkins-Ansible server make sure that git is installed with git --version, then go to ‘ansible-config-mgt’ directory and run
git init
git pull https://github.com/<your-name>/ansible-config-mgt.git
git remote add origin https://github.com/<your-name>/ansible-config-mgt.git
git branch roles-feature
git switch roles-feature
Next is to create a role for MySQL database – it should install the MySQL package, create a database and configure users. Instead of re-inventing the wheel however, we shall use roles already developed by open source engineers. With Ansible Galaxy, we can simply download a ready to use ansible role, and keep going.
We will be using a MySQL role developed by 'geerlingguy'.
- Inside roles directory , create your new MySQL role with
ansible-galaxy install geerlingguy.mysql
and rename the folder to mysql;
mv geerlingguy.mysql/ mysql
- Read README.md file, and edit roles configuration to use correct credentials for MySQL required for the tooling website.
The point wherein changes were made in the mysql role is shown below:
- The main.yml file in the defaults folder: the database name was modified to "tooling" and the user to "webaccess". The configuration was set such that the user has access to the database , a privilege set with GRANT option.
- upload the changes into your GitHub:
git add .
git commit -m "Commit new role files into GitHub"
git push --set-upstream origin roles-feature
- Create a Pull Request and merge it to main branch on GitHub.
In order to be able to chose which loadbalancer to use (Apache or nginx), we need to have two roles respectively; Apache and Nginx.
-
Using the earlier introduced community role; geerlingguy, cd to the role directory, run
ansible-galaxy install geerlingguy.apache
andansible-galaxy install geerlingguy.nginx
. -
rename the role to apache and nginx respectively;
mv geerlingguy.apache/ apache
mv geerlingguy.nginx/ nginx
- Update both static-assignment and site.yml files to refer the roles.
- The static assignment folder was updated to contain db.yml file and lb.yml file.
The lb.yml file is as shown below;
- The site.yml file was then updated to refer the above two playbooks (db.yml and lb.yml) as well as the env-vars.yml file. A copy of the file is attached below;
- Since we cannot use both Nginx and Apache load balancer, we shall add a condition to enable either one – this will be done by making use of variables.
-
Declare a variable in defaults/main.yml file inside the Nginx and Apache roles. Name each variables 'enable_nginx_lb' and 'enable_apache_lb' respectively.
-
Set both values to false
-
Declare another variable in both roles called 'load_balancer_is_required' and set its value to false as well.
The snippet below is placed in the nginx roles' defaults/main.yml file.
enable_nginx_lb: false
load_balancer_is_required: false
while the below is placed in Apache's defaults/main.yml file.
enable_apache_lb: false
load_balancer_is_required: false
- We will then use 'env-vars\uat.yml' file to define which loadbalancer to use in UAT environment by setting respective environmental variable to true.
- Activate load balancer, and enable nginx by setting these in the respective environment’s env-vars file.
enable_nginx_lb: true
load_balancer_is_required: true
The same must work with apache LB, so you can switch it by setting respective environmental variable to true and other to false.
To test this, you can update inventory for each environment and run Ansible against each environment.
From our knowledge of loadbalancing with apache in the previous project, the following points are crucial when configuring apache as a loadbalancer; We shall highlight the points to guide us to where necessary modifications will be made in the downloaded roles.
This will be performed by default as part of the pre-configured settings in the apache role that was downloaded.
sudo a2enmod rewrite
sudo a2enmod proxy
sudo a2enmod proxy_balancer
sudo a2enmod proxy_http
sudo a2enmod headers
sudo a2enmod lbmethod_bytraffic
This will be included in the main.yml file present in the tasks directory of the role. The modules configurations were formatted in an ansible snippet format as shown below;
- name: enable configurations
become: true
shell: a2enmod rewrite proxy proxy_balancer proxy_http headers lbmethod_bytraffic
notify: restart apache
and carefully placed within the "#configure Apache" section present in the file:
<Proxy "balancer://myapp1">
BalancerMember http://<WebServer1-Private-IP-Address>:80 loadfactor=5 timeout=1
BalancerMember http://<WebServer2-Private-IP-Address>:80 loadfactor=5 timeout=1
ProxySet lbmethod=bytraffic
# ProxySet lbmethod=byrequests
</Proxy>
ProxyPreserveHost On
ProxyPass / balancer://myapp1/
ProxyPassReverse / balancer://myapp1/
The apache virtual host configuration for loadbalancing shown above was placed in the "templates/vhosts.conf.j2" file in the section "{% if vhost.documentroot is defined %}". See below
Other modifications that will be done in the defaults/main.yml file of the apache role are noted below ;
- Define the loadbalancer name (server group) in the apache vhost section as shown below:
- Set the apache loadbalancer conditions to "true"
- Remove the default apache configuration page by setting "apache_remove_default_vhost: true"
Finally, enable the apache loadbalacer conditions that was defined in env-vars/uat.yml file by setting the variable to true.
Above, the nginx condition was commented out. The converse will apply when we are set to use nginx as a loadbalancer.
- The playbook was run "ansible-playbook -i inventory/uat.yml playbooks/site.yml" in my "Ansible-congif" directory and the output is shown below:
- The public IP of the loadbalancer instance was rendered against our web browser
- To ascertain the load balancing action of apache, the access_log file of the two web server instances were checked
sudo tail -f -n8 /var/log/httpd/access_log
to see how traffic requests were efficiently loadbalanced.
---------------------END OF APACHE LB------------------------------------
From our knowledge of loadbalancing with Nginx in the previous project, the following points are crucial when configuring nginx as a loadbalancer;
Updating the '/etc/hosts' file to include the web servers’ names (e.g. Web1 and Web2) and their private IP addresses.
This was done using Ansible blockinfile module. The module is used to insert, update or remove a block of lines (multi-line text) from files on the remote nodes. These blocks are surrounded by the marker like begin and end which can be a custom marker. The module helps to work with the different types of files.
The code below was thus included in tasks/main.yml file within the vhost configuration section.
- name: set webservers host name in /etc/hosts
become: yes
blockinfile:
path: /etc/hosts
block: |
{{ item.ip }} {{ item.name }}
loop:
- { name: web1, ip: 172.31.83.5 }
- { name: web2, ip: 172.31.86.8 }
Editing nginx configuration file "/etc/nginx/nginx.conf" to include the upstream server and other http options.
The following was added in the defaults/main.yml file of our nginx role
upstream myapp1 {
server Web1 weight=5;
server Web2 weight=5;
}
server {
listen 80;
server_name <Private IP of lb server>;
location / {
proxy_pass http://myapp1;
}
}
In the same defaults/main.yml file, enable nginx extra http options by uncommenting the said section as shown below. Also, remove the default nginx page by setting the remove default page to true.
- Run the playbook
After running the playbook, the following error was encountered. The error was found to be due to the following reasons;
Reason 1: It was discovered that we can not have apache and nginx listening at port 80 simultaneously. As a result of this, the nginx service failed to install. I had to disable and stop apache in defaults/main.yml file of apache role.
Reason 2: My nginx vhost configuration was found to be missing quite an important snippet: marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.name }}"
.
As a result, the blockinfile didn't loop through the two web servers that was meant to be inserted in the "/etc/hosts" file. Only one web server was inserted as shown below from the nginx -t
configuration test output:
Solution: The missing snippet was included;
** The playbook was run and nginx was configured successfully.
- Input the public IP of the loadbalancer in a web browser;
-----------END------------------