416 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			416 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|   | --- | ||
|  | date: 2014-06-25 | ||
|  | title: Super Slick Agile Puppet for Devops | ||
|  | category:   devops | ||
|  | featured_image: https://i.imgur.com/3SJXbMb.jpg | ||
|  | --- | ||
|  | 
 | ||
|  | With a superb buzzword laden title like that, then I reckon massive | ||
|  | traffic boost is inevitable. | ||
|  | 
 | ||
|  | Puppet is my favourite Configuration Management tool. This is not a post | ||
|  | to try and persuade anyone not to use Ansible, Chef or any other. What I | ||
|  | want to do is show I build Puppet based infrastuctures in such away that | ||
|  | it meets all the basic tenets of DevOps/Agile/buzzword-of-the-month. | ||
|  | 
 | ||
|  | <!-- more --> | ||
|  | What to we need: | ||
|  | 
 | ||
|  | -   CentOS 6 - RHEL/CentOS is pretty much the defacto enterprise distro. | ||
|  |     This will easily translate to Debian/Ubuntu or anything else. | ||
|  | -   Puppet 3 - I like a traditional Master/Agent set up, if you prefer | ||
|  |     master-less good for you. This is my blog, my rules. | ||
|  | -   Git | ||
|  | -   Dynamic Environments | ||
|  | -   PuppetDB | ||
|  | -   Hiera | ||
|  | -   Jenkins | ||
|  | 
 | ||
|  | All the config is stored in Git, with Jenkins watching it. | ||
|  | 
 | ||
|  | Puppet tends to fall apart pretty quickly if you do not have DNS in | ||
|  | place. You can start using host files, but that will get old quickly. | ||
|  | Ideally, the first thing you will do with Puppet is install a DNS server | ||
|  | managed by Puppet. Maybe that will be the next post. | ||
|  | 
 | ||
|  | # Puppet
 | ||
|  | 
 | ||
|  | Starting with a base Centos 6 install, the installation is very easy: | ||
|  | 
 | ||
|  |     yum -y install https://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm | ||
|  |     yum -y install puppet puppet-server rubygem-activerecord | ||
|  | 
 | ||
|  | Our environments need a place to go, so create that: | ||
|  | 
 | ||
|  |     mkdir /etc/puppet/environments | ||
|  |     chgrp puppet /etc/puppet/environments | ||
|  |     chmod 2775 /etc/puppet/environments | ||
|  | 
 | ||
|  | The configuration will look like: | ||
|  | 
 | ||
|  |     [main] | ||
|  |         logdir = /var/log/puppet | ||
|  |         rundir = /var/run/puppet | ||
|  |         ssldir = $vardir/ssl | ||
|  |         trusted_node_data = true | ||
|  |         pluginsync = true | ||
|  | 
 | ||
|  |     [agent] | ||
|  |         classfile = $vardir/classes.txt | ||
|  |         localconfig = $vardir/localconfig | ||
|  |         report = true | ||
|  |         environment = production | ||
|  |         ca_server = puppet.chriscowley.lan | ||
|  |         server = puppet.chriscowley.lan | ||
|  | 
 | ||
|  |     [master] | ||
|  |         environmentpath = $confdir/environments | ||
|  |         # Passenger | ||
|  |         ssl_client_header        = SSL_CLIENT_S_DN | ||
|  |         ssl_client_verify_header = SSL_CLIENT_VERIFY | ||
|  | 
 | ||
|  | Do not use the Puppetmaster service. It uses Webrick, which is bad. Any | ||
|  | more than 5 agents and it will start slowing down. Puppet is a RoR app, | ||
|  | so stick it behind | ||
|  | [Apache/Passenger](https://docs.puppetlabs.com/guides/passenger.html). | ||
|  | We installed the `puppet-server` package for a simple reason: when you | ||
|  | start it the first time, it will create your SSL certificates | ||
|  | automatically. After that initial start you can stop it and forget it | ||
|  | ever existed. So just run: | ||
|  | 
 | ||
|  |     service puppetmaster start | ||
|  |     service puppetmaster stop | ||
|  | 
 | ||
|  | Unfortunately, you will need to put SELinux into Permissive mode | ||
|  | temporarily. Once you have fired it up you can [build a local | ||
|  | policy](https://wiki.centos.org/HowTos/SELinux#head-faa96b3fdd922004cdb988c1989e56191c257c01) | ||
|  | and re-enable it. | ||
|  | 
 | ||
|  |     yum install httpd httpd-devel mod_ssl ruby-devel rubygems gcc gcc-c++ curl-devel openssl-devel zlib-devel | ||
|  |     gem install rack passenger | ||
|  |     passenger-install-apache2-module | ||
|  | 
 | ||
|  | Next you need to configure Apache to serve up the RoR app. | ||
|  | 
 | ||
|  |     mkdir -p /usr/share/puppet/rack/puppetmasterd | ||
|  |     mkdir /usr/share/puppet/rack/puppetmasterd/public /usr/share/puppet/rack/puppetmasterd/tmp | ||
|  |     cp /usr/share/puppet/ext/rack/config.ru /usr/share/puppet/rack/puppetmasterd/ | ||
|  |     chown puppet:puppet /usr/share/puppet/rack/puppetmasterd/config.ru | ||
|  |     https://gist.githubusercontent.com/chriscowley/00e75ee021ce314fab1e/raw/c87abc38182eafc6d22a80d13078ac044fdde49f/puppetmaster.conf | sed 's/puppet-server.example.com/puppet.yourlan.lan/g' | ||
|  | 
 | ||
|  | You will need to modify the `sed` command in the last line to match your | ||
|  | environment. | ||
|  | 
 | ||
|  | You may also need to change the Passenger paths to match what the output | ||
|  | of `passenger-install-apache2-module` told you. It is up to date as of | ||
|  | the time of writing. | ||
|  | 
 | ||
|  | # Hiera
 | ||
|  | 
 | ||
|  | Your config file (`/etc/puppet/hiera.yaml`) will already be created, | ||
|  | mine looks like this: | ||
|  | 
 | ||
|  |     --- | ||
|  |     :backends: | ||
|  |       - yaml | ||
|  |     :hierarchy: | ||
|  |       - defaults | ||
|  |       - "nodes/%{clientcert}" | ||
|  |       - "virtual/%{::virtual}" | ||
|  |       - "%{environment}" | ||
|  |       - "%{::osfamily}" | ||
|  |       - global | ||
|  | 
 | ||
|  |     :yaml: | ||
|  |       :datadir: "/etc/puppet/environments/%{::environment}/hieradata" | ||
|  | 
 | ||
|  | There is also an `/etc/hiera.yaml` which Puppet does not use. change | ||
|  | this to a symbolic link to avoid confusion. | ||
|  | 
 | ||
|  |     ln -svf /etc/puppet/hiera.yaml /etc/hiera.yaml | ||
|  | 
 | ||
|  | If you were to test it now, you will see a few errors: | ||
|  | 
 | ||
|  |     Info: Retrieving pluginfacts | ||
|  |     Error: /File[/var/lib/puppet/facts.d]: Could not evaluate: Could not retrieve information from environment production source(s) puppet://puppet/pluginfacts | ||
|  |     Info: Retrieving plugin | ||
|  |     Error: /File[/var/lib/puppet/lib]: Could not evaluate: Could not retrieve information from environment production source(s) puppet://puppet/plugins | ||
|  | 
 | ||
|  | Don\'t worry about that for now, the important thing is that the agent | ||
|  | connects to the master. If that happens the master does return an HTTP | ||
|  | error, then you are good. | ||
|  | 
 | ||
|  | # R10k
 | ||
|  | 
 | ||
|  | This is the tool I use to manage my modules. It can pull them off the | ||
|  | Forge, or from wherever you tell it too. Most often that will be Github, | ||
|  | or an internal Git repo if that\'s what you use. | ||
|  | 
 | ||
|  | You need to install it from Ruby Gems, then there is a little | ||
|  | configuration to do. | ||
|  | 
 | ||
|  | : | ||
|  | 
 | ||
|  |     gem install r10k | ||
|  |     mkdir /var/cache/r10k | ||
|  |     chgrp puppet /var/cache/r10k | ||
|  |     chmod 2775 /var/cache/r10k | ||
|  | 
 | ||
|  | The file `/etc/r10k.yaml` should contain: | ||
|  | 
 | ||
|  |     # location for cached repos | ||
|  |     :cachedir: '/var/cache/r10k' | ||
|  | 
 | ||
|  |     # git repositories containing environments | ||
|  |     :sources: | ||
|  |       :base: | ||
|  |         remote: '/srv/puppet.git' | ||
|  |         basedir: '/etc/puppet/environments' | ||
|  | 
 | ||
|  |     # purge non-existing environments found here | ||
|  |     :purgedirs: | ||
|  |       - '/etc/puppet/environments' | ||
|  | 
 | ||
|  | # Git
 | ||
|  | 
 | ||
|  | The core of your this process is the ubiquitous Git. | ||
|  | 
 | ||
|  |     yum install git | ||
|  | 
 | ||
|  | You need a Git repo to store everything, and also launch a deploy script | ||
|  | when you push to it. To start with we\'ll put it on the Puppet server. | ||
|  | In the future I would put this on a dedicated machine, have Jenkins run | ||
|  | tests, then run the deploy script on success. | ||
|  | 
 | ||
|  | However, it is not a standard repository, so you cannot just run | ||
|  | `git init`. It needs: | ||
|  | 
 | ||
|  | -   To be **bare** | ||
|  | -   To be **shared** | ||
|  | -   Have the **master** branch renamed to **production** | ||
|  | 
 | ||
|  | <!-- --> | ||
|  | 
 | ||
|  |     mkdir -pv /srv/puppet.git | ||
|  |     git init --bare --shared=group /srv/puppet.git | ||
|  |     chgrp -R puppet /srv/puppet.git | ||
|  |     cd /srv/puppet.git | ||
|  |     git symbolic-ref HEAD refs/heads/production | ||
|  | 
 | ||
|  | Continuing to work as root is not acceptable, so create user (if you do | ||
|  | not already have one). | ||
|  | 
 | ||
|  |     useradd <username> | ||
|  |     usermod -G wheel,puppet <username> | ||
|  |     visudo | ||
|  | 
 | ||
|  | Uncomment the line that reads: | ||
|  | 
 | ||
|  |     %wheel        ALL=(ALL)       ALL | ||
|  | 
 | ||
|  | This gives your user full `sudo` privileges. | ||
|  | 
 | ||
|  | # Deploy script
 | ||
|  | 
 | ||
|  | This is what does the magic stuff. It needs to be | ||
|  | `/srv/puppet.git/hooks/post-receive` so that it runs when you push | ||
|  | something to this repository. | ||
|  | 
 | ||
|  |     #!/bin/bash | ||
|  | 
 | ||
|  |     umask 0002 | ||
|  | 
 | ||
|  |     while read oldrev newrev ref | ||
|  |     do | ||
|  |         branch=$(echo $ref | cut -d/ -f3) | ||
|  |         echo | ||
|  |         echo "--> Deploying ${branch}..." | ||
|  |         echo | ||
|  |         r10k deploy environment $branch -p | ||
|  |         # sometimes r10k gets permissions wrong too | ||
|  |         find /etc/puppet/environments/$branch/modules -type d -exec chmod 2775 {} \; 2> /dev/null | ||
|  |         find /etc/puppet/environments/$branch/modules -type f -exec chmod 664 {} \; 2> /dev/null | ||
|  |     done | ||
|  | 
 | ||
|  | Run `chmod 0775 /srv/puppet.git/hooks/post-receive` to make is | ||
|  | executable and writable by anyone in the `puppet` group. | ||
|  | 
 | ||
|  | # The first environment
 | ||
|  | 
 | ||
|  | Switch to your user | ||
|  | 
 | ||
|  |     su - <username> | ||
|  | 
 | ||
|  | Clone the repository and create the necessary folder structure: | ||
|  | 
 | ||
|  |     git clone /srv/puppet.git | ||
|  |     cd puppet | ||
|  |     mkdir -pv hieradata/nodes manifests site | ||
|  | 
 | ||
|  | Now you can create `PuppetFile` in the root of that repository. This is | ||
|  | what tells R10k what modules to deploy. | ||
|  | 
 | ||
|  |     # Puppet Forge | ||
|  |     mod 'puppetlabs/ntp', '3.0.0-rc1' | ||
|  |     mod 'puppetlabs/puppetdb', '3.0.1' | ||
|  |     mod 'puppetlabs/stdlib', '3.2.1' | ||
|  |     mod 'puppetlabs/concat', '1.0.0' | ||
|  |     mod 'puppetlabs/inifile', '1.0.3' | ||
|  |     mod 'puppetlabs/postgresql', '3.3.3' | ||
|  |     mod 'puppetlabs/firewall', '1.0.2' | ||
|  |     mod 'chriscowley/yumrepos', '0.0.2' | ||
|  | 
 | ||
|  |     # Get a module from Github | ||
|  |     #mod 'custom', | ||
|  |     #  :git => 'https://github.com/chriscowley/puppet-pydio.git', | ||
|  |     #  :ref => 'master' | ||
|  | 
 | ||
|  | A common error I make if I am not looking properly is to put the SSH URL | ||
|  | from Github in there. This will not work unless you have added your SSH | ||
|  | key on the Puppet server. Better just to put the HTTPS URL in there, | ||
|  | there is need to write back to it after all. | ||
|  | 
 | ||
|  | Next you need to tell Puppet what agents should get what. To begin with, | ||
|  | everything will get NTP, but only the Puppetmaster will get PuppetDB. To | ||
|  | that end create `hieradata/common.yaml` with this: | ||
|  | 
 | ||
|  |     --- | ||
|  |     classes: | ||
|  |       - ntp | ||
|  | 
 | ||
|  |     ntp::servers: | ||
|  |       - 0.pool.ntp.org | ||
|  |       - 1.pool.ntp.org | ||
|  |       - 2.pool.ntp.org | ||
|  |       - 3.pool.ntp.org | ||
|  | 
 | ||
|  | Next create `hieradata/nodes/$(hostname -s).yaml` with: | ||
|  | 
 | ||
|  |     --- | ||
|  |     classes: | ||
|  |       - puppetdb | ||
|  |       - puppetdb::master::config | ||
|  | 
 | ||
|  | Finally, you need to tell Puppet to get the data from Hiera. Create | ||
|  | `manifests.site.pp` with | ||
|  | 
 | ||
|  |     hiera_include('classes') | ||
|  | 
 | ||
|  | You should need nothing else. | ||
|  | 
 | ||
|  | Now you can push it to the master repository. | ||
|  | 
 | ||
|  |     git add . | ||
|  |     git commit -a -m "Initial commit" | ||
|  |     git branch -m production | ||
|  |     git push origin production | ||
|  | 
 | ||
|  | # Testing
 | ||
|  | 
 | ||
|  | Of course, the whole point of all this is that we do as much testing as | ||
|  | we can before any sort of deploy. We also want to keep our Git | ||
|  | repository nice clean (especially if you push it to Github), so if we | ||
|  | can avoid commits with stupid errors that would be great. | ||
|  | 
 | ||
|  | To perform your testing you need to replicate your production | ||
|  | environment. From now on, I\'m going to assume that you are working on | ||
|  | your own workstation. | ||
|  | 
 | ||
|  | Clone your repository: | ||
|  | 
 | ||
|  |     git clone ssh://<username>@puppet.example.com/srv/puppet.git | ||
|  |     cd puppet | ||
|  | 
 | ||
|  | To perform all the testing, [RVM](https://rvm.io/) is your friend. This | ||
|  | allows you to replicate the ruby environment on the master, have all the | ||
|  | necessary gems installed in a contained environment and sets you up to | ||
|  | integrate with Jenkins later. Install is with: | ||
|  | 
 | ||
|  |     curl -sSL https://get.rvm.io | bash -s stable | ||
|  | 
 | ||
|  | Follow any instructions it gives your, then you can create your | ||
|  | environment. This will be using a old version of ruby as we are running | ||
|  | CentOS 6 on the master. | ||
|  | 
 | ||
|  |     rvm install ruby-1.8.7 | ||
|  |     rvm use ruby-1.8.7 | ||
|  |     rvm gemset create puppet | ||
|  |     rvm gemset use puppet | ||
|  |     rvm --create use ruby-1.8.7-head@puppet --rvmrc | ||
|  | 
 | ||
|  | Create a Gemfile that contains: | ||
|  | 
 | ||
|  |     source 'https://rubygems.org' | ||
|  | 
 | ||
|  |     gem 'puppet-lint', '0.3.2' | ||
|  |     gem 'puppet', '3.6.2' | ||
|  |     gem 'kwalify', '0.7.2' | ||
|  | 
 | ||
|  | Now you can install the gems with `bundle install`. | ||
|  | 
 | ||
|  | The tests will be run by a pre-commit hook script, that looks something | ||
|  | like: | ||
|  | 
 | ||
|  |     #!/bin/bash | ||
|  |     # pre-commit git hook to check the validity of a puppet main manifest | ||
|  |     # | ||
|  |     # Prerequisites: | ||
|  |     # gem install puppet-lint puppet | ||
|  |     # | ||
|  |     # Install: | ||
|  |     # /path/to/repo/.git/hooks/pre-commit | ||
|  |     # | ||
|  |     # Authors: | ||
|  |     # Chris Cowley <chris@chriscowley.me.uk> | ||
|  | 
 | ||
|  |     echo "Checking style" | ||
|  |     for file in `git diff --name-only --cached | grep -E '\.(pp)'`; do | ||
|  |       puppet-lint ${file} | ||
|  |       if [ $? -ne 0 ]; then | ||
|  |         style_bad=1 | ||
|  |       else | ||
|  |         echo "Style looks good" | ||
|  |       fi | ||
|  |     done | ||
|  | 
 | ||
|  |     echo "Checking syntax" | ||
|  |     for file in `git diff --name-only --cached | grep -E '\.(pp)'`; do | ||
|  |       puppet parser validate $file | ||
|  |       if [ $? -ne 0 ]; then | ||
|  |         syntax_bad=1 | ||
|  |         echo "Syntax error in ${file}" | ||
|  |       else | ||
|  |         echo "Syntax looks good" | ||
|  |       fi | ||
|  |     done | ||
|  | 
 | ||
|  |     for file in `git diff --name-only --cached | grep -E '\.(yaml)'`; do | ||
|  |       echo "Checking YAML is valid" | ||
|  |       ruby -e "require 'yaml'; YAML.parse(File.open('$file'))" | ||
|  |       if [ $? -ne 0 ]; then | ||
|  |         yaml_bad=1 | ||
|  |       else | ||
|  |         echo "YAML looks good" | ||
|  |       fi | ||
|  |     done | ||
|  | 
 | ||
|  |     if [ ${yaml_bad}  ];then | ||
|  |       exit 1 | ||
|  |     elif [ ${syntax_bad}  ]; then | ||
|  |       exit 1 | ||
|  |     elif [ ${style_bad}  ]; then | ||
|  |       exit 1 | ||
|  |     else | ||
|  |       exit 0 | ||
|  |     fi | ||
|  | 
 | ||
|  | This should set you up very nicely. Your environments are completely | ||
|  | dynamic, you have a framework in place for testing. | ||
|  | 
 | ||
|  | For now the deployment is with a hook script, but that is not the | ||
|  | ultimate goal. This Git repo needs to be on the Puppet master. You may | ||
|  | well already have a Git server you want to use. TO this end, in a later | ||
|  | post I will be add Jenkins into the mix. As you are running the tests in | ||
|  | an RVM environment, it will be very easy to put into Jenkins. This can | ||
|  | then perform the deployment. |