The primary purpose of this post is to demonstrate how to manage systemctl services. This is useful when deploying a new service and when modifying a service's configuration. Since we need some kind of change that will trigger Ansible to restart the service when done, we also introduce making a change to a text file.
Restarting a Service
The block required for service management is short and not at all complicated:
- name: Start Nginx Service
tags: nginx,web,rocky
service:
name: nginx
state: started
enabled: true
We start with name and tags directives, then call the service module. We pass into that module the name of the service as a name directive, and the state (action) we want. Note that just as on the command line, the ".service" is not required at the end of the service name. If you are familiar with systemd, you'll know that services must be both started (so the application runs) and enabled (so it can be started the next time the server boots).
If the service is currently running, and we need to make a change to that service, in many cases, the service must be restarted to pick up the change. When restarting, we don't need to specify the 'enabled' directive. We do, however, need a trigger for the restart. The following can replace the last two lines of the example above:
state: restarted
when: nginx_conf.changed
The when directive should be lined up with the 'service:' module line. The when directive in the last line checks the value of a variable, nginx_conf to see if it has been set. If so, the state directive above it will be triggered. We'll discuss how to set that variable below. One final word about the nginx_conf variable before we get there: It is not uncommon to make multiple changes to a file at the same time. When Ansible inspects this variable, it has only one value, and that value reflects the last change that was made. So, if you attempt two different changes to the file at once, the first change is committed, and the second change is not required to be committed (due to impotency), Ansible will not trigger the restart. This is because as mentioned above, the variable only reflects the last change attempt.
Editing a File
We switch now to editing files. If you are familiar with sed and regex expressions, you will find the lineinfile module familiar. It contains many similar constructs, plus many more. The following will modify an existing line in an existing file:
- name: Enable HTTPS in Nginx - Rocky
tags: nginx,rocky,web
lineinfile:
path: "/etc/httpd/conf.d/httpd.conf"
regexp: '^ServerAdmin'
line: "ServerAdmin rlohman@erehwon.net"
when: ansible_distribution == "Rocky"
register: httpd_conf
In this example, we start out by calling the lineinfile module. That will require three parameters: path, regexp, and line. The path parameter tells us in which file the change will be made. The regexp command tells Ansible to search for the string "ServerAdmin," but only when that string starts at the first character in the line (so a line that has one or more spaces in front of Serveradmin will be ignored). Finally, the line parameter instructs Ansible what to change that line to. The example above was not used in the site.yml file; it is strictly an example to illustrate how to specify changing a line. As such, I'll over the register directive below in the block that I actually used.
Because maintaining configurations is so common, I'll add provide another with a different approach:
- name: Enable HTTPS in Nginx - Rocky
tags: nginx,rocky,web
lineinfile:
path: "/etc/nginx/nginx.conf"
line: " listen *:443 ssl;"
insertafter: 'listen 80'
firstmatch: true
backup: true
when: ansible_distribution == "Rocky"
register: nginx_conf
In this block, we're adding a new line (and new capability) to our configuration file for Nginx. We continue to use the lineinfile module, as well as the path and line directives. These all work identically to those above. In this case, we're modifying our nginx.conf file to allow it to accept HTTPS requests (over port 443) in addition to the standard unencrypted port, 80. I wanted to add this configuration instruction immediately after the one that tells Nginx to open port 80, so I perform a regex search on the line I'm looking for that contains "listen 80" and because I searched using the insertafter directive, my line will be inserted on the next line below the one I searched for. This directive has a complement, insertbefore which works similarly.
The firstmatch directive tells ansible to only add the line after the first occurrence of "listen 80." This is handy as sometimes we may edit a file in which our search string appears more than once (not the case, here, as our search string will only appear once in the nginx.conf file).
The backup directive tells Ansible to make a backup copy of the file if and only if it is going to modify that file. If the desired change is already done, no backup will be created.
This takes us to a new directive,
register. Register creates a variable called nginx_conf. It also will
modify the value of that variable to indicate that a change was made if the file was modified.
This is the variable that we inspected above with this directive:
when: nginx_conf.changed
This nginx_conf.changed will evaluate to true if a change was made to the file, and in the desired action (restarting the service) will be triggered.
A final note on this example: Your landing page will likely still not be accessible over the HTTPS port, 443, when following this tutorial. This is because Nginx needs to know what certificate to use when processing inbound SSL/TLS connections. We have not specified that so the browser will give an error. That said, running the playbook after adding these plays will still trigger Ansible to restart nginx (which is, after all, the goal of this post).
A Word About Cruft
In the sysadmin world, cruft can be a problem. Cruft is considered anything that remains after some work was performed that isn't necessary for the server or service to operate. In the post on Managing Files (#10), I noted that the .zip file we decompressed was no longer on the server, even though Ansible pulled it down from a website. Had that file been left behind, it would be considered "cruft." In addition, when we made the change above, adding the https configuration line, Ansible left that file sitting in the same directory as the configuration file we changed. Ansible left that file behind as we may need it to revert the change if we inadvertently broke something with our change. That said, it too, is considered cruft.
Cruft can also be a line in a file. For example, we often make small changes to directives in files, by copying a line we want to change, commenting out the original line, and then making the change to the un-commented line. The commented line, just like the backup file, is cruft.
Cruft is generally considered a bad thing in systems administration. It can populate directories with numerous backup files that make looking for a specific file more difficult. Likewise, a configuration file with many commented versions of different configuration parameters can be difficult to read, and may even cause errors when performing a regex search as a prerequisite to committing a change.
There are a number of strategies to keep cruft at a minimum, and they should be employed by the sysadmin. Ansible is a proper tool to use, as is wrapper scripts that may perform some cleanup after the playbook has completed successfully.
My experience teaches me that backup/saved data in files and on disk tend to lose their value after the next change or two (that is, if you need to roll back to a state that has gone through multiple changes, it is more often than not, likely that the rollback would require so many other fixes, that only a good configuration management system will aid in the rollback.
<PREV - CONTENTS - NEXT>