The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.
CFEngine is a scalable configuration management tool for your IT infrastructure. CFEngine allows you to use the same tool and policy language to manage everything in your infrastructure: Linux, Mac, Windows, BSD, Solaris, IBM AIX, HP-UX, and others. You install an agent (written in C) on each machine you want to manage. CFEngine supports many platforms and is useful for resource-constrained environments such as large infrastructures or small Internet of Things (IoT) devices.
In this tutorial, you will install CFEngine Community Edition 3.21 on Ubuntu 20.04, write sample policy files, and automate your policy runs.
To complete this tutorial, you will need:
sudo
user. To set this up, follow our guide Initial Server Setup with Ubuntu 20.04.sudo
), such as ssh
.nano
.In this step, you will install CFEngine using apt
and the package repositories. The main advantage of using apt
for installation is that you can use apt
to update CFEngine later.
There are other ways to install CFEngine for different use cases:
cf-remote
is a flexible choice that handles installing on multiple machines, different versions of CFEngine, and nightly builds.quick-install
shell script is a good choice for a single machine. You can install by copying a single command into the terminal.If you use one of these alternatives to install CFEngine Community or Enterprise, skip to Step 2 — Starting CFEngine after installing or go to Step 3 — Creating Your First Policy after bootstrapping.
To install CFEngine using apt
, you need apt
to be able to access the repository. The first step is to add the CFEngine public GPG key to the apt
key collection so that apt
can trust packages from the repository.
Download the key with this command:
- wget https://cfengine.com/pub/gpg.key -O ~/cfengine-gpg.key
Then use apt-key
to add it:
- sudo apt-key add ~/cfengine-gpg.key
Next, you need to add the CFEngine repository as a source to apt
with the following command:
- sudo sh -c "echo 'deb https://cfengine-package-repos.s3.amazonaws.com/pub/apt/packages stable main' > /etc/apt/sources.list.d/cfengine-community.list"
This command adds the CFEngine repository to a file in the apt
sources list directory.
Without the sh
argument and -c
option, this command would get a Permission denied
error because the redirection of the output is not performed by sudo
. To address this issue, you use sh
to run a shell with sudo
and give the command to it by using the -c
option.
Now, run the update
command so that apt
can see the new packages from the CFEngine repository:
- sudo apt update
apt
With the CFEngine package now registered with apt
, you can use apt
to install CFEngine:
- sudo apt install cfengine-community
If you receive a warning about the package being built on a different (older) operating system, you can ignore it. The package still works.
When the installation is complete, you will see the components that make up CFEngine in /var/cfengine/bin
. Out of all of these, the agent (cf-agent
) is the one you will interact with most of the time. This binary evaluates the policy you write, makes changes to the system, and records information about what it did. By default, the agent will wake up every five minutes, fetch new policy if necessary, and enforce it.
Use the following command to check that the agent was installed successfully:
- sudo cf-agent --version
The version number will print to the screen:
OutputCFEngine Core 3.21.0
Note: At the time of this writing, the current version is 3.21.0
. Your version number may differ.
If the cf-agent
command doesn’t work, check that /var/cfengine/bin/
is in your PATH
variable and that cf-agent
exists in that directory.
You can also check the installation log file in /var/log/CFEngine-Install.log
for some hints about what went wrong.
To start CFEngine, you need to bootstrap the agent. Use the following command to do that:
- sudo cf-agent --bootstrap 127.0.0.1
This command tells the agent to start the CFEngine components and to fetch policy from this machine, 127.0.0.1
.
In the future, you will want multiple machines to fetch policy from the same server. In that case, 127.0.0.1
will be replaced by the IP address of that server. However, for getting started and learning about CFEngine, you’ll use one machine and 127.0.0.1
in this tutorial.
You now have CFEngine installed and running on your server. The next step is to start writing policy.
To automate a system administration task using CFEngine, you create a policy file, which is written in CFEngine’s own Domain Specific Language (DSL). To learn more about the policy language, such as what it can do and how it’s structured, see the CFEngine reference documentation.
Policy files differ from scripts in that they are declarative: you describe the desired state for the system, and CFEngine will check if that’s already the case and will make changes only if necessary. Some examples of things you might want to express in policy are:
sammy
exists.curl
package updated (and installed).telnet
is not installed.httpd
, if it’s running./usr/bin
folder.In all of these cases, you would write policy and put it in the correct place on your server (/var/cfengine/masterfiles). Then CFEngine will:
sammy
, or changed the permissions on /usr/bin
, these changes would be restored automatically within five minutes.In this step, you’ll create a “Hello World” policy to output the text “Hello!” to the terminal.
Use nano
or your favorite text editor to create a new file, ~/hello_world.cf
:
- nano ~/hello_world.cf
Within the file, add the following:
bundle agent main
{
reports:
"Hello!";
}
CFEngine policy is declarative and is not evaluated from top to bottom.
In CFEngine policy language, each statement you make about what you are managing is called a promise. Promises are organized into bundles. Bundles are useful because they allow you to choose which parts of the policy are run (and which parts aren’t) and what order they run in.
In this policy, reports
is a promise type, responsible for printing the message to the terminal. The agent
keyword, after bundle
, signifies that this bundle is for the cf-agent
component. The name of the bundle is main
. The bundle sequence specifies the bundles to evaluate. By default, it includes the main
bundle, so you don’t need to change it.
Save and close the file. Using nano
, press CTRL+X
to exit, Y
to save, and ENTER
to confirm the filename and close the file.
Since CFEngine typically runs as the root user, it tries to prevent unauthorized changes to the system by requiring strict permissions on policy files. (Allowing other users to edit the policy CFEngine runs would allow them to make changes to the system as a privileged user). You can use chmod
to edit the file permissions and thus prevent other users from editing this file:
- chmod 600 hello_world.cf
To run the policy, use the following command:
- sudo cf-agent ~/hello_world.cf
The CFEngine agent finds your bundle (because of the main
name), looks at the promises inside of it, and evaluates them. When evaluating promises, the agent checks whether it needs to make changes to the system. In this case, there was just one promise, and the agent determined that it needs to print a message to the terminal to satisfy it.
You’ll receive the following output:
OutputR: Hello!
By default, CFEngine keeps track of what has been done and skips promises evaluated recently (within the last minute). If you try to rerun the policy, it won’t print anything. You can disable this locking with the --no-lock
command line option:
- sudo cf-agent --no-lock hello_world.cf
You’ve created and run your first policy. Although it is useful to be able to output to the console, this policy doesn’t actually change the system. You’ll do that in the next step.
In the previous step, you created a policy to print a message to the terminal. However, a more realistic use case is ensuring that a file with some specific content exists on all the hosts in your infrastructure. In this step, you will write a policy that creates a text file (/tmp/my_file.txt
) with the contents Hello, CFEngine!
.
Open a new policy file, ~/file_management.cf
:
- nano ~/file_management.cf
Add the following content:
bundle agent manage_my_file
{
files:
"/tmp/my_file.txt"
content => "Hello, CFEngine!$(const.n)";
}
The bundle’s name is manage_my_file
instead of main
. As you write more policy, you should give each bundle a unique and descriptive name. There can only be one main
bundle.
Because this policy manages files, the promise type is files
. The content
attribute is used for files
promises, stating what the content of that file should be. The last part of the string, $(const.n)
, expands a special variable const.n
, which results in a new line at the end of the file.
Save and close the file.
As before, set the strict permissions on the policy file:
- chmod 600 file_management.cf
Now run the file with some additional command line options:
- sudo cf-agent --no-lock --info ~/file_management.cf --bundle manage_my_file
Specifying the bundle with --bundle manage_my_file
is necessary since there is no longer a main
bundle.
The --info
option makes CFEngine print informational messages about the changes it’s making to the system.
The output from this command will contain information about these changes:
Outputinfo: Using command line specified bundlesequence
info: Created file '/tmp/my_file.txt', mode 0600
info: Updated file '/tmp/my_file.txt' with content 'Hello, CFEngine!
This output indicates that CFEngine created a text file called my_file.txt
in the /tmp
directory with the contents Hello, CFEngine!
.
If you run the same command again, the messages about creating and updating the file will no longer display. CFEngine recognizes that the contents of the file are already correct, and it doesn’t make any changes.
Note: --no-lock
and --info
are commonly used together when writing and testing policy files. To save some typing, there are shortcuts available; -KI
is equivalent to --no-lock --info
.
Now that you have a working system policy, you may want to run it without your supervision. You’ll do that in the next step.
You probably don’t want to run policy manually from the command line all the time. CFEngine includes automation features to handle that.
In this step, you will automate policy runs by putting your policy files in the expected location and updating a JSON file. The JSON file periodically tells the CFEngine component what to do in the background without your having to run commands explicitly and manually from the command line.
Use the following command to copy the policy file you created in the previous step to the recommended location:
- sudo cp file_management.cf /var/cfengine/masterfiles/services/
All CFEngine policy is inside /var/cfengine/masterfiles/
. This includes policy you didn’t write that came with CFEngine. To keep your custom policy separate from the default policy, it is recommended to put your policy files inside the services/
subdirectory.
When the CFEngine agents fetch new policy files, they copy them from this directory on the hub. Even if you are just using one machine, the agent still works in the same way: it will look for files in /var/cfengine/masterfiles
and copy them to /var/cfengine/inputs
. For new users, it’s best to use these paths. Customizing the paths or putting policy files in other locations requires more work since you’d have to ensure that permissions as well as copying and finding the files work correctly.
Next, create an augments JSON file to specify where the policy file is and what bundle should be run:
- sudo nano /var/cfengine/masterfiles/def.json
Add the following content to that file:
{
"inputs": [ "services/file_management.cf" ],
"vars": {
"control_common_bundlesequence_end": [ "manage_my_file" ]
}
}
There are two steps necessary to make your policy run regularly (every five minutes): make sure the agent finds and reads the file and ensure the agent runs your bundle.
The inputs
key in def.json
tells the agent what policy files to read. In this case, the agent will read the policy you created in the last step, file_management.cf
.
The vars
key can be used to define variables. The control_common_bundlesequence_end
variable is used in the default policy, so any bundle names you put there will be added to the end of the bundlesequence
and evaluated after all the default bundles. Together, these two pieces of information mean that cf-agent
knows what policy files to read and which bundles within them to evaluate without having to specify these things from the command line.
At this point, you are editing policy inside /var/cfengine/masterfiles/
and automation takes care of the rest. More specifically, cf-agent
periodically wakes up and fetches the new policy files you’ve written. The agent will read and evaluate the policy, enforcing all the promises and making changes to the machines as necessary (such as editing files and creating users).
Based on the policy you’ve written in this tutorial, every time the agent runs, it will ensure /tmp/my_file.txt
exists with the content you specified in the policy file.
Save and close the file.
To confirm that the automation is working, delete the text file created when you first ran the file management policy:
- sudo rm /tmp/my_file.txt
After five minutes, you can confirm whether CFEngine re-created my_file.txt
in the background:
- cat /tmp/my_file.txt
OutputHello, CFEngine.
Alternatively, you can also force the file creation to happen sooner:
- sudo rm /tmp/my_file.txt ; sudo cf-agent -Kf update.cf ; sudo cf-agent -KI
The rm
command deletes the file so CFEngine will see that changes are necessary.
The first cf-agent
command updates the policy file, copying it from /var/cfengine/masterfiles
to /var/cfengine/inputs
.
The last cf-agent
command enforces your policy, which makes it look for the /tmp/my_file.txt
file, and create and edit it if necessary.
In this case, you are running the agent immediately after deleting the file, so cf-agent
should print that it created the file. (The chances of an agent run happening in the background between these commands are slim to none.)
Note: The command sudo cf-agent -Kf update.cf ; sudo cf-agent -KI
is similar to the command CFEngine runs by default every five minutes. So, running this command should have the same result as waiting five minutes if CFEngine is working correctly. The first agent run updates the policy and the second one evaluates the policy and makes changes to the system.
In this step, you’ve automated your first system administration task with CFEngine. While this example focused on creating and editing a file, the process for another task would be identical: write the policy, put it in the correct directory, and update the def.json
file accordingly.
You’ve now installed and started CFEngine on a single server. You wrote your first policy and set it up to run automatically.
As a next step, check out the CFEngine official documentation, such as this tutorial on managing files: Create, Modify, and Delete files.
If you have any questions or need help, feel free to post in the Q&A section of CFEngine’s GitHub Discussions.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.