Ansible Refactoring Part 3 - First Refactor
Part 3 - First Refactor
This is Part 3 of an Ansible Refactoring Series - Start Here
In our first pass through the project we will just focus on breaking it up into more modular components, breaking apart the variables from the code, and cleaning up the base directory by moving out any files or templates into sub-directories. If you are Jeff Geerling and very comfortable with writing roles and collections then this part can be skipped!
-
Break
main.yml
into 3 purpose based playbooks -
Create a
site.yml
wrapper to allow a full deploy with 1 run ofansible-playbook
-
Break out the variables into separate files
-
Clean up the project directory
-
Move any templates into a
./templates
directory and update playbooks -
Move any files (e.g. used by
copy
module or similar) into a./files
directory and update playbooks
-
Plan of Attack
-
Make a refactor
git
branchEven if you don’t use
git
or other SCM (Source Code Management) tool it is a very good place to start. This will give you safety, and confidence, to make major changes with little to no risk. We’ll introduce basic git commands as we go along.git checkout -b refactor-pass-01
Sample OutputSwitched to a new branch 'refactor-pass-01'
cp main.yml provision_database_tier.yml cp main.yml provision_app_tier.yml cp main.yml provision_load_balancer_tier.yml
-
Edit each file so it contains only the appropriate play
Using the editor of your choice,
vim
is installed, delete the redundant extra plays from each of your new files. Your objective is to have 3 focussed playbooks which do 1 tier each.You can verify you haven’t introduced syntax errors by checking with
ansible-playbook --syntax-check
ansible-playbook --syntax-check *.yml
Sample Outputplaybook: main.yml playbook: provision_app_tier.yml playbook: provision_database_tier.yml playbook: provision_load_balancer_tier.yml playbook: teardown-app.yml
NoteLinters such as
yamllint
,ansible-lint
, andansible-review
are outside the scope of this particular stage If you are curious to try then installpython3
and the usepip3
to installansible-review
sudo yum install python3 -y sudo pip3 install ansible-review
Output omitted for brevity and yes using
sudo
forpip3
is a bad practice but a useful shortcut for now (use virtualenvs or link to/usr/local/bin
would be better but a few more steps)Try it on your new playbooksansible-review provision*.yml
Sample OutputWARN: Best practice "Playbooks should not contain logic (vars, tasks, handlers)" not met: provision_database_tier.yml:2: [EXTRA0008] tasks should not be required in a play WARN: Best practice "Playbooks should not contain logic (vars, tasks, handlers)" not met: ....
Lots of warnings but no errors - lets return to those warnings later.
-
Wrap your 3 playbooks in a
site.yml
usingimport_playbook
cat site.yml
Sample Output- import_playbook: provision_database_tier.yml - import_playbook: provision_app_tier.yml - import_playbook: provision_load_balancer_tier.yml
-
Verify your new
site.yml
(if necessary runansible-playbook teardown.yml
first to delete the install)ansible-playbook site.yml
Success (hopefully) - now cleanup and move on to the next stage
A git
Digression
Now we have made some changes, added some files/playbooks, amd main.yml
is redundant it would be nice to snapshot our progress in git
.
This allows us to move forward confidently, and yet revert or recover back to a known good state
-
Configure some
git
global variablesFeel free to use whatever values you want below
git config --global user.email "tok@example.com" git config --global user.name "tok" git config -l
Sample Outputuser.email=tok@example.com (1) user.name=tok (2) core.repositoryformatversion=0 core.filemode=true core.bare=false core.logallrefupdates=true remote.origin.url=https://github.com/tonykay/ansible_flask_app_loader_all_in_one.git remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* branch.main.remote=origin branch.main.merge=refs/heads/main
Your changes have been applied
-
Your email
-
Your name
-
-
Now cleanup your repo and commit your changes
git rm main.yml git add --all git commit -m "Refactored and removed main.yml to site.yml"
Sample Output[refactor-pass-01 a61fb5c] Refactored and removed main.yml to site.yml 5 files changed, 300 insertions(+), 296 deletions(-) delete mode 100644 main.yml create mode 100644 provision_app_tier.yml create mode 100644 provision_database_tier.yml create mode 100644 provision_load_balancer_tier.yml create mode 100644 site.yml
TipYou can check on your changes and state with git status
and view the commit history withgit log
Refactoring the Variables
It is, generally, a bad practice to store code and configuration together, and your 3 playbooks are full of variables. Variables, or vars, can change frequently and being able to modify these or supply alternatives simply is very powerful. In a mature codebase the playbooks, roles, and collections may become predominately read-only in day to day use with the var or inputs changing far more frequently.
-
Break each set of vars out into separate "var files".
There are a number of places we could put them and many ways we can read them back into our playbooks. However in this case the simplest and easiest option is to move them into files in a
group_vars
directory. Each file will take the name of itsgroup
postfixed by.yml
and Ansible will automatically include it at run time.-
make the
group_vars
directorymkdir group_vars
-
Remind yourself of your
group
namesansible-inventory --graph
Sample Output@all: |--@app_servers: | |--app1.fe87.internal | |--app2.fe87.internal |--@database_servers: | |--appdb1.fe87.internal |--@load_balancers: | |--frontend1.fe87.internal |--@ungrouped:
-
Copy your playbooks into
group_vars
using thegroup
names above postfixed with.yml
cp provision_database_tier.yml group_vars/database_servers.yml cp provision_app_tier.yml group_vars/app_servers.yml cp provision_load_balancer_tier.yml group_vars/load_balancers.yml
-
Cleanup each new variable file
-
Delete all non variable lines including
vars:
-
Fix the indentation, aligning the vars with column 1
Tipvim
is extremely good at these types of operationsTable 1. vim command mode
optionsCommand Function ndd
Delete n lines (
ex
mode is even more powerful)n<<
allows you to change indentation levels over n multiple lines
For example your files should look like this:
head group_vars/database_servers.yml
Sample Outputpostgres_rhel7_repo: "https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm" postgres_packages: - postgresql10 - postgresql10-server - postgresql10-contrib - postgresql10-libs postgres_library: python-psycopg2 postgres_10_data_dir: /var/lib/pgsql/10/data postgres_10_bin_path: /usr/pgsql-10/bin
-
-
Now remove the vars from each of your playbooks
Edit each playbook removing the
vars:
section completely
-
-
Test your changes remembering to run
ansible-playbook teardown.yml
first if necessaryansible-playbook site.yml
Your, slowly getting better,
site.yml
should run successfully. If not debug, fix, until successful.TipYAML at first appears very fussy about indentation etc but soon this becomes natural. Adopt a consistent style as when creating lists for example you have 2 indentation styles to chose from. ansible-playbook <playbook-name> --syntax-check
us useful andpip3
can also installyamllint
-
Finally
commit
your changesgit add --all git commit -m "Refactored all vars to group_vars"
Sample Output[refactor-pass-01 9399a27] Refactored all vars to group_vars 6 files changed, 53 insertions(+), 54 deletions(-) create mode 100644 group_vars/app_servers.yml create mode 100644 group_vars/database_servers.yml create mode 100644 group_vars/load_balancers.yml
-
Examine your
git
history withgit log
git log
Sample Outputcommit 9399a277637e74cc9ccb167daa464d6b813dd552 Author: tok <tok@example.com> Date: Thu Jul 23 17:58:42 2020 +0000 Refactored all vars to group_vars commit a61fb5ce457e88759a8a63cdf4b938c9df73581e Author: tok <tok@example.com> Date: Thu Jul 23 17:10:22 2020 +0000 Refactored and removed main.yml to site.yml commit 531439bee9f84c1068be761e5c10fa65ad4abb7a Author: Tony <tony.g.kay@gmail.com> Date: Tue Jul 21 13:30:19 2020 -0600 ....
Notice you also see my own earlier commits in the history prior to your own work
Clean Up your Templates
The root directory of your project is a bit cluttered, including several template files.
-
Make a
templates
sub-directorymkdir templates
-
Move all the jinja template files (ending
.j2
)mv *.j2 templates
-
All your playbooks now have an incorrect path
Tip
|
Sample Output
|
-
Fix each of the
src:
lines above to include thetemplates
sub-directory in the path -
Validate your work by running
ansible-playbook teardown-app.yml
and thenansible-playbook site.yml
-
Before committing your changes use
git status
to see the changes.git diff
will show the details of your editsgit status
Sample Output# On branch refactor-pass-01 # Changes not staged for commit: # (use "git add/rm <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # deleted: flask_service.j2 # deleted: haproxy.cfg.j2 # deleted: launch_resource_hub.j2 # deleted: pg_hba.conf.j2 # modified: provision_app_tier.yml # modified: provision_database_tier.yml # modified: provision_load_balancer_tier.yml # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # templates/ no changes added to commit (use "git add" and/or "git commit -a")
-
Save your changes with
git add
andgit commit
git add --all git commit -am "Cleaned up jinja templates to templates directory"
Sample Output[refactor-pass-01 7e0d63a] Cleaned up jinja templates to templates directory 7 files changed, 4 insertions(+), 4 deletions(-) rename flask_service.j2 => templates/flask_service.j2 (100%) rename haproxy.cfg.j2 => templates/haproxy.cfg.j2 (100%) rename launch_resource_hub.j2 => templates/launch_resource_hub.j2 (100%) rename pg_hba.conf.j2 => templates/pg_hba.conf.j2 (100%)
-
Finally
merge
you changes into yourmain
branchgit checkout main git merge refactor-pass-01
Sample OutputUpdating 531439b..7e0d63a Fast-forward group_vars/app_servers.yml | 26 ++++++++++++ group_vars/database_servers.yml | 24 +++++++++++ group_vars/load_balancers.yml | 3 ++ main.yml | 296 --------------------------------------------------------------------------------------------------------- provision_app_tier.yml | 77 +++++++++++++++++++++++++++++++++++ provision_database_tier.yml | 85 +++++++++++++++++++++++++++++++++++++++ provision_load_balancer_tier.yml | 81 +++++++++++++++++++++++++++++++++++++ site.yml | 3 ++ flask_service.j2 => templates/flask_service.j2 | 0 haproxy.cfg.j2 => templates/haproxy.cfg.j2 | 0 launch_resource_hub.j2 => templates/launch_resource_hub.j2 | 0 pg_hba.conf.j2 => templates/pg_hba.conf.j2 | 0 12 files changed, 299 insertions(+), 296 deletions(-) create mode 100644 group_vars/app_servers.yml create mode 100644 group_vars/database_servers.yml create mode 100644 group_vars/load_balancers.yml delete mode 100644 main.yml create mode 100644 provision_app_tier.yml create mode 100644 provision_database_tier.yml create mode 100644 provision_load_balancer_tier.yml create mode 100644 site.yml rename flask_service.j2 => templates/flask_service.j2 (100%) rename haproxy.cfg.j2 => templates/haproxy.cfg.j2 (100%) rename launch_resource_hub.j2 => templates/launch_resource_hub.j2 (100%) rename pg_hba.conf.j2 => templates/pg_hba.conf.j2 (100%)
Solution
I’ve deliberately created a second repo with a solution, to avoid the temptation of just checking out the relevant commit/tag/branch (more on them later). Meanwhile it can be found here
Next Steps
Congratulations, you know have a cleaner codebase that is more modular and easier to maintain. However it is still a bit "clunky" and it would be awkward for another team to "borrow" say your Postgres playbook.
In Part 4 we will look more closely at roles
and both convert the bulk of provision_database_tier.yml
into a role and also look to see if someone else has already written a good HAProxy role on Ansible Galaxy