Anda di halaman 1dari 6

:FM<IJKFIP Cfengine 3

DXeX^`e^k_\e\knfibn`k_:]\e^`e\

9@><E>@E<
Automate admin tasks with the powerful Cfengine framework.

BY BRENDAN STREJCEK

:
fengine [1] is a flexible frame- central repository of Cfengine code, and contributes significantly to Cfengine.
work for automating system ad- the Client machine will receive the If you are building Cfengine from
ministration tasks. With Cfen- Apache configuration. source, first obtain the latest Cfengine 2
gine, you can manage one machine or a and 3 tarballs from the project website
heterogeneous network. The first version ;fnecfX[`e^Xe[9l`c[`e^ [2]. Also, you need Flex, Bison, and
of Cfengine was released more than 15 First, install Cfengine on both the client Make to compile Cfengine, as well as the
years ago by Mark Burgess, a professor and server. Packages are available for static library libcfengine from Cfengine
at Oslo University. According to usage many popular Linux distributions, or 2. Once you have the dependencies in
estimates, Cfengine has managed more you can build the tool from source code. place, first build Cfengine 2, then Cfen-
than 1 million computers over the years. CFengine 3 has very few build-time gine 3 (you can use the same procedure
Version 3 of the Cfengine framework dependencies and even fewer run-time for both):
rolls out some new capabilities and does dependencies (only OpenSSL libcrypto
away with all the old historical layers. and Berkeley DB libdb). Although not ./configure
The developers have even retooled the strictly necessary, the Perl-compatible make
language so that all elements are han- regular expression library (libpcre) also sudo make install # Or use su
dled in a uniform way.
To show what is possible with Cfen- Design Principles
gine 3, I introduce various Cfengine Cfengine is built around a number of design principles. In general, the language is de-
components in a running example. To scriptive rather than iterative: As much as is possible, you are attempting to describe the
follow along, you need two networked “what” of the system rather than the “how.” In practice, this approach usually means
Linux machines that I call PolicyServer that Cfengine actions are idempotent; that is, applying the same function twice will re-
and Client. The end goal is to have the sult in the same result. This characteristic is important because Cfengine continually
client machine running a fully config- monitors the state of your nodes and, depending on how you write your policy, corrects
any divergences.
ured and managed Apache web server,
with no manual configuration required, Another principle that Cfengine adheres to is the “pull” architecture, which means that
clients request new policy code from a server. This behavior is in contrast to the “push”
other than installing Cfengine.
system, which requires a central node to connect periodically to all clients to configure
The basic model I use will store and them. The use of a pull architecture allows you to configure a machine that is down or
distribute all of the policy code centrally not yet built because any changes will be picked up automatically when the machine
from a single server. Cfengine can be comes onto the network. Cfengine has the facilities to do a push if you really need it, but
used many ways because it is very flexi- even these features are built around an underlying pull mechanism. The pull principle
ble, but this is a common design, and it also has important implications for the autonomy of the configured node: If the Cfengine
serves many sys admins well. Policy- server crashes, or if it is unavailable to the client for some reason, the client can continue
Server will hold and make available the to use its cached policy until the next time it can connect successfully.

22 ISSUE 101 APRIL 2009


Cfengine 3 :FM<IJKFIP

By default, files are installed to /usr/ dles are the primary statement aggrega- gine. PolicyServer will probably also be a
local, but you can change this by adding tion construct in Cfengine (in the same client; that is, the server will update its
the --prefix=/some/other/path argument way that a function is the primary con- policy and evaluate it with cf-agent.)
to configure, although you need to make struct of C, although bundles are not Within your central repository, create
sure that the Cfengine build process can functions in a mathematical sense). Bod- an inputs directory (I am mirroring the
find libcfengine. All the binaries are in- ies are groupings of parameters. Both the contents of the working directory /var/
stalled in sbin under the relevant prefix body and the bundle specify which com- cfengine, but this is the only subdirec-
(by default, /usr/local/sbin). The next ponent of Cfengine they are to be con- tory that I care about for now). In /srv/
version of Cfengine, 3.0.1, due for re- sumed by; in the case of the control cf-serverd, you need to create four files.
lease later this year, will not require lib- body, the consumer is common, a special The first step is to create cf-serverd.cf
cfengine, so this is just an intermediate keyword meaning the Cfengine suite as a (Listing 1). This file will control which
measure. whole, and in the case, of the hello bun- machines can connect to the server and
dle, the consumer is agent, which refers which files they will have access to, and
?\ccf#Nfic[ to the Cfengine binary cf-agent. it also will have some cf-serverd–specific
With the software safely installed on the The name of the bundle, hello, is refer- configuration variables.
server and client, you are ready for a enced in bundlesequence, which is a spe- Now, create update.cf, which will con-
first look at Cfengine in action. A simple cial directive that tells cf-agent what tain code that the client runs to synchro-
“Hello, World” example will demon- code to execute, and in what order. The nize its local policy to the central policy
strate a working Cfengine 3 program special token reports is a promise type – in the repository (Listing 2).
First, run cf-key with no arguments. This one of many kinds of statements that Listing 2 introduces some Cfengine
command creates some dot files in your you can make about how you want your variables. Unlike past versions, variables
home directory and generates a keypair system to function. are now all dynamic types (before, they
(which you won’t need right away, but it Bundles are made up of promises. In were all just strings). Other variable
is necessary for remote copies). this case, as you can probably guess, re- types include slist (a list of strings), real
This “Hello, World” program will be ports is a way to generate output. The (a number with decimal precision), and
written for cf-agent, which is the pro- next token, linux, followed by a double int (an integer). Variables are substituted
gram that does the bulk of the configura- colon, is a class. Later, I will explain (as in the shell) with ${variablename}.
tion work in Cfengine. cf-agent monitors classes in more detail, but for now, just Listing 3 shows the file promises.cf.
system state and applies corrective ac- know that code following this class will The final configuration file is failsafe.cf,
tion when necessary. Much as perl and only execute on a Linux node. which simply contains the following:
sh binaries are interpreters for Perl and
the Bourne shell, respectively, you can :fe]`^=`c\j body common control {
think of cf-agent as a command inter- Now that cf-agent is up and running, the bundlesequence => 5
preter for the Cfengine language. By de- next step is to configure the cf-serverd { "update" };
fault, as an unprivileged user, cf-agent daemon on PolicyServer so that the client inputs => { "update.cf" };
reads and executes code in ~/.cfagent/ can download an updated policy. }
input/promises.cf. So, with your favorite cf-serverd functions as a secure file
editor, create that file and enter: server that provides external access to The special promises.cf and failsafe.cf
the cf-agent running on a specific node. files are basically just dispatches specify-
body common control { On PolicyServer, designate a directory ing what other code cf-agent should exe-
bundlesequence => { "hello" }; as the canonical policy repository. Here, cute. The names for cf-serverd.cf and up-
} I use /srv/cf-serverd, but you can select date.cf I made up myself (you can call
bundle agent hello { whatever location fits best in your envi- them whatever you want, but I suggest
reports: ronment. (This should not be /var/cfen- names that are similarly suggestive).
linux::
# This is a comment Listing 1: cf-serverd.cf
"Hello, world."; 01 body server control {
}
02 trustkeysfrom => { "192.168.1.62", "192.168.1.61" };

03 allowconnects => { "192.168.1.62", "192.168.1.61" };


White space does not matter in the Cfen-
04 maxconnections => "10";
gine language, so you can indent this
code as you see fit. With no arguments 05 logallconnections => "true";

at the shell, go ahead and run it with 06 }

cf-agent. It doesn’t matter which of the 07 bundle server access_rules {


two machines you run this on because 08 access:
you have installed the software on both. 09 "/srv/cf-serverd"
As you can see, the two main entities
10 admit => { "192\.168\..*" };
present in the code are a body named
11 }
control and a bundle named hello. Bun-

APRIL 2009 ISSUE 101 23


:FM<IJKFIP Cfengine 3

The hard-coded entry point for cf-agent up your production systems if you deem cf-agent --bootstrap
is promises.cf. All of the code you want it worth the effort. However, then you
to run needs to be either in this file or need to deal with key distribution If you switch back to the server console,
referenced by this file. Strictly speaking, through an external channel. (One com- you should see many messages about
failsafe.cf is not required, but if prom- mon way to improve this is to distribute what is going on from the server end. If
ises.cf does not parse, cf-agent will fall the server’s public key with the use of you didn’t configure your access control
back to failsafe.cf, so it is a good idea to your OS install system but to allow the correctly, diagnostics explain why the
make sure that a very simple, known, server to accept new clients on trust. connection or copy was denied. Once
good failsafe.cf is available. Only you can decide what an appropri- you have verified that the network copy
failsafe.cf merely attempts to update ate level of security is for your site.) was successful, you can kill the fore-
the policy files from the server. Because ground cf-serverd process (Ctrl+C) and
I designed failsafe.cf to get only the most >\kk`e^JkXik\[ start it up as a daemon by running it
recent policy, it also functions as a boot- To start, manually, copy cf-serverd’s con- with no arguments.
strap procedure for the Cfengine client. figuration files into place:
Thus, to configure the client initially, JZ_\[lc\i
you only need to copy failsafe.cf (and cd /srv/cf-serverd/inputs The last bit of Cfengine infrastructure is
any files it references) onto the client. cp promises.cf update.cf 5 the periodic scheduler. cf-execd is a
cf-serverd.cf 5 scheduler daemon similar to cron. Per-
>\kk_\B\pj /var/cfengine/inputs haps you wonder why Cfengine doesn’t
Before you can test the system, you need just use cron. In fact, many people run
to generate public key cryptography key- Note that the server is also getting copies Cfengine out of cron as well for an
pairs for each node. As root, run cf-key of update.cf and all the other files; dur- added level of reliability, and they con-
on both the PolicyServer and the client. ing normal functioning, any changes you figure cf-agent to restart either crond or
This command will create identities in make to cf-serverd.cf in the central repos- cf-execd if necessary. The use of cf-execd
/var/cfengine/ppkeys. Because of how itory will be picked up automatically. has a number of benefits, though, in-
cf-serverd is configured in cf-serverd.cf With the configuration files in place, cluding the power to control the execu-
start up cf-serverd. The following com- tion schedule within the central Cfen-
trustkeysfrom => 5 mand starts cf-serverd verbosely and in gine policy, as well as the ability to for-
{ "192.168.1.62", 5 the shell foreground. If you leave these mat and send email reports about any
"192.168.1.61" }; options off, cf-serverd will silently go actions. If you do decide to run cf-agent
into the background. with cron as well, I recommend having
and cf-agent is configured in update.cf, cron execute cf-agent via the foreground
cf-serverd --verbose --no-fork version of cf-execd; that way, you will get
trustkey => "true"; the same email settings in both systems,
Now, bootstrap the client by copying and cf-execd will log any output in /var/
the behavior of the machines will be to failsafe.cf and update.cf manually to cfengine/outputs. In this case, however, I
accept the key of a remote node on trust /var/cfengine/inputs on the client (re- assume you are only running cf-agent
the first time, but from then on only ac- member, in a production environment, out of cf-execd and not cron.
cept clients coming from that same IP this is something that could be taken First, create a new file that controls
address on trust if they can prove these care of automatically), then run cf-agent the functionality of cf-execd (cf-execd.cf)
clients have the same key. This stance is to execute the code in failsafe.cf from the and add it to the inputs list (Listing 4).
rather permissive, but you can tighten directory that contains failsafe.cf: Listing 4 states that cf-execd will run

Listing 2: update.cf
01 body copy_from 11 body depth_search recurse 20 "client_inputs" string =>
remote(server, path) { { "${sys.workdir}/inputs";

02 servers => { 12 depth => "inf"; 21 files:


"${server}" }; 13 } 22 any::
03 encrypt => "true"; 14 bundle agent update { 23 "${client_inputs}"
04 trustkey => "true"; 15 vars: 24 copy_from =>
05 source => "${path}"; 16 any:: remote("${cfserverd}",

06 compare => "digest"; "${server_inputs}"),


17 "cfserverd" string =>
07 preserve => "true"; "192.168.1.61"; 25 depth_search =>

# Preserve permissions recurse;


18 "policyfiles" string =>
08 verify => "true"; "/srv/cf-serverd"; 26 }

09 purge => "true"; 19 "server_inputs" string =>

10 } "${policyfiles}/inputs";

24 ISSUE 101 APRIL 2009


:FM<IJKFIP Cfengine 3

twice per hour (that is the “schedule” comes, cf-execd will wake up, run dles. A bundle of type common can be
line, and the two members of the list are cf-agent, and deposit any output in /var/ consumed by any Cfengine component
called Cfengine time classes) and that it cfengine/outputs. and need not be listed in bundlese-
will send mail to root@example.com quence. Each bundle has its own scope,
with 92.168.1.61 as a relay. If you have a DX`ekX`e`e^XJ\im`Z\ and variables from a foreign bundle can
usable email address and relay, I recom- Suppose I want to use Cfengine to install be accessed with the interpolation form
mend using them to get a feel for how and configure Apache httpd. In fact, I ${bundlename.variable}. So, the code in
the whole system produces feedback for will even build httpd from source so the Listing 5 allows other bundles to make
the admin. solution will be portable across many use of, for example, ${httpd.conf},
The item most in need of explanation distributions and platforms. In a produc- which will evaluate to the full path.
is splaytime. If a splaytime is set, cf-ex- tion environment, I would hesitate to Particular promises, such as com-
ecd effectively waits a pseudo-random have servers compile their own software. mands, files, or reports, often have pa-
number of minutes before attempting to If I truly needed to build from source, I rameters that determine the nature of
connect to the server, with the splaytime would most likely build a custom pack- the promise. The appropriate key/value
number as a ceiling. So, in Listing 4, age and then distribute that. However, pairs follow the promise. For example,
cf-execd waits up to 1 minute. The point the use of cf-agent to build from source consider the following promise:
is to avoid resource contention. directly offers a nice (cross-platform)
In this case, I have set it to the artifi- way to display some of the available fea- processes:
cially low value of 1 so that the user will tures. any::
not need to wait long to see activity from First, download the Apache source "cf-execd"
cf-execd. In a production environment, it into the Cfengine repository [3]. restart_class => 5
would probably be better to set this to Rather than configuring the client to "start_cfexecd";
something on the order of 15 or 20 min- download the source from the Internet,
utes for the schedule given in Listing 4. it is better to cache the source code lo- This promise has a parameter called re-
Remember to add the executor bundle cally, so you are not dependent on exter- start_class that takes a string value for
that you just created to a bundlesequence nal resources. Just put the tarball in /srv/ its right-hand side. (In this case, that
in promises.cf. Now, go back to the client cf-serverd/inputs on PolicyServer (in a string will become a defined class if no
and run cf-agent again. This should up- subdirectory for good organization), cf-execd processes are running.) Some
date the policy from the server and then let the update bundle take care of parameters take external bodies for their
execute it. distributing it. right-hand side. The use of an external
Afterwards, check a process listing Create a new file to store all the httpd- body allows multiple key/value pairs
and you should see that cf-execd was related code – say, web_server.cf. This and further parameterization, which al-
started. From the client’s point of view, file needs to be added to inputs in prom- lows reuse. To make the concept con-
the process is now truly “hands-off”: ises.cf, and any bundles contained crete, consider the example that I will
Any modifications you make to the cen- within it to bundlesequence. The first soon use to compile Apache. The follow-
tral policy repository will be picked up step is to create a bundle with some vari- ing body, which takes one argument, al-
automatically. Once the scheduled time ables that can be re-used by other bun- lows me to run commands in a particu-

Listing 3: promises.cf Listing 4: cf-execd.cf


01 body common control { 01 body executor control {

02 bundlesequence => { 02 splaytime => "1";

"update" }; 03 mailto => "root@example.com";

03 inputs => { "update.cf", 04 smtpserver => "192.168.1.61";


"cf-serverd.cf" }; 05 mailmaxlines => "1000";
04 } 06 schedule => { "Min00_05", "Min30_35" };

05 # Some arbitrary harmless 07 executorfacility => "LOG_DAEMON";


actions that will generate some
08 }
output
09 bundle agent executor {
06 bundle agent hello {
10 processes:
07 commands:
11 any::
08 any::
12 "cf-execd"
09 "/bin/date"; 13 restart_class => "start_cfexecd";
10 reports: 14 commands:

11 linux:: 15 start_cfexecd::

12 "Hello, world."; 16 "/usr/local/sbin/cf-execd";

13 } 17 }

26 ISSUE 101 APRIL 2009


Cfengine 3 :FM<IJKFIP

lar directory and without a shell: class expression (strings ending with ::) see whether the software is installed by
are only enforced when the class is true. checking for a particular file (more pre-
body contain cd(dir) { For example, read cise heuristics could be devised); if not,
useshell => "false"; build the program with the standard
chdir => "${dir}"; bundle agent a 5 untar, configure, make, and make install
} { reports: linux:: "asdf"; } procedure as usual.
Many server applications come with
Such bodies can be stored in any Cfen- as “print asdf if the class linux is de- configuration files that must be in place
gine input file, but because they are fined.” As it happens, cf-agent automati- before a complete service is deployed.
often general and can be reused by many cally defines the class linux on Linux In this case, I will configure Apache to
promises, it makes sense to keep them in nodes. The special class any has been allow server-info and server-status re-
their own file, which I will call library.cf. used several times already; this class is quests. This requires editing two differ-
If you have not already done so, put this always true. It is often used, even when ent configuration files. Cfengine 3 in-
cd body in library.cf and add it to the not strictly necessary, to maintain cor- cludes four types of promises that reside
bundlesequence in promises.cf. Remem- rect indentation. By running cf-agent -pv in special external edit_lines bundles –
ber, when changing such an external (this will not execute policy code, so it delete_lines, replace_patterns, field_
body later, you might be affecting nu- is always safe), you can see all the auto- edits, and insert_lines – and support ad-
merous active promises, so it makes matically defined classes. On one of my ditional parameters.
sense to treat them with the care af- test nodes, some of the automatically With these promises, you can set con-
forded to any shared resource. defined classes are: 64_bit, Friday, figuration variables, comment out key
In Cfengine, a class is a boolean condi- debian_4, and xen. lines, and maintain configuration files.
tion meant to represent some aspect of Before you can use edit_lines in a “files”
the system state, be that state an operat- 8gXZ_\<oXdgc\ promise, you need to create some edit_
ing system or the time of day. Many Listing 6 shows the bundle that will un- lines bundles. Think of these edit_lines
classes are defined automatically by pack, compile, and install Apache. On bundles as custom-made editfiles func-
cf-agent, and you can define others from most systems, the special predefined tions; they are usually general enough to
the return values of programs and by variable sys.workdir will resolve to /var/ re-use over many components. Two that
other means. Any promises following a cfengine, which essentially says: Test to I will make use of are DeleteLinesCon-
taining and ReplaceAll. If you are follow-
Listing 5: The common Bundle ing the file organization I have been
01 bundle common httpd { using so far, it makes sense to put these
02 vars:
in the library.cf file with other shared
bodies (Listing 7). As you can see, they
03 any::
have pretty much the same structure as
04 "version" string => "httpd-2.2.10";
other bundles, and they can be parame-
05 "prefix" string => "/opt/httpd/${version}"; terized as well.
06 "server" string => "${prefix}/bin/httpd"; In addition, I need a way to define a
07 "apachectl" string => "${prefix}/bin/apachectl";
class that, if I edit any files, lets me trig-
ger a service restart later:
08 "conf" string => "${prefix}/conf/httpd.conf";

09 }
body classes 5

Listing 6: Setting Up Apache


01 bundle agent install_web_server { 11 commands:

02 vars: 12 !web_server_installed::

03 any:: 13 "/bin/tar xzf ${source} -C

04 "source" string => ${sys.workdir}/inputs";

05 "${sys.workdir}/inputs/support_files/ 14 "/bin/sh configure --prefix=

${httpd.version}.tar.gz"; ${httpd.prefix} --enable-modules=all"

06 15 contain => cd("${compiledir}");

07 # Will get automatically cleaned up by the 16 "/usr/bin/make"

update purge 17 contain => cd("${compiledir}");

08 "compiledir" string => "${sys.workdir}/ 18 "/usr/bin/make install"


inputs/${httpd.version}"; 19 contain => cd("${compiledir}");
09 classes: 20 }
10 "web_server_installed" expression =>
fileexists("${httpd.server}");

APRIL 2009 ISSUE 101 27


:FM<IJKFIP Cfengine 3

DefineIfChanged(class) {
Listing 7: library.cf promise_repaired => 5
01 bundle edit_line DeleteLinesContaining(pattern) {
{ "${class}" };
02 delete_lines: }
03 ".*${pattern}.*";

04 } Once I have all these components in


05 body replace_with ReplaceValue(value) {
place, I can tell cf-agent to use them to
edit the config files; in this case, I need
06 replace_value => "${value}";
to uncomment the httpd-info line in
07 occurrences => "all";
httpd.conf and remove the access control
08 } from httpd-info.conf (Listing 8).
09 bundle edit_line ReplaceAll(from,to) {

10 replace_patterns:
NXkZ_[f^
11 "${from}"
If you need a way to keep an eye out for
the web server process – to restart it if it
12 replace_with => ReplaceValue(${to});
is not running – create another bundle,
13 }
or simply add the promises in Listing 9.
The code in Listing 9 wraps the pro-
Listing 8: httpd.conf cess detection with a class so I am sure
01 bundle agent configure_web_server { the web server is running on nodes that
02 classes:
have a web server installed.
For even greater reliability, you might
03 "web_server_installed" expression =>
want to create a functional test – that is,
fileexists("${httpd.server}");
a test that queries the service. In this
04 vars:
case, you need to fetch some data from
05 "info" string => "Include conf/extra/httpd-info.conf";
port 80 and make sure it is the data you
06 files: expect.
07 web_server_installed::

08 # Uncomment httpd-info.conf line :feZclj`fe


09 "/tmp/httpd.conf" Now that your Cfengine framework is
10 edit_line => ReplaceAll("^#${info}.*", "${info}"),
configured, here are a few ideas for con-
tinued improvements:
11 classes => DefineIfChanged("restart_httpd");
฀ ฀ ฀ ฀
12 # Remove access control from httpd-info.conf
฀ ฀ ฀ ฀ ฀ -
13 "/tmp/httpd-info.conf" ment systems by having cf-agent auto-
14 edit_line => DeleteLinesContaining("(Allow|Order|Deny)"), matically configure monitors
15 classes => DefineIfChanged("restart_httpd"); ฀ ฀ ฀ ฀ ฀ ฀
16 commands: your deployment system
17 restart_httpd::
฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ -
ured to log centrally
18 "${httpd.apachectl} graceful";
The more functionality you bring within
19 }
Cfengine’s realm, the easier it will be to
bring new services online and to recover
Listing 9: Watchdog from problems such as hardware failures
01 bundle agent monitor_web_server { or security compromises. Because you
02 classes: can code all the rules on how to create a
03 "web_server_installed" expression =>
node of type X in a machine-executable
fileexists("${httpd.server}");
language, all you need to do is prepare a
fresh base OS install, then install Cfen-
04 processes:
gine and let it rebuild your replacement
05 web_server_installed::
node for you. p
06 # Define a class if httpd is not running so that we can start it

07 "httpd"
INFO
08 restart_class => "start_httpd";
[1] Cfengine: http://www.cfengine.org
09 commands:
[2] Cfengine source code: http://www.
10 start_httpd:: cfengine.org/downloads/
11 "${httpd.apachectl} start"; [3] Apache tarball: http://httpd.apache.
12 } org/download.cgi

28 ISSUE 101 APRIL 2009

Anda mungkin juga menyukai