2.1. Writing a deploy script
2.1.1. Overview
This page describes how to maintain DM/WM software deployment recipes. All the
code resides in DM/WM github deployment.
For each service there is a sub-directory which contains the deployment script
deploy, server management script,
monitoring specs and any configuration files except
security-sensitive authentication data.
2.1.2. (Not) making changes to the script
Please do not commit any changes directly. Please submit tested changes as
github pull requests as per dev-git guidelines.
All changes pertaining to the service – deploy
scripts, manage scripts, monitoring specs
and server configuration files – should be supplied in the pull request. We
will review your patches, and may request changes. Once they are
approved, we will commit them to github for you. You should assume progressive
clean-up of your deployment action can happen on every new server version
submission.
2.1.3. How the deployment script is used
The deployment script is used for both first installations and upgrades.
The package deployment routine should work for both situations, including
when there is nothing new to do, and when upgrade requires clean-up. The
service must be fully functional but not yet started at the end of the
installation. Commands for major one time migrations should be highlighted
as such with $MIGRATION (see below for complete details).
The script must operate on fully relocatable destination. Assume as little
as possible about the destination machine. If you have special requirements
you want to add to the script, please enquire on the
hypernews forum.
All application servers run under a separate daemon account. Software and
configuration is owned by yet another separate account. We encourage developers
to use identical setup. You can try make everything work for
your app when using just a single account, but it’s not something we support
centrally. To hide most of the details there are several helper functions and
the install is divided into three parts: prep, sw and post.
In production, different deployment stages are executed on different hosts.
Software installation, prep and sw stages, are run on the image server,
which only hosts a disk image of the software, but doesn’t ever run any
applications. The entire installation is pushed from the image server to the
actual servers, currently using rsync but in future this may be a disk
image mount instead. Finally the post stage is executed on each server.
Thus, all host-specific configuration must be done in the post action; it
makes no sense for other deploy actions to refer for example to $host,
it will not be the host where the servers run.
2.1.4. What the script should and should not do
The deployment action should do everything required to install or upgrade
the service, bringing it fully to runnable state. The script should not stop
or start the server, that will happen outside using the
management scripts during upgrades.
In general, deploying the server should install both the software and the
specific configuration required for CMSWEB cluster systems: production,
pre-production and development clusters. If at all possible the script should
work for any user on any system and any path. Please follow these conventions:
- The installation must be completely confined to the installation root
(referred to as $root here). This implies that making any reference anywhere
to AFS is forbidden. Explicit references to the production installation
location /data are also unwelcome.
- If your server software provides ability to download files from local file
system, including CherryPy static_file() method, you must provide positive
proof the file access so granted is guaranteed to be safe and will restrict
itself to specific set of files. Unrestricted access to the local file system
is prohibited, even if it’s just the directory trees from your RPM-installed
software. Exposing more sensitive directories will likely lead to immediate
deactivation of your application.
- Instance-specific configuration files and management scripts should be
included in the same directory as the deployment script. The files are
usually copied automatically to $root/current/config/<service>. In
production your application should be able to read but not write the files.
- RPMs should install into $root/current/sw. RPMs should be installed
only from the comp repository and its derivatives like comp.pre.
- The software should take as command line options paths to the
instance-specific configuration files, which should specify everything about
the server identity. Modifying RPM-installed software area to configure or
patch the server software is not accepted except for old unmaintained legacy
servers.
- Server logs should be written to $root/logs/<service> via the
rotatelogs program.
- Server run time state, if any, should be kept in $root/state/<service>.
- Security sensitive configuration files can be found in
$root/current/auth/<service>. These files may not be copied anywhere
else. You may of course refer to the path of these files in your
configuration files.
- If your service requires a grid proxy, you need to inform us exactly how
your service is making use of it. Use mkproxy to register the use as
shown below. The proxy appears in $root/state/<service>/proxy/proxy.cert,
you should point $X509_USER_PROXY to this path in your server startup
scripts.
- The service must run under application-specific daemon account. The
configuration for this is explained below.
2.1.5. Typical deployment
Normally, when services runs under separate accounts, the deployment is
roughly like this, where 1207c is the version of the release series
you want to install for, and the 12.07c is the corresponding deployment
tag in github:
cd /data
(git clone git://github.com/dmwm/deployment.git cfg && cd cfg && git reset --hard 12.07c)
$PWD/cfg/Deploy -t 1207c -a -s prep /data myapp
sudo -H -u _sw bashs -lc "$PWD/cfg/Deploy -R cmsweb@1207c -t 1207c -a -s sw /data myapp"
$PWD/cfg/Deploy -t 1207c -a -s post /data myapp
2.1.6. Maintaining a deployment action
Each service has a subdirectory parallel to the Deploy script. Any
sub-directories which contain a deploy script are installable products.
These scripts should contain deploy_<product>_<stage> functions as
described below. Unnecessary functions do not need to be defined. For
particularly complex deployment, the file can define other functions, as
long as the names don’t conflict with those used in Deploy script itself
or those in other products.
The functions should have a desired target state and perform actions which
bring the system to that target. All actions should be executed always, such
that they are idempotent when there is nothing to change. This allows the
script to be used for upgrades and to fix broken installations. The script
should also handle migrations whenever service setup changes, including
cleaning up old files and preserving any precious files.
All commands always run with set -e in effect, so every command must
succeed with exit code $? equal to 0. This means that if statements must
always containing both then and else parts. It is sometimes easier to
write choices as case .. esac because of this. On the other hand you can
make “assertions” by simply executing a command which returns true for the
arguments.
The scripts enforce standard naming of directories and accounts. The names are
all determined from the sub-directory name. For
service myapp the names will be:
- deployment functions deploy_myapp_stage with any dashes converted to
underscores
- configuration files under $root/current/config/myapp
- server logs under $root/logs/myapp
- server state under $root/state/myapp
- proxy under $root/state/myapp/proxy/proxy.cert
- server account _myapp.
The following variables are available in all deployment actions:
- $variant is user-selected installation variant, default by default
- $project is the name of the service/project being installed
- $project_config_src is the project configuration directory fetched from github, e.g.
$PWD/cfg/myapp
- $project_config is the project configuration directory, e.g.
$root/current/config/myapp
- $project_auth is the project authentication directory, e.g.
$root/current/auth/myapp
- $project_state is the project state directory, e.g.
$root/state/myapp
- $project_logs is the project log directory, e.g. $root/logs/myapp.
2.1.7. Describing variants
The service may optionally list names of available installation variants.
deploy_myapp_variants="default offsite"
Please use this possibility sparingly. Most packages do not need this
definition. Unless user requests a specific variant, the Deploy script will
attempt to install the default variant. If you do not include default
in the variants list, the user is always forced to make a choice when
installing the service.
Actual variant handling is done in subsequent stages. At each function call
the $variant variable will contain the user’s selection. Use for example
a case $variant in foo ) ... ;; esac statement to perform commands
depending on variant choice.
2.1.8. Installing dependencies
The first deployment function to provide is deploy_myapp_deps.
deploy_myapp_deps()
{
deploy backend
}
The deploy_myapp_deps should install any other services which must
be co-hosted with your service. It is always executed first when
deploying myapp, and ensures required dependencies are installed before your
package. For most services this should be just deploy backend, but if your
service requires X509 proxy certificate, say deploy admin. If your
application uses the WMCore’s security module, add dependency on wmcore-auth.
In some cases you may not want to have any dependencies at all, in which case
you can omit this function entirely.
2.1.9. Preparing directories for service installation
The prep stage should normally look like this.
deploy_myapp_prep()
{
mkproj
}
The deploy_myapp_prep should create server working directory using
mkproj function. This automatically creates project state directory
$root/state/myapp and log directory $root/logs/myapp; you can
suppress creating these using -s and -l options, respectively.
You can request mkproj to create additional directories. Relative
paths are relative to the project subdirectory. Absolute paths can also
be given, but they need to subdirectories of $root to keep installation
relocatable.
Use setgroups to assign correct ownership on the remaining extra
directories. The command is no-op when multiple accounts are disabled. The
first parameter is chmod argument, the second is chgrp argument and
the remaining arguments are directories. You can also give -R option if
you want to perform recursive changes, but be careful with these – the
install actions cannot modify the group ownership on files created by the
server. The chmod argument should always be relative, not an absolute
mode like 775.
In production, with per-service accounts, mkproj automatically assigns
_sw group to the project configuration directory so software installation
can later modify it. The myapp server will run under the _myapp account
and _myapp group, and needs to be able to write to the logs directory,
so the log directory is assigned _myapp group and made group-writable.
If your application needs a X509 proxy certificate, add mkproxy call
something like the following. It will automatically create a proxy
subdirectory in $root/state/myapp/proxy.
deploy_myapp_prep()
{
mkproj
mkproxy
}
In addition to the standard commands above, any migration from version to
another are best implemented in the prep stage. Delete any old directories
or files here, especially if they will be on the way of sw stage. Prefix
such commands with the word $MIGRATION.
After mkproj, all commands execute with current directory in
$root/state/myapp if it exists, in $root otherwise. This applies
to all subsequent installation stages, not just the prep one.
2.1.10. Installing software
The next stage is software installation:
deploy_myapp_sw()
{
deploy_pkg comp cms+myapp
}
In production the deploy_myapp_sw runs under _sw account and leaves
files owned by the _config group. This is to protect them so that the
running server can read the files, but not modify them. You normally just
run deploy_pkg function, which is documented below.
Here we install CMS RPM package myapp from comp repository into
$root/current/sw base path. The version of the package is not normally
defined in the deploy script, it is normally automatically determined
from the release series meta-package command-line -R option. In other words,
you tell the system to install “whatever is current for this release series.”
Assuming this version is x.y.z, we also create symlink
$root/current/apps/myapp which points to
$root/current/sw/slc5_amd64_gcc434/cms/myapp/x.y.z. Other files such as
management scripts and configuration files are copied
from the project configuration directory fetched from github
($project_config_src) into $root/current/config/myapp
($project_config). Hence the files to be installed are determined by what
was fetched from github in the first place. (Replace comp with cms if
the software is in cms repository.)
If your configuration files are not fully relocatable, you may need to fix
them up with a command such as this:
perl -p -i -e "s{/data}{$root}g" $project_config/myconfig.file
2.1.11. Post-installation actions
The last stage is to run post-install actions.
deploy_myapp_post()
{
case $host in vocms53 ) disable ;; * ) enable ;; esac
(mkcrontab; sysboot; echo "17 2 * * * $project_config/daily") | crontab -
}
The deploy_myapp_post should record whether the service is enabled or
disabled on this particular host. Recall that in production the prep and
sw stages run on the image server, so the post stage needs to record
which services are actually going to be used on which hosts.
The deploy_myapp_post should also install or upgrade cron jobs for
automatic server management. The mkcrontab is just a shortcut for
current crontab minus anything which mentions $project_config. To this
you should add a cron @reboot stanza to start the server automatically
on reboot, the sysboot automates this. This will invoke the
manage script with sysboot action, which is like
start but protects against spurious restarts caused by crond
restarts outside system boot.
Typical other management tasks would include for example daily purging of
any old state files. The admin package installs a log archiver which
automatically compresses and stashes away all old log files into zip files,
by application and server.
2.1.12. Example
In general there should be little other content in your deployment action.
If your myapp requires no content for some of the above functions, just
leave the function out.
The complete set of functions for a standard installation with full management
automation for myapp would look like this.
deploy_myapp_deps()
{
deploy backend
}
deploy_myapp_prep()
{
mkproj
}
deploy_myapp_sw()
{
deploy_pkg comp cms+myapp
}
deploy_myapp_post()
{
(mkcrontab; sysboot; echo "17 2 * * * $project_config/daily") | crontab -
}
2.1.13. Documentation on internal helper commands
2.1.14. mkproj
The mkproj function accepts the following options:
- -l suppresses creation of log directory.
- -s suppresses creation of state directory.
Any other arguments are directories to be created in project state directory
(if relative), or anywhere else (if absolute). The log, state directory and
any additional directories are made owned and writeable by group _myapp,
but just the directory itself, not its contents. The project configuration
directory is created owned and writeable by group _sw.
2.1.15. setgroup
The setgroup function accepts any options which are valid to chmod or
chgrp, for example -R to apply the operation recursively. Non-option
arguments are mode, group and possibly empty list of path names to apply the
operation on. It the list is non-empty, setgroup first applies the chgrp
operation with the requested group, then chmod.
2.1.16. deploy_pkg
The deploy_pkg function accepts the following options:
- -a dest[:source] adds the pair to list of authentication files to
install under $root/current/auth. Note that if you use this argument,
you need to define deploy_myapp_auth function as described below.
- -l name creates symbolic link name to the installed package under
$root/current/apps. By default the link is named the same as the
installed RPM, for example myapp when installing RPM cms+myapp+1.2.3.
The remaining arguments are either none, and two or three arguments:
- Repository, usually comp.
- Package to install, usually cms+app.
- Optionally an explicit package version to install. Normally this is omitted,
which is interpreted as ‘auto’, which means the version is determined from
the meta-package given with -R command line option. The version specified
here can be overridden from command line.
The function first installs the RPM if any was provided. A link to the
extracted RPM package is automatically created under $root/current/apps.
If the RPM was omitted, the other parts below are still done. This can be
useful to install for example just authentication file or scripts for the
configuration area without RPMs.
Next the function copies any files from $project_config_src into
$project_config, i.e. the github repository directory contents to
$root/current/config/myapp. Everything in the directory is assigned to
_config group, readable but not writeable by the group.
Finally the function installs any authentication files specified with -a
option. These can be simple file names such as phedex/DBParam, or source /
destination name pair such as myapp/foo:myapp/bar. You’d use the latter to
select the file dynamically by some logic, such as using different files for
production, pre-production and development.
The authentication files are searched under the directory given with -p
command line option to Deploy. If no such file exists, such as when no
-p option is used at all, the deploy_myapp_auth function will be
invoked with the destination file name as argument. The function should output
a template auth file, with actual secret details dummied out. If the template
contains database connection details, make sure all of account, password and
database id are dummied out so that installations with fake authentication
details do not lock up production databases by attempting to login with wrong
credentials.
Using the -a option does not require the original files to ever exist.
This can be used to trigger deploy_myapp_auth to run always for that
destination file. This is useful to generate the file contents from some
other already installed file.
If your application uses WMCore’s security module with front-end
authentication, your service should just depend on installing wmcore-auth.
2.1.17. mkproxy
The mkproxy does not take any arguments. It creates proxy sub-directory
under $project_state and records under $root/current/auth/proxy a note
for ProxyRenew to update a $project_state/proxy/proxy.cert.
2.1.18. enable and disable
The enable and disable do not take any arguments. They create and remove,
respectively, a $root/enabled/myapp flag to indicate the service myapp is
enabled or disabled on this host.
2.1.19. Command line options
The Deploy script accepts the following command line options:
- -A arch installs for architecture _arch_ instead of the default.
- -M disables $MIGRATION commands.
- -R package@version is normally a required option. Normally this would
be a meta-package version such as cmsweb@1207c. For all deploy_pkg
commands which do not specify an explicit package version, the correct
package version is automatically determined from the dependencies of the
meta-package given here. A meta-package is a package which has no content,
just dependencies, which is how we normally generate a consistent build and
installation configuration for all the software for the entire cluster.
- -H host runs as if current host was host. This sets $host variable
to the specified value instead of current host name.
- -a activates multi-account configuration and is used for production.
Not using this option is no longer supported.
- -r comp=comp.pre overrides corresponding deploy_pkg repository
parameter. Use this option to install pre-release or user-private builds. Do
note comp and comp.pre have different package rebuild counts, so the
-cmp* version suffixes vary. Hence -r override may not be enough,
you may need to resort to editing deploy_pkg version arguments.
- -t version names the installation area version and points the
current symlink to it. The production installations use the release
name, such as hg1207c-frontend.
- -p authdir instructs to use authentication secrets from directory
authdir. This should contain subdirectories and files named by
deploy_pkg -a options. Omitting this argument tells Deploy to
generate dummy authentication info by invoking deploy_myapp_auth
actions.
- -s stages installs only the specified parts of recipes. It should be
space separated list of words “prep sw post”.
- -h shows help for the command.
The rest of the arguments are services to install, in the form
service[@version][/variant]. If @version is included, the version
overrides all package versions specified in deploy script and in the
meta-package dependencies. If the /variant is included, the
$variant variable will have that value when executing the deploy
script functions.
2.1.20. Testing your deployment action
In general you should be able to test your deployment action as follows.
Just edit the Deploy script locally and repeat as long as necessary. Use
-r comp.pre or -r comp.$user to test with pre-release and private
builds. See vm-setup for details on how to set up an
environment for this.
# Generally assumed working area, but can be anything.
cd /data
# Create secrets. Do this just once.
mkdir -p auth/myapp
vi auth/myapp/mysecret # whatever your app requires
chgrp -R _sw auth
chmod ug=r,o-rwx $(find auth -type f)
chmod u=rwx,g=rx,o-rwx $(find auth -type d)
# Grab configuration from github, in this case the HEAD of the master branch.
# Edit 'deploy', 'manage', etc. files as you wish, then redeploy.
git clone git://github.com/dmwm/deployment.git cfg
# Basic standard deployment. Repeat as often as necessary. Note that
# if you edit your 'manage' script or other configuration contents in
# the github cloned area ($PWD/cfg), you normally need to re-run all
# the three stages to install new files to the server area.
$PWD/cfg/Deploy -a -p $PWD/auth -t mydev -s prep $PWD admin myapp
sudo -H -u _sw bashs -lc "$PWD/cfg/Deploy -R cmsweb@1207c -a -p $PWD/auth -t mydev -s sw $PWD admin myapp"
$PWD/cfg/Deploy -a -p $PWD/auth -t mydev -s post $PWD admin myapp
# Same as above, but deploy from pre-release repository, using fake authentication.
$PWD/cfg/Deploy -a -t mydev -s prep -r comp=comp.pre $PWD admin myapp
sudo -H -u _sw bashs -lc "$PWD/cfg/Deploy -R cmsweb@1207c -a -t mydev -s sw -r comp=comp.pre $PWD admin myapp"
$PWD/cfg/Deploy -a -t mydev -s post -r comp=comp.pre $PWD admin myapp
# Override package version and variant when installing software.
sudo -H -u _sw bashs -lc "$PWD/cfg/Deploy -R cmsweb@1207c -a -t mydev -s sw -r comp=comp.pre $PWD myapp@1.2.3/dev"
# Start / stop / check server status.
verb=status; for f in enabled/*; do
app=${f#*/}; case $app in frontend) u=root ;; * ) u=_$app ;; esac; sudo -H -u $u bashs -lc \
"$PWD/current/config/$app/manage $verb"
done
verb=start; for f in enabled/*; do
app=${f#*/}; case $app in frontend) u=root ;; * ) u=_$app ;; esac; sudo -H -u $u bashs -lc \
"$PWD/current/config/$app/manage $verb 'I did read documentation'"
done
verb=stop; for f in enabled/*; do
app=${f#*/}; case $app in frontend) u=root ;; * ) u=_$app ;; esac; sudo -H -u $u bashs -lc \
"$PWD/current/config/$app/manage $verb 'I did read documentation'"
done