Based on the definition from official Cfengine.org web-site: Cfengine is an automated tool for configuring and maintaining Unix-like computers.
This tutorial is created to help system administrators who need to manage multiple servers and would like to have the ability to manage server configurations in a centrally managed manner.
This tutorial assumes:
- the reader is already familiar with basic system administration.
- the reader heard at least what Cfengine is, and would like to try it in a test environment of his or hers.
- OS choice is the Redhat, Centos or Fedora Linux distributions
- the reader knows basic Subversion.
- the reader is willing to accomodate a few way-of-doing-things that is forced upon the reader
This tutorial also includes:
- a minimal cfengine configuration
- a simple application deployment via cfengine: name services.
Obviously other operating systems are supported, and you can deploy any application you want with Cfengine, however to be consistent with the title of this document, we will keep things as simple as possible.
Throughout this tutorial we will assume that all Cfengine services runs on all servers that you will deploy Cfengine to. However one of the servers will be Cfengine server (let's say srv01) and the rest of them will be Cfengine clients (let's say dns01 and dns02). In addition within this tutorial we will show how to force clients to get updates irregularly. This tutorial consists of following sections:
- Cfengine Installation
- Cfengine Minimal Configuration
- First application deployment using Cfengine
- Cfrun: central push
One last note: at a very minimum you can think of cfengine server as a configuration file server, and cfengine client as a cron job that wakes up every so often to connect to that file server (pull strategy) and compare if the client is up-to-date with those configurations. Obviously cfengine does much more than that. But this is at a minimum what you can think cfengine does if you don't know what it is. There is a way to avoid the pull strategy, and make cfengine server interfere with the execution timing or commands, namely utilizing cfrun.
A. Cfengine Installation
All systems that will be managed by cfengine needs to get the Cfengine installed and have cfengine services running:
- Install cfengine for your distribution on all cfengine servers and cfengine clients. One way to obtain the cfengine is to use Dag's repo for cfengine: http://dag.wieers.com/rpm/packages/cfengine. Go to that URL and identify which cfengine RPM you need and install it via:
- After installation, enable all cfengine services on both servers and clients. The cfservd component on cfengine clients will later be utilized for cfrun:
- cfservd: This is the cfengine server process that lets the clients to connect to. In the case of a server pushing something to the client, this process on the client accepts the request from the server.
- cfenvd: This provides machine learning support for cfengine. This requires no configuration.
- cfexecd: This is a wrapper to execute the cfagent binary on regular intervals. Instead of running this you can have a cron entry to run cfagent as well. Cfagent binary is the client program that connects to the cfservd.
- cfagent: the binary that does all the magic on the client side. You can execute this manually as well, you don't need to wait cfexecd to execute this.
- cfrun: this binary is to send commands to cfengine clients as long as the clients are running cfservd.
- Ensure that your firewall rules accomodate cfengine server to accept requests on port 5308 from all cfengine clients, and all cfengine clients to accept requests on port 5308 from the cfengine server.
# rpm -ivh http://dag.wieers.com/rpm/packages/cfengine/cfengine-2.2.1-1.<distro>.rf.<arch>.rpm
# chkconfig cfenvd on # chkconfig cfexecd on # chkconfig cfservd on
The description of daemons are:
The description of important binaries are:
B. Cfengine minimum configuration for Redhat, Centos or Fedora
Here, we recommend to use subversion or any other version controlling solution to keep track of the versioning. It is up to the reader to skip the subversion steps, but it is not recommended to skip these steps by the author.
- Here we will create a subversion trunk so that our configurations will have versioning and thus ability to roll-back changes when there is need to. In the subversion trunk we will have the following directory structure:
- Directory "info": this is to put notes in such as how to install cfengine on a cfengine client, etc which cannot be automated by cfengine.
- Directory "build": this is where we will keep the configuration files or other files that cfengine will be deploying to the cfengine server or clients.
- Directory "cfengine": this is where we will keep the cfengine configuration files.
- Now let's checkout subversion trunk to start working on it:
- Now we need to create the following 4 cfengine configuration files for a working minimum:
- cfagent.conf : all the cfengine servers and clients will have their cfagent (the binary that implements the cfengine magic) use this configuration.
- cfservd.conf: this is the configuration file for cfservd daemon.
- update.conf: this is for the cfagent as well, but it tells the cfagent where to connect and get cfengine initial configuration files.
- cfrun.hosts: this is for cfrun binary to specify which cfengine clients to connect to and how many at a time to execute commands on, etc.
- cfagent.global.conf: this include file will be included by all cfengine servers and clients.
- Now check out this subversion trunk under /export/admin-rXXX and create a symlink /export/admin to it. Since above configuration files, cfengine assumed that /export/admin will be where all cfengine related files will be in:
- Enable the cfengine server now by copying the cfengine configurations:
- Let's run the cfengine manually on the cfengine server:
- Enable the cfengine clients now by copying the cfengine configurations and let's run the cfengine manually on the server. Note that due to the cfagent.global.conf configuration we set for SPlayTime, you will need to wait 1 minute to elapse before the next cfagent can run.
trunk ---> info
|-> build ---> <role #1>
| |-> <role #2> ---> etc
| |-> <other unix directories if necessary>
|-> cfengine
Now that what we decided on the directory structure, let's create our subversion trunk. You can do this on any computer: on your laptop, on your workstation, on your cfengine server itself, namely wherever you want. Let's say our svn repository will be called "admin".
$ cd ~/src $ mkdir admin $ chdir admin $ mkdir info build cfengine $ <create a svn repo for your cfengine configurations> $ svn import -m "Initial import" . <newly created svn repo URL>/trunk
$ cd ~/src $ rm -rf admin $ svn co <newly created svn repo URL>/trunk $ mv trunk admin
Additionally we will divide and conquer the cfengine configurations by creating include files. The first one that we will create is:
Below are example configuration files contents to get you started. Go through them and ensure that IP block, domain-names are to your liking:
cfagent.conf: (see inside for description of fields)
# cfagent.conf: read by cfagent
# Classes: are good place to create aliases for group of hosts. Think of these
# as macros that you are creating and you can use anywhere.
classes:
# Cfengine server: you can use hostname or IP address/range here (or
# previously defined classes, etc)
cfe_servers = ( srv01 )
# Cfengine clients: you can use hostname or IP address/range here (or
# previously defined classes, etc)
dns_servers = ( dns01 dns02 )
# Import: Instead of putting all cfagent configurations in one file, let's
# divide them into different include files and include them only for
# classes that we defined above.
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
import:
# Global policy: which cfengine server or clients should include?
# "any" is a special word, it means any server that reads this cfagent.conf
# will have this configuration file included.
any::
cfagent.global.conf
# End
cfservd.conf: (see inside for description of fields)
# cfservd.conf: read by cfservd
# Groups: are good place to create aliases for group of hosts.
groups:
# Cfengine server: you can use hostname or IP address/range here (or
# previously defined classes, etc)
cfe_servers = ( srv01 )
# Cfengine clients: you can use hostname or IP address/range here (or
# previously defined classes, etc)
cfe_clients = ( dns01 dns02 )
# Control: define the variables for the cfservd service
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
control:
# Global definitions for all hosts
domain = ( example.com )
IfElapsed = ( 1 )
ExpireAfter = ( 15 )
LogAllConnections = ( true )
MaxConnections = ( 20 )
MultipleConnections = ( true )
cfrunCommand = ( "/usr/sbin/cfagent" )
# Define these variables for host group cfe_servers
# Allow connection from 192.168.1.0 subnet. Don't use DNS, otherwise
# if you break DNS with cfengine, you won't be able to fix DNS with
# cfengine
cfe_servers::
AllowConnectionsFrom = ( 192.168.1.0/24 )
AllowUsers = ( root )
TrustKeysFrom = ( 192.168.1.0/24 )
# Define these variables for host group cfe_clients
# Allow connection from srv01(192.168.1.11). Don't use DNS, otherwise
# if you break DNS with cfengine, you won't be able to fix DNS with
# cfengine
cfe_clients::
AllowConnectionsFrom = ( 192.168.1.11/32 )
AllowUsers = ( root )
TrustKeysFrom = ( 192.168.1.11/32 )
# Spread the load
!cfe_servers::
SplayTime = ( 1 )
# Grant: access to all hosts at example.com
# Allow connection from 192.168.1.0 subnet. Don't use DNS, otherwise
# if you break DNS with cfengine, you won't be able to fix DNS with cfengine
grant:
/export/admin/cfengine 192.168.1.*
/export/admin/build 192.168.1.*
/usr/sbin/cfagent 192.168.1.*
# End
update.conf: (see inside for description of fields)
# update.conf: used by cfagent to obtain the updated cfengine configurations
# Groups: are good place to create aliases for group of hosts.
groups:
# Cfengine server: you can use hostname or IP address/range here (or
# previously defined classes, etc)
cfe_servers = ( srv01 )
# Cfengine clients: you can use hostname or IP address/range here (or
# previously defined classes, etc)
cfe_clients = ( dns01 dns02 )
# Control: define the variables for the cfservd service
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
control:
# Global definitions for all hosts
workdir = ( /var/cfengine )
master_cfinput = ( /export/admin/cfengine )
actionsequence = ( copy tidy )
sysadm = ( root@example.com )
domain = ( example.com )
policyhost = ( srv01.example.com )
server = ( srv01.example.com )
TrustKeysFrom = ( 192.168.1.0/24 )
# Spread the load
!cfe_servers::
SplayTime = ( 1 )
# Copy: this is where you specify for the cfengine server and clients
# where to copy the cfagent configurations
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
copy:
# Load policy
$(master_cfinput) dest=$(workdir)/inputs
server=$(policyhost) mode=0600 r=inf type=binary purge=true
exclude=*.lst exclude=*~ exclude=#* ignore=.svn
# Tidy: what to clean up
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
tidy:
# Cleanup policy logs
$(workdir)/outputs pattern=* age=7
# End
cfrun.hosts: (see inside for description of fields)
# cfrun.hosts: this file is read by cfrun # # Domain to use for connection(s) domain = example.com # Maximum number of children to spawn during run. maxchild = 24 # Directory where to put host output files. outputdir = /var/cfengine/outputs # User allowed to do cfrun? access = root # One host per line list to cycle through. # Only the hosts are required for cfrun to operate. dns01 dns02
cfagent.global.conf: (see inside for description of fields)
# cfagent.global.conf: included by cfagent.conf
# Control: define the variables for the cfagent service
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
control:
# Logging information
Syslog = ( on )
Inform = ( on )
moduledirectory = ( /var/cfengine/modules )
# Cfagent action sequence: in what order it will perform tasks
actionsequence = ( packages directories copy editfiles files links processes shellcommands tidy )
# Max size of file that will be edited (for editfiles directive)
editfilesize = ( 0 )
# E-mail settings
sysadm = ( root@example.com )
EmailMaxLines = ( inf )
smtpserver = ( smtp.example.com )
# Timing: every hour between XX:00 and XX:05
schedule = ( Min00_05 )
# Define a variable so that "copy" directive will use to copy files from
# Don't use FQDN of server name, otherwise if you break DNS with cfengine,
# you won't be able to fix DNS with cfengine
cfsrvhost = ( 192.168.1.11 )
# Other parameters
timezone = ( PST PDT )
workdir = ( /var/cfengine )
Repository = ( /var/cfengine/outputs )
DefaultPkgMgr = ( rpm )
# Copy: this is where you specify for the cfengine server and clients
# where to copy the cfagent configurations
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
copy:
# This copy is just for sake of giving an example
# Sudoers: let's copy sudoers files to every cfengine sever and clients
/export/admin/build/all/etc/sudoers dest=/etc/sudoers
owner=root group=root mode=0440
server=$(cfsrvhost) type=checksum
# Tidy: what to clean up on all server
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
tidy:
# Clean up /var/tmp: all files that are older than 7 days
/var/tmp pattern=* age=7
# End
Now create all these files in this directory
$ cd ~/src/admin/cfengine $ vi cfagent.conf $ vi cfservd.conf $ vi update.conf $ vi cfrun.hosts $ vi cfagent.global.conf $ svn add * $ cd ~/src/admin/build $ mkdir all # create a role called "all" to put files that will be deployed $ mkdir all/etc $ vi all/etc/sudoers # copy your sudoers file $ svn add all $ cd ~/src/admin $ svn commit -m "created initial bare minimum cfengine configuration files"
srv01# cd /export srv01# svn export <newly created svn repo URL>/trunk srv01# mv trunk admin-r2 srv01# ln -s admin-r2 admin
srv01# cp /export/admin/cfengine/* /var/cfengine/inputs
srv01# cfagent
srv01# scp /export/admin/cfengine/update.conf dns01:/var/cfengine/inputs/ srv01# scp /export/admin/cfengine/update.conf dns02:/var/cfengine/inputs/ dns01# cfagent dns02# cfagent
C. Deploy our first application: Bind
Now let's setup a basic name server to be deployed using Cfengine to the servers dns01 and dns02.
- Let's update the cfengine configurations in our svn trunk and upload our DNS configurations. Let's say we are deploying a caching only DNS server with default settings.
- Now check out this subversion trunk under /export/admin-rXXX and update the symlink /export/admin to it.
- Now let's deploy the dns configuration to clients now by either waiting until the next hour or running it manually on all servers. Note that sometimes you might have to execute it multiple times due to ActionSequence. Note that due to the cfagent.global.conf configuration we set for SPlayTime, you will need to wait 1 minute to elapse before the next cfagent can run.
We need to update cfagent.conf: (see the updates in bold)
# cfagent.conf: read by cfagent
# Classes: are good place to create aliases for group of hosts. Think of these
# as macros that you are creating and you can use anywhere.
classes:
# Cfengine server: you can use hostname or IP address/range here (or
# previously defined classes, etc)
cfe_servers = ( srv01 )
# Cfengine clients: you can use hostname or IP address/range here (or
# previously defined classes, etc)
dns_servers = ( dns01 dns02 )
# Import: Instead of putting all cfagent configurations in one file, let's
# divide them into different include files and include them only for
# classes that we defined above.
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
import:
# Global policy: which cfengine server or clients should include?
# "any" is a special word, it means any server that reads this cfagent.conf
# will have this configuration file included.
any::
cfagent.global.conf
# DNS server policy: which cfengine server or clients should include?
dns_servers::
cfagent.dns.conf
# End
We need to add cfagent.dns.conf: (see inside for description of fields)
# cfagent.dns.conf: to be included only for DNS Servers
# Packags: what packages should be installed. If the package was not already
# already installed action "install", and if it is newly installed then
# define the class "reinit_named". Later we will do something with that
# defined class "reinit_named". Cfengine knows how to install packages,
# since we told it how to do it in cfagent.global.conf.
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
packages:
bind action=install elsedefine=reinit_named
# Copy: this is where you specify for the cfengine client which is a DNS
# server where to copy the named configurations. The location specified
# to be copied recursively, and if there was a need to copy over (think
# of rsync) then define the class "restart_named" to be used later.
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
copy:
/export/admin/build/dns/var/named dest=/var/named
owner=root group=named recurse=inf define=restart_named
server=$(cfsrvhost) type=checksum ignore=.svn purge=true
# Directories: what directories should be monitored. If the named log
# directory does not exist, create it, if it doesn't match my ownership
# and permissions, then update it. If there was a reason to do any of
# these define a class "restart_named". Later we will use this class to
# restart named.
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
directories:
# Content directories
/var/log/named mode=0755 owner=named group=named define=restart_named
# Links: we didn't copy named.conf to /etc, but to /var/named, therefore
# let's create a symlink from /etc to /var/named. If there was a reason
# to recreate that link then define a class "restart_named". Later we will
# use this class to restart named.
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
links:
/etc/named.conf ->! /var/named/named.conf
define=restart_named
# Processes: let's monitor for a process called named. If it doesn't
# exist in process list, let's echo a message and define the class
# reinit_named. We don't know whether named is not running because
# of init script error, or it crashed, or somebody stopped it.
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
processes:
"named" restart "/bin/echo Named DEAD" elsedefine=reinit_named
# Shellcommands: We defined classes if something extraordinary happenned
# above. If those classes got defined, then let's execute a few commands
# for those classes
#
# PLEASE NOTE THAT INDENDATION AND 1+LINE SPACING ARE VERY IMPORTANT
shellcommands:
# named restart
restart_named::
"/sbin/service named restart"
# named re-init.d
reinit_named::
"/sbin/chkconfig named on"
"/sbin/service named restart"
# End
$ cd ~/src/admin/cfengine $ vi cfagent.conf $ vi cfagent.dns.conf $ svn add * $ cd ~/src/admin/build $ mkdir dns # create a role called "dns" to put files that will be deployed $ mkdir -p dns/var/named $ cp -r <some location>/named dns/var/named # copy your bind configs $ svn add dns $ cd ~/src/admin $ svn commit -m "created initial dns deployment"
srv01# cd /export srv01# svn export <newly created svn repo URL>/trunk srv01# mv trunk admin-r3 srv01# rm -f admin srv01# ln -s admin-r3 admin
dns01# cfagent dns02# cfagent
Now your DNS servers are configured and ready to go.
D. Cfrun: central push
In the above DNS configuration example, in order to manually deploy we had to logon to each server and run cfagent manually. However, since we have configured cfservd to run on all client systems, we can use cfrun to manually run cfagent on all client servers that are listed in our cfrun.hosts
To do one at a time: srv01# cfrun -f /export/admin/cfengine/cfrun.hosts dns01 srv01# cfrun -f /export/admin/cfengine/cfrun.hosts dns02 To do all at once: srv01# cfrun -f /export/admin/cfengine/cfrun.hosts
CFengine Reference Documentation
How-to Version: 1.00
Hi, thanks for a great quickstart guide!
Symlinks doesn't work in cfservs grant
Posted by: Mattias Berge | March 03, 2009 at 12:40 PM
If your sudoers file doesn't copy over try adding trustkey=true to the copy section of the update.conf on the clients (if you have enough logging turned on you will see an error about "ID false" from cfservd).
copy:
# Load policy
$(master_cfinput) dest=$(workdir)/inputs
server=$(policyhost) mode=0600 r=inf type=binary purge=true
exclude=*.lst exclude=*~ exclude=#* ignore=.svn trustkey=true
Posted by: Brad Guillory | April 22, 2009 at 06:21 PM