TPA uses Puppet to manage all servers it operates. It handles most of the configuration management of the base operating system and some services. It is not designed to handle ad hoc tasks, for which we favor the use of fabric.
- Tutorial
-
How-to
- Modifying an existing configuration
- Adding a new module
- Contributing changes back upstream
- Running tests
- Validating Puppet code
- Listing all hosts under puppet
- Batch jobs on all hosts
- Progressive deployment
- Debugging things
- Password management
- Getting information from other nodes
- Revoking and generating a new certificate for a host
- Pager playbook
- Disaster recovery
- Reference
-
Discussion
- Overview
- Goals
- Approvals required
-
Proposed Solution
- Use a control repository
- Get rid of 3rdparty
- Deploy with g10k
- Authenticate code with checksums
- Deploy to branch-specific environments
- Rename the default branch "production"
- Push directly on the Puppet server
- Use a role account
- Use local test environments
- Develop a test suite
- Hook into continuous integration
- OpenPGP verification and web hook
- Cost
- Alternatives considered
Tutorial
This page is long! This first section hopes to get you running with a simple task quickly.
Adding an IP address to the global allow list
In this tutorial, we will add an IP address to the global allow list, on all firewalls on all machines. This is a big deal! It will allow that IP address to access the SSH servers on all boxes and more. This should be an static IP address on a trusted network.
If you have never used Puppet before or are nervous at all about making such a change, it is a good idea to have a more experienced sysadmin nearby to help you. They can also confirm this tutorial is what is actually needed.
-
To any change on the Puppet server, you will first need to clone the git repository:
git clone pauli.torproject.org:/srv/puppet.torproject.org/git/tor-puppet
This needs to be only done once.
-
The firewall rules are defined in the
ferm
module, which lives inmodules/ferm
. The file you specifically need to change ismodules/ferm/templates/defs.conf.erb
, so open that in your editor of choice:$EDITOR modules/ferm/templates/defs.conf.erb
-
The code you are looking for is
ADMIN_IPS
. Add a@def
for your IP address and add the new macro to theADMIN_IPS
macro. When you exit your editor, git should show you a diff that looks something like this:--- a/modules/ferm/templates/defs.conf.erb +++ b/modules/ferm/templates/defs.conf.erb @@ -77,7 +77,10 @@ def $TPO_NET = (<%= networks.join(' ') %>); @def $linus = (); @def $linus = ($linus 193.10.5.2/32); # kcmp@adbc @def $linus = ($linus 2001:6b0:8::2/128); # kcmp@adbc -@def $ADMIN_IPS = ($weasel $linus); +@def $anarcat = (); +@def $anarcat = ($anarcat 203.0.113.1/32); # home IP +@def $anarcat = ($anarcat 2001:DB8::DEAD/128 2001:DB8:F00F::/56); # home IPv6 +@def $ADMIN_IPS = ($weasel $linus $anarcat); @def $BASE_SSH_ALLOWED = ();
-
Then you can commit this and push:
git commit -m'add my home address to the allow list' && git push
-
Then you should login to one of the hosts and make sure the code applies correctly:
ssh -tt perdulce.torproject.org sudo puppet agent -t
Puppet shows colorful messages. If nothing is red and it returns correctly, you are done. If that doesn't work, go back to step 2. If that doesn't work, ask for help from your colleague in the Tor sysadmin team.
If this works, congratulations, you have made your first change across
the entire Puppet infrastructure! You might want to look at the rest
of the documentation to learn more about how to do different tasks and
how things are setup. A key "How to" we recommend is the Progressive deployment
section below, which will teach you how to make a change
like the above while making sure you don't break anything even if it
affects a lot of machines.
How-to
Modifying an existing configuration
For new deployments, this is NOT the preferred method. For example,
if you are deploying new software that is not already in use in our
infrastructure, do not follow this guide and instead follow the
Adding a new module
guide below.
If you are touching an existing configuration, things are much
simpler however: you simply go to the module where the code already
exists and make changes. You git commit
and git push
the code,
then immediately run puppet agent -t
on the affected node.
Look at the File layout
section above to find the right piece of
code to modify. If you are making changes that potentially affect more
than one host, you should also definitely look at the Progressive deployment
section below.
Adding a new module
This is a broad topic, but let's take the Prometheus monitoring system as an example which followed the role/profile/module pattern.
First, the Prometheus modules on the Puppet forge were evaluated for quality and popularity. There was a clear winner there: the Prometheus module from Vox Populi had hundreds of thousands more downloads than the next option, which was deprecated.
Next, the module was added to the Puppetfile (in
3rdparty/Puppetfile
):
mod 'puppet-prometheus', '6.4.0'
... and librarian was ran:
librarian-puppet install
This fetched a lot of code from the Puppet forge: the stdlib, archive and system modules were all installed or updated. All those modules were audited manually, by reading each file and looking for obvious security flaws or back doors. Then the code was committed into git:
git add 3rdparty
git commit -m'install prometheus module after audit'
Then the module was configured in a profile, in modules/profile/manifests/prometheus/server.pp
:
class profile::prometheus::server {
class {
'prometheus::server':
# follow prom2 defaults
localstorage => '/var/lib/prometheus/metrics2',
storage_retention => '15d',
}
}
The above contains our local configuration for the upstream
prometheus::server
class installed in the 3rdparty
directory. In
particular, it sets a retention period and a different path for the
metrics, so that they follow the new Prometheus 2.x defaults.
Then this profile was added to a role, in
modules/roles/manifests/monitoring.pp
:
# the monitoring server
class roles::monitoring {
include profile::prometheus::server
}
Notice how the role does not refer to any implementation detail, like that the monitoring server uses Prometheus. It looks like a trivial, useless, class but it can actually grow to include multiple profiles.
Then that role is added to the Hiera configuration of the monitoring
server, in hiera/nodes/hetzner-nbg1-01.torproject.org.yaml
:
classes:
- roles::monitoring
And Puppet was ran on the host, with:
puppet --enable ; puppet agent -t --noop ; puppet --disable "testing prometheus deployment"
This led to some problems as the upstream module doesn't support
installing from Debian packages. Support for Debian was added to the
code in 3rdparty/modules/prometheus
, and committed into git:
emacs 3rdparty/modules/prometheus/manifests/*.pp # magic happens
git commit -m'implement all the missing stuff' 3rdparty
git push
And the above puppet command-line was ran again, continuing that loop until things were good.
If you need to deploy the code to multiple hosts, see the Progressive deployment
section below. To contribute changes back upstream (and
you should do so), see the section right below.
Contributing changes back upstream
For simple changes, the above workflow works well, but eventually it is preferable to actually fork the upstream repository and operate on our fork until the changes are merged upstream.
First, the modified module is moved out of the way:
mv 3rdparty/modules/prometheus{,.orig}
The module is then forked on GitHub or wherever it is hosted, and then added to the Puppetfile:
mod 'puppet-prometheus',
:git => 'https://github.com/anarcat/puppet-prometheus.git',
:branch => 'deploy'
Then Librarian is ran again to fetch that code:
librarian-puppet install
Because Librarian is a little dumb, it might checkout your module in "detached head" mode, in which case you will want to fix the checkout:
cd 3rdparty/modules/prometheus
git checkout deploy
git reset --hard origin/deploy
git pull
Note that the deploy
branch here is a merge of all the different
branches proposed upstream in different pull requests, but it could
also be the master
branch or a single branch if only a single pull
request was sent.
Since you now have a clone of the upstream repository, you can push and pull normally with upstream. When you make a change, however, you need to commit (and push) the change both in the sub-repository and the main repository:
cd 3rdparty/modules/prometheus
$EDITOR manifests/init.pp # more magic stuff
git commit -m'change the frobatz to a argblu'
git push
cd ..
git commit -m'change the frobatz to a argblu'
git push
Often, I make commits directly in our main Puppet repository, without pushing to the third party fork, until I am happy with the code, and then I craft a nice pretty commit that can be pushed upstream, reversing that process:
$EDITOR 3rdparty/prometheus/manifests/init.pp # dirty magic stuff
git commit -m'change the frobatz to a quuxblah'
git push
# see if that works, generally not
git commit -m'rah. wanted a quuxblutz'
git push
# now we are good, update our pull request
cd 3rdparty/modules/prometheus
git commit -m'change the frobatz to a quuxblutz'
git push
It's annoying to double-commit things, but I haven't found a best way to do so just yet. This problem is further discussed in ticket #29387.
Also note that when you update code like this, the Puppetfile
does
not change, but the Puppetfile.lock
file does change. The GIT.sha
parameter needs to be updated. This can be done by hand, but since
that is error-prone, you might want to simply run this to update
modules:
librarian-puppet update
This will also update dependencies so make sure you audit those changes before committing and pushing.
Running tests
Ideally, Puppet modules have a test suite. This is done with rspec-puppet and rspec-puppet-facts. This is not very well documented upstream, but it's apparently part of the Puppet Development Kit (PDK). Anyways: assuming tests exists, you will want to run some tests before pushing your code upstream, or at least upstream might ask you for this before accepting your changes. Here's how to get setup:
sudo apt install ruby-rspec-puppet ruby-puppetlabs-spec-helper ruby-bundler
bundle install --path vendor/bundle