Slide 1

Slide 1 text

Testing Your Automation TDD for Chef Cookbooks Nathen Harvey @nathenharvey https://github.com/nathenharvey/bigruby

Slide 2

Slide 2 text

What is Chef? Recipes and Cookbooks that describe Infrastructure as Code. Chef enables people to easily build & manage complex & dynamic applications at massive scale • New model for describing infrastructure that promotes reuse • Programmatically provision and configure • Reconstruct business from code repository, data backup, and bare metal resources Chef is an automation platform for developers & systems engineers to continuously define, build, and manage infrastructure. CHEF USES: “ ”

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Writing Infrastructure Code https://github.com/nathenharvey/bigruby

Slide 5

Slide 5 text

Evolving towards Configuration Management •Just build it •Keep notes in server.txt •Move notes to the wiki •Custom scripts (in scm?!) •Snapshot & Clone

Slide 6

Slide 6 text

Building something with Chef •Come up with your policy •Abstract the resources •Write recipes •Apply recipes to nodes

Slide 7

Slide 7 text

Trivial Example

Slide 8

Slide 8 text

Cookbook $ knife cookbook create website ** Creating cookbook website ** Creating README for cookbook: website ** Creating CHANGELOG for cookbook: website ** Creating metadata for cookbook: website

Slide 9

Slide 9 text

Resources in Recipe package "apache2" template "/var/www/index.html" do owner "www-data" group "www-data" mode 644 source "index.html.erb" end service "apache2" do action [:start, :enable] end

Slide 10

Slide 10 text

templates/default/index.html.erb <%= node['conference']['name'] %>

Hello, <%= node['conference']['name'] %>

• I told you, this is a trivial example! Stick with me. • No, you wouldn’t really manage your content in Chef but this may be easier to grok than sysctl settings.

Slide 11

Slide 11 text

attributes/default.rb default['conference']['name'] = "Big Ruby"

Slide 12

Slide 12 text

Apply This Recipe to a Node $ vagrant init web A `Vagrantfile` has been placed in this directory. You are now ready to `vagrant up` your first virtual environment! Please read the comments in the Vagrantfile as well as documentation on `vagrantup.com` for more information on using Vagrant.

Slide 13

Slide 13 text

Vagrantfile # -*- mode: ruby -*- # vi: set ft=ruby : Vagrant::Config.run do |config| config.vm.box = "web" config.vm.forward_port 80, 8080 config.vm.provision :chef_client do |chef| chef.chef_server_url = "https://api.opscode.com/organizations/bigruby" chef.validation_key_path = "bigruby-validator.pem" chef.validation_client_name = "bigruby-validator" end end

Slide 14

Slide 14 text

Launch the VM $ vagrant up [default] Importing base box 'web'... [default] Matching MAC address for NAT networking... [default] Forwarding ports... [default] -- 22 => 2222 (adapter 1) [default] -- 80 => 8080 (adapter 1) ... [2013-02-01T05:24:19+00:00] WARN: Node vagrant.vm has an empty run list. [2013-02-01T05:24:20+00:00] INFO: Chef Run complete in 1.962777136 seconds [2013-02-01T05:24:20+00:00] INFO: Running report handlers [2013-02-01T05:24:20+00:00] INFO: Report handlers complete

Slide 15

Slide 15 text

Provision the Node $ knife cookbook upload website Uploading website [0.1.0] Uploaded 1 cookbook. $ knife node run_list add vagrant.vm "recipe[website]" vagrant.vm: run_list: recipe[website] $ vagrant provision

Slide 16

Slide 16 text

Test

Slide 17

Slide 17 text

Testing Infrastructure Code https://github.com/nathenharvey/bigruby

Slide 18

Slide 18 text

Why test?

Slide 19

Slide 19 text

Testing Tools • Vagrant • knife cookbook test

Slide 20

Slide 20 text

knife cookbook test $ knife cookbook test website checking website Running syntax check on website Validating ruby files Validating templates

Slide 21

Slide 21 text

Testing Tools • Vagrant • knife cookbook test • Foodcritic

Slide 22

Slide 22 text

Foodcritic • A lint tool for your Opscode Chef cookbooks • Flag problems in your Chef cookbooks that will cause Chef to blow up when you attempt to converge • Encourage discussion within the Chef community on the more subjective stuff - what does a good cookbook look like?

Slide 23

Slide 23 text

Foodcritic $ foodcritic cookbooks/website FC006: Mode should be quoted or fully specified when setting file permissions: cookbooks/website/recipes/default.rb:11 FC008: Generated cookbook metadata needs updating: cookbooks/website/metadata.rb:2 FC008: Generated cookbook metadata needs updating: cookbooks/website/metadata.rb:3

Slide 24

Slide 24 text

Foodcritic

Slide 25

Slide 25 text

Foodcritic

Slide 26

Slide 26 text

Testing Tools • Vagrant • knife cookbook test • Foodcritic • Chefspec

Slide 27

Slide 27 text

Chefspec require 'chefspec' describe 'website::default' do chef_run = ChefSpec::ChefRunner.new chef_run.converge "website::default" it "should install apache package" do chef_run.should install_package "apache2" end it "should create a home page" do chef_run.should create_file "/var/www/index.html" end

Slide 28

Slide 28 text

Chefspec it "should create a home page with our content" do chef_run.should create_file_with_content( "/var/www/index.html","Big Ruby") end it "should start the apache service" do chef_run.should start_service "apache2" end it "should enable the apache service" do chef_run.should enable_service "apache2" end end

Slide 29

Slide 29 text

Chefspec chef_run.should create_file_with_content( "/var/www/index.html","Big Ruby") <%= node['conference']['name'] %> default['conference']['name'] = "Big Ruby"

Slide 30

Slide 30 text

"memory": { "swap": { "cached": "0kB", "total": "4128760kB", "free": "4128760kB" }, "total": "2055676kB", "free": "1646524kB", "buffers": "35032kB", "cached": "210276kB", "active": "125336kB", "inactive": "142884kB", "dirty": "8kB", "writeback": "0kB", "anon_pages": "22976kB", "mapped": "8416kB", "slab": "121512kB", "slab_reclaimable": "41148kB", "slab_unreclaim": "80364kB", "page_tables": "1784kB", "nfs_unstable": "0kB", "bounce": "0kB", "commit_limit": "5156596kB", "committed_as": "74980kB", "vmalloc_total": "34359738367kB", "vmalloc_used": "274512kB", "vmalloc_chunk": "34359449936kB" }, Ohai! "block_device": { "ram0": { "size": "32768", "removable": "0" }, "ram1": { "size": "32768", "removable": "0" }, "ram2": { "size": "32768", "removable": "0" }, "hostname": "server-1", "fqdn": "server-1.example.com", "domain": "example.com", "network": { "interfaces": { "eth0": { "type": "eth", "number": "0", "encapsulation": "Ethernet", "addresses": { "00:0C:29:43:26:C5": { "family": "lladdr" }, "192.168.177.138": { "family": "inet", "broadcast": "192.168.177.255", "netmask": "255.255.255.0" }, "fe80::20c:29ff:fe43:26c5": { "family": "inet6", "prefixlen": "64", "scope": "Link" } },

Slide 31

Slide 31 text

Chefspec & Fauxhai

Slide 32

Slide 32 text

Testing Tools • Vagrant • knife cookbook test • Foodcritic • Chefspec • Fauxhai • Minitest and the Minitest Chef Handler

Slide 33

Slide 33 text

Handlers $ vagrant up [default] Importing base box 'web'... [default] Matching MAC address for NAT networking... [default] Forwarding ports... [default] -- 22 => 2222 (adapter 1) [default] -- 80 => 8080 (adapter 1) ... [2013-02-01T05:24:19+00:00] WARN: Node vagrant.vm has an empty run list. [2013-02-01T05:24:20+00:00] INFO: Chef Run complete in 1.962777136 seconds [2013-02-01T05:24:20+00:00] INFO: Running report handlers [2013-02-01T05:24:20+00:00] INFO: Report handlers complete

Slide 34

Slide 34 text

minitest class TestWebsite < MiniTest::Chef::TestCase include MiniTest::Chef::Assertions include MiniTest::Chef::Context include MiniTest::Chef::Resources def test_succeed assert run_status.success? end def test_that_the_package_installed package("apache2").must_be_installed end def test_that_the_service_is_running service("apache2").must_be_running end

Slide 35

Slide 35 text

minitest def test_that_the_service_is_enabled service("apache2").must_be_enabled end def test_home_page_file file("/var/www/index.html").must_exist end def test_home_page_content file("/var/www/index.html").must_include node['conference']['name'] end

Slide 36

Slide 36 text

minitest-handler $ vagrant provision [2013-02-01T06:43:34+00:00] INFO: Running report handlers Run options: -v --seed 12405 # Running tests: TestWebsite#test_succeed = ... Finished tests in 0.098367s, 60.9959 tests/s, 60.9959 assertions/s. 6 tests, 6 assertions, 0 failures, 0 errors, 0 skips [2013-02-01T06:43:34+00:00] INFO: Report handlers complete

Slide 37

Slide 37 text

Testing Tools • Vagrant • knife cookbook test • Foodcritic • Chefspec • Fauxhai • Minitest and the Minitest Chef Handler • Why-run

Slide 38

Slide 38 text

Launch a Staging Node $ knife ec2 server create -I ami-f4fb7c9d -f m1.small -E staging Instance ID: i-1e3a4b6d Flavor: m1.small Image: ami-f4fb7c9d Region: us-east-1 Availability Zone: us-east-1d Security Groups: default Tags: {"Name"=>"i-1e3a4b6d"} SSH Key: nharvey-oc Waiting for server.................... Public DNS Name: ec2-107-22-155-182.compute-1.amazonaws.com Public IP Address: 107.22.155.182 Private DNS Name: ip-10-190-223-78.ec2.internal Private IP Address: 10.190.223.78 Waiting for sshd....done Bootstrapping Chef on ec2-107-22-155-182.compute-1.amazonaws.com

Slide 39

Slide 39 text

Launch a Staging Node $ knife node run_list add i-1e3a4b6d "recipe[website]" i-1e3a4b6d: run_list: recipe[website]

Slide 40

Slide 40 text

Why Run $ knife ssh "chef_environment:staging" "sudo chef-client --why-run"

Slide 41

Slide 41 text

knife ssh chef_enviornment:staging

Slide 42

Slide 42 text

knife ssh chef_environment:staging "sudo chef-client --why-run"

Slide 43

Slide 43 text

Why Run $ knife ssh "chef_environment:staging" "sudo chef-client --why-run" Starting Chef Client, version 11.4.0 ... Converging 3 resources * package[apache2] action install - Would install version 2.2.22-1ubuntu1 of package apache2 * template[/var/www/index.html] action create * Parent directory /var/www does not exist. * Assuming directory /var/www would have been created - Would create template[/var/www/index.html] * service[apache2] action start - Would start service service[apache2] * service[apache2] action enable - Would enable service service[apache2] WARN: In whyrun mode, so NOT performing node save. Chef Client finished, 4 resources would have been updated

Slide 44

Slide 44 text

Why Run • Promises, Lies, and Dry-Run Mode • http://bit.ly/guessrun • Why Run reported 4 resource updated • “real run” only 2 resources are updated

Slide 45

Slide 45 text

Testing Tools • Vagrant - Local development and testing • knife cookbook test - Verify ruby syntax • Foodcritic - Cookbook linter • Chefspec - Unit testing recipes • Fauxhai - Mock all the things • Minitest Chef Handler - post-converge tests • Why-run - Best guess

Slide 46

Slide 46 text

Moar Testing Tools • Test Kitchen • Cross-platform testing • Cucumber Chef • acceptance & integration testing

Slide 47

Slide 47 text

Continuous Integration •Travis •Jenkins •etc.

Slide 48

Slide 48 text

Gating Your Releases https://github.com/nathenharvey/bigruby

Slide 49

Slide 49 text

Requirements Have Changed!

Slide 50

Slide 50 text

Bump Cookbook Version

Slide 51

Slide 51 text

Update Tests

Slide 52

Slide 52 text

Watch Tests Fail $ rpsec . 5 examples, 1 failure Failed examples: rspec ./cookbooks/website/spec/default_spec.rb:18 # website::default should create a home page with our content $ vagrant provision FATAL: RuntimeError: MiniTest failed with 0 failure(s) and 1 error(s). Error: test_home_page_content(TestWebsite):

Slide 53

Slide 53 text

Update Cookbook

Slide 54

Slide 54 text

Converge Development Node $ rspec . 5 examples, 0 failures $ knife cookbook upload website && vagrant provision 6 tests, 7 assertions, 0 failures, 0 errors, 0 skips

Slide 55

Slide 55 text

Verify

Slide 56

Slide 56 text

Converge Staging Node $ knife ssh "chef_environment:staging" "sudo chef-client" Starting Chef Client, version 11.4.0 ... Chef Client finished, 0 resources updated

Slide 57

Slide 57 text

Y U No Change? { "name": "staging", "description": "", "cookbook_versions": { "website" : "0.1.2" }, "json_class": "Chef::Environment", "chef_type": "environment", "default_attributes": { }, "override_attributes": { } }

Slide 58

Slide 58 text

Promote the New Version

Slide 59

Slide 59 text

Converge the Staging Node $ knife environment from file staging.json Updated Environment staging $ knife ssh "chef_environment:staging" "sudo chef-client" Starting Chef Client, version 11.4.0 ... Chef Client finished, 1 resources updated

Slide 60

Slide 60 text

Environments & Continuous Integration • Tests + Environments + Continuous Integration • ONE Build Pipeline to Rule them All

Slide 61

Slide 61 text

Monitoring!

Slide 62

Slide 62 text

So, What Have We Learned? https://github.com/nathenharvey/bigruby

Slide 63

Slide 63 text

Testing Your Infrastructure • There are a number of frameworks to automate your infrastructure testing • Graduate changes through your environment • Trust your monitoring to catch issues quickly

Slide 64

Slide 64 text

Chef is Awesome! • Manage all of the infrastructure for your single page, static website! •

Slide 65

Slide 65 text

Chef is Awesome!

Slide 66

Slide 66 text

Knife Rocks! • Command line interface for • Creating Cookbooks • Searching the Chef Server • Provisioning Infrastructure • ...and more!

Slide 67

Slide 67 text

The Chef Community is Awesome! • Things in this presentation that exist because of the Community: • Foodcritic • Chefspec • minitest-chef-handler • Fauxhai • Cucumber Chef

Slide 68

Slide 68 text

How can you help? • You are a developer well-versed in Ruby test frameworks & TDD • Test frameworks & TDD are relatively new to infrastructure code

Slide 69

Slide 69 text

How can you help? •Stop testurbation!

Slide 70

Slide 70 text

#ChefConf 2013

Slide 71

Slide 71 text

Thank You •What questions do you have? •Nathen Harvey •[email protected] •@nathenharvey