Infrastructure testing with Jenkins, Puppet and Vagrant
Carlos Sanchez@csanchezhttp://csanchez.orghttp://maestrodev.com
@csanchez Apache Maven
ASF Member
Eclipse Foundation
csanchez.orgmaestrodev.com
How we got here
Agile
planningiterative developmentcontinuous integration
release soon, release often
Fear of changeRisky deployments
It works on my machine!Siloisation
Dev Change vs. Ops stability
OPs requirements
Operating Systemconfig filespackages installedmulti stage configurationsdevQApre-productionproduction
Deployment
How do I deploy this?documentationmanual stepsprone to errors
Cloud
How do I deploy this?to hundreds of servers
DevOps
DevQaOps ?
DEV QA OPS
DEV
QA
OPS
SpecsPackagesVersions
DEV PROD
TestabilityDEV QA
MetricsLogsSecurity updates
DEV PROD
is not about the toolsbut
how can I implement IT
Tools can enable change in behavior and eventually change culturePatrick Debois
everyone is intelligent enoughevery tool is cloud enabledevery tool is DevOps(tm)
3 key concepts
Continuous Delivery
Continuous delivery
Infrastructure as Codeit’s all been invented, now it’s standardized
manifestsruby-likeERB templates
exec { "maven-untar": command => "tar xf /tmp/x.tgz", cwd => "/opt", creates => "/opt/apache-maven-${version}", path => ["/bin"], } -> file { "/usr/bin/mvn": ensure => link, target => "/opt/apache-maven-${version}/bin/mvn", } file { "/usr/local/bin/mvn": ensure => absent, require => Exec["maven-untar"], } file { "$home/.mavenrc": mode => "0600", owner => $user, content => template("maven/mavenrc.erb"), require => User[$user], }
infrastructureIS code
package { 'openssh-server': ensure => present,}
declarative modelstate vs processno scripting
service { 'ntp': name => 'ntpd', ensure => running, }
Follow development best practicestaggingbranchingreleasingdev, QA, production
new solutions
new challenges
Self servicing
Infrastructure always availablevirtualization & cloudempower developersreduce time-to-market
devs buy-inWith great power comes great responsibility
Vagrantempower developersdev-ops collaborationautomation
Vagrant
Vagrant
Virtual and cloud automationVirtualBox
VMWare Fusion
AWS
Rackspace
Easy Puppet and Chef provisioningKeep VM configuration for different projects
Share boxes and configuration files across teamsbase box + configuration files
Vagrant base boxes
www.vagrantbox.espuppet-vagrant-boxes.puppetlabs.com
anywhere! just (big) files
using Vagrant
$ gem install vagrant$ vagrant box add centos-6.0-x86_64 \ http://dl.dropbox.com/u/1627760/centos-6.0-x86_64.box $ vagrant init myproject$ vagrant up$ vagrant ssh$ vagrant suspend$ vagrant resume$ vagrant destroy
Vagrant.configure("2") do |config|
# Every Vagrant virtual environment requires a box to build off of. config.vm.box = "CentOS-6.4-x86_64-minimal" config.vm.box_url = "https://.../CentOS-6.4-x86_64-minimal.box"
# web server config.vm.define :www do |config| config.vm.hostname = "www.acme.local" config.vm.network "forwarded_port", guest: 80, host: 10080 config.vm.network "private_network", ip: "192.168.33.12" end
config.vm.provision :puppet do |puppet| puppet.module_path = "modules" puppet.manifest_file = "site.pp" end
end
Vagrant
Geppetto
http://cloudsmith.github.com/geppetto
Maven and Puppet
What am I doing to automate deployment
Ant tasks pluginssh commands
Assembly pluginCargo
Capistrano
What can I do to automate deployment
Handle full deployment including infrastructurenot just webapp deployment
Help Ops with clear, automated manifestsAbility to reproduce production environmentsin local box using Vagrant / VirtualBox / VMWare
Use the right tool for the right job
Maven-Puppet module
A Maven Puppet module
https://github.com/maestrodev/puppet-maven
fetches Maven artifacts from the repomanages them with Puppet
no more extra packaging
Installing Maven
$repo1 = { id => "myrepo", username => "myuser", password => "mypassword", url => "http://repo.acme.com",}
# Install Mavenclass { "maven::maven": version => "2.2.1",} ->
# Create a settings.xml with the repo credentialsclass { "maven::settings" : servers => [$repo1],}
New Maven type
maven { "/tmp/maven-core-2.2.1.jar": id => "org.apache.maven:maven-core:jar:2.2.1", repos => ["http://repo1.maven.apache.org/maven2",
"http://mirrors.ibiblio.org/pub/mirrors/maven2"], }
New Maven type
maven { "/tmp/maven-core-2.2.1.jar": groupId => "org.apache.maven", artifactId => "maven-core", version => "2.2.1", packaging => "jar", repos => ["http://repo1.maven.apache.org/maven2",
"http://mirrors.ibiblio.org/pub/mirrors/maven2"], }
Examples
Infrastructure
Jenkins
Archiva
wwwhttpd
QAhttpd,
tomcat, postgres
tomcat1tomcat
dbpostgres
Tomcat cluster + postgres
postgresdb.acme.com
tomcattomcat1.acme.com
httpdwww.acme.com
tomcattomcat2.acme.com ...
Continuous Delivery
developer
commit git repo
jenkins
binary repository
QA vm
db
tomcat*
www
post commit hook
build
integration testing
production
Puppet Modules required
$ bundle install && librarian-puppet install
forge 'http://forge.puppetlabs.com'
mod 'puppetlabs/java', '>=1.0.0'mod 'puppetlabs/apache', '>=0.9.0'mod 'puppetlabs/postgresql', '>=3.0.0'mod 'puppetlabs/firewall'mod 'camptocamp/tomcat', :git => 'https://github.com/carlossg/puppet-tomcat.git', :ref => 'setclasspath'mod 'maestrodev/maven', '>=1.0.0'mod 'stahnma/epel', '>=0.0.2'mod 'maestrodev/avahi', '>=1.0.0'mod 'acme', :path => 'mymodules/acme'
mymodules/acme/manifests/db_node.pp
class 'acme::db_node' {
class { 'postgresql::server': ip_mask_allow_all_users => '192.168.0.0/0', listen_addresses => '*', postgres_password => 'postgres', } ->
postgresql::server::db { 'appfuse': user => 'appfuse', password => 'appfuse', grant => 'all', }
firewall { '100 allow postgres': proto => 'tcp', port => '5432', action => 'accept', }}
mymodules/acme/manifests/tomcat_node.pp I
class acme::tomcat_node( $db_host = 'db.local', $repo = 'http://carlos-mbook-pro.local:8000/repository/all/', $appfuse_version = '2.2.2-SNAPSHOT', $service = 'tomcat-appfuse', $app_name = 'appfuse', $tomcat_root = '/srv/tomcat') {
# install java class { 'java': }
# install tomcat class { 'tomcat': } ->
# create a tomcat server instance for appfuse # It allows having multiple independent tomcat servers in different ports tomcat::instance { 'appfuse': ensure => present, http_port => 8080, }
# where the war needs to be installed $webapp = "${tomcat_root}/${app_name}/webapps/ROOT"
mymodules/acme/manifests/tomcat_node.pp II
# install maven and download appfuse war file from our archiva instance class { 'wget': } -> class { 'maven::maven' : version => '3.0.4', } -> # clean up to deploy the next snapshot exec { "rm -rf ${webapp}*": } -> maven { "${webapp}.war": id => "org.appfuse:appfuse-spring:${appfuse_version}:war", repos => [$repo], require => File["${tomcat_root}/${app_name}/webapps"], notify => Service[$service], } ->
firewall { '100 allow tomcat': proto => 'tcp', port => '8080', action => 'accept', }}
mymodules/acme/manifests/www_node.pp
class acme::www_node($tomcat_host = 'tomcat1.local') {
class { 'apache': } class { 'apache::mod::proxy_http': }
# create a virtualhost that will proxy the tomcat server apache::vhost { "${::hostname}.local": port => 80, docroot => '/var/www', proxy_dest => "http://${tomcat_host}:8080", }
firewall { '100 allow apache': proto => 'tcp', port => '80', action => 'accept', }}
manifests/site.pp
import 'nodes/*.pp'
node ‘parent’ { class {'epel': } ->
class {'avahi': firewall => true, }}
manifests/nodes/tomcat.pp
# tomcat1.acme.com, tomcat2.acme.com,...node /tomcat\d\..*/ inherits ‘parent’ {
file {'/etc/motd': content => ”tomcat server: ${::hostname}\n”, }
class {'acme::tomcat_node'}}
manifests/nodes/qa.pp
node /qa\..*/ inherits ‘parent’ { class {'acme::db_node': }
class {'acme::tomcat_node': db_host => 'localhost', }
class {'acme::www_node': tomcat_host => 'localhost', }}
Infrastructure unit testing
rspec-puppet
Unit testing your puppet manifestsEnsuring packages, config files, services,...
spec/hosts/db_spec.pp
require 'rspec-puppet'
describe 'db.acme.com' do let(:facts) { { :osfamily => 'RedHat', :operatingsystem => 'CentOS', :operatingsystemrelease => ‘6.3’} }
it { should contain_class('postgresql::server') }end
spec/hosts/www_spec.pp
require 'rspec-puppet'
describe 'www.acme.com' do let(:facts) { { :osfamily => 'RedHat', :operatingsystem => 'CentOS', :operatingsystemrelease => ‘6.3’} }
it { should contain_package('httpd') }end
Example code and slides
Available athttp://slideshare.csanchez.org
http://github.csanchez.orghttp://blog.csanchez.org
Photo Credits
Brick wall - Luis Argerichhttp://www.flickr.com/photos/lrargerich/4353397797/
Agile vs. Iterative flow - Christopher Littlehttp://en.wikipedia.org/wiki/File:Agile-vs-iterative-flow.jpg
DevOps - Rajiv.Panthttp://en.wikipedia.org/wiki/File:Devops.png
Pimientos de Padron - Howard Walfishhttp://www.flickr.com/photos/h-bomb/4868400647/
Compiling - XKCDhttp://xkcd.com/303/
Printer in 1568 - Meggs, Philip Bhttp://en.wikipedia.org/wiki/File:Printer_in_1568-ce.png
Relativity - M. C. Escherhttp://en.wikipedia.org/wiki/File:Escher%27s_Relativity.jpg
Teacher and class - Herald Posthttp://www.flickr.com/photos/heraldpost/5169295832/