the-eye.eu...table of contents chef essentials credits about the author about the reviewers support...
TRANSCRIPT
ChefEssentials
TableofContents
ChefEssentials
Credits
AbouttheAuthor
AbouttheReviewers
www.PacktPub.com
Supportfiles,eBooks,discountoffersandmore
WhySubscribe?
FreeAccessforPacktaccountholders
Preface
Whatthisbookcovers
Whatyouneedforthisbook
Whothisbookisfor
Conventions
Readerfeedback
Customersupport
Errata
Piracy
Questions
1.InstallingChef
Terminology
WorkingwithChef
Installingchef-solo
TheRubygem
Managinggems
Verifyingthatchef-soloworks
InstallingaChefserver
Requirementsandrecentchanges
Installationrequirements
Whatyouwillbeinstalling
Gettingtheinstaller
Installationoutline
InstallingonUbuntu
Downloadingthepackage
Installingthepackage
InstallingonRedHatEnterpriseLinux
Downloadingthepackage
ConfiguringaChefserver
Understandinghowchef-server-ctlworks
What’shappeningonmyserver?
Verifyingthattheservicesarerunning
Validatingthatyourserviceisworking
Ensuringthatyourknifeconfigurationworks
Summary
2.ModelingYourInfrastructure
GettingtoknowChef
Modelingyourinfrastructure
Roles
Definingroles
Awebapplicationservicerole
Animage-processingrole
Animagesearchrole
APostgreSQLservicerole
ASolrservicerole
AnOpenSSHservicerole
Implementingarole
Determiningwhichrecipesyouneed
Installingacookbook
Applyingrecipestoroles
Mappingyourrolestonodes
Converginganode
Environments
Organizingyourconfigurationdata
Exampleattributedata
Databags
Knowingwhentousedatabags
Large-scaleinfrastructure
Summary
3.IntegratingwiththeCloud
Leveragingthecloud
AmazonEC2
InstallingtheEC2knifeplugin
SettingupEC2authentication
Provisioninganinstance
Bootstrappingtheinstance
Terminatingtheinstance
RemovingtheChefnode
RackspaceCloud
Provisioninganinstance
Terminatinganinstance
RemovingtheChefnode
Summary
4.WorkingwithCookbooks
Attributes
Multipleattributefiles
Supportingmultipleplatforms
Loadingexternalattributes
Usingattributes
Metadata
Recipes
Resources
Usingresources
Overridingadefaultbehavior
Templates
Whyusetemplates?
AquickERBprimer
ExecutingRuby
Variablereplacement
Thetemplateresource
Thetemplatevariables
Passingvariablestoatemplate
Accessingcomputedconfigurations
Searchingfortemplates
Definitions
Recipes
Developingrecipes
Writingrecipes
Startingoutsmall
Installingasimpleservice
Gettingmoreadvanced
Summary
5.TestingYourRecipes
Testingrecipes
RSpec
RSpecandChefSpec
Testingbasics
ComparingRSpecwithothertestinglibraries
UsingChefSpec
GettingstartedwithChefSpec
InstallingChefSpec
LockingyourdependenciesinRuby
CreatingasimplerecipeandamatchingChefSpectest
WritingaChefSpectest
Buildingyourrecipe
Executingtests
Understandingfailures
Expandingyourtests
Multipleexamplesinaspectest
Testingformultipleplatforms
Summary
6.FromDevelopmenttoDeployment
Describingthesetup
DeployingsoftwarewithChef
Configuringyourlocalenvironment
ModelingasimplePythonapplication
Managingthecookbooks
Downloadingcookbooks
Lookingatthedatabaserecipe
Lookingatyourapplicationdeploymentcookbook
Preparingthedirectories
ConstructingyourPythonvirtualenvironment
Checkingthesourcecode
Installinganyextradependencies
ManagingdependenciesinChef
Managingdependencieselsewhere
UsingPython’srequirementsfile
Configuringyourapplication
Keepingyourapplicationrunning
Definingroles
Creatingthebaseserverrole
Creatingthedatabaseserverrole
Creatingthewebserverrole
Addingusers
ProvisioningEC2instances
Configuringthedatabasehost
Configuringthewebserver
Deployingyoursoftware
Manuallydeployingupdates
Automatingdeployment
Summary
7.BeyondBasicRecipesandCookbooks
Managingusers
Evolutionofashelluserrecipe
Storingdataindatabags
Creatingadatabagforusers
Searchingfordata
Searchinginsiderecipes
Enhancingyourusercookbook
DistributingSSHkeys
Templatingtheauthorizedkeys
Addingdeploymentkeys
Writingcustomextensions
Developingacustomdefinition
Organizingyourcode
WritingadefinitionforusingPIP
Definingafullapplicationtemplate
Buildingaresource
Definingtheresource
Implementingtheprovider
Modifyingresources
Loadinganexistingresource
Declaringthataresourcewasupdated
Workingwithdatabags
Securingyourdatabags
Secretkeys
Encryptingyourdata
Decryptingyourdata
Storingkeysonnodes
Searchingyourdata
Searchingyourdatabagswithknife
Searchingyourdatabagsfromarecipe
Queryingyourdata
Managingmultiplemachineswithsearchqueries
Summary
8.ExtrasYouNeedtoKnow
VagrantandChef-solo
InstallingVagrant
ProvisioninganewhostwithVagrant
BootingyourVagrantimage
CombiningVagrantwithChef-solo
UnderstandingthelimitationsofChef-solo
ConfiguringChef-solo
TellingChef-solowhattorun
UsingrolesanddatabagswithChef-solo
InjectingcustomJSONdata
Providingacustomnodename
GettingtoknowtheChefshell
UsingtheChefshell
Thestandalonemode
Thesolomode
Theclientmode
InteractingwiththeChefserverusingtheshell
Interactingwithdata
Searchingyourdata
Editingyourdata
Transformingdata
ExecutingrecipeswithChefshell
Creatingarecipeintheshell
Definingnodeattributes
Settingattributes
Accessingattributes
Usingconfigurationblocks
Interactivelyexecutingrecipes
DebuggingwiththeChefshell
Usingthebreakpointresource
Integrationtesting
UsingTestKitchen
InstallingTestKitchen
TestingwithTestKitchen
Buildingasimplecookbook
Preparingyourcookbookforthekitchen
Testingyournewcookbook
Provisioningtheinstance
Convergingthenewlycreatedinstance
Writingasimpletest
Combiningallthesteps
ExtendingChef
WritinganOhaiplugin
AnoteaboutwritingOhaiplugins
ChefwithCapistrano
Automationandintegration
Automatedupdatesanddeployments
Summary
Index
ChefEssentials
ChefEssentialsCopyright©2014PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:September2014
Productionreference:1190914
PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
BirminghamB32PB,UK.
ISBN978-1-78398-304-9
www.packtpub.com
CoverimagebyPrashantTimappaShetty(<[email protected]>)
CreditsAuthor
JohnEwart
Reviewers
JoshuaBlack
LaurenMalhoit
EricMaxey
CommissioningEditor
EdwardGordon
AcquisitionEditor
LlewellynRozario
ContentDevelopmentEditor
GovindanK
TechnicalEditor
ShubhangiDhamgaye
CopyEditors
RoshniBanerjee
MradulaHegde
GladsonMonteiro
ProjectCoordinator
SageerParkar
Proofreaders
SimranBhogal
MariaGould
AmeeshaGreen
LindaMorris
Indexer
RekhaNair
ProductionCoordinator
ArvindkumarGupta
CoverWork
ArvindkumarGupta
AbouttheAuthorJohnEwartisasystemarchitect,softwaredeveloper,andlecturer.Hehasdesignedandtaughtcoursesatavarietyofinstitutions,includingtheUniversityofCalifornia,TheCaliforniaStateUniversity,andlocalcommunitycolleges.Thesecoursescoverawiderangeofcomputersciencetopics,includingJava,datastructuresandalgorithms,operatingsystemsfundamentals,UnixandLinuxsystemadministration,andwebapplicationdevelopment.Inadditiontoworkingandteaching,hemaintainsandcontributestoanumberofopensourceprojects.HecurrentlyresidesinRedmond,Washington,withhiswife,Mary,andtheirtwochildren.
AbouttheReviewersJoshuaBlackhasbeenworkingwithcomputersprofessionallyfor20years.Hehasawiderangeofexperienceandexpertise,whichincludessystemsandnetworkadministration,mobileappdevelopment,andproductionwebapplications.HeearnedaBSdegreeinComputerSciencewithaminorinMathfromCaliforniaStateUniversity,Chico,in2005.HecurrentlyresidesinChico,California,withhiswife,Rachel,andtheirfourchildren.
LaurenMalhoithasbeeninthefieldofITforover10yearsandhasacquiredseveraldatacentercertifications.She’scurrentlyatechnicalvirtualizationarchitect,specializinginvirtualizationandstorageindatacenter.ShehasbeenwritingforafewyearsforTechRepublic,TechRepublicPro,andVirtualizationSoftware.AsaCiscoChampion,EMCElect,VMwarevExpert,andPernixPro,shestaysinvolvedinthecommunity.Shealsohostsabi-weeklytechnologypodcastcalledAdaptingIT(http://www.adaptingit.com/).ShehasbeenadelegateforTechFieldDayseveraltimesaswell.Sherecentlypublishedherfirstbook,VMwarevCenterOperationsManagerEssentials,PacktPublishing.
EricMaxeyhasavariedbackgroundinwritingsoftware,includingmakingconsolevideogamesandanalyzingtheadrevenuedata.Heranasmallbusinessthatpioneeredanewkindofbitcoinminingpool,whichhasnowbecomesomethingofastandard.Whennotjackedintothemetaverse,helikestoworkonelectricbicyclesandridethemoffroad.
www.PacktPub.com
Supportfiles,eBooks,discountoffersandmoreYoumightwanttovisitwww.PacktPub.comforsupportfilesanddownloadsrelatedtoyourbook.
DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<[email protected]>formoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
http://PacktLib.PacktPub.com
DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcanaccess,readandsearchacrossPackt’sentirelibraryofbooks.
WhySubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,printandbookmarkcontentOndemandandaccessibleviawebbrowser
FreeAccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandviewnineentirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.
PrefaceChefisanindispensabletooltomanageyourinfrastructure.Itconsistsofasetoftoolsthataredesignedtoworktogethertoenableyoutomodelandmanageyoursystems.Thisisalargespacetofill,andChefprovidesyouwiththetoolstodothisinaveryflexibleandpowerfulway.Itachievesthisthroughacombinationofservices,endhostagents,awebinterface,andcommand-linetoolsthatworkinunisontodeliveranincrediblesuiteoftools.
Chef’sservicesareresponsibleforstoring,managing,anddistributingdataaboutyourinfrastructurethroughanAPI.Endhostsoftwareagentsthatrunonnodes(managedsystems)areresponsibleforperformingupdatestosystems,andthewebinterface,alongwithcommand-linetools,allowsanadministratortoeditandconsumeinformationthatisvendedbytheAPIservice.
OneofthemostattractivefeaturesofChefisthatyoucanleverageitsAPItoeasilyintegrateexistingtools,oryoucandevelopnewtoolstomeetspecificneeds.AnyorganizationwithamoderatenumberofdeveloperresourcescanharnessthepowerofCheftomanagetheirsystems.Forexample,onecaneasilybuildsoftwaretoimportdatafromChefintoareportingtoolofsomeformanddynamicallyreconfigureinfrastructurebasedonathird-partytool’soutput—thesky’sthelimit.ThisisincrediblyvaluabletoanybodywhohasanexistinginfrastructurebecauseitprovidesaconvenientpathtointegrateChefintotheirenvironment.
ThereareanumberofwaystoaccessChef.Thequickestwayforasingleusertomanagehis/herinfrastructure(virtualmachines,ahandfulofhosts,andsoon)istouseChef-solo,aproductgearedtowardssingle-userenvironments.Inasmallenvironment,settingupahostedserverisagoodwaytomanageinfrastructureautomationamongteammembers.Ifyouneedto,youcanconfigurethehostedenvironmentasahighlyavailablesystemusingloadbalancersandothertechnologies.Alternatively,ifhostingtheserviceyourselfisnotanoption,youcanusehostedChef,asoftware-as-a-service(SaaS)model,thuspayingforaccesstoahostedservice.
Configurationmanagementsoftwarewascreatedtofillaneed—managinginfrastructureisachallengingtask.Regardlessofthescaleyouoperateon,keepingtrackofsoftwareversions,upgradingsystems,andgeneratingconsistentconfigurationdataisalotofwork.Itistemptingtoupdateaconfigurationfileononesystem,onlytoforgettocommitthosechangessomewhere,ortoapplythemtoexistingorfuturehosts.Thisisveryconvenient,butitquicklyleadstoinconsistencybetweenhosts.Whenyouareworkingwithonlyoneortwohosts,thismaybeacceptable.Assuch,asystemgrowsfromafewserverstodozens,hundreds,orpossiblythousands—thistypeofsystemmanagementdoesnotscaleduetotimerequirementsandconfigurationerrorsthatresultfromsizeandcomplexity.
Considerascenariowhereyouaremigratingadatabaseservertoanewhost.Thiswouldinvolve:bringingupanewhost,installingalloftherequiredsoftwareonyourlisttoensureithasparitywiththeoldserver(youdidkeepalist,right?),ensuringthatyourdatabaseserverwasconfiguredwiththesameoptions,puttingthecorrectfirewallrulesin
place,tuningthefilesystem,settingupmonitoringtools,updatingDNSrecordsorchangingwebapplicationconfigurationstopointtothenewhost,andsoon.Nowimaginethat,insteadofoneserverinonedatacenter,youhave10databaseserversin10datacenters,eachwiththeirownIPranges,hardwareconfigurations,andnetworkingrules.Situationssuchasthisareexactlywhysystemconfigurationmanagementsoftwarepackagesweredeveloped:tomakethelivesofsystemadministratorsandengineersmucheasier.
Thisscenario,andmanyotherslikeit,iswhereChefisindispensable.Havingtheabilitytodescribeyourhosts,configurationdata,androles,andthenapplythatacrossasmanyhostsasyoulikemeansthatyoucanmanagelargefleetsofhostsjustaseasilyasyoucanmanageoneortwo.
WhatthisbookcoversChapter1,InstallingChef,introducesyoutothearchitectureofChef,variousinstallationmethods,andaguidetosettingupChef(soloandself-hosted).ItincludesinformationonusinghostedChef(andwhatthatmeansforyourteam)andVagrantwithChef-solo.
Chapter2,ModelingYourInfrastructure,introduceshowtomodelyourinfrastructurewithChefusingyournewlyinstalledsystem.Thischapterwillcovermodelingenvironments,smallandlarge,aswellashowtointegratewithcloudtechnologiesusingChef(AWS,RackspaceCloud,andsoon).
Chapter3,IntegratingwiththeCloud,covershowChefhelpsyouscaleyourinfrastructureusinganycombinationofphysical,virtual,andcloud-hostedsystems.ThischapterdiscusseshowtouseCheftoprovisionandmanagehostsusingcloudprovidersaseasilyasyourlocalsystemsincludingAWSandRackspaceCloud.
Chapter4,WorkingwithCookbooks,covershoweveryChefneedscookbooks—onceyoursystemsarepartofyourChef-managedfleet,youcanbegincollecting,developing,andapplyingrecipestoyourhosts.Itincludesin-depthexplanationsofthestructureanddevelopmentofcookbooksandrecipes,aswellashowtotest,publish,andsharethem.
Chapter5,TestingYourRecipes,focusesononecompellingreasontouseCheftoconfigureyourinfrastructure,thatis,recipesarewritteninRubycodeandcanbetestedasanyprogramwouldbetested.Here,youwilllearnhowtotestyourrecipesthroughavarietyoftestingmechanisms.
Chapter6,FromDevelopmenttoDeployment,covershowtotakeacustomapplicationfromdevelopmenttoaproductiondeploymentwithChef.Itcontainsacompleteexamplethatincludesprovisioningawebserver,databaseserver,andusersaswellasdeployingcodefromsourcecontrol.
Chapter7,BeyondBasicRecipesandCookbooks,delvesintodevelopingextensionstoChefthroughadvancedconcepts,includingcustomprovidersandresourcetypes,usingtheChefsearchengine,advancedscripting,andmore.
Chapter8,ExtrasYouNeedtoKnow,expandsyourknowledgeofhowtoleverageChefforinfrastructureautomation,complexsystemsintegration,andsecurelystoringanddistributingsensitivedatawithChef.
WhatyouneedforthisbookThisbookassumesthatyouarefamiliarwithatleastoneprogramminglanguage(itdoesnotneedtobeacompiledlanguage,andknowledgeofaninterpretedlanguagewillbesuitable.ChefusesRubyforitsdynamic,scriptablecomponentsandanyexperiencewithRubywillbevaluable.However,havingastrongunderstandingofprogramlogicwillprovideyouwiththebackgroundtobeproductivewithChef.
ForthosewhoarenotexpertswithRuby,therewillbeawidearrayofexamplelistingsthatcanbecopieddirectlyandexecutedaspartofthebook’sofferings.ThiswillenableyoutousetheexampleswithoutanypreviousRubyexperience.However,aworkingknowledgeofRubywillbeneededinordertoexpandonthebook’scodeexamplesorwhilewritingyourownrecipesfromscratch.
YouwillbewalkedthroughthestepsrequiredtoinstallChefonaLinux-basedhost.Inordertobeimmediatelysuccessful,youwillneedadministrativeaccesstoahostthatrunsamodernversionofLinux;Ubuntu13.10iswhatwillbeusedfordemonstrationpurposes.Ifyouareamoreexperiencedreader,thenarecentreleaseofalmostanydistributionwillworkjustaswell(butyoumayberequiredtodoalittlebitofextraworkthatisnotoutlinedinthebook).IfyoudonothaveaccesstoadedicatedLinuxhost,avirtualhost(orhosts),runninginsideofvirtualizationsoftware,suchasVirtualBoxwillwork.
Additionally,youwillneedaccesstotheInternettodownloadsoftwarepackagesthatyoudonotalreadyhave,aswellasaninstallationoftheRubyprogramminglanguageVersion1.9orhigher.
WhothisbookisforThisbooktargetsdevelopersandsystemadministratorswhoneedtomanageinfrastructureandarelookingtoautomatetheirsystemmanagement.Thisincludesinfrastructureranginginsizefromsmall-scaleinstallationswithahandfulofhoststomulticontinentcorporateITsystemswithhundredsoreventhousandsofhosts.Anybodywhosejobinvolvesmaintainingsystemswillbenefitfromtheconceptsbeingcovered.
ConventionsInthisbook,youwillfindanumberofstylesoftextthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestyles,andanexplanationoftheirmeaning.
Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“Thesearchmethodhasasimilarformattotheknifecommand.”
Ablockofcodeissetasfollows:
all_users=search(:users,'id:*')
users_s=search(:users,'id:s*')
all_nodes=search(:node,'*')
Anycommand-lineinputoroutputiswrittenasfollows:
$knifedatabagshowcredentialsaws
Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,inmenusordialogboxesforexample,appearinthetextlikethis:“Oncethere,atablabeledChefServerwillbepresentatthetopofthepage.”
NoteWarningsorimportantnotesappearinaboxlikethis.
TipTipsandtricksappearlikethis.
ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedormayhavedisliked.Readerfeedbackisimportantforustodeveloptitlesthatyoureallygetthemostoutof.
Tosendusgeneralfeedback,simplysendane-mailto<[email protected]>,andmentionthebooktitleviathesubjectofyourmessage.
Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideonwww.packtpub.com/authors.
CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.
ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyouwouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheerratasubmissionformlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedonourwebsite,oraddedtoanylistofexistingerrata,undertheErratasectionofthattitle.Anyexistingerratacanbeviewedbyselectingyourtitlefromhttp://www.packtpub.com/support.
PiracyPiracyofcopyrightmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.Ifyoucomeacrossanyillegalcopiesofourworks,inanyform,ontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.
Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.
Weappreciateyourhelpinprotectingourauthors,andourabilitytobringyouvaluablecontent.
QuestionsYoucancontactusat<[email protected]>ifyouarehavingaproblemwithanyaspectofthebook,andwewilldoourbesttoaddressit.
Chapter1.InstallingChefBeforeyoucanstartusingChef,youwillneedtoinstallit.HereyouwillfindaguidetoinstallChef,andbecauseChefrequiresRuby,someRubyconceptsaswell.Thischapterdiscussesthefollowing:
KeyterminologyandconceptsrelatedtoChefAnoverviewofChef’sarchitectureWorkingwithRubygemsInstallingchef-solo(alocal-onlyenginetouseChef)Abriefexampleonusingchef-soloInstallingtheChefserveronyourownhostVerifyingyourChefinstallation
TerminologyAswithanyothertechnology,Chefhasitsownterminology.Asyouwillsee,Chef’snomenclatureisamixoftechnologicalterms(nodes,workstations,servers,roles,andsoon)andcookingterms(cookbooks,recipes,andsoon).Therearethreeprimaryactorsthatweareconcernedwithatthispoint:nodes,theChefservice,andworkstations.
Node:Anodeisaclientthatappliesrolesandrecipes,asdescribedbytheadministratorintheChefservice(thatis,aserverinyourenvironmentthatisbeingconfiguredviaChef).Thesearetheconsumersoftheconfiguration,theelementsofyourinfrastructure.TheycanbephysicalorvirtualmachinesandcanrunonLinux,Windows,ortechnicallyanyothersystemthatiscapableofrunningRuby(somesystemsmaynotbesupportedoutoftheboxbyChef).Chefservice:TheChefserviceisamulticomponentsystemthatcombinesseveralservicestoprovideitsfunctionality.TheprimaryfunctionalcomponentsareanAPIservice,full-textsearchingviaSolr,persistentstorageusingPostgreSQL,andRabbitMQforinterservicecommunication.Additionally,thereisawebinterfacethatprovidesagraphicaltooltomanagesystemdata.Clients(nodes)usetheAPIservicetodeterminewhichrolesandrecipestoapply,andknife(acommand-linetool)usestheAPItoallowanadministratortoeditandmanagetheirChefconfiguration.Workstation:Aworkstationisahostthatisusedtoissuecommands.AworkstationcanbeaseparatehostoutsideofyourChefserviceinstallation,amanagednode,ortheserverthattheChefcomponentsarerunningon.Thereareavarietyofcommand-linetoolsthatareprovidedtointeractwiththeservice,whichwillbeinstalledontoyourworkstation(s).Recipe:Arecipeisascriptthatdescribesasetofstepstotaketoachieveaspecificgoal.Asanexample,arecipemightdescribehowtodeployyourcustomsoftware,provisionadatabase,oraddahosttoaloadbalancer.Cookbook:Acookbookisacollectionofrecipesthatareusedtocollectivelydescribehowtoinstall,configure,andmanagevariousaspectsofyourinfrastructure.Forexample,acookbookmightdescribehowtoprovisionMySQL,PostgreSQLorApache,manageusers,installprinters,orperformanyothersystemtasks.
WorkingwithChefForsingleusersetups,chef-soloisaversionofthechef-clientthatallowsyoutouseChefwithoutrequiringaccesstoaserver.Chef-solorunslocallyandrequiresthatacookbookandanyofitsdependenciesbepresentonthenodebeingmanaged.Asaresult,chef-soloprovidesalimitedsubsetofthefullchefservermodeofoperation.Mostofthefeaturesthatchef-soloismissingrevolvearoundsearchandcentralizeddatamanagement,whicharenotcriticalformanagingvirtualmachinesorasmallcollectionofnodes.Theinstallationandmaintenanceissimple,butthefeaturesetissmaller.
InstallingtheChefserverwillgiveyouaccesstothefullsetofCheffunctionality.ThismoderequiresaccesstoaLinux-basedhostthatisnetwork-accessiblebythenodesandworkstationsthatwillinteractwiththesystem.ThankstotherecenteffortfromthefolksatChef(formerlyOpscode),theprocessofinstallingChefhasbeengreatlysimplified.Thebenefitsofthisinstallationmodelarethatyougetcentralizedmanagement,search,userauthentication,andsuch,butatthecostofmanagingyourownservice.
IfyouneedthefeaturesofChefbutdonotwanttomaintainyourownserver,hostedChefisagreatoptionforyou.HostedChef(https://manage.opscode.com/signup)givesyouallthefeaturesofaself-hostedChefbutwithouthavingtoworryaboutupgrades,extrahardware,orsystemavailability.Forasmallinfrastructure(uptofivehosts),hostedChefisfreeandagreatwaytogetstarted.Beyondthis,planshaveamonthlyfee,andthepricewillvaryaccordingtothenumberofhostsyouwanttomanage.
Installingchef-soloChef-soloisdesignedforindividualswhodonotneedahostedinstallationforalarge-scaleinfrastructuremanagement.Typicalusecasesofchef-soloincludedevelopersmanagingvirtualmachines,testinstallations,orsmall-scaleinfrastructuremanagement.Theinstallationofchef-soloisassimpleasinstallingasingleRubygem.
TheRubygemForthosewhoarenotintimatelyfamiliarwithRuby,aRubygemisamechanismtopackage,deliver,andmanageRubycode.Thesepackagesmaybelibrariesthatprovidefunctionalityfordevelopers,ortheymaybecomposedonlyofscriptsandtools.Chef-solois,likemanythingsinlife,somewhereinthemiddle.Thegemcontainsasetoflibrariesthatmakeupthecorefunctionalityaswellasasuiteofscriptsthatareusedbyendusers.BeforeyouinstallChef,youshouldconsiderinstallingRubyVersionManager(RVM),rbenv,chruby,oranotherRubymanagerofyourchoicetokeepyourgemcollectionsisolated.
ManaginggemsAgreattooltomanageyourgemsisRVM.ThesimplestwaytoinstallRVMistousetheinstallationscriptprovidedbythedevelopmentteamontheRVMwebsite(http://rvm.io).Thefollowingcommandwilldownloadthescriptandpipeitthroughbash:
curl-sSLhttps://get.rvm.io|bash-sstable
Onceitisinstalled,youwillinitiallyneedtoincludeRVM’sfunctionalityinyourshell:
source~/.rvm/scripts/rvm
Additionally,youmightneedtoaddthepreviouscommandlinetoyourshell’sstartupscripts(suchas~/.bashrcor~/.zshrc,dependingonwhichshellyouuse).OnceRVMisinstalled,youwillwanttoinstallarecentversionofRuby,forexample,Ruby1.9.3:
rvminstall1.9.3
OnceRuby1.9isinstalled,youwillwanttocreateagemset.AgemsetisRVM’swayofisolatinggemsinsideacontainer,anditwillprovideyouwithaplacetoinstallgemsinsuchawaythattheywillnotconflictwithothergems.Thishasthebenefitofallowingyoutoinstallanythingyouwant,withoutrequiringadministrativeprivilegesandkeepinggemsfromconflictingwitheachother.Agemsetcanbecreatedusingthefollowingcommand:
rvmuse1.9.3@chef--create
Thepreviouscommandwillsimultaneouslycreatethegemsetnamedchef(ifitdoesnotexist)foryourinstallationofRuby1.9.3andthensetitastheactivegemset.Onceyoustartusingthisnewgemset,youwillwanttoinstalltheChefgem—thiscontainschef-soloandallthecommand-linetoolsyouneedtoworkwithChef—usingthegemcommand-linetool:
geminstallchef
Verifyingthatchef-soloworksNowthattheChefgemisinstalled,itistimetoverifythateverythingisworkingfine.Inordertousechef-solo,youneedtogivethefollowinginformationtoit:
Whatrecipestoapplybyprovidingarunlistinafilenamednode.jsonWhattherecipesare—thesearestoredincookbooksthatarefoundinthecookbooksdirectoryHowtofindthecookbooksandtherunlistviaafilenamedsolo.rb
Forsimplicity,wewillstoreallofthesefilesinsideofthechefdirectoryinyourhomedirectory.YouarefreetoputthingswhereyouseefitasyoubecomemorecomfortableworkingwithChef.
Inordertoexerciseournewtool,wewilldosomethingsimple:we’llwritearecipethatwillcreateanexample.txtfileinyourhomedirectory.Therecipewecreatewillbecalledcreate_file,andwe’llputthatrecipeinsideacookbook,whichwillbenameddemo.
First,createthedirectorythatwillcontainthedemocookbook’srecipes(andanyinbetween):
user@host:~$mkdir-p~/chef/cookbooks/demo/recipes
Next,addthefollowingcodetoafile,create_file.rb,locatedinthedemocookbookdirectoryyoucreatedat~/chef/cookbooks/demo/recipes:
file"#{ENV['HOME']}/example.txt"do
action:create
content"Greetings#{ENV['USER']}!"
end
ThistellsChefthatwewanttocreateafile,$HOME/example.txt.ItscontentsshouldbeGreetings$USER,where$USERwillbereplacedwiththevalueof$USER,typicallytheloginnameofwhoeverisexecutingtherecipe.
TipForthoseunfamiliar,UNIX(andWindowsaswell)usesenvironmentvariablesasamechanismtoexchangedatabetweenprocesses.SomeenvironmentvariablesaresetwhentheuserlogsintothesystemsuchasHOME,USER,andavarietyofothers.ThesevariablesareavailableinRubyusingtheENVhash,wherethekeysarethevariablenames.InaUNIXshell,theseareaccessedusingthe$prefix.So,theuser’shomeisreferredtoas$HOMEintheshellandENV['HOME']insideRuby.
NowwewillneedtocreateaJSONdocumentthatdescribeswhatchef-soloshouldexecute.JSONisanacronymforJavaScriptObjectNotation,andChefusesJSONextensivelybecauseitiseasytoparse,humanreadable,andeasytogeneratefromallsortsoftoolsandlanguages.Createafile,node.json,locatedinourworkdirectory(~/chef/inthiscase)andaddthefollowingcontentinordertotellChefthatwewanttoexecutethenewlycreatedcreate_filerecipeinthedemocookbook:
{
"run_list":[
"recipe[demo::create_file]"
]
}
Here,wearedefiningthenodeashavingarunlist,whichisjustanarrayofthingstodo,andthattherunlistcontainsonerecipe,create_file,whichitcanfindinthedemocookbook(thegeneralformofarecipebeingcookbook::recipe).
Finally,we’lltellChefwheretofindthefileswejustcreatedusingasolo.rbfilethatwewillstoreinourworkingdirectory(~/chefinourcase):
CHEF_ROOT="#{ENV['HOME']}/chef"
file_cache_path"#{CHEF_ROOT}"
cookbook_path"#{CHEF_ROOT}/cookbooks"
json_attribs"#{CHEF_ROOT}/node.json"
Nowthatyouhavepopulatedtherequiredconfigurationfiles,youcanrunchef-soloandexecutetherunlistspecified.Inourcase,therunlistisdefinedasonlyonerecipe,create_file,butcanbeassimpleorascomplexasneeded.ThepreviousconfigurationtellsCheftoloadthenodeconfigurationfromthefilenode.jsontolookforcookbooksin~/chef/cookbooks/andtostoreanystatedatain~/chef/.Inordertoexecutethesecommands,youwillwanttorunchef-solo:
chef-solo-c~/chef/solo.rb
The-coptiontellschef-solowhichscriptcontainstheconfiguration.Onceyoudothis,youwillseetheactionsthatyourrecipeisperforming:
StartingChefClient,version11.8.2
CompilingCookbooks…
Converging1resources
Recipe:demo::create_file
*file[/home/user/example.txt]actioncreate
-createnewfile/home/user/example.txt
-updatecontentinfile/home/user/example.txtfromnonetob4a3cc
---/home/user/example.txt 2014-01-2023:59:54.692819000-0500
+++/tmp/.example.txt20140122-13411-1vxtg7v 2014-01-20
23:59:54.692819000-0500
@@-1+1,2@@
+Greetingsuser!
ChefClientfinished,1resourcesupdated
Onceitiscompleted,youwillseethat~/example.txtcontainsthegreetingthatyoudefinedintherecipe.Nowthatyouhavesuccessfullyusedchef-solo,let’smoveontotheChefservice.
InstallingaChefserverIfyourteamneedstohavecentralizedinfrastructuremanagementanddoesnotwanttouseahostedplatform,thenaself-installedChefserverisaperfectfit.ThisinstallationguideassumesthatyouwillberunningtheChefserveronasupportedLinux-basedsystem.
TheChefservicecomponentscanbeinstalledonasinglemachinewithoutanyissue.Installingitonasinglehostwilllimityourabilitytoscaleorbehighlyavailable,butwillprovideaverysimplepathtogettingstartedwiththeChefservice.
RequirementsandrecentchangesSincetheChefserviceisdesignedtobeamultiuserplatformandprovidesfunctionalitiesthatchef-solodoesnotoffer,theinstallationismorecomplexandinvolvesmoresoftwaretoachievethisfunctionality.ServicessuchasSolrforfull-textindexingandPostgreSQLfordatastoragecanbeasignificantresourceforconsumers,soyouwillwanttoinstallChefonahostwithsufficientmemoryanddiskspace.Asystemwith2GBofmemoryand5-10GBofdiskspaceavailablewillbeplentyforasmalltomediumsizedinstallation.Youwillneedmoreresourcesasyourrequirementsfordatastorageandindexingincreaseovertime,soplanaccordingly.
Additionally,forthosewhohaveinstalledtheChefserverbefore,theinstallationpathhasbeengreatlysimplified.InadditiontoreplacingCouchDBwithPostgreSQLastheprimarydatastorageengine,thereisnowasingleomnibusinstallationpackageforChefthatinstallsalloftherequirementsforChefatasinglelocationsothatitoperatesinisolationanddoesnotrequiredependenciestobeinstalledseparately.
InstallationrequirementsInordertoinstalltheChefservice,youwillneedtohavethefollowing:
AsystemrunningasupportedLinuxvariant(64bitUbuntuLinux10.04through12.10or64bitRedHatEnterpriseLinux5or6)—thiscanbephysicalorvirtual.Ifyoudonothavethelocalresourcesforthis,AWSorRackSpacecloudserversaregoodoptions.Anetworkconnectiontothehostinordertodownloadtheinstaller.Administrativeprivileges(usingsudoordirectrootaccess)onthehostwhereyouareinstallingtheservices.Enoughfreespaceonthehosttoperformthedownloadandinstallation(minimum500MB,includingthedownload,but1GBto2GBispreferred).
WhatyouwillbeinstallingAttheendofthissection,youwillhaveafullyfunctionalChefserviceinstalledandreadytoworkwith.Beforeyougetstarted,let’slookatwhatyouwillbeinstallingonyoursystemsothatyouknowwhattoexpect.ThecomponentsthatmakeupaChefserviceareasfollows:
TheChefAPIserviceMessagequeue(AMQP)DatastorageSearchserviceWeb-basedmanagementconsole
TheChefAPIserviceisresponsiblefordeliveringrunlistsandreceivinginformationfromnodesaswellasprovidingawayforasystemadministratortoconfigurerecipes,runlists,databags,andthelike.Inordertogeneratethisdata,theAPIservicereliesonitspersistentdatastorageengine,inthiscasePostgreSQL,tostoreitsdata.TheoptiontosearchfordataisprovidedbytheSolrsearchengine,andRabbitMQisresponsibleforgluingthemalltogether.Together,thesecomponentsprovideChefwiththeabilitytodistribute,store,index,andmanageyourinfrastructure’sconfigurationdata.
GettingtheinstallerTheeasiestwaytoinstallChefisthroughasingledownloadablepackage,whichisprovidedforUbuntu10.04through12.10andRedHatEnterpriseLinuxVersions5and6.Thispackage,referredtoastheomnibusinstaller,containseverythingyouneedtogetaserverupandrunning.Youcanfinditonhttp://www.getchef.com/chef/install/.
Atthetimeofwritingthis,11.0.10isthelatestversionandistheonethatwillbeusedforthisbook.Thenewerversionofthe11.xseriesofChefshouldhaveaverysimilar,ifnotidentical,configuration.Notethattheseinstallersaresomewhatlarge,beingthattheycontainallofthedependenciesneeded.Forexample,theUbuntu12.10packageforChef11.0.10isapproximately200MBinsize.
TipAlthoughthesearetheofficiallysupporteddistributionsandreleases,itisentirelypossiblethattheseinstallerswillworkondifferentbutcompatibledistributions.Itmaybepossible,forexample,touseCentOSinsteadofRedHatEnterpriseLinuxorDebianinsteadofUbuntu.However,thesewillmostlikelyrequiresomemanualdependencyresolutionsandmaynotworkwithoutalotofeffort(andeventhen,possiblynotatall).
InstallationoutlineInstallationonallsupportedplatformsisrelativelysimilar.TheonlykeydifferencesarethenamesofthepackagefilesthatyouwilldownloadandthecommandsyouwillusetoinstallChef.
Thehigh-levelstepsyouwilltakeareasfollows:
1. DownloadingtheChefinstallerforyourplatform.2. Installingthepackageasanadministrativeuser.3. ConfiguringtheChefservice.4. Testingtheserverusingcommand-linetools.
Becausesteps3and4willbethesameforbothUbuntuandRedHatinstallationprocedures,theinstructionswillbeinasectionfollowingtheRedHatinstallationguide.
InstallingonUbuntuThefollowingareinstructionsforanUbuntu-basedsystem;theywereperformedonanUbuntu12.04host,butshouldbeidenticalforallsupportedUbuntudistributions.ForRedHat-basedinstallationinstructions,seethenextsection.
DownloadingthepackageYoucandownloadthepackagebyreturningtothedownloadpagereferencedpreviously(http://www.getchef.com/chef/install/),oryoucandownloadVersion11.0.10directlyfromhttps://opscode-omnibus-packages.s3.amazonaws.com/ubuntu/12.04/x86_64/chef-server_11.0.10-1.ubuntu.12.04_amd64.deb.
InstallingthepackageInordertoperformtheinstallation,openaterminalonyourUbuntuhost(eitherlocallyorconnectviaSSH)asauserwhohasadministrativeprivileges.Thiscanbedonedirectlyeitherastherootoranyuserwhohaspermissiontoexecutearbitrarycommandsviasudo.
Onceyoulogintothehost,navigatetowhereyouwanttostorethepackage(rememberit’squitelarge,approximately200MB)anddownloadthefileusingcurl:
user@ubuntu:~$curl-Ohttps://opscode-omnibus-
packages.s3.amazonaws.com/ubuntu/12.04/x86_64/chef-server_11.0.10-
1.ubuntu.12.04_amd64.deb
Oncethefileisdownloaded,thedpkgtoolwillbeusedtoperformthepackageinstallation:
user@ubuntu:~$sudodpkg-ichef-server_11.0.10-1.ubuntu.12.04_amd64.deb
Oncethisisfinished,theUbuntu-specificportionofthesetupiscomplete,andyouwillneedtoconfigureChefusingthechef-server-ctlcommand,whichwewilldiscussintheConfiguringChefServersection,followingtheInstallingonRedHatEnterpriseLinuxsection.
InstallingonRedHatEnterpriseLinuxInstallationonaRedHatEnterpriseLinuxdistributionisasstraightforwardasinstallinganyotherpackage.YoudownloadthepackagetothelocaldiskandinstallitusingRPMtools.
DownloadingthepackageYoucandownloadthelatestversionofthepackagebyreturningtothedownloadpagereferencedpreviously(http://www.getchef.com/chef/install/),oryoucandownloadVersion11.0.10directlyfromhttps://opscode-omnibus-packages.s3.amazonaws.com/el/6/x86_64/chef-server-11.0.10-1.el6.x86_64.rpm.
Inordertoperformtheinstallation,openaterminalonyourRedHathost(eitherlocallyorconnectviaSSH)asauserwhohasadministrativeprivileges.Thiscanbedonedirectlyeitherastherootoranyuserwhohaspermissiontoexecutearbitrarycommandsviasudo.
Onceyoulogintothehost,navigatetowhereyouwanttostorethepackage(rememberit’squitelarge,approximately200MB)anddownloadthefileusingcurl:
user@rhel:~$curl-Ohttps://opscode-omnibus-
packages.s3.amazonaws.com/el/6/x86_64/chef-server-11.0.10-1.el6.x86_64.rpm
Howlongthistakeswillvaryaccordingtotheavailablebandwidthbutshouldtakesomewherebetween5and20minutesonareasonablyfastconnection.
Oncethefileisdownloaded,therpmtoolwillbeusedtoperformthepackageinstallation:
user@rhel:~$sudorpm-ivhchef-server-11.0.10-1.el6.x86_64.rpm
Oncethisisfinished,theRedHat-specificportionofthesetupiscomplete,andyouwillneedtoconfigureChefusingthechef-server-ctlcommand,whichwewilldiscussinthefollowingsection.
ConfiguringaChefserverHistorically,installingChefrequiresmanualeditingofconfigurationfiles,choosingRabbitMQcredentials,installingCouchDB,andahandfulofothertasks.Now,withtheomnibusinstaller,allofthisistakencareofforyou.Ifyouhavebeenfollowingalong,yoursystemhastheChefserverandallofitsdependenciesinstalledonthesysteminthe/opt/chef-serverdirectory.
Includedwiththeinstallationofthepackageisashellscript,chef-server-ctl(locatedat/opt/chef-server/bin),whichisresponsibleforconfiguringyournewlyinstalledChefserver.Inordertoconfigureyourservices,youwillneedtorunitasrootbecausethescriptswillneedtomodifyyoursysteminwaysthatyourregularaccountmaynotbeableto.Initializingtheconfigurationtoolisassimpleasissuingthefollowingcommand:
sudochef-server-ctlreconfigure
Runningthisscriptmaytakeafewminutes,anditwillproducealotofoutputwhileitisdoingitswork.Whileitisrunning,let’stakeafewminutestodiscusshowitworksandwhatitisdoing.
Understandinghowchef-server-ctlworksEarlierinthischapter,youwerebrieflyintroducedtothechef-solotool.Yousawhowitcanbeusedtomanageyourlocalserverusingon-diskrecipesandconfigurationdata.TheChefteamhasleveragedthisabilitytodojustthatwiththeChefserverusingchef-solotobootstraptheserverconfiguration.Ifyouweretolookatthecodeforthe/opt/chef-server/bin/chef-server-ctlscript,youwouldseethatthelastlineinthescriptexecutesthefollowingcommand:
/opt/chef-server/embedded/bin/omnibus-ctlchef-server/opt/chef-
server/embedded/service/omnibus-ctl$@
Ifyoufollowthetrailanddigintotheomnibus-ctlscript,youwillfindthatitisjustawrapperaroundtheomnibus-ctlRubygem.Diggingintotheomnibus-ctlgem,youwillseethatintheend,thereconfigurecommandyoupassonthecommandlineisaRubymethodthatmakesthefollowingcall:
run_command("chef-solo-c#{base_path}/embedded/cookbooks/solo.rb-j#
{base_path}/embedded/cookbooks/dna.json")
ThistellsusthattheChefomnibuspackageuseschef-solotoconfigureitself—aprettyclevertrickindeed!Youcanseejusthowpowerfulatoolchef-solocanbe,beingabletoconfigureandreconfiguretheChefservice.
What’shappeningonmyserver?Whatyouwillprobablynoticerightawayisthatalotoftextisbeingscrolledpastinyourterminalwindow.Ifyouweretolookatthecontents,youwouldseethatitshowsyoutheactionsthatarebeingtakenbychef-solotoprovisionyournewservices.Asthereisalotofinformationgoingpast(thousandsoflines),hereisahigh-leveloverviewofwhatishappeningonyourhost:
1. Anewuser,chef_server,anditscorrespondinggrouparebeingprovisioned.2. Chefservicesarebeingsetup,andstartupscriptsforupstartarebeingplacedinthe
appropriatesystemdirectories.TheRunscriptsforChefservicesarelocatedat/opt/chef-server/sv.
3. Chefstatedirectoriesarebeingcreatedin/varincluding/var/opt/chef-serverand/var/log/chef-server.
4. RabbitMQisbeingconfiguredtostoredatain/var/opt/chef-serverandlogtheoutputto/var/log/chef-serveraswellasitsstartupscriptsin/opt/chef-server/sv/rabbitmq/run.
5. PostgreSQLisbeingconfiguredwithitsdatain/var/opt/chef-server/postgresql/dataalongwithauser,opscode-pgsql,toruntheservice.Somesystem-levelchangestosharememorysizesarebeingsetviasysctltomakePostgreSQLworkaswellaspersistedinsystctl.conf.
6. Solrisbeingsetuptoworkwiththeconfigurationanddatarootedin/var/opt/chef-server/chef-solr/,withtherunscriptbeingplacedin/opt/chef-server/sv/chef-solr/run.
7. Chef-expander(thedata-indexingservice)isbeingconfiguredfor/var/opt/chef-server/chef-expanderasitsworkingdirectorywithSolrandRabbitMQendpointsonthelocalhost.Therunscriptislocatedat/opt/chef-server/sv/chef-expander/run.
8. TheChefbookshelfmetadataserviceisreadiedin/var/opt/chef-server/bookshelf/withitsrunscriptat/opt/chef-server/sv/bookshelf/run.
9. Erchef,theErlangChefservice,isinstalledandpointedatthelocalSolr,RabbitMQ,bookshelf,andPostgreSQLservices.
10. Thesystemisthenbootstrappedusingthebootstraprecipe.Thisrecipeverifiesthatthesystemisrunning(bycheckingthatthehttp://localhost:8000/_statusreturnsanHTTP200response)andinstallstheSSLcertificatefortheweb-basedUIin/etc/chef-server/chef-webui.pem.
11. Theweb-basedUIconfigurationfilesaregeneratedandplacedin/var/opt/chef-server/chef-server-webui/.
12. AcopyofnginxtohostthewebUIisplacedin/var/opt/chef-server/nginx,andtheinitialself-signedSSLcertificatesaswellasthestaticassetsareinstalledin/var/opt/chef-server/nginx/html.
13. TheChefAPItestingframework,chef-pedant,isinstalled.14. Finally,/etc/chef-server/chef-server-running.jsonisgeneratedwiththe
currentconfigurationsettingsforyourChefservices.
Clearly,thereisalothappeninghere;ifyouhaveanyoutstandingconcernsaboutwhatisbeingdone,besuretoreadthroughtheoutput.OneofthegreatthingsaboutChefisthattherecipesarejustasetofscriptsthatyoucanopenandviewthecontentsof,andtheoutputshowsyouwhatishappeningduringtheexecution.Everythingitdoesistransparentandmanageablebyyou.
VerifyingthattheservicesarerunningOncetheconfigurationofyourservicesiscomplete,youwillwanttovalidatethattherequiredservicesarerunning.Again,thechef-server-ctlscriptwillbeused,butwewillinvokethestatussubcommandinsteadofthereconfiguresubcommand,asshowninthefollowingcode:
user@host:~$sudochef-server-ctlstatus
run:bookshelf:(pid3901)3123s;run:log:(pid3900)3123s
run:chef-expander:(pid3861)3129s;run:log:(pid3860)3129s
run:chef-server-webui:(pid4053)3095s;run:log:(pid4052)3095s
run:chef-solr:(pid3819)3135s;run:log:(pid3818)3135s
run:erchef:(pid4230)3062s;run:log:(pid3937)3117s
run:nginx:(pid4214)3064s;run:log:(pid4213)3064s
run:postgresql:(pid3729)3146s;run:log:(pid3728)3146s
run:rabbitmq:(pid3423)3172s;run:log:(pid3422)3172s
ThestatussubcommandwillshowyoutheprocessIDofeachcomponent,howlongithasbeenrunningfor,thePIDoftheloggingprocessassociatedwiththatservice,andhowlongtheloggingservicehasbeenrunning.Forexample,wecanseethatchef-server-webuihasaPIDof4053andhasbeenrunningforclosetoanhour,andtheloggerhasaPIDof4052,havingbeenrunningforjustaslongastheservice.
Asyoucansee,theinstallationofChefyieldsanumberofcomponentsthatwillneedtobeupandrunninginordertosuccessfullyuseChef.Youshouldhavethefollowingcomponentsrunningandlisteningonthefollowingnetworkports:
Component Whattolookforintheprocesslist Port(s) Public?
ChefAPIserver Erchefandnginx 80,443 Yes
Webmanagementconsole chef-server-webuiandnginx 80,443 Yes
Dataindexer chef-expander N/A N/A
Solr java(runningstart.jarintheChefdirectory) 8,983 No
PostgreSQL postmaster 5,432 No
RabbitMQ beam.smprunningrabbit 5,672 No
Publiccomponentsneedtobemadeavailabletoanyclients,nodes,orendusersthatexpecttousetheChefserviceoverthenetwork.Configuringyourinfrastructuretoensurethatyourservicesareavailableviathenetworkisoutsideofthescopeofthisbookasthereareanear-infinitenumberofpossiblenetworkconfigurations.
Atahigherlevel,makesurethatanyfirewalldevicesorpacket-filteringsystemsarenotpreventingtrafficfromreachingtheseservicesifyouseethattheyarerunning,butarehavingdifficultiesinconnectingtothem.Ifanyoftheseservicesarenotrunning,youwillneedtoconsultthelogfilesgeneratedbytheservicetodeterminewhatmightbepreventingthemfromstartingup.
ValidatingthatyourserviceisworkingInordertoworkwithChef,youwillneedawaytointeractwithit.Fortunately,Chefprovidesasuiteofcommand-lineutilities,whichwewilldiscussatlengthasthebookprogresses.Thereisoneprimarytool,knife,thatallowsanadministratortointeractwiththeserviceinthecommandline.Theknifetoolisrunfromaworkstationandprovidesmanycommandstoview,search,andmodifydatamaintainedbytheChefservice.Onceyouhaveinstalledandverifiedthatalltheservicesarerunning,wecanmoveontosettingupknife.
TipYouwillseethatthestandardplacetostoreyourChefconfigurationdataisin$HOME/.chef(onaUNIX-likesystem.)Thisisnotmandatory,andthesefilescanbestoredanywhereyoulike.
TheknifetoolcommunicateswiththeChefserverviaHTTPandusescertificatesforauthenticationbetweentheworkstationandtheserver.Inordertogetstartedwithknife,wewillneedtodotwothings:gainaccesstothecertificatesthatweregeneratedduringtheinstallationofChefandthenusethosecredentialstosetupanewuserinthesystem.
Inthefollowingexamples,wewillbeusingthehostthattheChefserviceswereinstalledonasourworkstation(wherewewilluseknife).Ifyouwanttouseadifferenthost,youwillneedtogettherequiredcertificate(.pem)filestoyourlocalmachineusingscporsomeothermechanism.Byusingthefollowingcommands,wecangettherequiredauthenticationmaterialsintoourworkdirectory:
mkdir$HOME/.chef
sudocp/etc/chef-server/admin.pem$HOME/.chef
sudocp/etc/chef-server/chef-validator.pem$HOME/.chef
sudocp/etc/chef-server/chef-webui.pem$HOME/.chef
sudochown–R$UID$HOME/.chef
TipChefusesasignedheaderauthenticationforrequeststotheAPI,whichmeanstheremustbeasharedkeythatispresentonboththeclientandtheserver.Chef-serverwillgeneratethechef-validator.pemfilewhenitisconfigured.Newnodesorclientsusethechef-validator.pemfiletosigntherequestsusedtoregisterthemselveswiththesystem.
OnceyouhavethesefilescopiedintoyourChefworkdirectory,itistimetoconfigureknifeitself.Fortunately,knifehasaninteractiveconfigurationmodethatwillwalkyouthroughtheprocessofgeneratingaconfigurationfile.First,ensurethatyouareusingyourChefgemset(ifyouareusingRVMaswediscussedearlier)andthenrunknifeonyourworkstation(again,inthisexample,weareusingourChefservicehostforbothpurposes):
user@chef:~$rvmuse1.9.3@chef
user@chef:~$knifeconfigure-i
Whenyourunknifewiththe-iflag,youwillbepromptedbythefollowingquestions,
whichyoucananswerwiththedefaultsforalmosteverything(non-defaultanswersareinbold):
WARNING:Noknifeconfigurationfilefound
WhereshouldIputtheconfigfile?[/home/user/.chef/knife.rb]
PleaseenterthechefserverURL:[https://localhost:443]
Pleaseenteranameforthenewuser:[user]
Pleaseentertheexistingadminname:[admin]
Pleaseenterthelocationoftheexistingadmin'sprivatekey:[/etc/chef-
server/admin.pem]~/.chef/admin.pem
Pleaseenterthevalidationclientname:[chef-validator]
Pleaseenterthelocationofthevalidationkey:[/etc/chef-server/chef-
validator.pem]~/.chef/chef-validator.pem
Pleaseenterthepathtoachefrepository(orleaveblank):
CreatinginitialAPIuser…
Pleaseenterapasswordforthenewuser:
Createduser[user]
Configurationfilewrittento/home/user/.chef/knife.rb
user@chef:~$
Asmentionedearlier,thisdoestwothings:
First,itusesthevalidationkeyandclientnamespecifiedatthepromptstocontacttheAPIserviceandregisteranewclient(user)withtheserviceSecondly,itgeneratesaconfigurationfileforknifethathasthesettingsneededtoconnecttotheservicefromnowon
SinceChefanditscomponentsarewritteninRuby,theresultingconfigurationfileisaRubyscript,whichcontainssomecodethatconfiguresknifesothatitknowswhatAPIservertoconnectto,whichkeyfilestouse,whatclientnametouse,andsoon.
Aninspectionoftheconfigurationfilethatwasgeneratedbythepreviouscommandwilllooklikethefollowing:
log_level:info
log_locationSTDOUT
node_name'user'
client_key'/home/user/.chef/user.pem'
validation_client_name'chef-validator'
validation_key'/home/user/.chef/chef-validator.pem'
chef_server_url'https://localhost:443'
syntax_check_cache_path'/home/user/.chef/syntax_check_cache'
Becauseweareusingtheservicehostasourworkstation,theChefserverURLpointstothelocalhost.Ifyourworkstationweretobeadifferentsystemsuchasyourlaptop,thenthisURLwouldbetheIPorhostnameofthehostrunningtheChefservice.
EnsuringthatyourknifeconfigurationworksAftersettingupknife,wecanuseittovalidatethatitwasconfiguredcorrectlybyqueryingtheChefserverusingsomesimplecommands.Theknifecommandsfollowtheformatknife<command><subcommand>,wherecommandiseitheraclient,configuration,cookbook,cookbooksite,databag,environment,exec,help,index,node,recipe,role,search,ssh,status,ortag.Subcommandswillvarywiththecommand,buttheytypicallyincludethingssuchasshow,create,list,anddelete(amongothers).
Astherewillinitiallybenonodes,cookbooks,recipes,roles,databags,andsuch,wewillquerythelistofclientsthattheserverknowsabout.Thisshouldbealistoftwoclients:chef-webui(asitisaconsumeroftheAPIitself)andchef-validator(withoutit,itwouldn’tbepossibletoregisteranewclient).
Theclientcommand,withthelistsubcommand,printsalistofclientsthattheserverknowsabout.Atthispoint,runningthecommandwouldlooklikethis:
user@chef:~$knifeclientlist
chef-validator
chef-webui
user@chef:~$
TipIfyoudonotgetthepreviousoutput,butgetanerrorinstead,youwillneedtogobackandmakesurethatallthepreviousstepsarecompletedandverified.
Onceyouknowthatitworks,youcanuseknifetointeractwiththeAPI.Unfortunately,wedonothavemuchdatainthesystemjustyet,butwecanusetheshowsubcommandinconjunctionwiththeclientcommandandaclientnametodisplaymoredetailedinformationaboutaclient:
user@chef:~$knifeclientshowchef-webui
admin:true
chef_type:client
json_class:Chef::ApiClient
name:chef-webui
public_key:-----BEGINPUBLICKEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAos5cQ1NxP7zKf1zRM33g
YeVyHNOO5NcICjSIvqQ5A37wwLfgtPLJQqboW7ZcNL3xYcKOlfYSEK7xha3ss8tT
A+XMifaFp3JsdheyPeIJir2bc9iltUUcbpw9PJ2aQKTBlFNx23A7ag+zBfxcDjbY
7RkdcziwB74ynd6e/K8c0JTRnA5NxoHkFc6v8a/itwujGwugWJXDQunWfCmAvjws
JgDOUu2aHOCVIVkc8it51Sc7Anx0YnCjNmdhz1xIo0MOVNOEmC9ypP0Z7mVv1C69
WWBOEvS9zimjXo4rxBwFmWkPEIG6yPQjhuNmFd69K14vZQtAsH07AZFRSS7HLWnZ
WQIDAQAB
-----ENDPUBLICKEY-----
validator:false
user@chef:~$
SummaryCongratulations!Ifyouhavegottenthisfar,younowhaveafullyfunctionalChefserviceandacopyofthecommand-lineutilities,includingchef-solo.Younowhavecoveredthefollowing:
UsingRVMInstallingchef-soloCreatingasimplerecipeRunningrecipeswithchef-soloInstallingtheChefserviceGettingstartedwiththeknifeutilityVerifyingthatyourChefserviceisoperatingcorrectly
NowthatyouareabletouseyourChefservice,wecanbegintoinvestigatehowtomodelourenvironmentwithChefandseewhatitcandoforus.
Chapter2.ModelingYourInfrastructureNowthatyouhavesetupyourserverorareusingthehostedoffering,let’sdiscusshowtomodelyourinfrastructurewithChef.Chefallowsyoutodothisusingbuildingblocksthatshouldbefamiliartoanysystemadministrator.
ThischapterwillcoverhowtomodelyourinfrastructurewithChef.Thiswillinvolvethefollowing:
LearningsometerminologiesandconceptsrelevanttoChefAnalyzingasimpleapplicationinfrastructureandseeinghowitcanbemodeledwithChefDecomposingourarchitectureintothevariouscomponentstobemodeledExamininghowdataisstoredandconfigurationsaregeneratedwithChefBootstrappingandprovisioninghostswithcloudproviderssuchasAWSandRackspaceCloud
GettingtoknowChefAswithanyothertoolorsystem,therearenewconceptsandterminologiestobelearned.Herearesometermsthatyoumayhaveseenorwillseeinthischapter:
Node:AnodeisasystemthatismanagedbyChef.Thesecanbeservers,desktopsystems,routers,andanythingelsethatiscapableofrunningtheChefclientandhasasupportedoperatingsystem.Workstation:AworkstationisaspecialnodethatisusedbyasystemadministratortointeractwiththeChefserverandwithnodes.Thisiswherethecommand-linetoolsareexecuted,specificallytheknifecommand-linetool.Bootstrap:ThisisaprocessofsettingupanodetobeusedasaChefclient.ThisinvolvesperforminganyworkrequiredtoinstallthedependenciesforChefaswellasChefitself.BootstrapScript:ThereareanumberofpossiblewaystoinstallChef,Ruby,othercorerequirements,aswellasanyadditionalconfigurationthatisneededforyourspecificsystems.Toprovidethislevelofflexibility,thebootstrapprocessisscripted;onWindows,thisisabatchfile.Recipe:Recipesprovidetheinstructionsrequiredtoachieveagoal,suchasinstallingasoftwarepackage,configuringafirewall,provisioningusers,installingprinters,ormanagingothersystemresources.ThesearewritteninRubyandexecutedonthenodesspecifiedbythesystemadministratorthroughtheChefconsole.Cookbook:Acookbookisacollectionofrecipes;typically,acookbookprovidesonespecificgroupofactionssuchasinstallingApacheorMySQL,providingChefresourcesforaspecificsoftwaretool,andsoon.Attributes:Variouscomponentsofthesystemhavetheirownattributesandpropertiesthatdescribehowthesoftwareistobeconfigured.Thesepropertiesaredefinedatvariouslevels,rangingfromnode-specificsettingstogeneraldefaultsforacookbookorarole.Role:Aroleisacollectionofrecipesandconfigurationdatathatdescribehowaresourceshouldbeconfiguredinordertoplaythatroleinyouroverallsystemarchitecture.ExamplesofrolesmightincludeMSSQLServers,ExchangeServers,IISServers,fileservers,andsoon.Aroledoesnotcontainanyknowledgeofresources(systems)toapplytheroleto,onlytheconfigurationdata.RunList:Arunlistisalistofrecipestobeappliedtoagivennodeinacertainorder.Arunlistcanbecomposedofzeroormorerolesorrecipes,andtheorderisimportantastherunlist’sitemsareexecutedintheorderspecified.Therefore,ifonerecipeisdependentupontheexecutionofanother,youneedtoensurethattheyruninthecorrectorder.Resource:Resourcesareawayofdescribingwhatarecipeisperforming.Someexamplesofresourceswouldincludefiles,directories,printers,users,packages,andsoforth.Aresourceisanabstractionofsomethingthatisconcretelyimplementedinaprovider.Provider:Aproviderisaconcreteimplementationofaresource.Forexample,auser
isagenericresource,butLDAPusersorActiveDirectoryusersareconcreteimplementationsofauserresource.Thetypeofproviderbeingselectedwilldependonsomefactors,suchastheplatform.Databags:Databagscontainshareddataaboutyourinfrastructure.Informationthatisnotspecifictoaroleoraresource,suchasfirewallrulesanduseraccounts,willbestoredindatabags.Thisisagoodplacetostoresystem-wideconfigurationdata.Environments:Environmentsprovidealevelofencapsulationforresources.Forexample,youmayhavetwoidenticalenvironments,onefortestingandoneforproduction.Eachofthesemayhavesimilarsetupsbutdifferentconfigurations,suchasdifferentIPaddressesandusers.
ModelingyourinfrastructureNowthatyou’remorefamiliarwithsomeofthetermsyouneedtoknow,let’stakealookatasamplemodelandmapittoChef’scomponents.Atahigherlevel,theapproachwewilltakeisasfollows:
1. Defineanoverviewofyourinfrastructurethatisdecomposedintorolestobeperformedwithinthemodel(webservers,firewalls,databaseservers,andsoon).
2. Collectordeveloprecipesthatdescribetheconfiguration,software,andactionstobeappliedforthoseroles.
3. BootstraphostswiththeChefclientsothattheycanparticipateinbeingmanaged.4. Addanyrequiredconfigurationdataintodatabagstobeusedbynodeswhenrunning
recipessuchasIPaddressranges,hostnames,users,softwarekeys,oranythingelsethatisspecifictotheactiveconfiguration.
5. Segregatehostsandconfigurationsintodifferentenvironmentstoprovideareplicatedinfrastructure(development,staging,production,andsoon).Thisstepisoptional.
Inthischapter,wewillbeusingCheftobuildtheinfrastructureforamulti-tiered,photo-sharingapplicationwhosecomponentsarediagrammedinthefollowingimage:
Buildinganarchitecturediagramgivesusagoodoverviewofoursystemsothatwecanhaveamapofthesystembeforewestartbuildingit.Itisimportanttonotethatamodelof
ourinfrastructuredoesn’tneedtobemappeddirectlytoresources(physicalorvirtual);rather,itprovidesanabstract,high-leveloverviewofyoursystems.Onceyouhavethemodel,youcanapplyittotheresourcesavailableasyouseefit.
Oursampleservice-orientedwebapplicationiscomposedofthefollowingsoftwareservices:
AfrontendwebapplicationserviceAnimage-processingengineAnimagesearchengine
Eachofthesecomponentsisarolethatisbeingplayedinthesystem.Theserolesmaycoexistonsharedresourcesormaybeappliedtodedicatedresources.Aservice-orientedarchitectureisagoodexampletoworkwithforseveralreasons:
ItisflexibleandscalableItwillprovideuswithacompletesystemthatiscomposedofmultipleindependentcomponentstomodel,makingitmoreinterestingasanexample
Inthisexample,inadditiontotheseroles,wemightwanttofurtherconfigureourinfrastructuretoprovidetwodifferentenvironments:oneforstagingandintegrationtestingandoneforproduction.Again,becausethisisamodel,ourstagingenvironmentandproductionenvironmentwillbecomposedofthesamerolesandhavethesameoverallarchitecture;however,eachwillhavedifferentresourcesandconfigurationdataassociatedwiththem.Youmaychoose,forexample,toconsolidateresourcesinatestenvironmentinordertokeepcostsdown.
Forthisinitialoverview,wewillassumethatwehaveanaccountwithapopularcloud-server-hostingcompany,thatthenetworkandoperatingsystemsareinstalledandoperational,andthatwehaveafunctionalandconfiguredChefserviceandworkstation.
Inourhypotheticalsystem,eachservicecanbemappedtoaspecificroleinChef.Tomodeltheinfrastructuredescribed,wewillhaveanumberofroles,oneperelementinourarchitecture.Inthiscase,wewillbuildoneroleforeachserviceinourstackastheyprovideveryspecificfeatures.
RolesAroledescribesapartthatasystemplaysinyourinfrastructurethroughacombinationofrecipestoexecuteandconfiguredata.Theserolescanbefine-grainedorbroadlydescribed,dependingonyourneeds.Therearebenefitsanddrawbackstoboththeapproaches:fine-grainedrolesaresmallerandeasiertoworkwithbutrequirealargernumberofrolestomanage,whereasbroadlyscopedrolesarelessflexibleandnotasreusable.
Forexample,consideratypicalLAMP(Linux,Apache,MySQL,andPHP)stack.Thestackcouldberepresentedbythreeroles:anApachewebservicewithPHP,aMySQLdatabaseservice,andanOpenSSHserviceforadministration.Alternatively,youcoulddefineonerolethatdescribestheinstallationoftheMySQLdatabaseservice,theSSHservice,andtheApacheservice.
Rolesthemselvesknownothingaboutresources;instead,theyareadescriptionofhowtoconfigureasysteminordertofillthatrole.Thesystemadministrator,viathechefconsole,assignsrolestothenode(s)thattheywillbeappliedto.Thismaybeaone-to-one,one-to-many,ormany-to-onemapping,dependinguponyourcapacityplanning.Atanytime,anadministratorcanchangethelistofrolesthatareappliedtoanode,addingorremovingthemasneeded.Forexample,youmightdecidetoapplyallyourrolestoonehosttodayforcostsavings,butscalethemoutinthefutureasyourbudgetandneedsgrow.
DefiningrolesLet’stakealookatsomeroleswemightdefinetomodelourSOAapplicationonasdescribedearlierinthechapter.Here,wewilldefinefine-grainedrolesastheyareeasiertodissectanddeployontoseparatenodeslater.Atahigherlevel,thefollowingrolesarewhatourservicesneedtoprovide.
Awebapplicationservicerole
Whendefiningwhatawebapplicationserverwillneedtodo,wewillneedthefollowing:
nginxHTTPserviceRuby2.0MemcachedservicePostgreSQLclientlibrariesOpenTCPportsontheexternalnetworks:80forHTTPand443forHTTPS
Animage-processingrole
Thisrolerequiressomeimage-processinglibrariesandcustomsoftwaretobeinstalled:
ImageMagicklibrariesGit(tocheckoutthesourcecode)Buildtools(tocompileoursource)Thelatestversionofourimage-processingsoftware
Animagesearchrole
Aservicethatprovidesimagesearchingthroughperceptualhashingwillprovideanimagesearchrolefunctionality.Thisrolewillrequirethefollowing:
AJavaruntimeenvironment(JREorJDK)Ourcustom-builtservicethatisdevelopedinJavaTCPport8999opentointernalhosts
APostgreSQLservicerole
ForthePostgreSQLdatabaseservicerole,thelistisasfollows:
PostgreSQL9.xserverTCPport5432opentointernalnetworkclientsDatabasebackupsoftwaretobackupdatatoanexternalclouddatastorageservicesuchasS3
ASolrservicerole
AsystemthatprovidestheApacheSolrservicewillneedthefollowing:
AcompatibleJavaruntime(OracleJREorOpenJDK)TCPport8993opentointernalserversApacheSolritself
AnOpenSSHservicerole
AnOpenSSHservicerolewillneedthefollowing:
OpenSSHserverTCPport22openonalltheinterfaces
Noticethattheseroleshavenospecifichostinformation,suchasIPaddressesorserverstoinstallthesoftwareonto;instead,theyareblueprintsforthepackagesweneedtoinstallandtheconfigurationthatthoseroleswillprovide,suchasopenports.Inorderfortheseroledefinitionstobemadeasreusableaspossible,wewillwriteourrecipestousenode-androle-specificconfigurationordatafromourdatabagstoprovidetherequiredconfigurationdata.
Inordertodefinetheseroles,youwillneedrecipesthatdescribethesetsofstepsandoperationsthatwillbeappliedtohostsinordertofulfilleachrole.Forexample,thePostgreSQLdatabaseserverwillrequireyoutoinstallPostgreSQL,openthefirewall,andsoon.Thesedefinitionsarecreatedbydevelopingrecipesthatcontainthenecessaryinformationtoperformthetasksrequired,suchasinstallingpackages,generatingconfigurationfiles,executingcommands,andsoon.Mostoftheservicesmentionedhere(ourcustomimagingsoftwarebeingthelikelyexception)havecookbooksthatalreadyexistandareavailablefordownload.
ImplementingaroleNowthatyouhaveseenwhatourinfrastructuremightlooklikeatahigherlevel,let’stakealookathowwewillgoaboutimplementingoneofourrolesinChef.Here,wewillimplementthePostgreSQLserverroleasitissimpletoconfigureandhasaveryrobustcookbookavailablealready.
Asmentionedbefore,youwillneedtoeitherdevelopyourowncookbooksordownloadexistingonesinordertobuildyoursystems.Fortunately,therearethousandsofcookbooksalreadywritten(over1,500asofthiswritingintheChefSupermarket)and,aswewillseeinfurtherchapters,developingnewcookbooksisastraightforwardprocess.
Inordertodefinearole,weneedtocreateit;thiscanbeaccomplishedthroughawebinterfaceorbyusingknife.Here,andelsewhereinthisbook,wewilluseknifeasthewaytointeractwiththeChefservicebecauseitprovidesaconsistentexperienceacrossself-managedandhostedChef.Solet’sgetstarted!
Thefirstthingyouwillneedtodoiscreateanewrolewithknife,whichisassimpleasexecutingthefollowing:
kniferolecreate-dpostgresql_server
ThiswilltellknifetoconnecttoyourChefserver’sAPIandcreateanewrolenamedpostgresql_server.The-dflagtellsknifetoskipopeninganeditorandinsteadacceptthedefaultvalues.IfyouwanttoseewhattheunderlyingJSONlookslike,omitthe-dflagandmakesureyouhaveanappropriateEDITORenvironmentvariableset.Onceyourunthis,youcanverifythatyourrolewascreatedwiththefollowingcommand:
kniferolelist
Thiswillshowyouthatyouhaveasingleroleinthesystem,postgresql_server.Currently,however,thisroleisemptyandhasnoinformationassociatedwithit,justanameandanentryinthesystem.Nowthatwehavearoleinthesystem,let’slookathowwecanworkwithsomerecipestomakeourroledosomethinguseful,suchasinstallthePostgreSQLservice.
DeterminingwhichrecipesyouneedRecipesarehowChefknowshowtomakesurethatthecorrectpackagesareinstalled,whatcommandsneedtobeexecutedinordertoopenportsonthefirewall,whichportsneedtobeopened,andsoon.Likeanygoodcook,Chefhasawidearrayofcookbooksatitsdisposal,eachofwhichcontainsrecipesrelevanttothatparticularsetoffunctionality.ThesecookbookscaneitherbedevelopedbythesystemadministratorordownloadedfromavarietyofplacessuchasGitHub,BitBucket,orfromacollectionofcookbooksmaintainedbytheChefcommunityontheChefSupermarket(http://supermarket.getchef.com).Wewilldiscusshowtodownloadandgetstartedwithsomesimplerecipesandthenfurtherdiscusshowtodevelopanddistributeourownrecipesinlaterchapters.
Consideringhowwehavearrangedourroles,wewouldneedrecipestoinstallandconfigurethefollowing:
nginxAPostgreSQLserverAPostgreSQLclientRuby2.0SolrJavaOpenSSHAMemcachedserverMemcachedclientlibrariesImageMagickGitACustomimagingsoftware(wewillcallitImage-O-Rama)
Here,wewilltakeanin-depthlookatthereciperequiredforourPostgreSQLserverandhowwecanleveragethattoinstalltheserviceonahost.
InstallingacookbookInstallingacookbookforuseonourclientsisquitesimpleandinvolvesonlytwosteps:
1. Developingacookbook,ordownloadingthecookbookfromsomewhere.2. UploadingthecookbooktotheChefserviceusingknife.
Togetstarted,wewilldownloadanexistingPostgreSQLcookbookfromtheChefcookbookcollectionanduploadittoourChefservice.NotethatinordertoinstallthePostgreSQLcookbook,youwillalsoneedtoinstallanydependenciesthatarerequired.Forsimplicity,theyareprovidedhereaspartoftheinstructions;however,youmayfindthatwhenyouexperimentwithothercookbooksinthefuture,youwillneedtodownloadafewcookbooksbeforeallofthedependenciesaremet,oruseatoolsuchasBerkshelfformanagingthem.
TodownloadacookbookfromChef’sprovidedcollectionofcookbooks,wewilluseknife
withthefollowingcommand:
knifecookbooksitedownload<cookbook_name>
Inthiscase,wewillneedtodownloadfivedifferentcookbooks:
postgresqlbuild-essentialaptchef-sugaropenssl
Foreachoftheitemsinthelist,wewilldownloadthemusingthefollowingcommand:
knifecookbooksitedownloadpostgresql
knifecookbooksitedownloadbuild-essential
knifecookbooksitedownloadapt
knifecookbooksitedownloadchef-sugar
knifecookbooksitedownloadopenssl
Eachdownloadwillresultinanarchivebeingdownloadedtoyourworkstation.Thesearchivescontainthecookbooks,andyouwillwanttodecompressthemafterdownloadingthem.Theycanbedownloadedanywhere,butitwouldprobablybeagoodideatokeeptheminacommoncookbooksdirectory,somethinglikechef/cookbooksinsideyourhomedirectorywouldbeagoodideaifyouneedone.
Oncetheyaredownloadedanddecompressed,youwillneedtouploadthemtotheChefservice.Thiscanbedonewithonlyonecommandusingknifecookbookuploadasfollows;theyareuploadedfromthedirectoryinwhichyoustoredyourdecompressedcookbooks:
knifecookbookupload-o.aptbuild-essentialpostgresqlchef-sugar
openssl
Thiswilluploadthefivecookbookswedownloadedandtellknifetosearchthecurrentdirectorybywayofthe-o.directive.Oncethisisdoneyoucanverifythattheyhavebeeninstalledusingtheknifecookbooklistcommand.
Oncetheyareinstalled,yourcookbooksareregisteredwiththeChefservice,andwecantakealookathowwecanconfigureandapplythePostgreSQLservertoanewUbuntuhost.
ApplyingrecipestorolesNowthatyouhavesomecookbooksregisteredwithyourChefservice,youneedtoaddthemtoarole’srunlistinorderfortheirbehaviortotakeeffectonanyendhosts.Therelationshipbetweenarecipeandanygivennodeisshowninthefollowingdiagram:
Becauseofthenatureofthisrelationship,recipesdeliberatelyhavenoknowledgeofindividualnodes.Justasarecipeforchocolatechipcookieshasnoideaaboutwhomanufacturedtherollingpinandspatula;aChefrecipeissimplyasetofinstructionsonwhattodoandinwhatordertoperformthoseactions.
Becausewehaveuploadedourcookbookstothesystem,wehavealreadyaddedtherecipescontainedinsideofthosecookbookstooursystem;therefore,wecannowassociatearecipewithourrecentlycreatedrole.Ifyoulookatthecontentsoftherecipesdirectoryinsideofthepostgresqlcookbook,youwillseethatthereisaserver.rbfile.ThisdescribesarecipetoinstallthePostgreSQLserverandiswhatwewillbeaddingtoourpostgresql_serverroleinordertoperformtheactualinstallation.
Todothis,weneedtoeditourroleandaddtherecipetoitsrunlist;wewilldothisusingknife.
TipEnsurethatyouhaveavalidtexteditorinyourEDITORenvironmentvariable;otherwise,youwillhavedifficultyeditingyourentitieswithknife.
Inordertoeditourrole,wecanusethekniferoleeditcommand:
kniferoleeditpostgresql_server
ThiswillopentheJSONfilethatrepresentsthepostgresql_serverrolestoredintheChefserverinatexteditorwhereyoushouldseethefollowingcontent:
{
"name":"postgresql_server",
"description":"",
"json_class":"Chef::Role",
"default_attributes":{
},
"override_attributes":{
},
"chef_type":"role",
"run_list":[
],
"env_run_lists":{
}
}
ThemostimportantsectionofthisJSONblobatthismomentistherun_listkey—thisisanarrayofallthethingswewanttorun.Thiscanbealistofrecipesorroles,andeachofthosehasthefollowingnamingstructure:
recipe[cookbook::recipe]forrecipesrole[role_name]forroles
Soourserverrecipeinsideourpostgresqlcookbookwouldthereforebenamed"recipe[postgresql::server]".Thisisexactlywhatwewillbeaddingtoourrole’srunlistJSON.Updatetherun_listentryfromtheoriginalvalueofanemptyarray:
"run_list":[
],
ToincludeourPostgreSQLserverrecipe,usethefollowingcode:
"run_list":[
"recipe[postgresql::server]"
],
ThisisallweneedtochangenowinordertoapplythePostgreSQLserverroletoournode.
TipNoticethatwehavenotaddedanyvaluestotherole’sattributes;thismeansthatourrecipewillbeexecutedusingitsdefaultattributes.Mostrecipesarewrittenwithsomesetofacceptabledefaultvalues,andthePostgreSQLserverrecipeisnodifferent.
Fornow,thereisnoneedtomodifyanythingelse,sosavetheJSONfileandexityoureditor.DoingthiswilltriggerknifetouploadyourmodifiedJSONinplaceofthepreviousJSONvalue(afterdoingsomevalidationonyourJSONcontents),andtherolewillnowhavethepostgresql::serverrecipeinitsrunlist.Youshouldseeanoutputfromknifeindicatingthattherolewassaved,andyoucanverifythatthisisthecasewithasimplekniferoleshowcommand:
kniferoleshowpostgresql_server
Thiswillshowyouanoverviewoftheroleinamorehuman-readableformatthanthesourceJSON.Forexample,ourroleshouldnowhaveoneentryintherunlistasshowninthefollowingoutput:
chef_type:role
default_attributes:
description:
env_run_lists:
json_class:Chef::Role
name:postgresql_server
override_attributes:
run_list:recipe[postgresql::server]
Oncethisiscomplete,ourroleisnowreadytobeappliedtooneofournodes.Atthispoint,wehaveuploadedourcookbooks,definedarole,andassociatedarecipewithournewlycreatedrole.Nowlet’stakealookatthefinalstep:applyingournewroletoanode.
MappingyourrolestonodesAshasbeendiscussed,rolesareadefinitionofwhatcomponentsneedtobebroughttogethertoserveaparticularpurpose;theyareindependentofthehardwaretheyareappliedto.Thishelpstoseparateconcernsandbuildreusablecomponentstoacceleratetheconfigurationofinfrastructureinnewarrangements.Inordertomanifestarole,itmusthaveanodethattheroleisappliedto;inordertomanageanode,itmusthavetheChefclientanditsdependenciesinstalledandberegisteredwiththeChefservice.
OnceanodeisregisteredwithChef,youcansetnode-specificproperties,assignrolesandrunthechef-clienttoolonthehostinordertoexecutethegeneratedrunlists.Foroursampleapplicationstack,wemayhavethefollowinghostsrunningUbuntuLinux14.04:
cayennewatermelonkiwi
OncetheyarebootstrappedandregisteredwiththeChefservice,wewillthendecidewhichrolesaretobeappliedtowhichnodes.Thiscouldyieldaconfigurationthatlookslikethefollowing:
cayenne
Webapplicationservicerole
watermelon
APostgreSQLdatabaseroleASolrsearchenginerole
kiwi
Animage-processingroleAnimagesearchrole
Withoutanyhardware,rolesarejustanabstractblueprintforwhatneedstobeconfiguredtogethertoprovideaparticulartypeoffunctionality.Here,wehavecombinedourresources(cloudinstancesordedicatedhardware)andourrecipestobuildaconcreteinstanceofourservicesandsoftware.
Inordertoapplyournewlycreatedroletoourhost,watermelon,wewillneedtobootstrapthathost,whichwillinstalltheChefclientonthehostandthenregisteritwiththeChefservice.Thisisreallyasimpleprocess,aswewillseehere,andisachievedusingtheknifebootstrapcommand:
knifebootstrap-xroot-dubuntu14.04<ipaddress>
TipForourexample,thenodewilluseanUbuntu14.04hostcreatedonDigitalOcean,aninexpensivecloud-hostingprovider;youcanbootstrapjustaboutanymodernLinuxdistribution,butifyouarefollowingalongwiththecommandsinthebook,youwillget
thebestresultsbyusinganUbuntu14.04machine.
ThisprocesswillgothroughthestepsrequiredtoinstalltheChefclientonthenodeandregisteritwithyourChefservice.Onceitiscomplete,youwillseethattheChefclienthasfinishedwithanoutputsimilartothefollowing:
ChefClientfinished,0/0resourcesupdatedin4.264559367seconds
Ifyouwanttoverifythatthehosthasbeenadded,asimpleknifenodelistcommandwillshowyouthatithasbeenregisteredwiththeChefservice.Ifyoudon’tseetheclientoutputabove,oryoudon’tseethenewlybootstrappednodeinyourlist,makesurethattheoutputofknifebootstrapdoesn’tindicatethatanythingwentwrongalongtheway.
Onceournodeisregistered,wecanaddourpostgresql_serverroletoournode’srunlistusingthefollowingknifecommand:
knifenoderun_listsetwatermelonrole[postgresql_server]
Thiscommandwillsettherunlistonournewhost,watermelon,tocontainthepostgresql_serverroleasitsonlyentry.Thiscanbeverifiedusingtheknifenodeshowcommand:
knifenodeshowwatermelon
NodeName:watermelon
Environment:_default
FQDN:watermelon
IP:162.243.132.34
RunList:role[postgresql_server]
Roles:
Recipes:
Platform:ubuntu14.04
Tags:
Nowthatthenodehasarunlistwithentries,it’stimetoactuallyconvergethenode.ConverginganodemeansthattheChefserverwillcompiletheconfigurationattributesandthenprovidetheendhostwithacompletelistofrecipestorunalongwiththerequiredcookbookdataandthenexecutethemonthenode.
ConverginganodeConverginganodeisdonebyexecutingthechef-clientcommand-lineutilityonthehost;thiscanbedoneinoneoftwodifferentways.ThesimplestwayistoSSHintothehostusinganSSHclientandthenexecutechef-clientastheroot;anotherwayistouseknifetoexecuteacommandonasetofhostsinparallel,whichwillbediscussedinlaterchapters.Fornow,simplySSHintoyourserverandrunchef-clientastheroot:
root@watermelon:~#chef-client
TheChefclientwillconnecttotheChefserviceanddownloadanyinformationneededtoexecuteitscompleterunlist.Anode’srunlistisdeterminedbyexpandingeveryentryinthenode’srunlistuntilitisalistofrecipestoexecute.Forexample,ournodecontainsoneelementinitsrunlist,thepostgresql_serverrole.Thisrole,inturn,containsoneentry,thepostgresql::serverrecipe,whichmeansthatthefullyexpandedrunlistforournodecontainsonlyoneentry.Inthissimplecase,wecouldhavejustaddedtherecipedirectlytoournode’srunlist.However,thishasanumberofshortcomings,includingnotbeingabletoaddextraconfigurationtoallthePostgreSQLserversinourinfrastructure,aswellasanumberofotherreasonsthatwillbediscussedinthefuture.
Inadditiontocomputingtherunlist,theChefservicewillalsodeterminewhatthefinalsetofconfigurationdataforournodewilllooklikeandthendeliverittotheclient.Thisiscomputedaccordingtoasetofrulesshownlaterinthischapter.Oncethatisdelivered,theclientwillalsodownloadallthecookbooksneededinordertoknowhowtoexecutetherecipesspecifiedinthefinalrunlist.Inourcase,itwilldownloadallthefivecookbooksthatweuploadedpreviously,andthen,whentheclientruniscomplete,theresultwillbepresentedinarunningPostgreSQLserver.
Oncetheclientruniscomplete,itwillreportonhowlongtheruntookandhowmanyresourcesweremodified.Theoutputshouldlooksomethinglikethefollowing:
ChefClientfinished,8/10resourcesupdatedin61.524995797seconds
Assumingthatnothingwentwrong,youshouldnowhaveafullyfunctionalPostgreSQLserverdeployedtoyournewhost.ThiscanbeverifiedbylookingattheprocesslistforaPostgreSQLprocess:
root@watermelon:~#psax|greppost
11994?S0:00/usr/lib/postgresql/9.3/bin/postgres-D
Thereyouhaveit,withonlyonecommand;yournodehasnowbeenprovisionedasaPostgreSQLdatabaseserver.Nowlet’stakealookathowwecanusesomeotherfeaturesofCheftomodelourinfrastructure.
EnvironmentsBeyondcreatingrolesandhavingresourcestoapplythemto,thereareoftenrequirementsaroundgroupingcertainresourcestogethertocreateadistinctenvironment.Anexampleofthismightincludebuildingastagingenvironmentthatfunctionsexactlylikeaproductionenvironmentforthepurposesofpreproductiontestingandsimulation.Inthesecases,wewouldhaveanidenticalsetofrolesbutwouldverylikelybeapplyingthemtoadifferentsetofnodesandconfigurationvalues.Chefachievesthisseparationthroughtheenvironmentprimitive,whichprovidesawaytogroupseparateconfigurationsandhoststogethersothatyoucanmodelsimilar,yetseparate,portionsofyourinfrastructure.
Inourhypotheticalinfrastructure,thethreehostsinourproductionenvironmentmaybecondenseddowntooneserverinpreproductioninordertosavemoneyonresources(orforotherreasons).Todothis,wewouldneedtobootstrapanode,perhapsnamedpassionfruitandthenconfigureittohavealloftherolesappliedtoit,ratherthanspreadingthemoutacrosssystems,asshowninthefollowingfigure:
Here,inthepreviousimage,youcanseethateachenvironmenthasaverysimilarsetupbutadifferentsetofIPaddressesandresources.Eventhoughwehaveaheterogeneoushardwarescaleinourenvironments(productionhasthreenodes,andpreproductionhasonlyone),anychangeswemakewillbeappliedtoallofthesystemsinaconsistentmanner.
Inordertoachievethistypeoffunctionality,Chefneedsawaytoorganizeandcompiletheconfigurationdatainordertoprovideittotheendhostwhenthetimecomestoconfigurethehost.NowthatweunderstandhowtomodeloursystemswithChef,let’stakealookathowChefhandlestheconfigurationdatatomakeallofthishappen.
OrganizingyourconfigurationdataChefrunsonconfigurationdata—thisdatacanbestoredinavarietyofdifferentlocations,andwithavarietyofpurposes.Whencomputingthefinalconfigurationforagivennode,thesourcesofconfigurationdataarethen“squashed”intoasingle,authoritativeconfigurationtobedeployed.Thosesourcesincludethefollowing:
Cookbooks:ToprovidereasonabledefaultsforrecipesNodes:Node-leveloverridesanddefaultsRoles:Per-roleconfigurationdataDatabags:System-wideconfigurationdatastorage
Datafromtheselocationsiscombinedtoproduceafinalhashofattributeswhenaclientrequestsitsrunlistfromtheserver.Cookbooksprovideabaselinesetofattributesthattherecipesinsiderelyon.Theseattributesactas“sanedefaults”fortherecipesthat,intheabsenceofoverridingvalues,aresufficienttoexecutetherecipeswithoutextrawork.Othersources,includingtheenvironment,roleandnodeitself,mayinturnoverridetheseattributesinordertoprovidethefinalconfiguration.
Whendevelopingrecipes,theseattributescanbeaccessedthroughthenodehashandarecomputedbyChefusingasetofrulestodetermineprecedence.Theorderofprecedencewhencomputingthishashisbrokendownintothefollowinglevels(lowesttohighestpriority):
DefaultNormal(alsoset)Override
Withineachlevel,thesourcesofattributedatainorderofincreasingprecedenceareasfollows:
TheattributesfileinsideofacookbookEnvironmentRoleNode
Thismeansthatanode-specificoverrideattributetakesprecedenceoverallothers,whichinturnismoreimportantthantherole,environmentandcookbookoverrideattributes,andsodownthechainofprecedence.Asyourscopebecomesnarrowedfromthemostgenericdescriptionofarole—therecipes—tothemostspecificcomponentinthesystem—theactualnodeitself—thesesettingsoverridethemoregenericvalues.Anodeknowsbestwhattheauthoritativeconfigurationshouldbeasopposedtoarecipe,whichdoesnotknowanythingaboutresourcesonthehost.Forexample,considerthefollowingscenarioinwhichyouhavetwohosts,potassiumandchromium.Forsomelegacyreason,theirdisksareconfiguredslightlydifferently,asfollows:
Potassium:
16GBrootpartition
250GBSSDdatapartitionin/opt
Chromium:
32GBrootpartition400GBEBSdiskmountedat/usr
InordertoinstallthePostgreSQLdatabaseserver,youneedtomakesureyouinstallitatalocationthatprovidesenoughstoragespaceforthedata.Inthisexample,therewillbemoredatathaneitherrootdiskscancontain.Asaresult,thedatadirectorywillneedtoresidein/optonpotassiumand/usronchromium.ThereisnowaythatthePostgreSQLrecipecanaccountforthis,andthepostgresql_serverrecipedoesnotknowanythingaboutitsresources.Subsequently,thelogicalplacetoconfigurethedatadirectoryisatthenodelevel.Ifthedefaultlocationaccordingtotherecipewere/usr/local,thenanode-leveloverridemaynotbeneededforchromium;however,inthecaseofpotassium,itcouldbedirectedtostoredatain/opt/datainstead.
Whatallthismeansisthatasyoudeveloprecipes,anydefaultattributesetbyyourcookbookwillbethelowestpriority.Youcansafelysetsomereasonabledefaultsinyourcookbookknowingthattheywillonlybeusedasafallbackifnobodyoverridesthemfurtherdownthechain.
ExampleattributedataAsimpledefaultattributesfileforPostgreSQLcookbookmightlooklikethefollowing:
default['postgresql']['port']="5432"
default['postgresql']['data_dir']="/usr/local/pg/data"
default['postgresql']['bind_address']="127.0.0.1"
NoticethattheattributesforacookbookareaRubyhash.Typically,goodpracticedictatesthatthenamespace(firstkeyinthehash)isthesamenameasthecookbook(inthiscase,postgresql),butthisdoesnotneedtobethecase.Duetocookbooksoftencontainingmultiplerecipes,acookbook’sattributesfilewilloftenhaveper-recipedefaultconfigurations.ConsiderafurtherevolutionofthePostgreSQLattributesfileifitweretocontainrecipesforboththeserverandtheclientinstallation:
default[:postgresql][:client][:use_ssl]=true
default[:postgresql][:server][:port]="5432"
default[:postgresql][:server][:log_dir]="/var/log/pglog"
Therearetimeswhenjustasimpleattributesfiledoesn’tmakesensebecausetheconfigurationmaybedependentonsomepropertyofthenodebeingmanaged.ThefactthattheattributesfileisjustaRubyscriptallowsustoimplementsomelogicinsideourconfiguration(thoughyoushouldavoidbeingoverlyclever).Considerarecipewherethedefaultgroupfortherootuserdependsontheplatformyouareusing:"userdonBSDs,"nBSDsonUbuntuLinux,and"nUbuelsewhere.Chefprovidesamethod,value_for_platform,thatallowstheattributetobechangeddependingontheplatformtherecipeisbeingexecutedon,asthefollowingexampledemonstrates:
default[:users][:root][:primary_group]=value_for_platform(
:openbsd=>{:default=>"wheel"},
:freebsd=>{:default=>"wheel"},
:ubuntu=>{:default=>"admin"},
:default=>"root"
Whereitmakessense,attributescanalsobesharedbetweencookbooks.Therearelimitedusesforthis,anditshouldbeusedwithcareasitblurstheboundariesbetweencookbooksandcausesthemtobecometootightlycoupledwithoneanother.
DatabagsTherearetimeswhenconfigurationdatatranscendsarecipe,role,environment,ornode.Thistypeofconfigurationdatatendstobesystem-widedatasuchasthefollowing:
FirewallrulesforvarioustypesofhostsUseraccountsSSHkeysIPaddresslists(whitelistsandblacklists)APIkeysSiteconfigurationdataAnythingthatisnotuniquetoaspecificentityinyourinfrastructure
Databagsareveryfree-form,asthenameimplies;recipesthatrelyondatafromdatabagswillimposetheirownexpectationsoftheorganizationwithinadatabag,butChefitselfdoesnot.Databagscanbeconsidered,likeallotherChefconfigurationdata,tobeonelargehashofconfigurationdatathatisaccessibletoalltherecipesacrossallthenodesinthesystem.
KnowingwhentousedatabagsBuildingfirewallrulesareagoodusecasefordatabags.Agoodcookbookisanislandbyitself;itmakesasfewassumptionsabouttheworldaspossibleinordertobeasflexibleandusefulasitcanbe.Forexample,thePostgreSQLcookbookshouldnotconcernitselfwithfirewallrules,thatis,therealmofafirewallcookbook.Instead,anadministratorwouldleverageagenericfirewallmodelandacookbookwithaspecificfirewallimplementationsuchastheUFWcookbooktoprovidethosefeatures.Inthiscase,ifyouweretolookattheUFWcookbook,youwouldseetheufw::databagrecipemakinguseofdatabagstomakethefirewallrulesasflexibleaspossible.
TipUFWstandsforuncomplicatedfirewall,apopulariptables-basedfirewallrulegenerationpackageforLinuxthatcomeswithmanymoderndistributionsandeasesthemanagementofafirewallconfiguration.
Inthiscase,ufw::databagexpectsthatthereisaspecificdatabagnamedfirewallandinsideofitareitemsthatsharenameswithrolesornodes;thisisinlinewiththenotionthatdatabagsarefree-form,butcertaincookbooksexpectcertainstructure.Ifourinfrastructuremodelhadtworoles,web_server,anddatabase_server,thenourfirewalldatabagwouldhavecontainedtwoitemsnamedaccordingly.Theweb_serveritemcouldlooklikethefollowinghash:
{
"id":"web_server",
"rules":[{
"HTTP":{
"dest_port":"80",
"protocol":"tcp"
},
"HTTPS":{
"dest_port":"443",
"protocol":"tcp"
}
}]
}
Here,idoftheitemmapstothenameoftheitem,whichisalsothenameoftherole,sothattheufw::databagrecipeknowswheretofetchthedataitneedstobuilditsinternalfirewallrules.Tocomputeitslistoffirewallrulestoapply,theufw::databagrecipeexaminesthelistofrolesthatthenodeisconfiguredwithandthenloadsthecorrespondingdatafromtheitemsinthefirewalldatabag.
Asyoucansee,databagsallowyoutostorecentralized,free-formconfigurationdatathatdoesnotnecessarilypertaintoaspecificnode,role,orrecipe.Byusingdatabags,cookbooksforconfiguringusers,databases,firewalls,orjustaboutanypieceofsoftwarethatneedsshareddatacanbenefitfromthedatastoredinadatabag.
Onemightwonderwhywehavedatabagswhenwealreadyhaveattributedata,andthatwouldbeagoodquestiontoask.Attributesrepresentthestateofanodeataparticularpointintime,meaningthattheyaretheresultofacompactionofattributedatathatisbeingsuppliedtoanodeatthetimetheclientisbeingexecuted.WhentheChefclientruns,theattributedataforallthecomponentscontributingtothenode’srunlistisevaluatedatthattime,flattenedaccordingtoaspecificprioritychain,andthenhandedtotheclient.Incontrast,databagscontainarbitrarydatathathasnoattachmenttoaspecificnode,role,orcookbook;itisfree-formdatathatcanbeusedfromanywhereforanypurpose.Onewouldnot,forexample,belikelytostoreuserconfigurationdatainacookbookoronaspecificnodebecausethatwouldn’tmakemuchsense;usersexistacrossnodes,roles,andevenenvironments.Thesamegoesforotherdatasuchasnetworktopologyinformation,credentials,andotherglobaldatathatwouldbesharedacrossafleet.
Large-scaleinfrastructureOneofthemanybenefitsofChefisthepowertoapplyrolestonodesatscale.Thismeansthatonceyoudefineasetofrolesandsomesupportingrecipes,youcanapplythemtoonehostjustaseasilyasanyother.TherearemanyorganizationsthatmanageverylargeinfrastructureusingChef,includingcompaniessuchasFacebook,Ancestry,andRiotGames.WithChef,configuringonehundredhostsisasstraightforwardasconfiguringonehost.Beingabletoachievescalabilitygoalswhileremainingcost-effectiveisacriticalpartofrunningatechnologybusiness.Tothisend,Chefprovidestoolstoautomatethecreation,provisioning,maintenanceandterminationofvirtualhostsusingtheprovidedtools,whichcanhelpachievebothscalabilityandconservationofresources.ThenextchapterdiscusseshowtouseCheftoextendyourinfrastructureintothecloud.
SummaryNowthatyou’velearnedthekeyterminologythatChefusesanddissectedanexampleinfrastructureabit,youcanseethefollowing:
Infrastructurecanbedecomposedintothevariousrolesthatresources(nodes)playwithinthatinfrastructureAcombinationofrecipesandconfigurationdataprovideuswithrolesthatdescribeapartofouroverallinfrastructureChefanalyzestherolesappliedtohardwareresources(nodes)andgeneratesarunlistthatisspecifictothenodethatthoserolesarebeingappliedtoArunlististhencombinedwiththecookbooks,recipes,templates,andconfigurationdatatobuildaspecificsetofscriptsthatareexecutedonthenodewhenthechef-clientisrunWecanapplythesemethodologiesofautomatedconfigurationtocloudserversandphysicalsystemsalike.
NowthatyouunderstandhowChefmodelsinteract,let’stakealookathowwecangetstartedwithcloudservicesusingChef.
Chapter3.IntegratingwiththeCloudBeingabletoconfigurenewhostsautomaticallymeansthatifyououtgrowyourexistingresources,youcaneasilybringupnewserverstoincreaseyourcapacitywithverylittleeffort.TheChefcommand-linetool,knife,providestheabilitytoprovisionnewhostswithcloudservicesautomaticallyfromthecommandlineifconfiguredcorrectly.ThischapterintroducesyoutousingChefwithtwopopularcloudplatformproviders:AmazonEC2andRackspaceCloud.Here,youwilllearntouseknifewithbothoftheseprovidersinordertoperformthefollowing:
ProvisionnewhostsaccordingtoyourhardwareneedsBootstraptheChefclientandregisterhostswiththeChefserviceListyourexistingcapacityTerminateunneededcapacity
Youwillseethatallofthiscanbedoneusingthecommand-linetoolsprovidedwithouteverhavingtologintotheprovider’swebinterface,andallofthiswithinafewminutesoftime.
LeveragingthecloudCloudcomputingproviderssuchasRackspaceCloudandAmazonEC2provideon-demandcomputingpoweratthepushofabutton,afeaturethathasbecomeimmenselypopularwithdevelopersandsystemsadministratorsalike.Oneofthemosttoutedbenefitsofcloudcomputingiscostsavings;however,theseon-demandinstancescanbecomeveryexpensiveiftheyareleftrunning.Oftenthecapacityoftimewillbeconfiguredinordertohandlelarge-scaleeventsandthenleftonlinebecauseofthetimerequiredtoreconfigurethesystemsiftheyareneededagain.Asunderutilizedcapacityendsupcostingmoneyratherthansavingit,beingabletoreduceorexpandthecapacityquicklyandeasilywillhelpyoumatchyourcomputingneedswhilesavingbothtimeandmoney.
Thissectionspecificallylooksattwoofthemorepopularcloudproviders:AmazonEC2andRackspaceCloud;however,thereareothers,andthetechniquesdescribedherewillbebroadlyapplicabletoanyothersupportedcloudprovider.
AmazonEC2AmazonEC2isaverypopularcloud-computingplatform,andknifehassupporttomanageEC2instancesfromthecommandlinethroughtheknife-ec2plugin.ThefollowingstepsdemonstratehowyoucanworkwithEC2:
1. InstalltheEC2knifeplugin.2. SetupyourSSHkeysforusewithEC2.3. ConfigureknifewithyourAWScredentials.4. FindthedesiredAMI.5. Provisionanewhostwithknife.6. Bootstrapthenewlycreatedhost.7. Configurethenewhostwitharole.
InstallingtheEC2knifepluginAsofChef0.10,theec2subcommandshavebeenmovedfrombeingbuiltinknifetoanexternalgem,knife-ec2.InordertouseEC2commands,youwillneedtoinstallthegem,whichcanbedoneviathefollowingcommand:
geminstallknife-ec2
ThiswillinstallallofthegemdependenciesthattheEC2pluginrequires.
TipSomeofthecloudproviderpluginshaveconflictingdependencies,soitmaybebesttoleverageagemmanagerinordertoisolatethem.Forexample,usingRVMorrbenv,youmightcreateoneRubygemenvironmentperprovidersothatyoucouldswitchbackandforthwithasimplecommandsuchasrvmgemsetusechef-ec2.
SettingupEC2authenticationInordertomanageyourEC2hosts,youwillneedyourEC2key-pairproperlyregisteredwithSSHandyourAWSaccesskeyssetinyourknifeconfigurationfile.
Todothefirst,makesureyouhaveyourEC2SSHkeysdownloadedandregisteredwithyourSSHagent.OnewaytodothisistoaddthefollowingtoyourSSHconfigurationfile,typically,$HOME/.ssh/config:
Host*.amazonaws.com
ForwardAgentyes
CheckHostIPno
StrictHostKeyCheckingno
UserKnownHostsFile=/dev/null
IdentityFile~/.ssh/ec2_keypair.pem
InordertoconfigureyourAWSkeys,youwillneedtoaddsomeinformationtoyourknife.rbconfigurationfile($HOME/.chef/knife.rb):
knife[:aws_access_key_id]="YOURACCESSKEY"
knife[:aws_secret_access_key]="SECRETKEY"
ThesekeystellknifewhichAWScredentialstousewhenmakingAPIcallstoperformactionssuchasprovisionnewhostsandterminateinstances.Withoutthis,knifewillbeunabletomakeAPIcallstoEC2.Withtheserequiredchangesmade,let’slookathowtocreateanewEC2instancewithknife.
ProvisioninganinstanceInitially,wewilllookatprovisioninganinstanceusingoneoftheUbuntuAMIs.Withknife,wecanspecifytheAMItouse,theavailabilityzonetotarget,andthesizeinstancetobecreated.Forexample,tocreateanm1.largesizeintheus-east-1eavailabilityzonewithUbuntu12.04.3LTS,wewouldneedtousetheAMIwithami-23447f4aasitsidentifier.
InordertodeterminetheAMIID,youwillneedtolookitupatthefollowingURL:
http://uec-images.ubuntu.com/
TipRememberthatwhendecidingwhichAMItouse,someoftheEC2instanceswillbe32bitandsome64bit;choosetheappropriateAMIbasedontheinstancetype,region,andstoragemethodyouwanttouse.
Theprogressofprovisioningcanbeseenusingthefollowingcommand:
$knifeec2servercreate-Iami-23447f4a-fm1.large-Zus-east-1e-N
<nodename>-xubuntu--sudo
Theoutputfromthepreviouscommandwillshowyoutheprogressoftheprovisioning(thismaytakeaminuteortwo,dependingontheregion,instancesize,howlongstatuscheckstake,andsoon):
[user]%knifeec2servercreate-Iami-23447f4a-fm1.large-Sec2-keypair
-Zus-east-1e–N<nodename>-xubuntu--sudo
InstanceID:i-0dfec92d
Flavor:m1.large
Image:ami-23447f4a
Region:us-east-1
AvailabilityZone:us-east-1e
SecurityGroups:default
Tags:Name:i-0dfec92d
SSHKey:ec2-keypair
Waitingforinstance…..............
PublicDNSName:ec2-54-80-59-97.compute-1.amazonaws.com
PublicIPAddress:54.80.59.97
PrivateDNSName:ip-10-157-31-234.ec2.internal
PrivateIPAddress:10.157.31.234
BootstrappingtheinstanceAsyoucansee,knifewilltellyouthepublicIPandpublicDNSnameofthenewinstancealongwiththeinstanceID,tags,andsoforth.Oncetheinstanceisprovisionedandisonline,itwillneedtobebootstrapped.Rememberthatbootstrappingwillinstallthe
ChefclientandregistertheinstancewiththeChefservice,whichwecandointhesamewaywebootstrapanyotherhost:
$knifebootstrap<instance-public-ip-address>-N<node-name>-xubuntu--
sudo
AsEC2provisionseachinstancewithanubuntuuserthathassudoprivileges,weprovidethebootstrapcommandwith–xubuntuand--sudotoensurewehavetherequiredprivilegestoperformthebootstrapping.Additionally,asyoumorethanlikelydonotwanttheAWS-providedDNSnameasthenodename,theChefnodenameissetthroughthe-N<node-name>commandlineflag.Oncethebootstrapstepisfinished,assumingthattherearenoerrors,verifythatyournewlyprovisionedhostislistedinyourchefservice:
$knifenodelist
TheoutputwillcontainyournewlybootstrappednodeID,asspecifiedbyyouinthecommandlineortheDNSname,ifyoudon’tspecifyanodename.YouhavenowprovisionedanewEC2instanceandregistereditwithyourChefservicewithonlytwocommands!
TerminatingtheinstanceOnceyouaredonewithtesting,youmaynotwanttoleavetheEC2instancerunning,asitwillincurcostsifitremainsidle.Toensurethisdoesn’thappen,performthefollowingfoursteps:
1. ListyourEC2instances2. DeletetheserverfromEC23. RemovetheserverfromChef4. VerifythattheinstancenolongerexistsinCheforEC2
TolistourEC2instances,usetheserverlistsubcommandoftheec2command,whichwilllistalloftheEC2instancesinthespecifiedregion.Ifyoudonotspecifyaregion,us-east-1isthedefaultregion.ThefullcommandtolistEC2serversisasfollows:
$knifeec2serverlist
Asanexample,executingthiscommandafterprovisioningthefirsthostwillshowatableofoneinstanceasfollows:
InstanceIDNamePublicIPPrivateIP
i-0dfec92di-0dfec92d54.80.59.9710.157.31.234
Formostknifecommands,youwillneedtheinstanceIDsotheprevioustablecanbetruncatedtofitinprint.
TipListingEC2nodeswillresultinatablethatcontainsallthecurrentlyprovisionedEC2instancesintheregionbymeansoftheEC2API,whichisseparatefromtheChefserviceAPI.ThismeansyouwillgetalistofalltheinstancesinEC2whetherornottheyare
registeredwithChef.ThefulltablewillcontainmostoftheinformationyoucanseeontheEC2controlpanel,includingthepublicandprivateIP,flavor,AMI,SSHkey,andsoon.
Deletinganinstanceisjustaseasyascreatingorlistingthem.Here,theserverdeletesubcommandisinvokedwiththeinstanceidentifiertobeterminated.ThiswillusetheEC2APItoissueaterminatecommand—thisisnotreversibleandsothecommandwillpromptyoutoensurethatyoureallydidwanttodeletetheinstance:
[user]%knifeec2serverdeletei-0dfec92d
InstanceID:i-0dfec92d
Flavor:m1.large
Image:ami-23447f4a
Region:us-east-1
AvailabilityZone:us-east-1e
SecurityGroups:default
SSHKey:ec2-keypair
RootDeviceType:instance-store
PublicDNSName:ec2-54-80-59-97.compute-1.amazonaws.com
PublicIPAddress:54.80.59.97
PrivateDNSName:ip-10-157-31-234.ec2.internal
PrivateIPAddress:10.157.31.234
Doyoureallywanttodeletethisserver?(Y/N)
WARNING:Deletedserveri-0dfec92d
WARNING:Correspondingnodeandclientforthei-0dfec92dserverwerenot
deletedandremainregisteredwiththeChefServer
RemovingtheChefnodeAtthispoint,theEC2instanceisbeingterminatedandremovedfromyouraccount.However,itisnotremovedfromtheChefservicethatneedstobedoneseparatelywiththenodedeletecommand.Here,theChefnodenameisspecified,nottheinstanceidentifier:
$knifenodedeletemy-first-ec2-instance
VerifythatthenodewasremovedfromChefusingnodelist:
$knifenodelist
TheoutputshouldshowyouthatyourEC2instanceisnolongerregisteredwithChef.
RackspaceCloudRackspaceCloudisanotherpopularcloud-computingproviderthatiswellsupportedbyChef.SimilartoEC2,thereisaknifepluginforRackspaceCloud:
geminstallknife-rackspace
InthesamewaythatAWSrequiresasetofcredentialstointeractwiththeAPItocreateandterminateinstances,RackspaceCloudhasitsownconfiguration.However,theRackspaceCloudAPIisalittlesimpler;youwillneedtoprovideknifewithyourRackspaceCloud’susernameandAPIkey.ForthosewhodonotalreadyhavetheirAPIkey,itcanbefoundinyourRackspaceCloudcontrolpanel.Theappropriateconfigurationtoaddtoyourknife.rbfileisasfollows:
knife[:rackspace_api_username]="YourRackspaceAPIusername"
knife[:rackspace_api_key]="YourRackspaceAPIKey"
Thisdatacanbehardcodedintoyourconfigurationfile,orsincetheknifeconfigurationfileisjustRuby,itcanbegeneratedbyevaluatingenvironmentvariablesorlookingatalocalfile.Thisisusefulifyouaresubmittingyourknife.rbfileintoasourcerepositorysothatcredentialsarenotleaked.
ProvisioninganinstanceRackspaceCloudserverprovisioningisjustasstraightforwardasitiswithEC2.Thereissomevariationinthecommand-lineoptionspassedtoknifebecauseofthewayRackspaceprovidesimagesforsystems.InsteadofusingtheinstancesizeandanAMI,youcanspecifytheflavorofthesystemtoprovision(thenode’sCPU,memory,anddiskallocation)andtheoperatingsystemtoimagetheinstancewith.Inordertodeterminewhatflavorsareavailable,theknife-rackspacepluginprovidestherackspaceflavorlistsubcommand:
$kniferackspaceflavorlist--rackspace-region=IAD
Asitispossiblethattherearedifferentcapacitiesindifferentregions,itisagoodideatocheckwhatisavailableintheregionwhereyouwanttoprovisionanode.Thiswillresultinalistofflavorsandtheirspecifications;asofnow,someofthecurrentofferingsinIADareasfollows:
IDNameVCPUsRAMDisk
2512MBStandardInstance151220GB
31GBStandardInstance1102440GB
42GBStandardInstance2204880GB
performance1-11GBPerformance1102420GB
performance1-22GBPerformance2204840GB
performance2-120120GBPerformance3212288040GB
performance2-1515GBPerformance41536040GB
Inadditiontoknowingwhichflavortoprovision,youneedanimageidentifier(similartoanAMI)toapplytothenewhost.Again,thislistmayvarywithregionandpossiblychangeovertimesothereisacommand,rackspaceimagelist,tolistthevarious
images:
$kniferackspaceimagelist--rackspace-region=IAD
Theoutputhereisquitelong,soithasbeensampledtoshowenoughtobeuseful:
IDName
ba293687-4af0-4ccb-99e5-097d83f72dfeArch2013.9
41e59c5f-530b-423c-86ec-13b23de49288CentOS6.5(PVHVM)
857d7d36-34f3-409f-8435-693e8797be8bDebian7(Wheezy)
896caae3-82f1-4b03-beaa-75fbdde27969Fedora18(SphericalCow)
fb624ffd-81c2-4217-8cd5-da32d32e85c4FreeBSD9.2
1705c794-5d7e-44d6-87da-596e3cf92144RedHatEnterpriseLinux6.5
df27d481-63a5-40ca-8920-3d132ed643d9Ubuntu13.10
d88188a5-1b02-4b37-8a91-7732e42348c1WindowsServer2008R2SP1
Asyoucansee,thereareanumberofLinux,BSD,andWindowsdistributionsavailabletoprovision.Inordertoprovisionanewhost,youwillusetheservercreatecommand,similartotheEC2command.Thefollowingknifecommandwillprovisiona512MBhostwithUbuntu13.10intheIADdatacenter:
$kniferackspaceservercreate-Idf27d481-63a5-40ca-8920-3d132ed643d9-f
2--rackspace-region=IAD
AssoonastheAPIrespondstotherequesttoprovisionanewhost,youwillseetheRackspacemetadataforthehost,suchastheinstanceID,name,flavor,andimage:
InstanceID:993d369f-b877-4f0f-be4b-cfc45c240654
Name:rs-21230044929009695
Flavor:512MBStandardInstance
Image:Ubuntu13.10(SaucySalamander)
Shortlyafterthis—oncethesystemhasbeenprovisioned,thenetworkinterfaceshavebeenconfigured,andtherootpasswordhasbeenset—theIPandrootpasswordwillbedisplayed:
PublicDNSName:162.209.104.248.rs-cloud.xip.io
PublicIPAddress:162.209.104.248
PrivateIPAddress:10.176.65.92
Password:yZ3D3Tck8uGm
AfterSSHbecomesavailable,knifewillinitiatetheprocessofbootstrappingthehost.Bydefault,knifewillusethechef-fulltemplate,whichwillinstallChefviatheomnibusinstallerfortheplatformyouarebootstrapping.Thiscanbealteredbyprovidingknifewiththe–dcommand-lineoption.Assumingthatthehostisbootstrappedproperly,thesystemdatawillbedisplayedonceagainforyourinformation:
InstanceID:993d369f-b877-4f0f-be4b-cfc45c240654
HostID:c478865ebb70032120024a9a2c8c65b9bb0913087991d4bab5acde00
Name:rs-21230044929009695
Flavor:512MBStandardInstance
Image:Ubuntu13.10(SaucySalamander)
PublicDNSName:162.209.104.248.rs-cloud.xip.io
PublicIPAddress:162.209.104.248
PrivateIPAddress:10.176.65.92
Password:yZ3D3Tck8uGm
Environment:_default
Oncethebootstrapstepisfinished,assumingthattherearenoerrors,verifythatyournewlyprovisionedhostislistedinyourchefservice:
$knifenodelist
TheoutputwillcontainyournewlybootstrappednodeIDasspecifiedbyyouinthecommandline(via-N)orthenamegeneratedbyRackspace(inthisexample,itwillbers-21230044929009695).Congratulations!YouhaveprovisionedanewRackspaceinstancewithasinglecommand.
TerminatinganinstanceOnceyouaredonewithtesting,youmaynotwanttoleavetheEC2instancerunning,asitwillincurcostsifitremainsidle.Toensurethisdoesn’thappen,performthefollowingfoursteps:
1. ListyourRackspaceservers.2. DeletetheserverfromRackspace.3. RemovetheserverfromChef.4. VerifythattheinstancenolongerexistsinCheforRackspace.
TolistyourRackspaceinstances,usetheserverlistsubcommandoftherackspacecommand,whichwilllistalloftheRackspaceinstancesinthespecifiedregion.SimilartotheoutputfromtheEC2serverlistcommand,theoutputwilllooklikethefollowing:
$kniferackspaceserverlist--rackspace-region=IAD
InstanceIDName
993d369f-b877-4f0f-be4b-cfc45c240654rs-21230044929009695
TipSimilartotheEC2output,theresultingtableistoowideforprintsoonlytheinstanceIDandnodenameisshown.YoushouldexpecttoseepublicandprivateIPaddresses,instancetypes,andsomeotherdatathatyouwillbeabletoseeontheRackspaceCloudcontrolpanelaswell.
Youcandeleteaninstanceusingasinglecommand;theserverdeletesubcommandisinvokedwiththeRackspaceinstanceidentifiertobeterminated.Rememberthatthisisnotreversible,sothecommandwillpromptyoutoensurethatyoureallydowanttodeletetheinstance:
$kniferackspaceserverdelete993d369f-b877-4f0f-be4b-cfc45c240654--
rackspace-region=IAD
InstanceID:993d369f-b877-4f0f-be4b-cfc45c240654
HostID:c478865ebb70032120024a9a2c8c65b9bb0913087991d4bab5acde00
Name:rs-21230044929009695
Flavor:512MBStandardInstance
Image:Ubuntu13.10(SaucySalamander)
PublicIPAddress:162.209.104.248
PrivateIPAddress:10.176.65.92
Doyoureallywanttodeletethisserver?(Y/N)y
WARNING:Deletedserver993d369f-b877-4f0f-be4b-cfc45c240654
WARNING:Correspondingnodeandclientforthe993d369f-b877-4f0f-be4b-
cfc45c240654serverwerenotdeletedandremainregisteredwiththeChef
Server
RemovingtheChefnodeAtthispoint,theEC2instanceisbeingterminatedandremovedfromyouraccount.However,itisnotremovedfromtheChefservice;thisneedstobedoneseparatelywiththenodedeletecommand.Here,theChefnodenameisspecified,nottheinstanceidentifier:
$knifenodedeleters-21230044929009695
VerifythatthenodewasremovedfromChefwithnodelist:
$knifenodelist
TheoutputshouldshowyouthatyourrecentlycreatedRackspaceinstanceisnolongerregisteredwithChef.
SummaryTheabilitytoscaleyourinfrastructurethroughacombinationofon-andoff-sitehostsisincrediblypowerful.Ifyouneedmorecapacity,youcaneasilybringupnewhostsonEC2,RackspaceCloud,oranysimilarplatform.Additionally,thesetechniquesapplytonotonlypubliccloudprovidersbutalsotoprivatecloudplatformssuchasVMWarevSphereandothers(provided,asuitablepluginforknifeexists).
Asyouhaveseen,withChefyoucanspinupandspindowntheservercapacitytomeetyourneedswithverylittleeffort.Onceyourinfrastructuremanagementisautomated,youcanfocusonhigherlevelproblemssuchasbuildingscalableservicesandscalingtomeetyourcustomers’demands.
Expandingonthiscapability,youcouldusethesetoolstoperformthefollowing:
ManuallyincreaseordecreasethecapacityinordertomatchthedemandWriteatooltoanalyzethecurrentresourceloadandreactaccordinglyPredictthefuturecapacityandscaleappropriatelyonagivenschedule
Nowthatwehavetheabilitytobringupsomehoststoworkwith,wecantakealookathowtoworkwithcookbookstolearnhowtheyworkandhowtobuildnewones.
Chapter4.WorkingwithCookbooksCookbooksareoneofthefundamentalcomponentsoftheChefsystem.Theyarecontainersforrecipes,providers,resources,templates,andallthelogicandinformationrequiredtomanageyourinfrastructure.Thischaptercoversthefollowing:
OrganizationofcookbooksBuildingcookbooksDevelopingrecipesHandlingmultipleplatformsforacookbookorganization
CookbooksareoneofthecorecomponentsofChef.Theyare,astheirnamesuggests,acollectionofrecipesandotherdatathatwhencombinedprovideaspecificsetoffunctionalitytoasystemadministrator.Ineachcookbook,youwillfindacollectionofdirectoriesandfilesthatdescribethecookbookanditsrecipesandfunctionality.Thecorecomponentsofacookbookareasfollows:
CookbookmetadataAttributesRecipesTemplatesDefinitionsResourcesProvidersRubylibrariesSupportfiles
Acookbookisacollectionoffilesanddirectorieswithawell-knownstructure.Noteverycookbookhasallofthesecomponents.Forexample,theremaybenoneedtodevelopcustomresourcesorprovidersinacookbookthatonlyusesChef-suppliedresources.However,everycookbookdoesneedtohaveametadatafilethatprovidesvariousbitsofinformationsuchasitsname,version,dependencies,andsupportedsystems.
Let’stakealookatthememcachedcookbookasitisareasonablysimplecookbookthatiscapableofinstallingandconfiguringthememcacheserver,whichisadistributedmemory-backedcacheservice:
Here,youcanseethatthiscookbookcontainsattributes,definitions,recipes,andtemplates,aswellasafilenamedmetadata.rb(themetadatafile)andaREADME.mdfile.Itisagoodideatoprovideexamplesofhowtouseyourcookbookandrecipesinsomesortofdocumentation,suchasaREADME.mdfile.Whenyoulookattheprecedingscreenshot,youwillseethatthedirectorynamesmaptothecomponentnamesandeachcontainssomefilesorsubdirectorieswithfiles.WewilldiscusstheorganizationofthespecificChefcomponentsingreaterdetailfurtheronaswediveintomoredetailsoneachtypelater.Fornow,itissufficienttoknowthatthedirectorystructureisdesignedtogrouptogetherfilesforeachtypeofcomponent.Alsonoticethat,asmentionedearlier,thiscookbookisanexampleofonethatdoesnothaveallthecomponents,astherearenonewresourcesorprovidersinthiscookbook.
SomeofthesefilesarepurelyinformationalandhavenoeffectonyourrecipesorChefitself,suchastheREADME.mdfile.Thisfile,andotherssuchasCHANGELOG,LICENSE,orDEVELOPMENTfiles,isincludedtoconveyinformationtoyouabouthowtoparticipate,license,orotherwiseusethecookbook.
Thereisalotofinformationthatisstoredinsideacookbook—thisinformationincludesthestepstotakeinordertoachieveadesiredeffectsuchastheinstallationofaserviceorprovisioningofusersonahost.Ahigh-leveloverviewofthecontentthatwewillbelearningaboutinthischapter,sothatyouhaveanideaofhowthecomponentsworktogetherbeforeyoulearnaboutthemindepth,isshownasfollows:
Attributes:Theseareattributesthatthecookbook’srecipesrelyon.Awell-definedcookbookshouldcontainsomesanedefaultsfortherecipessuchasinstallationdirectories,usernames,downloadableURLs,andversionnumbers.Anythingarecipeexpectsthenodetohavedefinedshouldbegivenadefaultvaluesothattherecipewillbehaveasexpected.Recipes:Rubyscriptsdefinetherecipesinthecookbook.Acookbookcancontainasfewasoneorasmanyrecipesasitsauthorwouldliketoputintoit.Mostpackage-specificcookbooksonlycontainafewrecipes,whilesomecookbooks,suchasyour
organization’sprivatecookbook,mayhavedozensofrecipesforinternaluse.Templates:TheseareRubyERBfilesthatareusedtodescribeanyfilethatneedstohavesomedynamicdatainit;often,theseareusedforstartupscriptsorconfigurationfiles.Resources:Thesedescribearesourcethatcanbeusedinarecipe.ResourcesareRubyscriptsthatuseChef’sresourcedomain-specificlanguage(DSL)todescribevariousactions,attributes,andotherpropertiesoftheresource.Providers:Thesedescribeanimplementationofaresource;inthecaseofthesupervisordcookbook,theserviceproviderfileoutlinestheactualimplementation-specificlogicoftheactionsthataresourcecanperform.Therearemanytypesofservicesthatyoucouldhave:supervisord,runit,monit,bluepill,andsoon.
Additionally,cookbooksmayincludeavarietyofsupportfilesthatarenotdirectlypartoftherecipes,suchasthefollowing:
Definitions:Theseareusedtobuildreusabletemplatesforresources.Perhapsyouwanttodefinethestructureofauseraccount,abackgroundworker,orarunnableprocess.Theseareawaytoprogrammaticallydescribewhattheselooklikeandimplementanylogictheymightneed.Rubylibraries:Anyreusablecodethatyourrecipesneedcanbeincludedinthecookbook.Thingsthatgoinhereareaccessiblebyyourrecipesandautomaticallyloadedforyou.SupportFiles:Thesearearbitrarydatafilesthatdon’tfallintoanyoftheothercategories.Tests:Recipes,composedofRubycode,canincludeunittestsorcucumberteststoverifythatthelogicworks.Notethatthesetestsareunittests,notintegrationtests;theyarenotdesignedtoensurethatyouhaveconfiguredyournodesproperlyorthattherearenoconflictsorotherissueswhenapplyingtheserecipes.
AttributesAttributesareChef’swayofstoringconfigurationdataandcanbethoughtofasalarge,butdisjointed,hashstructure.Chefpullsdatafromvariouslocationsandcombinesthatdatainaspecificordertoproducethefinalhashofattributes.Thisdataiscomputedwhenaclientrequestsitsrunlistfromtheserver(suchaswhenyouexecutechef-clientonanode).Thismechanismallowsyoutodescribedatawithahigherlevelofspecificityateachstepoftheprocess,decreasinginscopegoingfromthecookbookattributesfilesdowntonode-specificconfigurationdata.
Forexample,imagineyouaredeployingPostgreSQLontothehostsinyourinfrastructure.WithPostgreSQL,thereareaverylargenumberofconfigurationoptionsthatcanbetuned,rangingfromopenportsandnumberofconcurrentconnectionsdowntomemoryusedforkeycachesandotherfine-grainedconfigurationoptions.Thecookbook’sattributesfilesshouldprovideenoughconfigurationsforPostgreSQLtoworkwithoutmakinganymodificationtoahostandwithoutotherthingsbeingdeployed;also,theywouldmostlikelycontainaprettyvanillasetofconfigurationvalues,whichatahighlevelmightlooklikethefollowing:
InstallVersion9.3fromthesourcecodeListenonPort5432onIP0.0.0.0/0Storedatain/var/lib/postgresqlCreateanduseapgsqluser
Attributedatahasnotonlyanumberofsourcesthatitcanbepulledfrombutalsoasetofpriorities:default,normal,andoverride(inincreasingorder).Withineachlevel,dataispulledfromthecookbookattributesfilesandthenfromtheenvironment,role,andnodeconfigurationdatastoredintheChefserver(inthatorder).Combined,thisprovidesacomprehensivemechanismtodefineandcustomizethebehaviorofyourrecipesastheyareappliedtothenodes.
Now,asyoucanimagine,thisisfineforanumberofinstallationswheretheserverhasallofthespaceallocatedontherootmountpoint,ordoesn’thavesecurityrestrictionsaboutwhichIPaddressesshouldbelistenedon,andsoon.Itwouldbenicetobeabletosaythatinproduction,wewanttouseVersion9.3,butinatestenvironment,wewanttoinstallVersion9.4inordertoperformsometeststhatwedon’twanttoruninproduction.Wemayalsowanttospecifythatinproduction,ourhostsareEC2instanceswithacustomizedEBSRAIDforourPostgreSQLdataandsothedatashouldbestoredin/vol/ebs00/postgresql.Usingthismultilayeredapproachforconfigurationdata,thisisentirelypossible.
AttributefilescontainRubycodethatstorestheconfigurationdata.Inthiscase,toachieveourdescribeddefaultbehavior,wecouldhaveafile,attributes/default.rb,thatcontainsthefollowingtext:
default['postgresql']['port']="5432"
default['postgresql']['listen_address']="*"
default['postgresql']['data_dir']="/var/lib/postgresql"
default['postgresql']['install_method']="source"
default['postgresql']['version']="9.3"
ThehashthatthisdescribeswilllooklikethefollowingJSONdictionary:
'node':{
'postgresql':{
'port':'5432',
'listen_address':'*',
'data_dir':'/var/lib/postgresql',
'install_method':'source',
'version':'9.3'
}
}
Now,asdescribed,wewanttooverridetheversioninourstagingenvironmenttoinstallVersion9.4;thismeansthatinourstagingenvironment,theconfiguration(exactlyhowtomakethischangewillbediscussedlater)willneedtohavethefollowinginformation:
'node':{
'postgresql':{
'version':'9.4',
}
}
WhentheChefclientrunsonanodeinthestagingenvironment,theChefserverknowsthatthenodeisinthestagingenvironmentandwilltakethestageconfigurationaboveandoverlayitontopofthedefaultsspecifiedinthecookbook.Asaresult,thefinalconfigurationdictionarywilllooklikethefollowing:
'node':{
'postgresql':{
'port':'5432',
'listen_address':'*',
'data_dir':'/var/lib/postgresql',
'install_method':'source',
'version':'9.4'
}
}
Noticethattheversionhasbeenchanged,buteverythingelseremainsthesame.Inthisway,wecanbuildveryspecificconfigurationsforourhoststhatpullinformationfromavarietyofplaces.
ItisimportanttonotethatbecausetheseareinterpretedRubyscripts,theircontentscanrangefromsimpleattribute-settingstatementstocomplexlogicusedtodetermineanappropriatesetofdefaultattributes.However,it’sworthrememberingthatthemorecomplicatedyourconfigurationis,theharderitmaybetounderstand.
MultipleattributefilesChefloadsattributefilesinalphabeticalorderandcookbookstypicallycontainonlyoneattributefilenameddefault.rb.Insomecases,itmakessensetoseparatesomeoftheattributesintoseparatefiles,particularlywhentherearealotofthem.Asanexample,thecommunity-maintainedMySQLcookbookhastwoattributefiles:server.rbfortheserverattributesandclient.rbwithclient-specificattributes.Eachfilecontainsanywherebetween50and150linesofRubycode,soitmakessensetokeepthemseparateandfocused.
SupportingmultipleplatformsTherearetimeswhenasimpleattributesfiledoesn’tmakesense,sobeingabletodynamicallydefinethedefaultsisveryuseful.Consideramultiplatformcookbookthatneedstoknowwhichgrouptherootuserisin.Thenameofthegroupwillvaryaccordingtotheoperatingsystemoftheendhost.IfyouareprovisioningaFreeBSDorOpenBSDhost,thenthegroupfortherootwillbewheel,butonanUbuntumachine,thegroupisnamedadmin.TheattributesfilecanuseplainoldRubycodeoroptionalChef-providedconveniencemethodssuchasvalue_for_platform,whichisaglorifiedbutcompactswitchstatement:
default[:users]['root'][:group]=value_for_platform(
"openbsd"=>{"default"=>"wheel"},
"freebsd"=>{"default"=>"wheel"},
"ubuntu"=>{"default"=>"admin"},
"default"=>"root"
)
LoadingexternalattributesSometimesitisusefultoloadattributesfromanothercookbook;ifyourcookbookistightlycoupledtoanothercookbookorprovidessomeextendedfunctionality,itmaymakesensetousethem.Thiscanbeachievedintheattributesfilebyusingtheinclude_attributemethod(again,thisisaChef-specificconveniencemethod).
Let’sconsiderthatyouwanttoknowtheportthatApacheisconfiguredtouse.Youcouldusetheportkeyfromtheapacheconfigurationsection,butitisnotguaranteedthatitexists(itmaynothavebeendefinedortherecipethatcontainsitmaynothavebeenloadedyet).Toaddressthis,thefollowingcodewouldloadthesettingsfromattributes/default.rbinsideoftheapachecookbook:
include_attribute"apache"
default['mywebapp']['port']=node['apache']['port']
Ifyouneedtoloadanattributesfileotherthandefault.rb,sayclient.rb,insidethepostgresqlcookbook,youcanspecifyitinthefollowingmanner:
include_attribute"postgresql::client"
Makesurethatanycookbooksyourelyonarelistedasadependencyinyourcookbook’smetadata.Withoutthis,theChefserverwillhavenowayofknowingthatyourrecipesor
configurationdatadependonthatcookbook,andsoyourconfigurationmayfailasaresultofthis.
UsingattributesOnceyouhavedefinedyourattributes,theyareaccessibleinourrecipesusingthenodehash.Chefwillcomputetheattributesintheorderdiscussedandproduceonelargehash,whichyouwillhaveaccessto.
TipChefusesaspecialtypeofhash,calledaMash.Mashesarehasheswithwhatisknownasindifferentaccess—stringkeysandsymbolkeysaretreatedasthesame,sonode[:key]isthesameasnode["key"]).
IfwehadthePostgreSQLattributesanduserattributesasspecifiedpreviously,withoutanyoverrides,thentheresultingconfigurationwilllooklikethefollowing:
'node':{
'postgresql':{
'port':'5432',
'listen_address':'*',
'data_dir':'/var/lib/postgresql',
'install_method':'source',
'version':'9.3'
},
'users':{
'root':{'group'=>'wheel'},
}
}
Thisdatacouldthenbeaccessedanywhereinourrecipesortemplatesthroughvariablessuchasnode[:postgresql][port]ornode[:users][:root][:group].Rememberthatthefinalversionofthenode’sconfigurationdataisdeterminedatthetimetheclientmakestherequestforitsconfiguration.ThismeansthatChefgeneratesasnapshotofthecurrentstateofthesystem,collapsedaccordingtoitsrulesofprecedence,forthatnodeandpassesittothehosttoperformitsoperations.
MetadataEachcookbookcontainsametadata.rbfileintherootdirectoryofthecookbookthatcontainsinformationaboutthecookbookitself,suchaswhomaintainsit,thelicense,version,containedrecipes,supportedplatforms,andthecookbook’sdependencies.ThecontentsofthisscriptareusedtogenerateaJSONfilethatdescribesthecookbook,whichisusedbytheChefserverfordependencyresolution,searchingandimportingintorunlists.
Thisisarequiredfileforacookbook,andhereisanexamplemetadata.rbfilefromthePostgreSQLdatabaseserver,whichisslightlymodifiedtofitthefollowing:
name"postgresql"
maintainer"Opscode,Inc."
maintainer_email"[email protected]"
license"Apache2.0"
description"InstallsandconfiguresPostgreSQL"
long_descriptionIO.read(File.join(
File.dirname(__FILE__),'README.md'
))
version"3.3.4"
recipe"postgresql","Includespostgresql::client"
recipe"postgresql::ruby","InstallsRubybindings"
recipe"postgresql::client",
"Installsclientpackage(s)"
recipe"postgresql::server","Installsserverpackages"
recipe"postgresql::server_redhat",
"InstallsRedHatserverpackages"
recipe"postgresql::server_debian",
"InstallsDebianserverpackages"
%w{ubuntudebianfedorasuseamazon}.eachdo|os|
supportsos
end
%w{redhatcentosscientificoracle}.eachdo|el|
supportsel,">=6.0"
end
depends"apt"
depends"build-essential"
depends"openssl"
Becausethemetadata.rbfileisaRubyscript,itallowsyoutousearbitraryRubycodeinsideofit.Here,forexample,thelong_descriptionentryisgeneratedprogrammaticallybyreadinginthecontentsofthesuppliedREADME.mdfile:
long_descriptionIO.read(File.join(File.dirname(__FILE__),
'README.md'))
Here,thePostgreSQLcookbooksupportsmultipleplatforms,soinsteadofwritingeachplatformthatissupportedonalineofitsown,youcouldusealoopsimilartotheoneusedinthemetadata.rbfile:
%w{ubuntudebianfedorasuseamazon}.eachdo|os|
supportsos
end
Additionally,ifitonlysupportscertainplatformswithaminimumversion,youcouldwritesomethingsimilartothefollowing,whichdeclaressupportforRedHat-baseddistributionsgreaterthan(orequalto)Version6.0:
%w{redhatcentosscientificoracle}.eachdo|el|
supportsel,">=6.0"
end
Inthiscookbook,thedependenciesarelistedlinebylinebutcouldberepresentedsimilarlyifyouhavealargenumber:
depends"apt"
depends"build-essential"
depends"openssl"
Dependenciescouldalsoberewrittenasfollows:
%w{aptbuild-essentialopenssl}.eachdo|dep|
dependsdep
end
Obviously,inthiscase,youaren’tsavinganyroom;however,ifyouhadtenormoredependencies,itcouldmakeitmorecompact.
AslongasyourRubycodeproducessomethingthatisacompatibleargumentorconfiguration,youcanbeascleverasyouwant.Takeadvantageofyourabilitytodynamicallygenerateaconfiguration.
RecipesRecipesarethecorecomponentofgettingthingsdone.Theyarescripts,writteninRuby,thatprovidetheinstructionstobeexecutedonendhostswhentheChefclientisrun.Recipesareplacedintherecipesdirectoryinsideofacookbook,andeachrecipeisdesignedtoachieveaspecificpurpose,suchasprovisioningaccounts,installingandconfiguringadatabaseserver,andcustomsoftwaredeployments.
Recipescombineconfigurationdatawiththecurrentstateofthehosttoexecutecommandsthatwillcausethesystemtoenteranewstate.Forexample,aPostgreSQLdatabaseserverrecipewouldhavethegoalofinstallingandstartingaPostgreSQLserveronanyhostthatrunstherecipe.Let’slookatafewpossiblestartingstatesandtheexpectedbehavior:
AhostwithoutPostgreSQLinstalledwouldbeginatthestateofnothavingtheservice;then,itwillexecutethecommandsrequiredtoinstallandconfiguretheserviceHostswithanexistingbutoutdatedPostgreSQLservicewouldbeupgradedtothelatestversionofthedatabaseserverHostswithacurrentinstallationofPostgreSQLwouldhaveitsPostgreSQLinstallationuntouchedInallcases,theconfigurationonthediskwouldbeupdatedtomatchtheconfigurationstoredintheChefserver
Toachievethesegoals,recipesuseacombinationofresources,configurationdata,andRubycodetodeterminewhattoexecuteontheendhost.Eachhost-levelresource—files,configurationfiles,packages,users,andsoon—ismappedtoaChefresourceinarecipe.Forexample,considertherecipethatwesawearlierinthebookthatwasusedtodemonstratethattheChef-soloinstallationwasfunctional:
file"#{ENV['HOME']}/example.txt"do
action:create
content"Greetings#{ENV['USER']}!"
end
Thisisacompleterecipe;ithasonesteptocreateafile,andthatisallitdoes.Thefilebeingcreatedontheendhostneedsaname;here,itwillbenamedENV['HOME']/example.txt,whichisRuby’swayofrepresenting$HOME/example.txt.Inadditiontoaname,weareinstructingCheftocreatethefile(wecouldjustaseasilyinstructCheftodeletethefile)andtoputthecontentsGreetings$USERintothefile,replacingwhatisinthere.
Wecouldextendourrecipetoensurethataspecificuserexistedonthehostandcreateafilewiththeownersettothenewlycreateduser:
user"smith"do
action:create
systemtrue
comment"AgentSmith"
uid1000
gid"agents"
home"/home/matrix"
shell"/bin/zsh"
end
file"/tmp/agent.txt"do
action:create
content"Hellofromsmith!"
owner"smith"
group"agents"
mode"0660"
end
Eachrecipeisascriptthatisrunfrombeginningtoend,assumingthatnothingcausesittoabort.Also,eachrecipecanaccessthenode’sattributedataandleverageresourcestocompiletemplates,createdirectories,installpackages,executecommands,downloadfiles,anddojustaboutanythingthatyoucandofromashellasanadministrator.Ifyoucan’taccomplishwhatyouneedtodousingtheexistingChefresources,youcaneithercreateyourowncustomresources,oryoucanexecuteanarbitraryuser-definedshellscript.
ResourcesResourcesareprogrammaticbuildingblocksinChef;theyareadeclarativemechanismtomanipulatearesourceonahost.Resourcesdeliberatelyhidetheunderlyingimplementationthatislefttoaprovider.Itisimportanttorecognizethataresourcedescribeswhatisbeingmanipulated,nothowitisbeingmanipulated;thisisbydesign,asitprovidesahighlevelofabstractionforChefrecipestobeasplatform-neutralaspossible.
Forexample,Chefhasbuilt-inresourcesthatincludethefollowing:
CronjobsDeploymentsFilesystemcomponents(mountpoints,filesanddirectories,andsoon)Sourcecoderepositories(Gitandsvn)LogsPowerShellscripts(Windowstargets)ShellscriptsTemplatesPackagesUsersandgroups
Resourcescombinedwithproviders(discussedshortly)arecollectivelyreferredtoasLWRPsorlightweightresourceproviders;theymakeupalargeportionofthefunctionalitywithinaChefrecipe.
Resourcesarecomposedofaresourcename(packagename,filepath,servicename,andsoon),anaction,andsomeattributesthatdescribethatresource.
UsingresourcesResources,aswehaveseeninsomeexamples,takethefollowingform:
resource_name<nameparameter><ruby_block>
Intheprecedingcode,resource_nameistheregisterednameoftheresource,suchasfile,package,anduser.Thenameparameterisaspecialargumenttotheresourcethatgivesthisresourceauniquename.Thisisoftenalsousedbytheresourceasadefaultvalueforanattributethatnaturallymapstothenameoftheresourcesuchasfilename,username,andpackagename(youcanseeapatternhere);however,youcanuseanarbitrarynameattributeandmanuallysettheattribute.TheRubyblockisjustablockofcodeinRuby;thisishowChef’sDSLworks.InChef,eachresourceexpectssomespecificthingsinsideitsconfigurationblock.Youwillfindthatmanyresourceshavedifferentexpectations,butingeneral,aresourceblockinarecipewillbeofthefollowingform:
resource_name"nameattribute"do
attribute"value"
attribute"value"
end
Thepreviousexample,whichcreatedanewuser,wasthefollowing:
user"smith"do
action:create
systemtrue
comment"AgentSmith"
uid1000
gid"agents"
home"/home/matrix"
shell"/bin/zsh"
end
Here,theresourcenameisuser,thenameattributeissmith,andtheblockofcodebeingpassedtotheresourcehassevenattributes:action,system,comment,uid,gid,home,andshell.Eachoftheseattributeshasavalueassociatedwithit;internally,theRubycodefortheresourcewillstoretheseinsomevariablestobeusedwhenmanipulatingthespecifiedresource.Inthiscase,constructingauserontheendhostthroughthecorrectproviderwillbehelpful.
Oneoftheseattributes,action,isabitunique;itsjobistotelltheresourcewhatactiontotakeontheresource.Thelistofavailableactionswillbedifferentwitheachresource,buttypically,aresourcewillhaveactionssuchascreate,delete,orupdate.Havealookatthedocumentationfortheresourceyouareworkingwith;thedocumentationwilldescribetheavailableactionsandwhattheydoseparatelyfromtheotherattributes.
Todemonstratehowthenameattributeisusedasadefaultvaluefortheuserresource,thefollowingrecipehasthesamebehaviorasthepreviousone,buthasoneminorchange:
user"agent_smith"do
username"smith"
action:create
systemtrue
comment"AgentSmith"
uid1000
gid"agents"
home"/home/matrix"
shell"/bin/zsh"
end
Here,youcanseethatanadditionalattribute,username,hasbeenaddedtotheresourceblockwiththevaluethatwaspreviouslyinthenameattribute.Additionally,thenameattributehasbeenchangedto"agent_smith".Ifyouweretoexecutethisrecipeorthepreviousexample,itwouldhavethesameeffect:tocreatealocalsystemuser,smith,withtheUID,GID,home,andotherattributesspecified.
OverridingadefaultbehaviorInadditiontoproperties,resourcesalsohaveadefaultaction.Moreoftenthannot,thedefaultactioniscreate,butagain,youwillwanttoconsultthedocumentationfortheresourceyouareworkingwithtomakesurethatyouknowwhatthedefaultbehaviorisforaresource.Youdon’twanttoaccidentallydestroysomethingyouthoughtyouwerecreating!
Aconcreteexamplemightbeinstallingthetcpdumppackageonyoursystem.Toinstallthedefaultversionwithnocustomization,youcouldusearesourcedescriptionsuchasthefollowing:
package"tcpdump"
Thisworksbecausethedefaultactionofthepackageresourceistoperformaninstallation.Ifyoulookatthesourcecodeforthepackageresource,youwillseethefollowingatthebeginningoftheconstructor:
definitialize(name,run_context=nil)
super
@action=:install
@allowed_actions.push(:install,:upgrade,
:remove,:purge,:reconfig)
@candidate_version=nil
@options=nil
@package_name=name
Thistellsusthatthedefaultaction,ifunspecified,istoinstallthepackageandtousethenameattributeasthepackagename.So,theprevioussimpleone-lineresourceisthesameaswritingoutthefollowingblock:
package"tcpdump"do
action:install
package_name"tcpdump"
end
Here,however,package_namewilldefaulttothenameattribute,sowedonotneedtoprovideitiftheresourcenameisthesameasthepackageyouwishtoinstall.Additionally,ifyouwantedtobemoreverbosewithyourresourcedescriptionandinstallaspecificversionofthetcpdumppackage,youcouldrewritethepackageresourcetolooksomethinglikethefollowing:
package"tcpdump"do
action:install
version"X.Y.Z"
end
Ifyoureadthedocumentationforthepackageresourceorexaminethefullconstructorforthepackageclass,youwillseethatithasanumberofotherattributesaswellaswhattheydoandwheretheirdefaultvaluescomefrom.Alltheresourcesfollowthisform;theyaresimplyRubyclassesthathaveanexpectedstructure,whichtheyinheritfromthebaseresourceclass.
TemplatesChefdynamicallyconfiguressoftwareandhosts,andalargepartofconfiguringUNIX-basedsystemsandsoftwareinvolvesconfigurationfiles.Chefprovidesastraightforwardmechanismtogenerateconfigurationfilesthatmakeiteasytocombineconfigurationdatawithtemplatefilestoproducethefinalconfigurationfilesonhosts.ThesetemplatesarestoredinthetemplatesdirectoryinsideofacookbookandusetheERBtemplatelanguage,whichisapopularandeasy-to-useRuby-basedtemplatelanguage.
Whyusetemplates?Withouttemplates,yourmechanismtogenerateconfigurationfileswouldprobablylooksomethinglikethis:
File.open(local_filename,'w')do|f|
f.write("<VirtualHost*:#{node['app]['port']}")
...
f.write("</VirtualHost>")
end
Thisshouldbeavoidedforanumberofreasons.First,writingconfigurationdatathiswaywouldmostlikelymakeyourrecipeveryclutteredandlengthy.Secondly,andmoreimportantly,itviolatesChef’sdeclarativenature.Bydesign,Chefprovidesyouwiththetoolstodescribewhattherecipeisdoingandnothowitisdoingit,whichmakesreadingandwritingrecipesmucheasier.Simplerrecipesmakeforsimplerconfiguration,andsimplerconfigurationscalesbetterbecauseitiseasiertocomprehend.Handrollingaconfigurationfileistheoppositeapproach;itveryspecificallydictateshowtogeneratethefiledata.Considerthefollowingcode:
config_file="#{node['postgresql']['dir']}/postgresql.conf"
pgconfig=node[:postgresql]
File.open(config_file'w')do|f|
f.write("port=#{pgconfig[:port]}")
f.write("data_dir=#{pgconfig[:data_dir]}")
f.write("listen_address=#{pgconfig[:listen_address]}")
end
File.chown(100,100,config_file)
File.chmod(0600,config_file)
ThiscodegeneratesaPostgreSQLconfigurationfilefromtheattributehash,onelineatatime.Thisisnotonlytime-consumingandhardtoreadbutpotentiallyveryerror-prone.Youcanimagine,evenifyouhavenotpreviouslyconfiguredanyPostgreSQLservers,justhowmanyf.write(...)statementscouldbeinvolvedingeneratingafullpostgresql.conffilebyhand.Contrastthatwiththefollowingblockthatleveragesthebuilt-intemplateresource:
template"#{node['postgresql']['dir']}/postgresql.conf"do
source"postgresql.conf.erb"
owner"postgres"
group"postgres"
mode0600
end
Theprecedingblockcouldbecombinedwithatemplatefilethatcontainsthefollowingcontent:
<%node['postgresql']sort.eachdo|key,value|%>
<%nextifvalue.nil?-%>
<%=key%>=<%=
casevalue
whenString
"'#{value}'"
whenTrueClass
'on'
whenFalseClass
'off'
else
value
end
%>
<%end%>
Ifwetakeourtemplateandthenapplythefollowingattributedataaswehadshownpreviously,thenwewouldhavegeneratedtheexactsameconfigurationfile:
'node':{
'postgresql':{
'port':'5432',
'listen_address':'*',
'data_dir':'/var/lib/postgresql',
'install_method':'source',
'version':'9.3'
},
'users':{
'root':{'group'=>'wheel'},
}
}
Onlynowwecanuseatemplatethatishighlyflexible.Ourtemplateusesthekey-valuecombinationsstoredintheconfigurationhashtodynamicallygeneratethepostgresql.conffilewithoutbeingchangedeverytimeanewconfigurationoptionisaddedtoPostgreSQL.
ChefusesERB,atemplatelanguage,thatisprovidedbythecoreRubylibrary.ERBiswidelyavailableandrequiresnoextradependencies;itsupportsRubyinsideoftemplatesaswellassomeERB-specifictemplatemarkup.
AquickERBprimerAsERBisverywelldocumentedandwidelyused,thisportionofthechapterservesonlyasaquickreferencetosomeofthemostcommonlyusedERBmechanisms.Formoreinformation,seetheofficialRubydocumentationathttp://ruby-doc.org/stdlib-2.1.1/libdoc/erb/rdoc/ERB.html.
ExecutingRuby
ToexecutesomearbitraryRubycode,youcanusethe<%%>container.The<%partindicatesthebeginningoftheRubycode,and%>indicatestheendoftheblock.Theblockcanspanmultiplelinesorjustonesingleline.Examplesofthisareasfollows:
ERBcode Output
<%
[1,2,3].eachdo|index|
putsindex
end
1
2
%> 3
<%users.collect{|u|
putsu.full_name}%>
BobSmith
SallyFlamingo
YoucanmixRubyandnon-Rubycode(usefultorepeatblocksofnon-Rubytext)asfollows:
<%[1,2,3].eachdo|value|%>
Non-rubytext…
<%end%>
Thiswouldyieldthefollowing:
Non-rubytext…
Non-rubytext…
Non-rubytext…
Variablereplacement
ERBhasasyntaxtoreplaceasectionofthetemplatewiththeresultsofsomeRubycoderatherthanrelyingonprintstatementsinsideyourRuby.Thatcontainerissimilartothelastone,withtheadditionoftheequalsigninsidetheopeningtag.Itlookslike<%=%>.AnyvalidRubycodeisacceptableinsidethisblock,andtheresultofthiscodeisputintothetemplateinplaceoftheblock.Examplesofthisareasfollows:
<%=@somevariable%>
<%=hash[:key]+otherhash[:other_key]%>
<%=array.join(",")%>
Thiscanbecombinedwiththepreviousexampletoproducecomplexoutput:
<%[1,2,3].eachdo|value|%>
Thevaluecurrentlyis<%=value%>
<%end%>
Thiswouldyieldthefollowing:
Thevaluecurrentlyis1
Thevaluecurrentlyis2
Thevaluecurrentlyis3
UsingjustthesebasicfeaturesofERB,combinedwithChef’sconfigurationdata,youcanmodeljustaboutanyconfigurationfileyoucanimagine.
ThetemplateresourceChefprovidesatemplateresourcetogeneratefilesviatemplates.Therearethreekeyattributesofthetemplateresource,whichareasfollows:
path:Thisspecifieswheretoputthegeneratedfilesource:Thistellstheresourcewhichtemplatefiletousevariables:Thisspecifieswhatdatatopopulatethetemplatewith
Thepathattributeusesthenameattributeasitsdefaultvalueandpopulatesthetemplatespecifiedbyasourcefilewiththedatapassedtoitthroughthevariablesattribute.Templatesarecontainedinsideofthetemplatesdirectory,whichisplacedinsideofacookbook;ifasourceisnotspecified,itwillbeexpectedthatafileexistsinsidethedirectorywiththesamenameasthepath,whichisonlyrootedinthetemplatesdirectorywitha.erbextension.Hereisasimpletemplateresourceexample:
template"/etc/motd"do
variables:users=>["Bart","Homer","Marge"]
end
Thisresourcewillexpectthatafileexistsinthetemplate’ssearchpath(moreonhowthatisdeterminedinabit)asetc/motd.erb,anditthenexposesanarrayofthreestringsasausersvariableandwritestheresultsoutas/etc/motdonthehost.ThecorrespondingMOTDtemplatecouldlooklikethefollowing:
Welcometocrabapple.mydomain.com!Ournewestusersare:
<%@users.eachdo|user|%>
*<%=user%>
<%end%>
ThetemplatevariablesTherearetwoprimarysourcesofdataforatemplate:datapassedexplicitlythroughtheresourceblockattributesandnodeconfigurationdata.Explicitvariablesareuserdefinedintherecipeandmaybeusedtooverridesomesettingsorpassinconfigurationthatisdynamicallygeneratedinsidetherecipe.ThenodeconfigurationdataiscomputedbyChefatruntimeandrepresentsasnapshotofthecurrentconfigurationthatwillbeappliedtothenode.
PassingvariablestoatemplateSometimesyouwillneedtopassdatatoatemplatefrominsideyourrecipeinsteadofrelyingontheglobalnodeattributes.Perhapsyouhavesomelogicthatcomputessomevariabledata,butitdoesn’tbelonginthenodehash;Chefsupportsdoingjustthisinthetemplateresource.ThedatapassedexplicitlyisavailabletotheERBtemplateasaninstancevariable,[email protected],considerthefollowingrecipesnippet:
config_hash={
:food=>"asparagus",
:color=>"blue"
}
template"/etc/myapp.conf"do
source"myapp.conf.erb"
owner"root"
mode"0664"
variables(
:install_path=>"/opt/#{hostname}/myapp",
:config=>config_hash
)
end
The:install_pathand:configkeysareaccessibleinthetemplateasinstancevariableswiththesamename.Theywillbeprefixedbythe@characterandcouldbeusedinatemplatesimilartothefollowing:
database_path="<%=@install_path%>/db"
storage_path="<%=@install_path%>/storage"
<%config.eachdo|key,value|%>
<%=key%>="<%=value%>"
<%end%>
Here,thetemplateexpectsaspecifickey,install_path,todeterminewheretostorethedatabase;thekey-valuehashspecifiedbyconfigisthenusedtogeneratesomearbitraryconfigurationsettingsinthetemplate.
AccessingcomputedconfigurationsInadditiontodatapassedviathevariablesattribute,atemplatecanaccessanode’scomputedconfigurationdatathroughthenodelocalvariable.ThisisaccessedasaRuby
hash,whichwillbestructuredsimilarlytoadictionaryorahashinanyotherlanguage.InourpreviousPostgreSQLattribute’sdataexample,thefollowingvaluesweredefined:
default['postgresql']['port']="5432"
default['postgresql']['listen_address']="*"
Evenifnootherconfigurationdatasupersedestheseconfigurationvalues,therewillbeapostgresqlkeyinthenode’sconfigurationdatathatcontainsthekey’sportandlisten_address.Usingthisinformation,wecanwritearecipethatusesatemplateresourceandamatchingtemplatelikethefollowing:
template"/etc/postgresql/postgresql.conf"do
source"postgresql.conf.erb"
owner"psql"
mode"0600"
end
listen_addresses='<%=node[:postgresql][:listen_address]'
port=<%=node[:postgresql][:port]%>
Whenthedefaultattributesdataiscombinedwiththeexampletemplate,theresulting/etc/postgresql/postgresql.conffilewillhavethefollowingcontent:
listen_addresses='*'
post=5432
Aspreviouslydiscussed,thecomputedattributeshashforagivennodecomesfromavarietyofsources.Thosesourcesincludeattributesfilesinthecookbook,role,environment,andnode-levelconfigurationvaluesstoredinChef,eachwithitsownlevelofprecedence.
SearchingfortemplatesAsyouhavelikelynoticed,Chefattemptstoallowyouasmuch,oraslittle,specificityasyouwantwhendefiningyourconfiguration,andtemplatesarenodifferent.Justasthefinalnodeconfigurationiscomputedfromavarietyoflocations,thetemplatesdirectoryhasaspecificsearchorder.Thisallowstheauthorofthecookbooktoprovideasetofdefaulttemplatesaswellassupportplatformandhost-specificoverrides.
Thedefaulttemplatedirectoryshouldbeusedtoprovidedefaultversionsofthetemplates.Anyplatform-orhost-specificdirectoriesareplacedalongsideitandwillbeusedwhenappropriate.Thesearchorderforatemplateisasfollows:
HostnameDistributionversionDistributionDefaultlocation
Asanexample,let’sconsiderascenarioinwhichweappliedarecipewiththepostgresql.conf.erbtemplateresourcetoanode,db1.production.mycorp.com,whichisrunningDebian6.0.Chefwillthenlookforthefollowingfilesinsideofthetemplatesdirectory:
host-db1.production.mycorp.com/postgresql.conf.erb
debian-6.0/postgresql.conf.erb
debian/postgresql.conf.erb
default/postgresql.conf.erb
Thesearchisperformedinthatorderwiththefirstmatchbeingthechosentemplate,applyingthehighestlevelofspecificitybeforethelowest(asisthepatternwithotherChefmechanisms,includingconfigurationdata).
Thisdifferentiationofconfigurationfilesbyhost,platform,andevenversionisveryuseful.Itallowsyoutoprovideasanesetofdefaultswhilesupportinghost-orsystem-specificquirkssimultaneously.
DefinitionsSometimes,youfindthatyouarecreatingsomethingrepeatedlyand,similartoaconfigurationtemplate,youneedatemplatetogenerateobjectsofagiventype.SomeexamplesofthismightbeApachevirtualhosts,aspecifictypeofapplication,oranythingelsethatisrepeatedalot.Thisiswheredefinitionscomein,andtheyarestoredinthedefinitionsdirectoryinsideofacookbook.
Definitionsareloadedandavailableasnamedresourcesjustasotherresourcessuchaspackages,files,andsoonare;theonlydifferenceisthatthereisnoprovider.Youcanthinkofthemasresourcesandprovidersallinone.Subsequently,theyaremuchmorerigidandlimitedinscopethananormalresourcewouldbe.HereisanexampledefinitiontoinstallPythonlibrariesusingpipandarequirements.txtfile:
define:pip_requirements,:action=>:rundo
name=params[:name]
requirements_file=params[:requirements_file]
pip=params[:pip]
user=params[:user]
group=params[:group]
ifparams[:action]==:run
script"pip_install_#{name}"do
interpreter"bash"
user"#{user}"
group"#{group}"
code<<-EOH
#{pip}install-r#{requirements_file}
EOH
only_if{File.exists?("#{requirements_file}")andFile.exists?("#
{pip}")}
end
end
end
Here,wearedeclaringanewtypeofdefinition,apip_requirementsobject.Thislooksandbehavessimilarlytoaresource,exceptthatitismuchsimpler(andlessflexible)thanaresource.Ithassomeattributes,whichareloadedviathespecialparamsargument,andcontainsalittlebitoflogicwrappedaroundascriptresource.Let’stakealookathowitwouldbeusedandthenseehowitworks:
pip_requirements"my_requirements"do
pip"#{virtualenv}/bin/pip"
usernode[:app][:user]
groupnode[:app][:group]
requirements_file"#{node[:app][:src_dir]}/requirements.txt"
end
Hereyouseewhatlookslikearesource,butisinfactadefinition.Asmentionedearlier,theselookverysimilarbecausetheybehavealike.However,youmusthavelikelynoticedthatthedefinitionofpip_requirementsitselfdidnothaveanysortofabstraction;thereisnopluggableprovider,novalidation,itdoesn’tsubclasstheResourceclass,amongother
differences.Definitionsprovideyouwithamechanismtodeclarereusablechunksofcodethatyourrecipeswouldotherwiseduplicatesothatyourrecipecanagaindescribethewhat,notthehow.
Thepreviousexampletellsusthatwehaveapip_requirementsobjectandthatwewanttopasssomeparameterstoit,namely,thepathtopip,theuserandgrouptorunpipas,andtherequirements.txtfiletoload.Thesearebroughtintothedefinitionthroughtheparamsargumentandcanbeaccessedasanyothervariabledata.Inthiscase,thedefinitionsaystorunbashasthespecifieduserandgroupandthatthescriptshouldruntheequivalentofthefollowing:
pipinstall-r/path/to/requirements.txt
Thiswillhappenonlyifpipandthe/path/to/requirements.txtfileexist(asindicatedbytheonly_ifguard).Bycreatingsuchadefinition,itcanbereusedanytimeyouneedtoinstallPythonmodulesfromaspecificrequirements.txtfileonyourhost.
RecipesRecipesarewhereallthemagichappenswithChef;theyarethesecretsauce,themanbehindthemask.TheyaretheworkhorsesofconfiguringhostswithChef.RecipesarescriptswritteninRubyusingChef’sDSLthatcontaintheinstructionstobeexecutedonendhostswhentheChefclientisrun.Everytimetheclientisexecutedontheendhost,afewthingshappen:
1. TheendhostmakesarequesttotheChefserversaying,“Ineedtodosomework”.2. TheChefserverlooksattherequestinghost’sidentityanddetermines:
Whichrecipesneedtoberunandinwhatorder(therunlist)Thecomputedconfigurationdataforthathost
3. Thisinformationispassedbacktotheendhostalongwiththenecessaryartifactsitneeds(recipes,templates,andsoon).
4. Theclientthencombinestheconfigurationdatawiththerecipesandbeginstoexecuteitsrunlist.
DevelopingrecipesAsadeveloper,youwillbeplacingyourrecipesinsidetherecipesdirectoryofyourcookbook.Eachrecipeisdesignedtoperformaspecificactionorsetofactionstoachieveagoalsuchasprovisioningaccounts,installingandconfiguringadatabaseserver,customsoftwaredeployments,orjustaboutanyotheractionthatyoucouldperformonaserver.
Akeyconceptwhendevelopingrecipesisthattheyshouldbeidempotent.Forthoseunfamiliarwiththeterm,anidempotentoperationisanoperationthatcanbeappliedmultipletimesandhavethesameoutcome.Considerthefollowingrecipe:
user"smith"do
action:create
systemtrue
comment"AgentSmith"
uid1000
gid"agents"
home"/home/matrix"
shell"/bin/zsh"
end
Onewouldexpect,fromlookingatthisrecipe,thatChefshouldbeabletoexecuteitonce,fivetimes,oronethousandtimes,anditwouldhavethesameeffectastheinitialapplicationoftherecipe.Therewouldnotbefiveoronethousandusersonthehostwiththeloginnamesmith;therewouldbeonlyonesingleuserwiththeloginnamesmith.Also,inalltheruns,itwouldbeconstructedwiththesameUID1000,thesamegroupname,andsoon.
Similarly,givenaparticularstateofthesystemandassumingnothinghaschangedinbetweenruns,subsequentclientexecutionsshouldproducethesame,consistentendingstate.Inshort,theChefclientshouldbeabletoruntwotimesinarow,andiftheconfigurationhasnotbeenupdated,thesystemshouldlookexactlythesameafterthesecondrunasafterthefirstrun.
Recipesuseprovidedconfigurationdataalongwiththecurrentstateofthehosttodeterminetheflowandactionstakenbythescript.Theexecutionofarecipewilltakethesystemfromitsinitialstate,Sintial,toitsnewstate,Sfinal.Well-writtenrecipesshouldbeidempotentsuchthatifthey’reexecutedimmediatelyafterwardsanynumberoftimeswithnoconfigurationorstatchanges,thenthesystemshouldgofromSfinaltothesameSfinalwithnonewchangestothesystem.Thisallowsyoutokeepyoursystemsinaconsistentstateatalltimes,assumingthatnothinggoeswrongduringtheexecutionofthoseoperations;ifsomethingdoesgowrong,youshouldbeabletoreverttoapreviouslyknowngoodstate.
WritingrecipesAsyouhavealreadyseen,cookbooksprovideawaytocombinerelevantpiecesofconfigurationdatasuchasattributes,templates,resources,providers,anddefinitionsinoneplace.Theonlyreasonthesecomponentsexististosupportourrecipes.Recipescombineresourcesinacertainordertoproducethedesiredoutcome;muchinthesamewayachefwouldcombineingredientsaccordingtohisorherrecipetoproducesomedeliciousfood.Byputtingalloftheseresourcestogether,wecanbuildourownrecipesthatrangefromverysimplesingle-steprecipestomultistep,multiplatformrecipes.
StartingoutsmallAverybasicrecipe,aswehavediscussedbefore,mightonlyleverageoneortworesources.OneofthesimplestconceivablerecipesistheoneweusedearliertoverifythatourChef-soloinstallationwasworkingproperly:
file"#{ENV['HOME']}/example.txt"do
action:create
content"Greetings#{ENV['USER']}!"
end
Hereagain,wearecombiningasingleresource,thefileresource,specifyingthatwewanttocreatethefilenamed$HOME/example.txt,andstorethestring"Greetings$USER"inthatfile.$USERand$HOMEwillbereplacedbytheenvironmentvariables,mostlikelytheloginnameoftheuserthatisexecutingchef-clientandtheirhomedirectoryrespectively(unlesstheenvironmentvariableshavebeentamperedwith).
Followingourgoalofidempotence,executingthisrecipemultipletimesinarowwillhavethesameeffectasonlyrunningitonce.
InstallingasimpleserviceNowthatwe’vecoveredasimplerecipe,let’stakealookatonethatconfigurestheRedisengineandusessupervisordtorunthedaemon.Thisrecipedoesn’tinstallRedis;instead,itdefineshowtoconfigurethesystemtostartandmanagetheservice.Itdoesnothaveanyadvancedlogic,butmerelyconstructssomerequireddirectories,buildsaconfigurationfilefromatemplate,andthenusesthesupervisor_serviceresourcetoconfigurethedaemontorunandbemonitored,asshowninthefollowingcode:
redis_user=node[:redis][:user]
redis_group=node[:redis][:group]
environment_hash={"home"=>"#{node[:redis][:home]}"}
#Createthelogdiranddatadir
[node[:redis][:datadir],node[:redis][:log_dir]].eachdo|dir|
directorydirdo
ownerredis_user
groupredis_group
mode"0750"
recursivetrue
end
end
#Generatethetemplatefromredis.conf.erb
template"#{node[:redis][:config_path]}"do
source"redis.conf.erb"
ownerredis_user
groupredis_group
variables({:data_dir=>"#{node[:redis][:data_dir]}"})
mode0644
end
#Conveniencevariablesforreadability
stdout_log="#{node[:redis][:log_dir]}/redis-stdout.log"
stderr_log="#{node[:redis][:log_dir]}/redis-stderr.log"
redis_bin="#{node[:redis][:install_path]}/bin/redis-server"
redis_conf="#{node[:redis][:config_path]}"
#Tellsupervisortoenablethisservice,autostartit,runitas
#theredisuser,andtoinvoke:
#/path/to/redis-server/path/to/redis.conf
supervisor_service"redis_service"do
action:enable
autostarttrue
user"#{redis_user}"
command"#{redis_bin}#{redis_conf}"
stdout_logfile"#{stdout_log}"
stderr_logfile"#{stderr_log}"
directory"#{node[:redis][:install_path]}"
environmentenvironment_hash
end
Youwillnoticethatinordertokeeptheconfigurationconsistent,wereusealotofattributes.Forexample,thebeginningoftherecipeusesnode[:redis][:datadir]and
node[:redis][:log_dir]toensurethatthedirectoriesexistbymakinguseofadirectoryresourceinsideofaloop;then,theseareusedlaterontodefinethesupervisorconfigurationvariables(wheretowritelogs)andthetemplatefortheconfigfile(wheretostorethedata).Inall,thisrecipeiscomposedoffourresources:twodirectoriesintheloop,onetemplate,andonesupervisorservice.Bytheendofthisrun,itwillhaveensuredthecriticaldirectoriesexist,Redisisconfigured,andasupervisorconfigurationfileisgenerated(aswellaspokedsupervisordtoreloadthenewconfigurationandstarttheservice).Again,runningthismultipletimes,assumingnoconfigurationchangesinbetweenrunswillputthesystemintheexactsamestate.Rediswillbeconfiguredaccordingtothehostproperties,andsupervisorwillruntheservice.
GettingmoreadvancedLet’smoveupandtakealookataslightlymorecomplicated,yetfairlysimple,recipefromthegitcookbookthatinstallsthegitclientonthehost.Thecookbookismultiplatform,solet’stalkaboutwhatitwillbedoingbeforeitshowsyouthesource.Thisrecipewillbeperformingthefollowingactions:
1. Determinewhichplatformtheendhostisrunningon(byinspectingthenode[:platform]attribute).
2. IfthehostisrunningaDebian-baseddistribution,itwillusethepackageresourcetoinstallgit-core.
3. IfthehostisaRHELdistribution,itwillperformthefollowing:
1. IncludetheEPELrepositorybypullingintheepelrecipefromtheyumcookbookiftheplatformversionis5.
2. Usethepackageresourcetoinstallgit(asthatistheRHELpackagename).
4. IfthehostisWindows,itwillinstallgitviathewindows_packageresourceandinstructittodownloadthefilelocatedatnode[:git][:url](whichinturnpullsfromthedefaultattributesoroverriddenconfiguration),validatethatthechecksummatchestheonespecifiedbynode[:git][:checksum],andtheninstallit;however,thisisonlyiftheEXEisnotalreadyinstalled.
5. IfthehostisrunningOSX,itwillleveragethedmg_packageresourcetoinstalla.pkgfilefroma.dmgimage.Here,thedownloadURL,volumename,packagefile,checksum,andappnameareallattributesthatneedtobeprovided.
6. Finally,ifnoneoftheconditionsaremet,itfallsbacktothepackageresourcetoinstallthegitpackageinthehopethatitwillwork.
Hereisthecodeforthisrecipe:
casenode[:platform]
when"debian","ubuntu"
package"git-core"
when"centos","redhat","scientific","fedora"
casenode[:platform_version].to_i
when5
include_recipe"yum::epel"
end
package"git"
when"windows"
windows_package"git"do
sourcenode[:git][:url]
checksumnode[:git][:checksum]
action:install
not_if{File.exists?'C:\ProgramFiles(x86)\Git\bin\git.exe'}
end
when"mac_os_x"
dmg_package"GitOSX-Installer"do
appnode[:git][:osx_dmg][:app_name]
package_idnode[:git][:osx_dmg][:package_id]
volumes_dirnode[:git][:osx_dmg][:volumes_dir]
sourcenode[:git][:osx_dmg][:url]
checksumnode[:git][:osx_dmg][:checksum]
type"pkg"
action:install
end
else
package"git"
end
Onethingwehaven’tseenyetistheuseofthenot_ifqualifier.Thisisexactlywhatitlookslike;iftheblocksuppliedtonot_ifreturnsatruevalue,theresourcewillnotbeprocessed.Thisisveryusefultoensurethatyoudon’tclobberimportantfilesorrepeatexpensiveoperationssuchasrecompilingasoftwarepackage.
SummaryThischapterintroducedyoutothecriticalcomponentsofacookbookthatareusedtowriterecipes.Italsoshowedyousomeexamplerecipestogetyoustarted;thereareanumberofadvancedactionsthatcanbeaccomplishedinyourrecipes,suchassearchingtheChefserverfordata,loadingdatafromdatabags,orusingencrypteddata.Additionally,youcanaddmorecomponentstoyourcookbookssuchascustomresourcesandproviders,tests,andarbitraryRubylibraries.Allofthesewillbediscussedindetailinlaterchapters,butfirstlet’stakealookatwritingsomecompletecookbooks.We’llthenlearnhowtotestthembeforewemoveontolookingatsomecookbooksforcommonsystemadministrationtasks,andthenwe’llprogressontoadvancedtopics.
Chapter5.TestingYourRecipesSofar,youhaveseenhowtomodelyourinfrastructure,provisionhostsinthecloud,andwhatgoesintoacookbook.Oneimportantaspectofdevelopingcookbooksiswritingtestssothatyourrecipesdonotdegradeovertimeorhavebugsintroducedintotheminthefuture.Thischapterintroducesyoutothefollowingconcepts:
UnderstandingtestmethodologiesHowRSpecstructuresyourtestsUsingChefSpectotestrecipesRunningyourtestsWritingteststhatcovermultipleplatforms
Thesetechniqueswillprovetobeveryusefultowriterobust,maintainablecookbooksthatyoucanusetoconfidentlymanageyourinfrastructure.Testsenableyoutoperformthefollowing:
IdentifymistakesinyourrecipelogicTestyourrecipesagainstmultipleplatformslocallyDeveloprecipesfasterwithlocaltestexecutionbeforerunningthemonahostCatchthechangesindependenciesthatwillotherwisebreakyourinfrastructurebeforetheygetdeployedWritetestsforbugstopreventthemfromhappeningagaininthefuture(regression)
TestingrecipesThereareanumberofwaystotestyourrecipes.Oneapproachistosimplyfollowtheprocessofdevelopingyourrecipes,uploadingthemtoyourChefserver,anddeployingthemtoahost;repeatthisuntilyouaresatisfied.Thishasthebenefitofexecutingyourrecipesonrealinstances,butthedrawbackisthatitisslow,particularlyifyouaretestingonmultipleplatforms,andrequiresthatyoumaintainafleetofhosts.Ifyourcookbookruntimesarereasonablyshortandyouhaveasmallnumberofplatformstosupportthem,thenthismightbeaviableoption.Thereisabetteroptiontotestyourrecipes,anditiscalledChefSpec.ForthosewhohaveusedRSpec,aRubytestinglibrary,theseexampleswillbeanaturalextensionofRSpec.IfyouhaveneverusedRSpec,thebeginningofthischapterwillintroduceyoutoRSpec’stestinglanguageandmechanisms.
RSpecRSpecisaframeworktotestRubycodethatallowsyoutouseadomain-specificlanguagetoprovidetests,muchinthesamewayChefprovidesadomain-specificlanguagetomanipulateaninfrastructure.InsteadofusingaDSLtomanagesystems,RSpec’sDSLprovidesanumberofcomponentstoexpresstheexpectationsofcodeandsimulatetheexecutionofportionsofthesystem(alsoknownasmocking).
ThefollowingexamplesinRSpecshouldgiveyouahigh-levelideaofwhatRSpeccando:
#simpleexpectation
it'shouldadd2and2together'do
x=2+2
expect(x).toeq4
end
#EnsureanyinstanceofObjectreceivesacallto'foo'
#andreturnapre-definedvalue(mocking)
it'verifiesthataninstancereceives:foo'do
expect_any_instance_of(Object)
.toreceive(:foo).and_return(:return_value)
o=Object.new
expect(o.foo).toeq(:return_value)
end
#Deepexpectations(i.eclientmakesanHTTPcallsomewhere
#insideit,makesureithappensasexpected)
it'shouldmakeanauthorizedHTTPGETcall'do
expect_any_instance_of(Net::HTTP::Get)
.toreceive(:basic_auth)
@client.make_http_call
end
RSpecandChefSpecAswithmosttestinglibraries,RSpecenablesyoutoconstructasetofexpectations,buildobjectsandinteractwiththem,andverifythattheexpectationshavebeenmet.Forexample,oneexpectsthatwhenauserlogsintothesystem,adatabaserecordiscreated,trackingtheirloginhistory.However,tokeeptestsrunningquickly,theapplicationshouldnotmakeanactualdatabasecall;inplaceoftheactualdatabasecall,amockmethodshouldbeused.Here,ourmockmethodwillcatchthemessageinthedatabaseinordertoverifythatitwasgoingtobesent;then,itwillreturnanexpectedresultsothatthecodedoesnotknowthedatabaseisnotreallythere.
TipMockmethodsaremethodsthatareusedtoreplaceonecallwithanother;youcanthinkofthemasstuntdoubles.Forexample,ratherthanmakingyourcodeactuallyconnecttothedatabase,youmightwanttowriteamethodthatactsasthoughithassuccessfullyconnectedtothedatabaseandfetchedtheexpecteddata.
ThiscanbeextendedtomodelChef’sabilitytohandlemultipleplatformsandenvironmentsverynicely;codeshouldbeverifiedtobehaveasexpectedonmultipleplatformswithouthavingtoexecuterecipesonthoseplatforms.ThismeansthatyoucantesttheexpectationsaboutRedHatrecipesfromanOSXdevelopmentmachineorWindowsrecipesfromanUbuntudesktop,withoutneedingtohavehostsaroundtodeploytofortestingpurposes.Additionally,thedevelopmentcycletimeisgreatlyreducedastestscanbeexecutedmuchfasterwithexpectationsthanwhentheyareperformingsomeworkonanendhost.
Youmaybeaskingyourself,“Howdoesthisreplacetestingonanactualhost?”Theansweristhatitmaynot,andsoyoushoulduseintegrationtestingtovalidatethattherecipesworkwhendeployedtorealhosts.Whatitdoesallowyoutodoisvalidateyourexpectationsofwhatresourcesarebeingexecuted,whichattributesarebeingused,andthatthelogicalflowofyourrecipesarebehavingproperlybeforeyoupushyourcodetoyourhosts.Thisformsatighterdevelopmentcycleforrapiddevelopmentoffeatureswhileprovidingalonger,moreformallooptoensurethatthecodebehavescorrectlyinthewild.
Ifyouarenewtotestingsoftware,andinparticular,testingRubycode,thisisabriefintroductiontosomeoftheconceptsthatwewillcover.Testingcanhappenatmanydifferentlevelsofthesoftwarelifecycle:
Single-modulelevel(calledunittests)Multi-modulelevel(knownasfunctionaltests)System-leveltesting(alsoreferredtoasintegrationtesting)
TestingbasicsInthetest-driven-development(TDD)philosophy,testsarewrittenandexecutedearlyandoften,typically,evenbeforecodeiswritten.Thisguaranteesthatyourcodeconformstoyourexpectationsfromthebeginninganddoesnotregresstoapreviousstateofnon-conformity.ThischapterwillnotdiveintotheTDDphilosophyandcontinuoustesting,butitwillprovideyouwithenoughknowledgetobegintestingtherecipesthatyouwriteandfeelconfidentthattheywilldothecorrectthingwhendeployedintoyourproductionenvironment.
ComparingRSpecwithothertestinglibrariesRSpecisdesignedtoprovideamoreexpressivetestinglanguage.ThismeansthatthesyntaxofanRSpectest(alsoreferredtoasaspectestorspec)isdesignedtocreatealanguagethatfeelsmorelikeanaturallanguage,suchasEnglish.Forexample,usingRSpec,onecouldwritethefollowing:
expect(factorial(4)).toeq24
Ifyoureadtheprecedingcode,itwillcomeoutlikeexpectfactorialof4toequal24.ComparethistoasimilarJUnittest(forJava):
assertEquals(24,factorial(4));
Ifyoureadtheprecedingcode,itwouldsoundmorelikeassertthatthefollowingareequal,24andfactorialof4.Whilethisisreadablebymostprogrammers,itdoesnotfeelasnaturalastheonewesawearlier.
RSpecalsoprovidescontextanddescribeblocksthatallowyoutogrouprelatedexamplesandsharedexpectationsbetweenexamplesinthegrouptohelpimproveorganizationandreadability.Forexample,considerthefollowingspectest:
describeArraydo
it"shouldbeemptywhencreated"do
Array.new.should==[]
end
end
ComparetheprecedingtesttoasimilarNUnit(.NETtestingframework)test:
namespaceMyTest{
usingSystem.Collection
usingNUnit.Framework;
[TestFixture]
publicclassArrayTest{
[Test]
publicvoidNewArray(){
ArrayListlist=newArrayList();
Assert.AreEqual(0,list.size());
}
}
}
Clearly,thespectestismuchmoreconciseandeasiertoread,whichisagoalofRSpec.
UsingChefSpecChefSpecbringstheexpressivenessofRSpectoChefcookbooksandrecipesbyprovidingChef-specificprimitivesandmechanismsontopofRSpec’ssimpletestinglanguage.Forexample,ChefSpecallowsyoutosaythingslike:
it'createsafile'do
expect(chef_run).tocreate_file('/tmp/myfile.txt')
end
Here,chef_runisaninstanceofafullyplannedChefclientexecutiononadesignatedendhost,aswewillseelater.Also,inthiscase,itisexpectedthatitwillcreateafile,/tmp/myfile.txt,andthetestwillfailifthesimulatedrundoesnotcreatesuchafile.
GettingstartedwithChefSpecInordertogetstartedwithChefSpec,createanewcookbookdirectory(hereitis$HOME/cookbooks/mycookbook)alongwitharecipesandspecdirectory:
mkdir-p~/cookbooks/mycookbook
mkdir-p~/cookbooks/mycookbook/recipes
mkdir-p~/cookbooks/mycookbook/spec
Nowyouwillneedasimplemetadata.rbfileinsideyourcookbook(here,thiswillbe~/cookbooks/mycookbook/metadata.rb):
maintainer"Yournamehere"
maintainer_email"[email protected]"
license"Apache"
description"Simplecookbook"
long_description"Supersimplecookbook"
version"1.0"
supports"debian"
Oncewehavethis,wenowhavethebarebonesofacookbookthatwecanbegintoaddrecipesandteststo.
InstallingChefSpecInordertogetstartedwithChefSpec,youwillneedtoinstallagemthatcontainstheChefSpeclibrariesandallthesupportingcomponents.Notsurprisingly,thatgemisnamedchefspecandcanbeinstalledsimplybyrunningthefollowing:
geminstallchefspec
However,becauseRubygemsoftenhaveanumberofdependencies,theRubycommunityhasbuiltatoolcalledBundlertomanagegemversionsthatneedtobeinstalled.SimilartohowRVMprovidesinterpreter-levelversionmanagementandawaytokeepyourgemsorganized,Bundlerprovidesgem-levelversionmanagement.WewilluseBundlerfortworeasons.Inthiscase,wewanttolimitthenumberofdifferencesbetweentheversionsofsoftwareyouwillbeinstallingandtheversionstheauthorhasinstalledtoensurethatthingsareassimilaraspossible;secondly,thisextendswelltoreleasingproductionsoftware—limitingthenumberofvariablesiscriticaltoconsistentandreliablebehavior.
LockingyourdependenciesinRubyBundlerusesafile,specificallynamedGemfile,todescribethegemsthatyourprojectisdependentupon.Thisfileisplacedintherootofyourproject,anditscontentsinformBundlerwhichgemsyouareusing,whatversionstouse,andwheretofindgemssothatitcaninstallthemasneeded.
Forexample,hereistheGemfilethatisbeingusedtodescribethegemversionsthatareusedwhenwritingtheseexamples:
source'https://rubygems.org'
gem'chef','11.10.0'
gem'chefspec','3.2.0'
gem'colorize','0.6.0'
Usingthiswillensurethatthegemsyouinstalllocallymatchtheonesthatareusedwhenwritingtheseexamples.Thisshouldlimitthedifferencesbetweenyourlocaltestingenvironmentsifyouruntheseexamplesonyourworkstation.
InordertouseaGemfile,youwillneedtohaveBundlerinstalled.IfyouareusingRVM,Bundlershouldbeinstalledwitheverygemsetyoucreate;ifnot,youwillneedtoinstallitonyourownviathefollowingcode:
geminstallbundler
OnceBundlerisinstalledandaGemfilethatcontainsthepreviouslinesisplacedintherootdirectoryofyourcookbook,youcanexecutebundleinstallfrominsideyourcookbook’sdirectory:
user@host:~/cookbooks/mycookbook$>bundleinstall
BundlerwillparsetheGemfileinordertodownloadandinstalltheversionsofthegemsthataredefinedinside.Here,Bundlerwillinstallchefspec,chef,andcolorizealongwithanydependenciesthosegemsrequirethatyoudonotalreadyhaveinstalled.
CreatingasimplerecipeandamatchingChefSpectestOncethesedependenciesareinstalled,youwillwanttocreateaspectestinsideyourcookbookandamatchingrecipe.InkeepingwiththeTDDphilosophy,wewillfirstcreateafile,default_spec.rb,inthespecdirectory.Thenameofthespecfileshouldmatchthenameoftherecipefile,onlywiththeadditionof_specattheend.Ifyouhavearecipefilenameddefault.rb(whichmostcookbookswill),thematchingspectestwouldbecontainedinafilenameddefault_spec.rb.Let’stakealookataverysimplerecipeandamatchingChefSpectest.
WritingaChefSpectestThetest,shownasfollows,willverifythatourrecipewillcreateanewfile,/tmp/myfile.txt:
require'chefspec'
describe'mycookbook::default'do
let(:chef_run){
ChefSpec::Runner.new.converge(described_recipe)
}
it'createsafile'do
expect(chef_run).tocreate_file('/tmp/myfile.txt')
end
end
Here,RSpecusesadescribeblocksimilartothewayChefusesaresourceblock(again,blocksareidentifiedbythedo…endsyntaxorcodecontainedinsidecurlybraces)todescribearesource,inthiscase,thedefaultrecipeinsideofmycookbook.Thedescribedresourcehasanumberofexamples,andeachexampleisdescribedbyanitblocksuchasthefollowing,whichcomesfromthepreviousspectest:
it'createsafile'do
expect(chef_run).tocreate_file('/tmp/myfile.txt')
end
Thestringgiventotheitblockprovidestheexamplewithahuman-readabledescriptionofwhattheexampleistesting;inthiscase,weareexpectingthattherecipecreatesafile.WhenourrecipesarerunthroughChefSpec,theresourcesdescribedarenotactuallycreatedormodified.Instead,amodelofwhatwouldhappenisbuiltastherecipesareexecuted.ThismeansthatChefSpeccanvalidatethatanexpectedactionwouldhaveoccurrediftherecipeweretobeexecutedonanendhostduringarealclientrun.
TipIt’simportanttonotethateachexampleblockresetsexpectationsbeforeitisexecuted,soanyexpectationsdefinedinsideofagiventestwillnotfallthroughtoothertests.
BecausemostofthetestswillinvolvesimulatingaChefclientrun,wewanttorunthe
simulationeverytime.Therearetwooptions:executethecodeineveryexampleoruseasharedresourcethatallthetestscantakeadvantageof.Inthefirstcase,thetestwilllooksomethinglikethefollowing:
it'createsafile'do
chef_run=ChefSpec::Runner.new.converge(described_recipe)
expect(chef_run).tocreate_file('/tmp/myfile.txt')
end
Theprimaryproblemwiththisapproachisrememberingthateverytestwillhavetohavetheresourcerunningatthebeginningofthetest.Thistranslatestoalargeamountofduplicatedcode,andiftheclientneedstobeconfigureddifferently,thenthecodeneedstobechangedforallthetests.Tosolvethisproblem,RSpecprovidesaccesstoasharedresourcethroughabuilt-inmethod,let.Usingletallowsatesttodefineasharedresourcethatiscachedforeachexampleandresetasneededforthefollowingexamples.Thisresourceisthenaccessibleinsideofeachblockasalocalvariable,andRSpectakescareofknowingwhentoinitializeitasneeded.
Ourexampletestusesaletblocktodefinethechef_runresource,whichisdescribedasanewChefSpecrunnerforthedescribedrecipe,asshowninthefollowingcode:
let(:chef_run){
ChefSpec::Runner.new.converge(described_recipe)
}
Here,described_recipeisaChefSpecshortcutforthenameoftherecipeprovidedinthedescribeblock.Again,thisisaDRY(don’trepeatyourself)mechanismthatallowsustorenametherecipeandthenonlyhavetochangethenameofthedescriptionratherthanhuntthroughthecode.Thesetechniquesmaketestsbetterabletoadapttochangesinnamesandresources,whichreducescoderotastimegoesby.
BuildingyourrecipeTherecipe,asdefinedhere,isaverysimplerecipewhoseonlyjobistocreateasimplefile,/tmp/myfile.txt,ontheendhost:
file"/tmp/myfile.txt"do
owner"root"
group"root"
mode"0755"
action:create
end
Putthisrecipeintotherecipes/default.rbfileofyourcookbooksothatyouhavethefollowingfilelayout:
mycookbook/
|-recipes/
||-default.rb
|-spec/
|-default_spec.rb
Executingtests
Inordertorunthetests,weusetherspecapplication.ThisisaRubyscriptthatcomeswiththeRSpecgem,whichwillrunthetestscriptsasspectestsusingtheRSpeclanguage.ItwillalsousetheChefSpecextensionsbecauseinourspectest,wehaveincludedthemviathelinerequire'chefspec'atthetopofourdefault_spec.rbfile.Here,rspecisexecutedthroughBundlertoensurethatthedesiredgemversions,asspecifiedinourGemfile,areusedatruntimewithouthavingtoexplicitlyloadthem.Thisisdoneusingthebundleexeccommand:
bundleexecrspecspec/default_spec.rb
ThiswillrunRSpecusingBundlerandprocessthedefault_spec.rbfile.Asitruns,youwillseetheresultsofyourtests,a.(period)forteststhatpass,andanFforanyteststhatfail.Initially,theoutputfromrspecwilllooklikethis:
Finishedin0.17367seconds
1example,0failures
RSpecsaysthatitcompletedtheexecutionin0.17secondsandthatyouhadoneexamplewithzerofailures.However,theresultswouldbequitedifferentifwehaveafailedtest;RSpecwilltelluswhichtestfailedandwhy.
UnderstandingfailuresRSpecisverygoodattellingyouwhatwentwrongwithyourtests;itdoesn’tdoyouanygoodtohavefailingtestsifit’simpossibletodeterminewhatwentwrong.Whenanexpectationinyourtestisnotmet,RSpecwilltellyouwhichexpectationwasunmet,whattheexpectedvaluewas,andwhatvaluewasseen.
Inordertoseewhathappenswhenatestfails,modifyyourrecipetoensurethatthetestfails.Lookinyourrecipeforthefollowingfileresource:
file"/tmp/myfile.txt"do
Replacethefileresourcewithadifferentfilename,suchasmyfile2.txt,insteadofmyfile.txt,likethefollowingexample:
file"/tmp/myfile2.txt"do
Next,rerunyourspectests;youwillseethatthetestisnowfailingbecausethesimulatedChefclientexecutiondidsomethingthatwasunexpectedbyourspectest.Anexampleofthisnewexecutionwouldlooklikethefollowing:
[user@host]$bundleexecrspecspec/default_spec.rb
F
Failures:
1)my_cookbook::defaultcreatesafile
Failure/Error:expect(chef_run).tocreate_file('/tmp/myfile.txt')
expected"file[/tmp/myfile.txt]"withaction:createtobeinChef
run.Otherfileresources:
file[/tmp/myfile2.txt]
#./spec/default_spec.rb:9:in`block(2levels)in<top(required)>'
Finishedin0.18071seconds
1example,1failure
Noticethatinsteadofadot,thetestresultsinanF;thisisbecausethetestisnowfailing.Asyoucanseefromthepreviousoutput,RSpecistellingusthefollowing:
Thecreatesafileexampleinthe'my_cookbook::default'testsuitefailedOurexamplefailedintheninthlineofdefault_spec.rb(asindicatedbythelinethatcontained./spec/default_spec.rb:9)Thefileresource/tmp/myfile.txtwasexpectedtobeoperatedonwiththe:createactionTherecipeinteractedwithafileresource/tmp/myfile2.txtinsteadof/tmp/myfile.txt
RSpecwillcontinuetoexecuteallthetestsinthefilesspecifiedonthecommandline,printingouttheirstatusastowhethertheypassedorfailed.Ifyourtestsarewellwrittenandruninisolation,thentheywillhavenoeffectononeanother;itshouldbesafetoexecuteallofthemevenifsomefailsothatyoucanseewhatisnolongerworking.
ExpandingyourtestsChefSpecprovidesacomprehensivesuiteoftoolstotestyourrecipes;youcanstubandmockresources(replacerealbehaviorwithartificialbehavior,suchasnetworkordatabaseconnections),simulatedifferentplatforms,andmore.Let’stakealookatsomemorecomplexexamplestoseewhatotherthingswecandowithChefSpec.
MultipleexamplesinaspectestSpectestsdonotneedtocontainonlyoneexample;theycancontainasmanyasyouneed.Inordertoorganizethem,youcangroupthemtogetherbywhattheydescribeandsomesharedcontext.InRSpec,contextblockscontainexamplesthatarerelevanttotherecipeorscriptbeingtested.Thinkofthemasself-containedtestsuiteswithinalargertestsuite;theycanhavetheirownresourcesaswellassetupandtear-downlogicthatarespecifictotheteststhatareruninthatcontext.
Asanexample,let’slookatpartofthespectestsuitefromtherender_fileexampleinsideofChefSpecitself.Considerthisportionoftherecipe:
file'/tmp/file'do
content'Thisiscontent!'
end
cookbook_file'/tmp/cookbook_file'do
source'cookbook_file'
end
template'/tmp/template'do
source'template.erb'
end
Therecipebeingshownhasthreeresources:atemplate,acookbook_file,andanordinaryfileresource.Asampleofthematchingspectest(testsremovedforformattingandeaseofreading)containsanouterdescribeblock,whichtellsusthatweareexecutingtestsfortherender_file::defaultrecipeandthreeseparatecontextblocks.Eachcontextdescribesadifferentportionoftherecipethatisbeingtestedandtheexpectationsofthatparticulartypeofresource.Together,theyareallpartofthedefaultrecipe,buttheybehaveverydifferentlyinwhatcontenttheyrenderaswellaswhereandhowtheystorefilesonthesystem.
Inthisexample,thefilecontextcontainsteststhatpertaintotheexpectedresultsofthefileresource,thecookbook_filecontextisconcernedwiththecookbook_fileresource,andsoon:
describe'render_file::default'do
let(:chef_run){
ChefSpec::Runner.new.converge(described_recipe)
}
context'file'do
it'rendersthefile'do
expect(chef_run).torender_file('/tmp/file')
expect(chef_run).to_notrender_file('/tmp/not_file')
end
end
context'cookbook_file'do
it'rendersthefile'do
expect(chef_run).torender_file('/tmp/cookbook_file')
expect(chef_run).to_not
render_file('/tmp/not_cookbook_file')
end
end
context'template'do
it'rendersthefile'do
expect(chef_run).torender_file('/tmp/template')
expect(chef_run).to_notrender_file('/tmp/not_template')
end
end
end
Contextscanbeusedtogrouptogetherasetofexamplesthatarerelated,notjustonesthatarespecifictoaparticularresource.Considerthefollowingexample:
describe'package::install'do
context'wheninstallingonWindows2012'do
end
context'wheninstallingonDebian'do
end
context'wheninstallingonFreeBSD'do
end
end
Inthepreviousexample,ourspectestcontainedteststhataregroupedtogetherbytheplatformbeingexecutedon.Insideofeachcontext,theChefrunwillbeconstructedwithaplatformargumentinsteadsothattheexpectationsbeingtestedwillbeconsideredagainstarunoftherecipeontheplatforminquestionratherthanthehost’soperatingsystem.Thisisincrediblyuseful,aswewillseeinthenextsectionontestingformultipleplatforms.
TestingformultipleplatformsOneofthemorenon-trivialusesofChefSpecistosimulateexecutingrecipesonmultipleplatforms.Thisisusefulfordeveloperswhoarebuildingrecipesthatneedtosupportmorethanoneoperatingsystem.SoftwarepackagessuchasPostgreSQL,MySQL,Java,PHP,Apache,andcountlessotherapplicationscanbeinstalledonmanydifferentplatforms.Becauseeachplatformvariesinitsinstallationmechanism,usercreation,andothercorefeatures,beingabletotestrecipesagainstallthesupportedplatformsisincrediblyuseful.
Let’slookatahypotheticalexampletodeveloparecipetoinstallMySQLonWindows2012andsomethingswemightwanttovalidateduringsucharun:
context'whenrunonWindows2012'do
let(:chef_run)do
#constructa'runner'(simulatechef-client)running
#onaWindows2012host
runner=ChefSpec::ChefRunner.new(
'platform'=>'windows',
'version'=>'2012'
)
#setaconfigurationvariable
runner.node.set['mysql']['install_path']='C:\\temp'
runner.node.set['mysql']['service_user']='SysAppUser'
runner.converge('mysql::server')
end
it'shouldincludethecorrectWindowsserverrecipe'do
chef_run.shouldinclude_recipe'mysql::server_windows'
end
it'shouldcreateanINIfileintherightdirectory'do
ini_file="C:\\temp\\mysql\\mysql.ini"
expect(chef_run).tocreate_templateini_file
end
end
Byconstructingtherunnerwiththeplatformandversionoptions,thetestwillexerciserunningthemysql::serverrecipeandpretendasthoughitwererunningonaWindows2012host.Thisallowsustosetupexpectationsaboutthetemplatesthatwillbecreated,recipesthatarebeingexecuted,andmoreonthatparticularplatform.
Presumingthatthemysql::serverrecipewasabletodelegatetotheOS-specificrecipeonagivenplatform,wecouldwriteanothertest:
context'whenrunonDebian'do
let(:chef_run)do
runner=ChefSpec::ChefRunner.new(
'platform'=>'debian'
)
runner.node.set['mysql']['install_path']='/usr/local'
runner.node.set['mysql']['service_user']='mysql'
runner.converge('mysql::server')
end
it'shouldincludethecorrectLinuxserverrecipe'do
chef_run.shouldinclude_recipe'mysql::server_linux'
end
it'shouldcreateanINIfileintherightdirectory'do
ini_file="/usr/local/mysql/mysql.ini"
expect(chef_run).tocreate_templateini_file
end
it'shouldinstalltheDebianMySQLpackage'do
expect(chef_run).toinstall_package('mysql-server')
end
end
Inthisway,wecanwriteourteststovalidatetheexpectedbehavioronplatformsthatwemaynothavedirectaccesstoinordertoensurethattheywillbeperformingtheexpected
actionsforacollectionofplatforms.
SummaryRSpecwithChefSpecextensionsprovidesuswithincrediblypowerfultoolstotestourcookbooksandrecipes.YouhaveseenhowtodevelopbasicChefSpectestsforyourrecipes,organizeyourspectestsinsideofyourcookbook,executeandanalyzetheoutputofyourspectests,andsimulatetheexecutionofyourrecipesacrossmultipleplatforms.
Infuturechapters,wewilllearnsomemoreadvancedtestingmechanismssuchasmockingandstubbingexternalservicessuchassearchanddatabags.Addingtestingtoyourdevelopmentcycleallowsyoutofeelconfidentinthecorrectnessofyourrecipes,whichisacriticalsteptowardsautomatingthemanagementofyourinfrastructure.
Now,let’stakealookathowwewillbuildacookbooktocomplementawebapplicationsothatwecanseethefullcycleofdevelopinganapplicationanddeployingitusingChef.
Chapter6.FromDevelopmenttoDeploymentThischaptercoversend-to-endsoftwaredeploymentofaPython-basedwebapplication.Itwillalsointroduceyoutosomecommoncookbooksandhowtoputthemalltogethertocreateafullyautomateddeploymentmechanism.
Wewillwalkthroughthefollowingtopics:
ConfiguringyourlocalsettingstoworkwithAWSModelingasimpleweb.pyapplicationwithChefInstallingthecookbooksyouneedProvisioningEC2instancesforwebandDBserversDefiningyourrolesAddinguserstohostsInstallingtherequiredsoftwareConfiguringanapplicationusingChefDeployingtheapplication
DescribingthesetupFromahighlevel,hereiswhatneedstohappeninordertotakeanapplicationthatwehavedevelopedfromadesktoptodeployment.Inordertodeployyourapplication,youwillprovisiontwohosts,webanddb(eachwithoneuseraccount)andwebapp,whosehomedirectorywillbein/home/webapp.ThesourcecodewillbehostedonGitHubanddeployedusingGitontothewebserver.Wewillcreateadatabase,provisionanaccounttoaccessthatdatabase,andthenconfigureanddeployaweb.pyapplicationinavirtualPythonenvironmentthatwillbestartedandmonitoredbysupervisord.Thisisafairlycommonpatternformodernwebapplications,regardlessoftheframeworkandlanguagebeingused.Thedemonstrationapplicationusedinthischapterconsistsofonlyahandfuloffilesmakingiteasytodeployandunderstand,butthiswillgiveyoutheconceptsandtoolstoexpandthisexampleforusewithfutureapplicationsyoumightdeveloporneedtodeploy.
DeployingsoftwarewithChefTherearenumerousbenefitstodeployingyoursoftwareusingChef;theprimarybenefitisautomation—thechef-clientcanberunperiodically,anditcanexecutefully-automateddeploymentswheneverchangesaremadetothesourcecoderepository.Additionally,Chefstoresallyourconfigurationdata,soyoucanavoidstoringsensitivesecretsandhard-codingURLsorotherdynamicdatainyourconfiguration.Forexample,ifyouhaveanapplicationwithadatabasepool,andyouaddanewdatabasehosttoyourpool,Chefcanuseasimplesearchtopopulatethelistofhoststoincludeintheconnectionpoolsothatitisalwaysuptodatewithyourinfrastructure.
However,deployingsoftwarewithChefdoesrequiresomecoordinationbetweenyourapplicationandChef.Youwillneedtomaintainrecipesrequiredfordeployingyourapplication,andyouwillalsowanttouseChefastheauthoritativesourceforyourconfigurationdata,whichinvolveswritingconfigurationtemplates.ByusingCheftomanageyourdeployments,youcanalsogenerateanyconfigurationdataneededtorunyoursoftwarebasedonyourinfrastructureconfiguration;inourcase,asimpleconfig.pyfileforyourweb.pyapplication.Thismethodcanalsobeusedtomanagethedatabase.ymlfile(andanyotherYAMLfiles)forRailsapplications,theserver.ymlfileforaDropwizardapplication,oranyotherconfigurationfilesneededtorunyourservice.
TipYAMLisasimplemarkuplanguagetostoreconfigurationdata.Itispopularwithmoderndevelopersbecauseitiseasytoparseandisveryexpressive,similartoJSON.
ConfiguringyourlocalenvironmentBynow,youshouldhaveaccesstoaChefserverofsomesort;here,wewillbeusingthehostedChefservice,butthework(asidefromconfiguringyourknife.rbfile)willremainthesameacrossself-managedandhostedChefinstances.Inordertofollowalongwiththeexamples,youwillneedtoconfigureyourworkstationwithanappropriateknife.rbfileandcertificates.ThesefilescanbedownloadedfromthehostedChefconsoleandmodifiedasneeded.
Additionally,youwillneedtohaveinstalledknifethroughthechefgemandhavetheknife-ec2geminstalledinordertointeractwithEC2.Ifyouprefertouseadifferentprovider,thenyoucanrefertothepreviouschapteronhowtoprovisioncloudhostswiththisproviderwhenyougettotheprovisioningstep.
Inourexample,usinghostedChefandEC2,ourknife.rbfilewillcontaincontentsimilartothefollowingcode:
current_dir=File.dirname(__FILE__)
log_level:info
log_locationSTDOUT
node_name"myorg"
client_key"#{current_dir}/myorg.pem"
validation_client_name"myorg-validator"
validation_key"#{current_dir}/myorg-validator.pem"
chef_server_url"https://api.opscode.com/organizations/myorg"
cache_type'BasicFile'
cache_options(:path=>"#{ENV['HOME']}/.chef/checksums")
cookbook_path["#{current_dir}/../cookbooks"]
knife[:aws_access_key_id]="YOURAWSACCESSKEY"
knife[:aws_secret_access_key]="YOURAWSSECRETKEY"
knife[:region]="AWSREGION"
Again,thebasefilescanbedownloadedfromthehostedChefconsole,orifyouareusingaself-managedChefinstallation,thiscanbefoundonyourChefserver.
ModelingasimplePythonapplicationHerewewillconsideraweb.pyapplicationthathastwoprimarycomponents:awebserverandadatabaseserver.Wewillprovisiononehostforeachrole,bootstrapthem,anddeploythesoftwareontoournewhosts.
Ourapplicationstackwillconsistofthefollowing:
web.pyasourwebframeworkPostgreSQLfordatastorageEC2forvirtualhosts
Wewanttodefinetwoprimaryrolesthatrepresentourwebserverandourdatabaseserver.Inaddition,wewillconstructabaselineroleforallourserversthatwillsupplyanycommondataweneedsuchasuseraccounts,SSHkeys,networkconfigurationdata,shells,commonutilities,libraries,andsoon.
Wewillneedtofindorwritecookbooksforthefollowingcomponentswewilluse:
PythonsupervisordPostgreSQLUseraccountsOurcustomwebapplication
ManagingthecookbooksThecookbooksthatwewillbeusingareallavailableatthefollowingURL:https://github.com/johnewart/simplewebpy_app.Becauseanumberofcookbooksusedinthisexampleareunderactivedevelopment,theonesrequiredfortheexampleshavebeenfrozen(asofwritingofthisbook)toensurecompatibilitywiththeexamples;itisbettertohavethemslightlyoutofdatethanbrokeninthiscase.
However,whenyouwriteyourowncookbooksanddeployyourownsoftwarebeyondthisexample,youwillfindthattherearealargenumberofcookbooksthatcanbefoundthroughtheChefcommunitysite—http://supermarket.getchef.com/—orbysearchingtheWebforcookbooks;manyofthesewillbehostedonGitHub,BitBucket,orsimilarsourcecode-hostingsites.
DownloadingcookbooksHereinthefollowingcode,wewillsimplydownloadthecookbookcollectionasawhole:
http://github.com/johnewart/chef_essential_files
Toinstallthecollection,wecandothefollowingfromthechef_essential_files/cookbooksdirectory:
knifecookbookinstall-o.*
Thiswillinstallallofthecookbooksthatareprovided.Theprovidedcookbooksareallthatisrequiredfortheexamplesinthischaptertobesuccessful.Let’stakealookatourcustomcookbook,thepythonwebappcookbook,asalloftheothersareoff-the-shelfcookbooksthataredesignedtoprovidesomegeneralsupportfunctionalities.
LookingatthedatabaserecipeWewilldoafewthingshere,solet’slookatourdatabaserecipe.Inorderforourwebapplicationtobeuseful,itneedsadatabasetoconnectto.Typically,thisinvolvesinstallingthedatabaseserversoftware,constructingadatabase,andgrantingaccesstothatdatabasebyaspecifieduser(orusers).Ourapplicationisnodifferent,sowewillleveragethedatabasecookbookinordertoaccomplishthis.
First,inourrecipe,weneedtoincludethePostgreSQL-specificresourcesfromthedatabasecookbook,whichwewilldousingthefollowingcode:
include_recipe"database::postgresql"
Youwillneedtoknowwhatdatabaseyouwillbecreatingandtowhichuseryouwillbegrantingaccesstoalongwiththepasswordthatwillbeusedtoidentifythem:
dbname=node[:webapp][:dbname]
dbuser=node[:webapp][:dbuser]
dbpass=node[:webapp][:dbpass]
Inordertocreateadatabaseanduseraswellasgrantaccess,youwillneedtoestablishaconnectiontothedatabaseserverwithauserthathaspermissiontodoso.Youwillseethatthisuserhasalsobeengrantedaccessinyourrole’spg_hbasettingssothatPostgreSQLknowstoallowthepostgresusertoconnecttothedatabaselocally,asshowninthefollowingcode:
postgresql_connection_info={
:host=>'localhost',
:port=>node['postgresql']['config']['port'],
:username=>'postgres',
:password=>node['postgresql']['password']['postgres']
}
Usingthisconnectioninformation,youcanconstructadatabaseandauser(iftheydon’talreadyexist),andthengrantthatuserfullaccesstoournewdatabase:
#Constructanactualdatabaseontheserver
postgresql_database'webapp'do
connectionpostgresql_connection_info
action:create
end
#Createadatabaseuserresourceusingourconnection
postgresql_database_userdbuserdo
connectionpostgresql_connection_info
passworddbpass
action:create
end
#Grantallprivilegesonalltablesin'webapp'
postgresql_database_userdbuserdo
connectionpostgresql_connection_info
database_namedbname
privileges[:all]
action:grant
end
Thishigh-levellanguageallowsustoeasilymanipulatethedatabasewithouttheneedtoknowanydatabase-specificSQLorcommands.IfyouwanttoconvertyourapplicationtouseMySQL,forexample,provisioninganewMySQLdatabasewouldlargelybeaseasyasconvertingthewordpostgresqltomysqlinourrecipe,andthedatabase-specificadapterinthedatabasecookbookwillberesponsiblefortheimplementationdetails.
LookingatyourapplicationdeploymentcookbookOnceourdatabasehasbeenprovisioned,youcanlookathowyoucaninstallourwebapplication.Inthepythonwebapp::webapprecipe,youhavealltheinformationyouneedtodothis.Thewaythatyoudefinearecipefordeployinganapplicationwillvarywildlyamongapplications,aseachapplicationisunique.However,thisparticularexamplewasdesignedtobearepresentativeofmostwebapplications(reasonably)andshouldpresentyouwithagoodstartingpointtounderstandthebasicsofdeployingawebapplicationwithChef.
Modernwebapplicationstypicallyfollowthesamepattern:provisionauser,installaninterpreter,orotherengine(suchasPython,Ruby,andJava),createdirectoriesifneeded,checkoutthesourcecode,runanydatamigrations(ifneeded)toupdateyourdatabase,andthenmakesurethatyourserviceisupandrunning;thisisnodifferent.Themorecomplicatedyourapplication,themoreinfrastructureyoumayneedtomodel,suchasjobqueueengines,asynchronousworkers,andotherlibraries.
Ifyoulookatthewebapplicationcookbooklocatedatcookbooks/pythonwebapp,youwillseethatithasthefollowing:tworecipes,atemplate,andaPIP-requirementdefinitioninsideit.TherecipesincludedareforthewebapplicationitselfandtomanagethecreationofthePostgreSQLdatabaseanduseronthedatabasehost.
Mostoftheinterestingworkisintheapplicationrecipe,cookbooks/pythonwebapp/recipes/webapp.rb;so,let’sstartbytakingalookatthat.Allapplicationsaregoingtohaveaslightlydifferentdeploymentlogic,butmodernwebapplicationsusuallyfollowapatternthatlookslikethefollowing:
Installanysystem-widepackagesrequiredConstructthedirectoriesneededforthesoftwareIfthisisPythonorRuby,possiblyinstallanewvirtualenvtoolorRVMgemsetInstallthelibrariesneededtoruntheapplicationCheckouttheapplication’ssourcecodeBuildandconfiguretheapplicationasneededCreateorupdatethedatabaseschemaStopthewebapplicationservicesStartthewebserverorprocessmanagerthatmonitorstheapplication
Thisexampleapplicationisnodifferent,solet’slookatthestepsneededtodeploythisweb.pyapplication.First,declareanyapplicationconfigurationdataneededwiththefollowingcommand:
app_root=node[:webapp][:install_path]
python_interpreter=node[:webapp][:python]
virtualenv="#{app_root}/python"
virtual_python="#{virtualenv}/bin/python"
src_dir="#{app_root}/src/"
#Grabthefirstdatabasehost
dbhost=search(:node,"role:postgresql_server")[0]['ipaddress']
environment_hash={
"HOME"=>"#{app_root}",
"PYTHONPATH"=>"#{app_root}/src"
}
Inthissnippet,weusedthecomputedattributestotellourrecipewheretoinstalltheapplication;inthiscase,thedefaultis/opt/webappbutthiscanbeoverriddenforflexibility.Additionally,wesetthepathtothePythoninterpreterwewanttouseforourPythonvirtualenv.However,youcanjustaseasilyspecifyaRubyorJavapathifyourapplicationusedoneofthoselanguages.Thereisapathtothesourcecodeandadatabasehostaddress.ThispathisdeterminedbysearchingtheChefdataforallnodeswiththepostgresql_serverrole,takingthefirstone,andusingitsIP.Thisallowsustoreplacethedatabaseserverandnothavetoworryaboutupdatingourconfigurationdata,whichwe’llseeinabit.
PreparingthedirectoriesInordertodeployourapplication,andforittorun,weneedtohavealocationtoputourdata.Inthisapplication,wehavedefinedaneedfor:aconfigurationdirectory,alogdirectory,andaplacetoviewthesourcecode.Inourrecipe,wewillcreatethesedirectoriesandsetproperownershiptoourdeploymentuserandgroup.Notethatyoudonotneedtocreatetheapplicationrootdirectoryifitalreadyexists,andyoudonotneedtosetspecialownershiporpermissionsontherootdirectory.Becauseweareleveragingtherecursivepropertyofthedirectoryresource,therootapplicationdirectorywillbeimplicitlycreated;however,weareconstructingithereforthesakeofcompleteness.
Itiscriticalthatourdirectorieshavethecorrectownershipandpermissions;withoutthis,theapplicationwillbeunabletointeractwiththosedirectoriestostorelogdataorread-and-writeanyconfigurationdata.Thefollowingcodeconstructsthesedirectoriesforusandchangestheownershipandpermissions:
directory"#{app_root}"do
ownernode[:webapp][:user]
groupnode[:webapp][:group]
mode"0755"
action:create
recursivetrue
end
#Createdirectories
["src","logs","conf"].eachdo|child_dir|
directory"#{app_root}/#{child_dir}"do
ownernode[:webapp][:user]
groupnode[:webapp][:group]
mode"0755"
action:create
recursivetrue
end
end
Onethingtonotehereisthatweareusingalooptoconstructourdirectories.Thisisagoodwaytomanagemultipleresourcesofthesametypethathavethesamesetofconfigurationparameters.Herewearesayingthatwehavethreesubdirectories,src,log,andconf.Also,wewanttoconstructadirectoryresourceinsideofourapplication’srootdirectoryforeachsubdirectorywithproperownershipandpermissions.Therecursiveflagissimilartothe-poptiononmkdir,whichtellsittocreateanydirectoriesthataremissinginbetweentherootandthedirectorybeingcreated.
ConstructingyourPythonvirtualenvironmentThismaybenewtonon-Pythondevelopersbutshouldbefairlystraightforward.AvirtualenvironmentoperatesinasimilarwaytoRVMorrbenvforRuby,oraself-containedJARfileforJava.Inthat,itisolatesthePythoninterpreterandinstalledlibrariestoaspecificlocationonthesystem.Inourcase,wewillusethefollowingcodetoachievethis:
python_virtualenv"#{virtualenv}"do
ownernode[:webapp][:user]
groupnode[:webapp][:group]
action:create
interpreter"#{python_interpreter}"
end
Thispython_virtualenvresourcecomesfromthepythoncookbookandwillconstructavirtualenvironmentinthelocationnamedbytheresource(inourcase,thedirectorystoredinvirtualenv,whichaswesawpreviously,isdefinedasthoughinapythondirectoryinsideourapplicationroot)usingthespecifiedinterpreterandownershipproperties.
Avirtualenvironmentwillbecreated,whichcontainsaminimalinstallationofthePythoninterpreteraswellasanyPythonlibrariesthatareinstalledintothevirtualenvironment.Thinkofitasyourapplication’sowninstallationofPythonthatisunaffectedby,andsubsequentlydoesnotaffect,anyotherPythoninstallationonthesystem.
ThisisaveryusefultechniquetoinstallandmanagePythonapplications,andthesameconceptcanbeextendedtotheRailsapplicationusinganysimilartechnologyfromtheRubyworldsuchasRVMorrbenv,asmentionedearlier.
CheckingthesourcecodeOneinterestingthinginthisrecipe,whichhasbeenincludedforfuturereference,istheusageofacookbook,ssh_known_hosts,thatgrabsahost’sSSHkeyandaddsittothesystem’slistofknownSSHkeys.ThisisextremelyusefultodeploysoftwareviaGitHuborBitBucket,whereyouareusingSSHtopulldownthesourcecode,especiallyastheirhostkeysmightchange:
#NeedtoinstallSSHkeyforgithub.com
ssh_known_hosts_entry'github.com'
Notethatitisalsosomewhatinsecureasyouareblindlyacceptingthehost’sfingerprint—ifyouareconcernedaboutsecurity,youcanprovidetheknownfingerprintsmanuallyusingthe:keyattribute.Supplyingthefingerprintisdonethroughthefollowingcode:
ssh_known_hosts_entry'github.com'do
key'github.comssh-rsaAAAAB3NzaC1yc….'
end
Iftherearealargenumberofhostfingerprintsthatyouneedtomanage,oriftheychangefrequently,youcanuseadatabagtostorethem.Ifyouareinterested,lookattheREADMEforthessh_known_hostscookbookformoreexamples.
OncetheSSHkeysareregistered,youcannowclonethesourcefromagit+sshURLsuchasGitHub’sauthenticatedSSHendpoint.
Inthisexample,weareusingapubliclyavailableHTTPSsourcecoderepository;ifyouweretoreplacethiswithyourownSSH-enabledrepository,youwouldneedtochangetherepositoryattributeandalsomakesuretostoreyourdeploymentkeyontheendhost:
#Cloneourapplicationsource
git"#{src_dir}"do
repository"https://github.com/johnewart/simplewebpy_app.git"
action:sync
branch'master'
usernode[:webapp][:user]
end
Byusingthegitresource,therepositorywillbeclonedintothedesignatedsourcedirectoryontheendhost.Here,wewillalsobepullingdatafromthemasterbranchandperformingthisactionasourwebappuser.
InstallinganyextradependenciesTherearetwowaystomodeldependenciesforyourapplication:inyourcookbookandrecipe,orthroughanexternalmechanismsuchasBundler,pip,orotherdependencyresolution,andthedownloadingtooldependingonthelanguageofyourchoice.Aswitheverything,therearebothinherentdrawbacksandbenefitstoeachofthesemethods.
ManagingdependenciesinChefBymodelingyourdependenciesinChef,youhaveaconsistentmodelthatyoucanlooktoinacentralizedlocation.ThismeansthatyourapplicationneedsanewRubygem,oraPythonlibrarythatsomeonemustupdateacookbookorChefconfigurationwiththatinformationinorderforthedeploymenttobesuccessful.Thiscanlimityourabilitytocontinuouslydeploybasedsolelyonthecontentsofasourcecoderepository.Ineffect,thisrequiresyoutomodelthefollowinginChef:
DependentlibrariesLibraryversionsPossibly,thedependenciesofanydeclareddependencies(whichcanspiralquickly)
However,modelingitthiswaydoesensurethatChefhasanaccuratepictureofalltheinformationassociatedwithyourapplication.Thissolutiondoesoffersomeotherbenefits:
DependenciesarepreciselymodeledinChefandcanbequeriedbyothertoolsAnysystem-specificpackagesthatareneededforyourinterpretedlibrariesaregoingtobemodeledbyChefanyway,soit’sallinoneplace(examplescanincludenativeXMLordatabaselibraries)Developerscan’tarbitrarilychangedependenciesandaccidentallybreakdeploymentsbecausetheunderlyinglibrarieshavenotbeeninstalledinproduction
Let’slookatsomethingstothinkaboutwhenusingtoolsexternaltoChefforthistask.
ManagingdependencieselsewhereUsinganexternaltoolsuchasBundlerorpiphassomeadvantages,includingflexibilityandeaseofusebydeveloperswhomaynotbeinvolvedininfrastructureconfiguration.Italsointroducesthepossibilityofmisconfigureddependenciesandunderlyinglibraries.Theprimaryadvantageofthismechanismisthatitprovidesasimplerdependencymanagementmodelfordevelopers—simplyaddarequirementtotheGemfile,requirements.txt,orothermetadatafile,andChefwillautomaticallyinstallthemduringthenextrun.Youalsonowhavetolookintwodifferentplacestodeterminewhatisbeinginstalledonendhosts.Thisalsomeansthatyouarenowconfiguringdependenciesinmultipleplaces,increasingthepossibilityofmakingawrongconfigurationchangeinoneplace.
It’simportanttotakeawaythatthereisnotalwaysonlyonetoolforthejob,anddependingonhowyourorganizationorteamoperates,youmaychoosetomixandmatchhowyoumodeltheapplication-leveldependencies.Forthesakeofdemonstratingthembothtoyou,theapplicationcookbookmodelsthedependenciesintherecipeaswellas
througharequirements.txtfileusingpip.Additionally,youmayfindthatinitiallyyourteamusesonewayandthenmovestoanotherasyourrequirementsstopchangingsofrequently,oryouareabletocombinethemtoyouradvantage.
UsingPython’srequirementsfileOurwebappcookbookhasacustompip_requirementsdefinitionthatprovidesaneasywaytoinstallanyrequirementsstoredinsidearequirements.txtfileintoaspecifiedvirtualenvironmentusingthecopyofpipprovidedbythatvirtualenvironment.Inthefollowingcode,youwillseehowwecanachievethis:
pip_requirements"webapp"do
action:run
pip"#{virtualenv}/bin/pip"
usernode[:webapp][:user]
groupnode[:webapp][:group]
requirements_file"#{src_dir}/requirements.txt"
end
Inthisexample,wearetellingpiptorunasourapplication’suserandgroupandtoinstallthedependenciesinourrequirements.txtintothevirtualenvironmentspecifiedbyvirtualenv.Again,asimilarresourcecanbecreated(ifonedoesnotalreadyexist)toexecuteBundlerforRuby,CPANforPerl,orPEARtomanagePHPdependencies.
ConfiguringyourapplicationNowthatyouhavepreparedyoursystemforyourapplication,youneedtoconfigureit.Inorderfortheapplicationtotalktoourdatabase,youmustprovidetherequireddatabaseconnectioninformationthatwehavestoredinChef.Here,wewilluseatemplatethatisstoredintemplates/default/config.py.erb,andinjectitwithourdatabaseconfiguration.Theresourceforthislookslikethefollowingcode:
template"#{src_dir}/config.py"do
source"config.py.erb"
usernode[:webapp][:user]
groupnode[:webapp][:group]
mode"0600"
variables({
:dbname=>node[:webapp][:dbname],
:dbuser=>node[:webapp][:dbuser],
:dbpass=>node[:webapp][:dbpass],
:dbhost=>dbhost
})
end
Here,weloadourdatabaseinformationontoourtemplateandstoreitinourapplication’sinstalldirectory(wherewecheckedoutthesourceforsimplicity),andsetsomesanefilepermissions.WerethisaRailsapplication,wecanuseasimilartemplatetogeneratedatabase.ymlandmatchingsettings.yml,orifitwereaDropwizardapplication,aservice.ymlfile,aPHPINIfile,oranyothertypeofconfigurationdatathatwereneeded.Inourcase,wearesimplypopulatingthefollowingPythoncodesothatwehaveadatabaseconnectionobject:
importweb
db_params={
'dbn':'postgres',
'db':'<%=@dbname%>',
'user':'<%=@dbuser%>',
'pw':'<%=@dbpass%>',
'host':'<%=@dbhost%>'
}
DB=web.database(**db_params)
cache=False
Thepreviousexampleusestheweb.pydatabasemoduletoconstructanewdatabaseconnectionusingthehash,whichcanthenbeimportedandusedintheotherportionsoftheapplication.Again,thisisagoodstartingexampleforourweb.pyapplicationthatcanbeusedasamodelforwhateverframeworkorapplicationserveryouareusinginyoursystems.
KeepingyourapplicationrunningAllapplicationsneedtobestartedandkeptrunninginsomemanner.IfyouareusingRailswithmod_passenger,thentheApachedaemonwillbetheprimaryentrypointforyourapplication,andthissoftwarewillneedtobeinstalledandconfigured.Inourexample,wewillbeusingthesupervisordservicefromhttp://supervisord.org,whichiswritteninPythonandservesasaveryconfigurable,lightweight,andreliableprocessmanager.Youcanconfigureanentryinthesupervisordsystemconfigurationusingasupervisor_serviceresourcethatisprovidedbythesupervisorcookbookinstalledearlier:
supervisor_service"webapp"do
action[:enable,:restart]
autostarttrue
usernode[:webapp][:user]
command"#{virtual_python}#{src_dir}/server.py#
{node[:webapp][:port]}"
stdout_logfile"#{app_root}/logs/webapp-stdout.log"
stdout_logfile_backups5
stdout_logfile_maxbytes"50MB"
stdout_capture_maxbytes"0"
stderr_logfile"#{app_root}/logs/webapp-stderr.log"
stderr_logfile_backups5
stderr_logfile_maxbytes"50MB"
stderr_capture_maxbytes"0"
directorysrc_dir
environmentenvironment_hash
end
Thepreviousexamplewillgenerateaconfigurationfileforsupervisordwiththesettingsspecifiedinourresourceblock.Unlessyouchangethelocation,theconfigurationfileswillbelocatedin/etc/supervisor.d/[service_name].conf.Inourcase,theserviceisnamedwebapp,anditsconfigurationfilewillbe/etc/supervisor.d/webapp.conf.
Here,wearetellingsupervisordthatwewanttoenableourserviceandthenrestartit(whichwillstartitifit’snotcurrentlyrunning),wherewewanttologtheprocess’soutput,howwewanttorotatethoselogfiles,wheretostartourprocess,whatenvironmentvariablestouse,andmostimportantlywhatcommandtoexecute.
Nowthatwe’velookedatourrecipes,let’sgoaheadandsetupourroles,provisionsomesystems,anddeployourapplication!
DefiningrolesHerewewillconstructourthreeroles,oneeachforourbaseserverconfiguration,databaseserver,andwebserver.Eachrolewillhaveasetofrecipestorun,withthebaseserverprovidingtheuseraccounts,SSHkeys,andothercommoncomponents,andthentheothersprovidingconfigurationdataforPostgreSQLandnginx,respectively.
CreatingthebaseserverroleThekeybitsthatareofinterestinourbaseserverrolearetherunlistandtheconfigurationdatathatspecifywhichgrouptopopulateusersfor.Ifyoulookatthefileroles/base_server.json,youwillseethatwehavedefinedonegroupofuserstopullfromourdatabags:
"override_attributes":{
"shell_users":{
"group":"webapp"
}
}
Andthen,therecipewewanttousethatwillpopulatetheusersonthehostisintherunlist:
"run_list":[
"users::shell_users"
],
InordertoloadtheroleintoChef,youcanissueafromfilecommand:
kniferolefromfilebase_server.json
UpdatedRolebase_server!
Youcanverifythattherolewascreatedwithasimplerolelistcommand:
[user]%kniferolelist
base_server
CreatingthedatabaseserverroleLet’stakealookatsomeportionsofourdatabaseserverrole,asdefinedinroles/postgresql_server.json.ThisfilecontainsthedescriptionofourPostgreSQLserverasmodeledearlierinthechapter.Whatisofinterestinthisfileisouroverride_attributessection;thesearesettingswewanttouseinplaceofthedefaultvaluesprovidedbyourpostgresqlcookbook.Asmentionedbefore,youwillwanttolookatthedocumentationandthedefaultattributes.rbfiletofindoutwhatattributesyoucansetforagivencookbookanditsrecipes.
ThePostgreSQLrecipesuseapostgresqlconfigurationsectionthatcontainsaconfigsectionforserver-specificconfigurationandproperties,andpg_hbafortheauthenticationdata.Lookingatthepostgresqlsection,wecanseethatwewanttoinstallVersion9.3,andwewantittolistenonalladdresses(0.0.0.0)onport5432:
"version":"9.3",
"config":{
"listen_addresses":"0.0.0.0",
"port":"5432"
},
Inaddition,thepg_hbasectioncontainsanarrayofJSONobjectsthatdescribeswhichusershaveaccesstotheservice,bywhatmechanismtheyareabletoauthenticatethemselves(MD5,trusted,localidentservice,andsoon),andfromwheretheycanconnect.Thisiscodedintoourconfiguration,buttherecipescanbeextendedtousedatabagstodeterminethisinformationaswell.Itistoolongtoincludeallofithere,butifyoulookatthepg_hbadata,youwillseethattherearethreeentries:onefortheuserwebusertoconnectfromanywhereusinganMD5password,andtwoforlocaluserstoaccessthedefaulttemplatedatabase,andforthepostgresuseritselftomodifythewebappdatabase.
Inadditiontotheconfigurationdata,thereisarunlist—thistellsChefwhatrecipesthisrolewillinclude.YoucanseefromthisexamplethatwewillbeinstallingthePostgreSQLserverandthenprovisioningourwebapp-specificuseranddatabase(foundincookbooks/pythonwebapp/recipes/database.rb):
"run_list":[
"postgresql::server",
"pythonwebapp::database"
],
Inordertousethis,wewanttoloadourJSONfileintoCheftodefineourdatabaseserverrole:
kniferolefromfilepostgresql_server.json
UpdatedRolepostgresql_server!
Youcancheckthattherolewascreatedwitharolelistcommand:
[user]%kniferolelist
base_server
postgresql_server
CreatingthewebserverroleOurwebapplicationroleislocatedintheroles/web_server.jsonfileandcontainstherequiredinformationforourwebserver.IfyoutakealookattheJSONfile,youwillseethattherunlistcontainsfourentries:
"run_list":[
"python",
"python::virtualenv",
"supervisor",
"pythonwebapp::webapp"
],
BecauseourapplicationreliesonPython,wewanttoinstalltherequiredversionofPythononourhostsaswellasbuildaPythonvirtualenvtoolforourapplication.Inaddition,wewillbeusingsupervisordastheprocessmonitorthatisresponsibleforensuringthatourwebservicestartsandstaysrunning.Wealsoneedtoinstallourwebapplicationoncewehavemetourprerequisites.
SimilartohowweloadedthePostgreSQLrolefromourJSONfile,wecanrepeattheprocessforourwebserverrole:
kniferolefromfileweb_server.json
UpdatedRoleweb_server!
Again,youcancheckthattherolewascreatedwithasimplerolelistcommand:
[user]%kniferolelist
base_server
postgresql_server
web_server
AddingusersWewillneedarecipetomanageourusers;here,wewillusetheuserscookbook.Wewillcreateoneuser,webuser,whichwillbetheaccountthatisusedfordeploymentanduserconnectivity.WewilldefineouruserinaJSONfilesimilartowhatwedidinthepreviouschapter;placethefollowinginausers/webuser.jsonfile:
{
"id":"webuser",
"uid":"1000",
"gid":1000,
"shell":"/bin/bash",
"comment":"Webappdeploymentuser",
"groups":["webusers"]
}
Then,youcanloadthisuserusingthefromfilecommand:
knifedatabagfromfileusersusers
Ensurethatyourhostshaveournewusersbyeditingthebase_serverroleandaddingourwebusersgroupsothatanyusersinthatgroupwillbeprovisionedonallourserversthatincorporatethebase_serverrole:
{
"shell_users":{
"group":"webusers",
}
}
ProvisioningEC2instancesHere,wewillbeprovisioninginstancesinus-west-1,butdependingonwhereyouhaveyourAWSinstancessetup,youwillneedtochangeyourknife.rbconfigurationtospecifytheregionofyourchoice.
Inorderforthemtocommunicatesecurely,wewillconstructasecuritygroupsothatalltrafficbetweenthemispermitted.Thisisoutsidethescopeofthisbook,butitwouldbesomethingtomakesureyouconfigureforproductionsystems,asyouprobablydonotwantthepublicontheInternettohavedirectaccesstoyourdatabaseserver.
Here,wewillassumethatyouhaveyourAWScredentialsandothercriticalcomponentsconfigured,aswecoveredinpreviouschapters.
Toprovisionourdatabaseserver,wewillusethefollowingcommand:
knifeec2servercreate-dubuntu14.04-Iami-ee4f77ab-fm1.small-Zus-
west-1a-Sjewartec2-Ndb00--ssh-userubuntu
Andtoprovisionthewebserver,wewillusethefollowingcommand:
knifeec2servercreate-dubuntu14.04-Iami-ee4f77ab-fm1.small-Zus-
west-1a-Sjewartec2-Nweb00--ssh-userubuntu
Onceyourinstancesareupandrunning,youcannowmoveontoconfiguringthemwiththerolesandconfigurationdatarequired!
ConfiguringthedatabasehostInordertoapplythePostgreSQLroletoourdatabasehost,weneedtomakesureit’sintherunlist.Wecanaccomplishthiswiththefollowingcommand:
knifenoderun_listadddb00"role[base_server]"
knifenoderun_listadddb00"role[postgresql_server]"
Afterensuringthatyournodehasthebase_serverandpostgresql_serverrolesaddedtotherunlist,youcanrunchef-clientonthenewlycreatedhost:
[jewart]%knifessh'name:db00'-xubuntu'sudochef-client'
Oncethisiscomplete,assumingthateverythingwentwell,yournewEC2instancewillhave:
PostgreSQL9.3serverinstalledandrunningAnewdatabase(thenameofwhichisdefinedfromyourconfiguration)AdatabaseuserthatisgrantedpermissiontoconnectCorrectpg_hba.confandpostgresql.conffilesforourservice
Nowthatwehaveourdatabaseserverconfigured,whichcanbeverifiedbyloggingontotheserverandensuringthattheserviceisrunning,let’stakealookatsettingupourwebapplication.
ConfiguringthewebserverInorderforthewebservertodeploythewebapp,weneedtoaddtherequiredrolestothewebserver,aswedidwiththedatabaseserver:
knifenoderun_listaddweb00"role[base_server]"
knifenoderun_listaddweb00"role[web_server]"
Now,wecanexecutechef-clientonthewebhost(againmakingsuretousesudosothatithaspermissiontodoitswork):
[jewart]%knifessh'name:web00'-xubuntu'sudochef-client'
Atthispoint,ourwebserverwillbeinthefollowingstate:
Thefollowingrequiredpackageswillbeinstalled:
Python2.7anddevelopmentlibrariesThePostgreSQLclientdevelopmentlibrariesGit
ThedirectoriesourapplicationneedstorunarecreatedAvirtualenvtool,whichisbasedonthesystemPython2.7iscreatedOurapplicationhasbeencheckedoutfromGitHubAconfigurationfilein/opt/webapp/src/config.pyiscreatedbyChefSupervisordisconfiguredtorunourapplicationandstartstheserver.pydaemon
Now,youshouldbeabletovisityournewlyinstalledwebapplicationatthefollowingURL:http://your-new-ec2-instance-hostname:8080
Ifyoudon’tseeyourapplication,makesurethateachoftheprecedingstepswassuccessful.
DeployingyoursoftwareDeployingsoftwareshouldbetreatedjustlikeyourinfrastructure;repeateddeploymentsofthesamecommitandthesameconfigurationshouldyieldaconsistentstateofyourenvironment.Inthisexample,wewillbeabletodeploynewupdatestoourwebapplicationsimplybyupdatinganynodesthatusetheweb_serverrole.ThecombinationofourrecipeandourconfigurationdatawiththesourcecodehostedinourGitHubrepositorywillensurethatthemostup-to-dateconfigurationandsourceareplacedonourhost.
ManuallydeployingupdatesFuturedeploymentsonlyrequirepushingchangestothemasterbranchandthenrunningchef-clientonanywebserversthatareinthefleet.Thiscanbeaccomplishedonasinglehost(web00)usingthefollowingcommand:
knifessh'name:web00'-xubuntu'sudochef-client'
ThiswilltellknifethatwewanttoSSHtothehostwhosenameisweb00astheubuntuuser(becausethat’sthedefaultEC2userwithsudoaccess)andexecutechef-clientasrootviasudo.Thiswillworkwellifweonlyhaveonehost;however,asyourcapacityincreasesandyouhavemultiplehosts,youwilllikelywanttoexecutethisonagroupofhostsinthefuture.ThiscanbeaccomplishedusingthesearchcapabilityofChefthatallowsyoutoexpandalistofnodesthatmatchasetofcriteria.Here,wewillwanttobuildalistofallthehoststhathaveourweb_serverroleassociatedwiththem.Thefollowingcommandwillaccomplishthis:
knifessh'role:web_server'-xubuntu'sudochef-client'
ThiswilluseChef’ssearchtofindallnodeswiththeweb_serverroleandthenSSHtothemsequentially,thesameasbeforebutonlyacrossmultiplehostsinsteadofjustone.
AutomatingdeploymentThewebapplicationrecipeisdesignedsothatitsyncsthesourcewiththeupstreamGitHubrepository.Bydoingthis,wecanexecutetherecipemultipletimes,andanytimethereareupdates,theywillbepulleddownontothelocalhost.Ifwewantedto,theprocessofdeploymentscouldbeautomatedinthefollowingfashion:
Activedevelopmentoftheapplicationhappensinaseparate,developmentbranchCodeistestedthoroughlyandthenmergedintoamaster(orwhateverbranchisbeingdeployedontohosts)whenitisstableandreadyforproductionHostsareconfiguredtorunchef-clientonafixedintervalusingatoolsuchascronandwillautomaticallyupdatethemselves
Thepossibleissueswiththisarethatbadcodegetsautomaticallydeployedtoendhostsandsoon.However,withenoughintegrationtestingandahighenoughconfidencelevel,ourcodeshouldbesafetodeploytoproductionifitisinthemasterbranch.Throughacombinationoftagsandpropersourcemanagement,rollbackscouldbeassimpleasrevertingthedeploybranchtoaknown-goodtagandtheywouldhappenassoonasthenextchef-clientexecutionorforcedusingknifeasoutlinedpreviously.
SummaryAmajorattractiontoChefandinfrastructureautomationistheabilitytodeploysoftwareandprovisionsystemsquicklyandconsistently.Usingthecookbooksandexamplesoutlinedinthischapter,youshouldbeabletomodelyourapplicationanditscomponents,gathercookbooksrequiredtodeployneededsoftware,buildcookbookstoconfigureanddeploycustomsoftware,andextendtheexamplestoprovidemorefunctionalitiesorenhanceyourinfrastructure.
Nowthatyouhaveseenhowtotakeanapplicationfromdevelopmenttodeployment,let’stakealookatsomemoreadvancedexamplesofcookbookdevelopment,includingwritingcustomprovidersandresources,workingwithsecuredata,searchingChef,andotherwaysofenhancingourrecipesandcookbooks.
Chapter7.BeyondBasicRecipesandCookbooksSofar,wehaveonlyreallylookedathowtousecookbooksasaconsumer,notasaproducer.InordertoharnessthetruepowerofChef,itisimportanttolearnhowtobuildourowncookbooksandrecipesusingthefullfeaturesetthatChefprovides.Thischaptercovers:
AdvancedrecipeconceptsManagingyourdatausingdatabagsSearchingCheffromrecipesAdvancedscriptinginrecipesAuthoringcustomproviders,resources,anddefinitionsDealingwithencrypteddata
ManagingusersBasicusermanagementinChefisachievedthroughtheuseoftheuserresource.Thisresourceallowsyoutoadd,remove,orotherwisemanipulateusersonyourhosts.However,youcan’tpossiblywriterecipesthatcontainoneresourceperuser;itsimplywouldn’tscale.Inordertomakelarge-scaleusermanagementeasier,wecancombinesomeofChef’scapabilitiessuchasdatabags,per-role,per-node,andper-environmentconfigurationtoenablescalableusermanagement.
Let’stakealookatausercookbookthatcanprovidetheseabilities.
EvolutionofashelluserrecipeFirst,let’stakealookataverynaiveusermanagementrecipe.Thiscookbookhasahardcodeduserslist;initially,itcontainsfrodoandsamwiseandsimplyiteratesthroughthelist,creatingusersasitgoes.Hereiswhatthelistmaylooklike:
users=[
{
'id'=>'frodo',
'uid'=>'100',
'gid'=>100,
'shell'=>'/bin/hobbitshell',
'comment'=>'Frodooftheninefingers'
},{
'id'=>'samwise',
'uid'=>'101',
'gid'=>101,
'shell'=>'/bin/gardenshell',
'comment'=>'Samwisethestrong'
}
]
users.eachdo|u|
home_dir="/home/#{u['id']}"
useru['id']do
uidu['uid']
gidu['gid']
shellu['shell']
commentu['comment']
supports:manage_home=>true
homehome_dir
end
end
Thisapproachwillworkforahandfulofusers,butithastheproblemofbeingverylimitedinscopeanddifficulttomaintain.Italsoisolatesthelistofuserstothisrecipe,makingitdifficulttoaccessdatafromotherrecipesandverybrittle.Thefirstthingwecanimproveismaketheusersaccessibletothisandanyotherrecipethroughtheuseofdatabags.Let’stakealookathowwecanusedatabagstomakeuserdatamanagementsimplerandmoreflexible.
StoringdataindatabagsDatabagsaredesignedtostorearbitraryconfigurationdatathatpertainstoyourentireinfrastructure.Thismayincludeusers,globalsettings,firewallrules,andsoon;ifitcanbemodeledusingbasicJSONdatastructuressuchasarraysanddictionaries,itcanbeincludedinadatabag.Wehaven’ttouchedmuchontheseyet,sonowisagoodtimetotakealookatwhattheycandowhilemodelingusers.
CreatingadatabagforusersDatabagsarecollectionsofdatathatarerelatedtooneanother;forexample,users,firewallrules,databaseservers,andsoon.Herewewillcreateadatabagthatcontainsouruserdata.ThisisnotintendedtobeareplacementforadirectoryservicesuchasLDAP,thoughyoucouldpotentiallyuseittostoreallyouruserdataandthenwriterecipestopopulateanLDAPserverwithuserdata(inthisway,youmaybeabletokeepanActiveDirectorysystemandaseparateLDAPsysteminsyncbymakingyourChefdatabagtheauthoritativesourceforuserdata).Let’stakealookathowtocreateandmanipulateadatabagwithuserinformation:
[jewart]%knifedatabagcreateusers
Createddata_bag[users]
Now,createanewuser,frodo(youwillneedtohavetheEDITORvariablesettoatexteditorsuchasvimonLinuxsystems):
[jewart]%exportEDITOR=vim
[jewart]%knifedatabagcreateusersfrodo
Databagusersalreadyexists
Youwillbepresentedwithanewentitytemplatethatcontainsonlyonekey,id,whichissettothenameoftheentityyoucreated;inourcase,frodo:
1{
2"id":"frodo",
3}
Savethisfileandyouwillnowhaveone,mostlyempty,entityinyourusersdatabagnamedfrodo.Youcancheckthiswiththeshowsubcommand:
[jewart]%knifedatabagshowusers
frodo
Everyiteminadatabaghastohaveauniqueidentifier,whichcanbemeaningfulorjustarandomidentifier;inourcase,itwilldoubleupastheloginnamefortheuser.WecantakeourpreviousdatafromtherecipeandconvertthattodatabagelementsbywritingthemtoJSONfilesanduploadingthemwithknife.Totakeadvantageofuploads,wecancreateadirectory,users,andcreateoneJSONfileperentry:
{
"id":"frodo",
"uid":"100",
"gid":100,
"shell":"/bin/hobbitshell",
"comment":"Frodooftheninefingers"
}
{
"id":"samwise",
"uid":"101",
"gid":101,
"shell":"/bin/gardenshell",
"comment":"Samwisethestrong"
}
Onceyouhavecreatedthese,youshouldhavetwofiles,frodo.jsonandsamwise.jsoninsideausersdirectory.Inordertobulkuploadthem,weuseaknifedatabagfromthe<dir><databagname>fileinthefollowingmanner:
[jewart]%knifedatabagfromfileusersusers
Updateddata_bag_item[users::frodo]
Updateddata_bag_item[users::samwise]
Youcanverifywhethertheentrieswerecreatedcorrectlywiththeknifedatabagshow<databag><entity_id>command:
[jewart]%knifedatabagshowusersfrodo
comment:Frodooftheninefingers
gid:100
id:frodo
shell:/bin/hobbitshell
uid:100
SearchingfordataNowthatwehaveourdatainadatabaginChef,wecansearchforitusingthesearchcriteria.Forexample,ifwewantedonlyalluserswhosenamesstartwiththeletters,wecansearchwiththefollowingcommand:
[jewart]%knifesearchusers'id:s*'
1itemsfound
chef_type:data_bag_item
comment:Samwisethestrong
data_bag:users
gid:101
id:samwise
shell:/bin/gardenshell
uid:101
Alternatively,ifwewantedalltheusersinagivendatabag,wecanperformthefollowingsearch:
[jewart]%knifesearchusers'id:*'
2itemsfound
chef_type:data_bag_item
comment:Frodooftheninefingers
data_bag:users
gid:100
id:frodo
shell:/bin/hobbitshell
uid:100
chef_type:data_bag_item
comment:Samwisethestrong
data_bag:users
gid:101
id:samwise
shell:/bin/gardenshell
uid:101
SearchinginsiderecipesNowthatwehavesomedatabagdatacreatedandcanperformbasicsearches,let’sseehowwecanusethattoenhanceourrecipeusingthebuilt-insearchmethod.Thisallowsustoperformthesearcheswejustranwithknifeinsideourrecipes.Thesearchmethodhasasimilarformattotheknifecommand:
search(search_scope,search_criteria)
Thefollowingaresomesimpleexamples:
all_users=search(:users,'id:*')
users_s=search(:users,'id:s*')
all_nodes=search(:node,'*')
Withthis,wecanenhanceourshelluserrecipetousetheentitiesintheusersdatabagratherthanhardcodethem.Ournewrecipewouldlooklikethefollowing:
#Replacethehard-codedusersarraywithasearch:
users=search(:users,'id:*')
#Sameasbefore,we'vejustmovedourdatasource
users.eachdo|u|
home_dir="/home/#{u['id']}"
useru['id']do
uidu['uid']
gidu['gid']
shellu['shell']
commentu['comment']
supports:manage_home=>true
homehome_dir
end
end
Thisisjustasimplesearch;thiswillworkforasmall-scaleinfrastructurewithafixedsetofusers,wherethere’snoneedtorestrictcertaingroupsofuserstocertainhosts.Youcaneasilyimagine,however,asituationwheresomeusersareprovisionedonlytocertainhoststhroughgroups.Let’slookathowwecanachievethiswithsomebetterusermetadataandamoreadvancedsearch.
EnhancingyourusercookbookInourpreviousexample,weusedthesearchmethodtofindalloftheusersinouruser’sdatabag.Herewewillgoonestepfurthertoisolateusersbasedonarbitrarygroupsandseehowwecanlimitthelistofuserstobeprovisionedusingacombinationofsearch,usermetadata,andnodeconfiguration.
First,weneedtoaddagroupskeytoourusers.Let’saddthattoourexistinguserJSONdatafilesandaddafewmoreusers,legolasandgimli:
{
"id":"frodo",
"uid":100,
"gid":100,
"shell":"/bin/hobbitshell",
"comment":"Frodooftheninefingers",
"groups":["hobbits","fellowship"]
}
{
"id":"gimli",
"uid":201,
"gid":201,
"shell":"/bin/csh",
"comment":"Grumpyolddwarf",
"groups":["dwarves","fellowship"]
}
{
"id":"legolas",
"uid":200,
"gid":200,
"shell":"/bin/zsh",
"comment":"KeeneyedLegolas",
"groups":["elves","fellowship"]
}
{
"id":"samwise",
"uid":"101",
"gid":101,
"shell":"/bin/gardenshell",
"comment":"Samwisethestrong",
"groups":["hobbits","fellowship"]
}
Onceagain,weupdatetheexistingrecordsandcreateournewrecordsusingknifedatabagfromfile:
[jewart]%knifedatabagfromfileusersusers
Updateddata_bag_item[users::frodo]
Updateddata_bag_item[users::gimli]
Updateddata_bag_item[users::legolas]
Updateddata_bag_item[users::samwise]
Nowthatyouhaveafewadditionalusersinyourdatabag,andeachuserhassomegroupmetadataattachedtoit,let’stakealookathowwecanusethistoprovisiononlycertainusersonspecifichosts.First,weneedtobeabletolimitoursearchscopedynamically;otherwise,wewillneedtomodifyourrecipeonaper-hostbasisandthatjustwon’tscale.Weneedtoaddadynamicsearchquerytoourrecipewithsomethinglikethefollowingcode:
search_criteria="groups:#{node[:shell_users][:group]}"
Thiscreatesasearchcriteriastringthatwillmatchobjectsthathavethevaluespecifiedsomewhereintheirgroupskey.Inordertomakethisdynamicperhost,wewillstorethisvalueinashell_usershashunderthegroupkey.Forexample,ifyouwantedtoaddallusersthatareinthehobbitsgrouptoaspecificnode,thenyournode’sconfigurationwouldneedtocontainthefollowing:
{
"shell_users":{
"group":"hobbits",
}
}
Thiswillbuildasearchcriteriaof"groups":"hobbits",whichifwepasstothesearchmethodwillyieldallentriesintheusersdatabagthathave"hobbits"insidetheirgroupslist.Considerthefollowingrecipecode:
users=search(:users,search_criteria)
Thenodeconfigurationdatawillexpandthesearchcriteriaduringanexecutiononthisnodetobethefollowing:
search_criteria="groups:hobbits"
Giventhedatawehavestoredinourusersdatabag,thiswouldmatchsamwiseandfrodoastheyhavethehobbitsgroupintheirgroupslist.Wecanverifythisbytryingthesamesearchonthecommandlinewithknife:
[jewart]%knifesearchusers"groups:hobbits"
2itemsfound
chef_type:data_bag_item
comment:Frodooftheninefingers
data_bag:users
gid:100
groups:
hobbits
fellowship
id:frodo
shell:/bin/hobbitshell
uid:100
chef_type:data_bag_item
comment:Samwisethestrong
data_bag:users
gid:101
groups:
hobbits
fellowship
id:samwise
shell:/bin/gardenshell
uid:101
Asyoucansee,thisallowsustonarrowlydefinethelistofuserstobemanagedthroughthecombinationofentitymetadataanddynamicsearchcriteria.Youcanbuildmoreadvancedapplicationsusingthismethodologywithmoreadvancedsearchcriteriaandincorporatingmoreoftheentities’metadata.
DistributingSSHkeysInadditiontomanaginguseraccounts,wecanalsouseCheftomanageSSHkeys.Becauseagivenuser’sacceptedSSHkeysarestoredinaper-userconfigurationfile,itisquitesimpletomanipulatethem.BycreatingatemplateforSSH-authorizedkeys,wecanbuildarecipethatwilltaketheSSHkeydatafromthedatabagandpopulatetheauthorizedkeysfileonthehost.Bydoingthis,users’SSHkeyscanbestoredinChefanddistributedtoanynumberofhostswithjustonecommand.ThissolvestheproblemstypicallyassociatedwithdistributionandrevocationofSSHkeysinsideanorganization.
TemplatingtheauthorizedkeysHereisasampletemplatewewilluseforouruser’sauthorizedkeysfile;thiswouldbedefinedinanauthorized_keys.erbfile:
<%if@ssh_keys.is_a?(Array)%>
<%=@ssh_keys.join("\n")%>
<%else%>
<%=@ssh_keys%>
<%end%>
Thisisaverysimpletemplatethathasonlytwocases:ifthetemplatevariablessh_keysisanarray,itwillprintthemoutwithanewlineinbetweenthem;otherwise,itwillsimplyprintoutthecontentsofthevariable.
Tousethistemplate,wewillsimplyprovideitwithalistofSSH-compatiblekeystrings:
template"#{home_dir}/.ssh/authorized_keys"do
source"authorized_keys.erb"
owneru['id']
groupu['gid']||u['id']
mode"0600"
variables:ssh_keys=>u['ssh_keys']
end
Now,wecanmodifyoneofourprevioususerJSONentitiestoaddSSHkeys:
{
"id":"frodo",
"uid":100,
"gid":100,
"shell":"/bin/hobbitshell",
"comment":"Frodooftheninefingers",
"groups":["hobbits","fellowship"],
"ssh_keys":[
"ssh-dss
RG9uJ3Qgd29ycnksIFNhbS4gUm9zaWUga25vd3MgYW4gaWRpb3Qgd2hlbiBzaGUgc2VlcyBvbmU
ufrodo@shire",
"ssh-dss
TXkgbWFzdGVyLCBTYXVyb24gdGhlIEdyZWF0LCBiaWRzIHRoZWUgd2VsY29tZS4gSXMgdGhlcmU
gYW55IGluIHRoaXMgcm91dCB3aXRoIGF1dGhvcml0eSB0byB0cmVhdCB3aXRoIG1lPyA=
sauron@mordor"
]
}
%knifedatabagfromfileusersssh_keys/frodo.json
Updateddata_bag_item[users::frodo]
Onceyouruserhasbeenupdated,checkwhetheryournewlyaddedmetadatahasbeenupdated,lookingforyournewssh_keyskeyintheentity.Inordertodothat,youcanshowthecontentsofyourdatabagusingthefollowingcommand:
%knifedatabagshowusersfrodo
TheoutputofthisshouldlineupwithyournewlyupdatedJSONcontent.Withtheseadded,wecanwriteanewrecipethatwillallowustodeployauthorized_keysfilesforeachuseronourhosts.OurrecipewillusethesamesearchcriteriafromourpreviousrecipeaswewanttoapplyourSSHkeystoallofourshellusers.
ThisrecipeisresponsibleformakingsurethattheproperdirectoryforSSHiscreatedandhasthecorrectpermissions,aswellascreatingtheauthorized_keysfilewiththenecessarypermissionsandstoringtheSSHkeysassociatedwiththeuserin/home/user/.ssh/authorized_keys:
search_criteria="groups:#{node[:shell_users][:group]}"
search(:users,search_criteria)do|u|
home_dir="/home/#{u['id']}"
directory"#{home_dir}/.ssh"do
owneru['id']
groupu['gid']
mode"0700"
recursivetrue
end
template"#{home_dir}/.ssh/authorized_keys"do
source"authorized_keys.erb"
owneru['id']
groupu['gid']
mode"0600"
variables:ssh_keys=>u['ssh_keys']
end
end
AddingdeploymentkeysIfyouhaveeverdeployedaRailsapplicationtohoststhatneedtohaveaccesstoyoursourcecodeinaGitHuborBitBucketrepository,thenyouwillknowhowhandyitistomanagedeploymentkeysacrossafleetofhosts.Wecaneasilygeneratearecipethatlooksatanode’slistofdeploymentusersfollowingourpreviousexamplesasastartingpoint.Here,welookfordeployusersinsteadofshellusers,asthesearetheoneswewanttomanagedeploymentkeysfor.Notethatinthisexample,theseuserswouldalsoneedtobeincludedintheshell_usersgrouptoensurethattheygetcreatedbyourpreviousrecipe:
search_criteria="groups:#{node[:deploy_users][:group]}"
search(:users,search_criteria)do|u|
home_dir="/home/#{u['id']}"
directory"#{home_dir}/.ssh"do
owneru['id']
groupu['gid']||u['id']
mode"0700"
recursivetrue
end
template"#{home_dir}/.ssh/id_rsa"do
source"deploy_key.erb"
owneru['id']
groupu['gid']||u['id']
mode"0600"
variables:key=>u['deploy_key']
end
end
Tousethisnewrecipe,thedeploymentuserswouldneedtobemodifiedtoincludeagroupidentifierandtheirprivatekey.Thegroupwouldbereservedforusersinvolvedindeployingyourapplicationandbeaddedtotheuser’sgroupskeyinChef.Additionally,anunencryptedSSHprivatekeywouldneedtobepresentinadeploy_keyfield.
TipIncludingunencryptedSSHkeyscanposeasecurityrisk.Thiscanbemitigatedusingencrypteddatabagsoranexternalsecuritymaterialmanagementservice.
WritingcustomextensionsWithChef,youaregivenimmediateaccesstoanumberofresources:files,users,packages,templates,andsoon.However,therewillalwaysbetimeswhenthisdoesnotprovideyouwitheverythingthatyouneed.Fortunately,thebuilt-inChefresourcesorLWRPs(light-weightresourceproviders)arejustRubycodeandwerebuiltwiththeintentionofprovidingaframeworkforenduserstobuildtheirowncomponents.Thismeansthatyoucaneasilybuildyourowncustomresources,andthesecanbesharedwithothersjustlikeanybuilt-inLWRP.
DevelopingacustomdefinitionOneofthesimplestresourcesthatwecanbuildisadefinition—adefinitionislikearesourcewithonlyonebuilt-inprovider.Thesecanbethoughtofasreusablemodulesthatyoucanleverageinsideofyourrecipes.Ifyoufindyourselfwritingthesamethingrepeatedlyinyourrecipes,thenitisprobablyagoodcandidatetowriteacustomdefinition.Forexample,let’slookathowwecanbuildtwodifferentdefinitions:oneforexecutingPython’sPIP,thePythonpackageinstallationtool,toinstallthecontentsofarequirements.txtfileforaPythonapplication,andanothertoinstallapplicationsthatfollowthesamepattern.
OrganizingyourcodeAsdiscussedbefore,cookbookscanhaveadefinitionsdirectoryinsidethem.Thecontentsofthisdirectoryareincludedinyourcookbookrunsandshouldhaveoneperdefinition.ForourPIPresource,wewillcreateafile,definitions/pip_requirements.rb,andforourapplicationtemplate,definitions/python_web_application.rb.Thesewilleachcontaintherespectivedefinitions.
WritingadefinitionforusingPIPDefinitionslooklikeanyChefcomponent—theyarecomposedofresources,variables,scripts,andanythingelseyouuseinarecipe.However,unlikearecipe,theyaredesignedtobereused.Wherearecipeisdesignedwithaspecificeffectinmindsuchasdeployingaspecificapplication,thedefinitionisdesignedtobeconsumedbyrecipestoreduceduplicatecode.
Eachdefinitionisencapsulatedinadefineblock,ano-opversion,orourPIPexamplewouldlooklikethis:
define:pip_requirementsdo
end
Thisexampledoesabsolutelynothing,butitcanbeusedinarecipeasfollows:
pip_requirements"app_requirements"do
end
Justinthesamewayyouwoulduseafile,user,ortemplateblockinyourrecipe,youcanuseyourcustomdefinitions.Now,let’senhanceourdefinitionbyusingthenameparameter—thestringargumentpassedtothepip_requirementsblockinyourrecipe;here,itisapp_requirements:
define:pip_requirements,:action=>:skipdo
name=params[:name]
end
Eachinvocationofadefinitionpassestheparametersintheblocktothedefinition;theseareaccessedinsidethedefinitionthroughtheparamshash.Thereisonespecialparameter,:name,whichcancomefromthefirstargumentbeforetheblock,asshowninthepreviouscode,orfromthenameparameterinsidetheblock.Thisisaconvenienceparameter
designedtomakerecipesmorereadablebyallowingthedevelopertowrite:
resource"somehandydescription"do
...
end
Thiscodeiseasiertoreadthan:
resourcedo
name"somehandydescription"
end
Giventhisinformation,let’slookatthePIPexamplefrompip_requirements.rb:
define:pip_requirements,:action=>:skipdo
name=params[:name]
requirements_file=params[:requirements_file]
pip=params[:pip]
user=params[:user]
group=params[:group]
ifparams[:action]==:run
script"pip_install_#{name}"do
interpreter"bash"
user"#{user}"
group"#{group}"
code<<-EOH
#{pip}install-r#{requirements_file}
EOH
only_if{File.exists?("#{requirements_file}")andFile.exists?("#
{pip}")}
end
end
end
Here,thedefinitionexpectsfivearguments:theresourcename,thepathtotherequirements.txtfile,thepipbinarytouse,aswellastheuserandgrouptoexecutepipas.ThereasonthattheresourceacceptsthepathtopipistoallowusingpipinsideaPythonvirtualenvironment.Bydoingthis,thedefinitionbecomesalittlemoreflexibleinsituationswhereyouneedtoinstallyourrequirementsintoadifferentlocationonthesystem.
Alsonotethatwecandefinedefaultparametersaspartofthedefinition’ssignature:
define:pip_requirements,:action=>:skipdo
Inthiscase,thedefaultactionis:skip,butitcanbesettoanythingyouwantittobe.Hereitissetto:skipsothatitonlygetsinvokeddeliberatelyratherthanbyvirtueofbeingusedinarecipe.
Asthisisasimpledefinition,itonlycontainsoneresource—ascriptblockthatwilleffectivelyexecutepipinstall-r/path/to/requirements.txtasthespecifieduserandgroup.Anexampleuseofthisdefinitioncanbeseenasfollows:
pip_requirements"requirements"do
action:run
pip"/usr/local/bin/pip"
usernode[:app][:user]
groupnode[:app][:group]
requirements_file"#{app_root}/src/requirements.txt"
end
Thiscanbeusedinplaceofthebuilt-inscriptresource:
script"pip_install_#{name}"do
interpreter"bash"
usernode[:app][:user]
groupnode[:app][:group]
code<<-EOH
/usr/local/bin/pipinstall-r#{app_root}/src/requirements.txt
EOH
only_if{
File.exists?("#{app_root}/src/requirements.txt")and
File.exists?("/usr/local/bin/pip")
}
end
FollowingChef’sdeclarativelanguage,buildingdefinitionssuchasthisonemakesitmoreobviousastowhatishappening,ratherthanhowitishappening.Wehaveabstractedtheshellscriptandguardtestsbehindafaçade,thatisthepip_requirementsdefinition,whichismoreclearinitseffectwhenyoureadarecipe;youdon’tneedtoexaminethecontentsofthescriptblocktodeducewhatishappeningastheresourcenametellsyouexactlywhat’sgoingtobedone.
DefiningafullapplicationtemplateIfyouhaveapplicationsthatfollowthesamestructure(thinkapplicationsthatuseacommonframeworksuchasRails,Django,Pyramids,Python-tornado,andsoon),thenyouwouldlikelywanttodefineacommondefinitionforwhatsuchanapplicationlookslike.ConsiderhereadefinitiontoinstallaPythonwebapplicationfromGitHubusingsomecommonidioms:
define:tornado_applicationdo
app_name=params[:name]
app_root=params[:app_root]
app_user=params[:user]
app_group=params[:group]
python_interpreter=params[:python_interpreter]||
"/usr/bin/python3.3"
github_repo=params[:github_repo]
deploy_branch=params[:deploy_branch]||"deploy"
virtualenv="#{app_root}/python"
virtual_python="#{virtualenv}/bin/python"
app_dir="#{app_root}/src/#{app_name}"
#NeedtoinstallSSHkeyforGitHub
#thiscomesfromthessh_known_hostscookbook
ssh_known_hosts_entry'github.com'
#Basepackagerequirements
package"git"
package"libpq-dev"
package"libxml2-dev"
package"python3.3"
package"python3.3-dev"
directory"#{app_root}"do
owner"#{app_user}"
group"#{app_group}"
mode"0755"
action:create
recursivetrue
end
#Createdirectories
["bin","src","logs","conf","tmp"].eachdo|child_dir|
directory"#{app_root}/#{child_dir}"do
owner"#{app_user}"
group"#{app_group}"
mode"0755"
action:create
recursivetrue
end
end
#InstallPythonvirtualenv
python_virtualenv"#{virtualenv}"do
owner"#{app_user}"
group"#{app_group}"
action:create
interpreter"#{python_interpreter}"
end
#Applicationcheckout
git"#{app_dir}"do
repository"#{github_repo}"
action:sync
user"#{app_user}"
branch"#{deploy_branch}"
end
#Pythondependenciesforapp
pip_requirements"tornado_app[#{app_name}]"do
action:run
pip"#{virtualenv}/bin/pip"
user"#{app_user}"
group"#{app_group}"
requirements_file"#{app_dir}/requirements.txt"
end
end
Thisdefinitioncanbeusedasshowninthefollowingexample:
tornado_application"image_resizer"do
app_root"/opt/webapps"
user"webapp"
group"webapp"
deploy_branch"master"
github_repo"[email protected]:myorg/image_resizer.git"
python_interpreter"/usr/bin/python3.3"
end
Accordingtothepreviousdefinition,thiswoulddothefollowing:
Addasystem-wideSSH-knownkeyforgithub.com(requiredtoperformaGitcloneandguaranteesthatfuturekeychangeswillwork)Installanyrequiredpackagesiftheydidn’talreadyexist,includingGit,Python,andthepostgresqlclientEnsureanyapplication-requireddirectoriesexistfordatasuchasbinaries,logs,configuration,andmoreCreateaPythonvirtualenvironmentbasedonthesuppliedPythoninterpreter(3.3)in<app_root>/python
Cloneorsync(ifitwasalreadycloned)thesourcecodefrom<github_repo>to<app_root>/src/<app_name>
Installtherequirementsspecifiedin<app_root>/src/<app_name>/requirements.txtusingthecopyofpipfromthevirtualenvironmentin<app_root>/python
Assumingyouhadanothersimilarlystructuredapplication,butyouwantedtouseadifferentuser,group,Pythoninterpreter,anddeploymentbranch,youcaneasilyconfigureitusingthefollowingresource:
tornado_application"restful_api"do
app_root"/opt/webapps"
user"restapi"
group"restapi"
deploy_branch"production"
github_repo"[email protected]:myorg/restful_api.git"
python_interpreter"/usr/bin/python3.2"
end
Asyoucansee,definitionsallowustodefinereusableresourcesinChef.Therearethreeprimarybenefitstothisapproach:
Simplifiedrecipesareeasiertoread,haveclearerintentions,andlesscodetoaudit,whichmakesthemlesserrorproneAnychangestothedefinitionareautomaticallyappliedtoanyusageofthedefinition,whichmeansyoudon’tneedtomaintainmultiplevariationsIt’seasiertotestbecauseit’sdesignedtobeparameterizedandmodular
NowthatyouseehoweasyitistowritecustomresourcesforChefthroughdefinitions,let’sexaminewritingafull-blownresourcethathasaseparateproviderimplementation.
BuildingaresourceAChefLWRPiscomposedoftwoprimarycomponents,aresourceandaprovider.Theresourceistheblueprintforwhatisbeingprovided;itdescribestheresource,includingwhatactionscanbetakenbyaresource,thepropertiesthatdescribetheresource,andanyotherhigh-levelinformationaboutit.Theproviderisresponsiblefortheactualimplementationoftheresource.Inprogrammingterms,theresourceisanabstractclassorinterfacewheretheproviderisaconcreteclassorimplementation.Forexample,oneofChef’sbuilt-inresourcesisthepackageresource;however,thisisaveryhigh-levelresource.Thepackageresourcedescribeswhatapackageisandwhatapackagecandobutnothowtomanagethem.Thatworkislefttotheproviders,includingRPM,APT,FreeBSDpackages,andotherbackendsystemsthatarecapableofmanagingon-diskinstallationofpackages.
DefiningtheresourceAsanexample,let’stakealookatanS3bucketresource:
actions:sync
default_action:syncifdefined?(default_action)#Chef>10.8
#DefaultactionforChef<=10.8
definitialize(*args)
super
@action=:sync
end
#TargetfolderonthehosttosyncwiththeS3bucket
attribute:destination,:kind_of=>String,
:name_attribute=>true
#Anythingtoskipwhensyncing
attribute:omit,:kind_of=>Array
#AWSAccess/secretkey
attribute:access_key_id,:kind_of=>String
attribute:secret_access_key,:kind_of=>String
Here,ourresourceisanS3bucketthatdeclarestheactionsitcantakealongwiththeattributesthatitrelieson.Here,ourresourcedeclaresthatithasoneavailableaction,sync,whichisthedefaultactionandthatithasfourattributes:thedestination,whatfilestoskip,theaccesskey,andthesecretkey.
ImplementingtheproviderTheprovideriswherethelogicfortheresourceisplaced—itisresponsibleforactingontheresourcebeingdescribed.ForourS3bucket,itlookslikethefollowing:
require'chef/mixin/language'
#Onlyrunasneeded
defwhyrun_supported?
true
end
action:syncdo
Chef::Log.debug("Checking#{new_resource}forchanges")
fetch_from_s3(new_resource.source)do|raw_file|
Chef::Log.debug"copyingremotefilefromorigin#{raw_file.path}to
destination#{new_resource.destination}"
FileUtils.cpraw_file.path,new_resource.destination
end
new_resource.updated_by_last_action(true)
end
defload_current_resource
chef_gem'aws-sdk'do
action:install
end
require'aws/s3'
current_resource=new_resource.destination
current_resource
end
deffetch_from_s3(source)
begin
protocol,bucket=URI.split(source).compact
AWS::S3::Base.establish_connection!(
:access_key_id=>new_resource.access_key_id,
:secret_access_key=>new_resource.secret_access_key
)
bucket.objects.eachdo|obj|
name=obj.key
if!new_resource.skip.contains(name)
Chef::Log.debug("Downloading#{name}fromS3bucket#{bucket}")
obj=AWS::S3::S3Object.findname,bucket
file=Tempfile.new("chef-s3-file")
file.writeobj.value
Chef::Log.debug("File#{name}is#{file.size}bytesondisk")
begin
yieldfile
ensure
file.close
end
else
Chef::Log.debug("Skipping#{name}becauseit'sintheskiplist")
end
end
rescueURI::InvalidURIError
Chef::Log.warn("ExpectedanS3URLbutfound#{source}")
nil
end
end
Let’stakealookattheprovider,piecebypiece.Thefirstthingtheproviderdoes,beyondincludinganyrequiredlibraries,istoinformChefthatitsupportswhy-run.ThisisamechanismthatChefprovidessothatresourcescanbemoreeasilytestedbyeffectivelynotwiringaresourcetoaprovider.Thisallowsdeveloperstotesttheirresources,inwhatiseffectivelyadry-runmode,beforerunningthemliveagainstasystem:
#Onlyrunasneeded
defwhyrun_supported?
true
end
Next,thereisanactionblock—thisregisterstheprovidedblockasthelogictobeexecutedforthespecifiedaction(inthiscase,:sync).Thishasthegeneralformsuchas:
action:<actionname>do
#Realworkinhere
end
Inthiscase,theonlysupportedactionissync,andsothereisonlyoneactionblock:
action:syncdo
Chef::Log.debug("Checking#{new_resource}forchanges")
fetch_from_s3(new_resource.source)do|raw_file|
Chef::Log.debug"copyingremotefilefromorigin#{raw_file.path}to
destination#{new_resource.destination}"
FileUtils.cpraw_file.path,new_resource.destination
end
new_resource.updated
end
Here,the:syncactionleveragesthefetch_from_s3method,whichyieldsalocalcopyofafileintheremotebucketonceithasbeendownloaded.Then,thefileiscopiedfromthetemporarylocationlocallyintothespecifieddestination.
ModifyingresourcesInsideofthisaction,youwillnoticethatthereisanactor,new_resource(whichisactuallyabuilt-inmethod).Thisdescribeswhatthestateofthenamedresourceshouldbewhentheproviderhascompleteditsexecutionforthespecifiedresource;thismayormaynotdifferfromthecurrentstateoftheresourceonthenode.Inthecaseofaninitialrun,new_resourcewillalmostcertainlybedifferentfromcurrent_resource,butthatmaynotalwaysbethecaseonsubsequentruns.
Asanexample,ifwehavearecipewiththefollowingS3bucketresourcedeclared:
s3_bucket"s3://mychefbucket/.resource"do
action:sync
skip["foo.txt","bar.txt]
destination"/opt/app_data"
access_key_idnode[:app][:aws_access_key]
secret_access_keynode[:app][:aws_secret_key]
ownernode[:app][:user]
groupnode[:app][:group]
mode"0755"
end
Then,thenew_resourceactorwouldhaveitsmembervariablespopulatedwiththeparameterspassedtothes3_bucketresource.Again,thisistheexpectedstateoftheresource,thewayitshouldbewhentheexecutionbytheprovideriscomplete.Inthiscase,whentheprovidercodeisexecuted,new_resource.destinationwillbe"/opt/app_data"andnew_resource.skipwillbealistof"foo.txt"and"bar.txt"andsoon.ThisallowsyoutopassdataintotheinstanceoftheresourceinthesamewaythatwaspossiblewiththePIPandTornadoapplicationdefinitions.
LoadinganexistingresourceOnethingthatislessobviousabouttheproviderscriptistheload_current_resourcemethodthatisnotcalledfromwithintheprovider.ThismethodisusedbyCheftofindaresourceonthenodebasedontheattributesthatareprovidedbytherecipe.Thisisusefultodetermineifanythingneedstobedonetobringanexistingresourceonthehostsuchasafile,auseraccount,oradirectoryoffiles,uptodatewiththedatathatisprovidedduringexecutionoftherecipe.
Itmightmakesensetoextendthisprovidertoprecomputethehashesofthefilesthatalreadyexistinthedirectoryon-diskasspecifiedbydestination.Thisway,theprovidercanbeupdatedtoonlydownloadanyremotefilesinS3thathaveadifferentfingerprintthanasimilarlynamedresourceondisk.Thispreventsunnecessaryworkfrombeingperformed,whichsavestime,bandwidth,andotherresources.
Here,however,itisalsousedtoensurethatanydependenciestodownloadfilesareinstalled;inthiscase,theAWSgemisrequiredtousetheS3client.Thisworksbecausetheload_current_resourcemethodgetscalledonearlytodeterminethecurrentstateoftheresource.Iftheresourcesarethesame,thentheproviderhasnothingtodo.ThecurrentimplementationjustclobberswhateverfilesarelocalwiththecontentsoftheS3bucket(moreofaone-waydownloadthanasync,really).
DeclaringthataresourcewasupdatedResourceshaveabuilt-inmethod,updated_by_last_action,whichiscalledinsidethe:syncactionallthetimeinthisexample.Thismethodnotifiestheresourcethatthenodewasupdatedsuccessfully.Thisshouldonlybesettotrueifeverythingwassuccessfullyupdated;failuresshouldnotmakethiscallunlesstheysetittofalse.Itisusefultoknowwhatresourceshavebeenupdatedforreportingorotherpurposes.Forexample,youcanusethisflagtoidentifywhatresourceshavebeenupdated:
moduleSimpleReport
classUpdatedResources<Chef::Handler
defreport
Chef::Log.info"Resourcesupdatedthisrun:"
run_status.updated_resources.eachdo|r|
Chef::Log.info"#{r.to_s}"
end
end
end
end
WorkingwithdatabagsThereareanumberofthingsyoucandowithdatabags.
SecuringyourdatabagsDatabagsarejustJSONdata,buttheyarestoredinthesystemasplaintext,withoutanysecurity.Theyarealsodownloadedontovarioushoststhroughoutthelifecycle,whichcanleadtoleakingofpotentiallysensitiveinformation.Fortunately,Chefhasamethodthatletsyousecurethisdatabyusingknife,alongwithsecretkeystokeepdataindatabagsencrypted.
SecretkeysEncryptingadatabagitemrequiresasecretkey;onewayofgeneratingasecretkeyistogeneratearandomnumberandusetheBase64encodingofthatnumberasthesecretkey.Thisshouldhaveanylineendingsremovedtoensureitworksproperlyonallplatforms,regardlessofplatform-specificlineendings.Hereisaquickwaytogenerateoneusingtheopensslcommandlinetoolcombinedwithtrtoremoveanylineendings:
$opensslrand-base64512|tr-d'\r\n'>~/.chef/data_bag_secret
EncryptingyourdataInordertoencryptyourdatabagitem,youmustuseknifeandpassthe--secretor--secret-fileflagstoknifewhencreatingtheitem.Forexample,tocreateadatabagcalledcredentialsandstoreanewentry,aws,insideit,youwouldusethefollowingcommand(makesureyousetyourEDITORenvironmentvariablefirst):
$knifedatabagcreatecredentialsaws--secret-file
~/.chef/data_bag_secret
Asmentionedbefore,youwillbepresentedwiththecontentsofyournewdatabagiteminyoureditor,unencrypted:
1{
2"id":"aws",·
3}
Here,wecanaddsomeproperties,suchasasecretkey:
1{
2"id":"aws",·
3"secret_key":"A21AbFdeccFB213f"
4}
Onceyousavethis,knifewilltellyouthatthenewdatabagwascreated,alongwiththenewdatabagiteminChef,justasitdidwiththeuserdataearlier.TheonlydifferencewillbethatthistimethedatastoredintheChefserverhasbeenencryptedusingthesymmetrickeyyouprovided:
$knifedatabagcreatecredentialsaws--secret-file
~/.chef/data_bag_secret
Createddata_bag[credentials]
Createddata_bag_item[aws]
Tocheckwhetheryournewlycreateddatabagentrywasencrypted,useknife,aswehave
before,toshowthecontentsofanitem:
$knifedatabagshowcredentialsaws
id:aws
secret_key:
cipher:aes-256-cbc
encrypted_data:
SG4z4jd4VAnJ4gG0wPcJWOX7H+ZNSxG5PH+n7EgHFV9e1SciVznjaAbzK61c
EW0/
iv:rKB0riCr84QhBkw+Wgc/5Q==
version:1
DecryptingyourdataInordertodecryptthedatainthedatabagitem,youneedtoprovidethesamesymmetrickeyasyouprovidedwhenyouencryptedit,usingthe--secretor--secret-fileargument,ascanbeseenhere:
$knifedatabagshowcredentialsaws--secret-file~/.chef/data_bag_secret
id:aws
secret_key:A21AbFdeccFB213f
Ifitwasn’talreadyobvioustoyou,makecertainyoudonotlosethisfile.Withoutyoursecretkeyorsecretfile,youwillnotbeabletodecryptthedatainyourdatabag.Itmaybeworthencryptingthesecretfilewithapassphraseifyouaregoingtobetransmittingittonontrustedlocationsaswell.
StoringkeysonnodesAnencryptionkeycanalsobestoredinanalternatefileonthenodesthatneedit,andyoucanspecifythepathlocationtothefileinsideanattribute;however,EncryptedDataBagItem.loadexpectstoseetheactualsecretasthethirdargumentratherthanapathtothesecretfile.Inthiscase,youcanuseEncryptedDataBagItem.load_secrettoslurpthesecretfilecontentsandthenpassthem:
#insideyourattributefile:
default[:app][:aws_creds_secret]="/opt/secret/aws.secret"
#Insideyourrecipe
aws_secret=Chef::EncryptedDataBagItem.load_secret
"#{node[:app][:aws_creds_secret]}"
aws_creds=Chef::EncryptedDataBagItem.load
"credentials","aws",aws_secret
aws_creds["secret_key"]
SearchingyourdataAswediscussedearlier,youcansearchthroughyourdatabagsusingBooleansearchlogic.Thispermitsyoutofindonlytheentriesinyourdatabagsthatyouneed.Thesamesearchquerylanguageisusedonthecommandlinewithknifeasitisinyourrecipes,sothatyoucantestyourqueriesonthecommandlinetoensurethattheyproducetherightresultsbeforeyouputtheminyourrecipes.Youcanalsosearchthroughotherresourcesaswell,notjustdatabags.
SearchingyourdatabagswithknifeTheknifetoolusesthesearchcommandtosearchthroughyourdatabags.Thegeneralsyntaxis:
knifesearch<source>"<searchcriteria>"
SearchingyourdatabagsfromarecipeInsidearecipe,thesearchmethodisusedtosearchthroughadatabag.Thesyntaxforthisis:
search(:source,"searchcriteria")
QueryingyourdataThesearchqueryformatisreasonablystraightforwardandlookslikemostothersearchenginesthatsupporttheBooleanlogic.
Searchesonattributescomeintheformofkey:value;soforexample,ifyouwantedtofindalloftheuserswhoweredwarvesfromourearlierdatasets,youcanusethesearchquery:
knifesearchusers"groups:dwarves"
NegatingasearchtermcanbeaccomplishedbyplacingNOTinfrontofthesearchterm.Forexample,alluserswhoarenothobbitswillbe:
knifesearchusers"NOTgroups:hobbits"
YoucanalsouseanORmodifier:
knifesearchusers"groups:elvesORgroups:hobbits"
Thislastsearchcriteriawouldyieldtheuserslegolas,samwise,andfrodoasFrodoandSamwiseareinthegroupcalledhobbitsandLegolasisintheelvesgroup.
Whilecombiningsearchterms,youcanlogicallyANDthemtogetheraswell.Forexample,alluserswithaGIDstartingwith20,whocontainthegroupelvescanbefoundusingthefollowingquery:
knifesearchusers"groups:elvesANDgid:20*"
Youcansearchyournodeswiththesamequerylanguage—inordertofindallnodesthatarerunningsomeformofwindows,youcansearchfortheplatformbeinganythingthat
startswithwin:
knifesearchnode"platform:win*"
ThiswillyieldallWindowshosts(resultshavebeenshortenedabit):
4itemsfound
NodeName:i-13d0bd4f
Roles:
Platform:windows6.2.9200
NodeName:WIN-CJDQ9DEOJFK
Roles:umbraco_cms
Platform:windows6.2.9200
NodeName:00c0ff3300
Roles:
Platform:windows6.2.9200
NodeName:rs-5889646228538071
Roles:
Platform:windows6.2.9200
Or,youcansearchforallnodesthatarerunningwindowsandcontaintheroleumbraco_cms:
knifesearchnode"platform:win*ANDrole:umbraco_cms"
Or,ifyouwantedtoeliminatethosenodesthatruntheUmbracoCMS,youcaneasilyinverttherolecondition:
knifesearchnode"platform:win*ANDNOTrole:umbraco_cms"
BecauseChefusesApacheSolrtosearchitsdata,youcanrefertotheApacheSolrdocumentationonbuildingmoreadvancedquerylogicathttp://wiki.apache.org/solr/SolrQuerySyntax.
ManagingmultiplemachineswithsearchqueriesThesearchcriteriacanbeusedforallsortsofplaces:inrecipes,onthecommandline,throughAPIcalls,andmore.OneveryinterestingapplicationisbeingabletousethesearchquerytoSSHtomultiplemachinestoperformcommandsinparallel:
knifessh"fqdn:*.east.mycorp.comANDplatform:ubuntu""chef-client"-x
app_user
ThiswillcontacttheChefserverandaskforthenodesthatmatchthegivenquerystring(machineswhoseFQDNmatchesthewildcardexpression"*.east.mycorp.com"andthatarerunningUbuntu)andthenconnecttothemviaSSHastheuserapp_userandrunthechef-clientcommandoneachofthem.Again,youcanrestrict(orexpand)theserverlistbyusingamore(orless)specificquery.
Onceyouhavemasteredthisaspectofusingknife,youcanlearnmoreaboutitssupportforexecutingmultipleconnectionsconcurrentlyandeveninteractwithterminalmultiplexerssuchasscreenandtmux.
SummaryChefhaslotsofmechanismstobuildadvancedautomation,includingbuildingyourowndefinitions,resources,andproviders,aswellasstoringandaccessingcomplexconfigurationdataandevensecurelyencryptingit.Thischapterhasshownyouhowtomanagedataindatabags(includingencrypteddata),useChef’sadvancedsearchenginetofindandmanipulatedatainyoursystemfromthecommandlineandinrecipes,aswellasdevelopdefinitionsforreusablerecipedevelopment,andevenbuildcustomresourcesandprovidersforuseinyourcookbooks.
Inthenextchapter,wewillcoversomemoreadvancedwaystouseChef,includinginteractingwiththeChefshell,automationandintegrationwithChefusingscriptsandAPIs,externaltoolsandresources,advancedtestingincludingintegrationtesting,andusingChef-soloandVagranttomanageyourdevelopmentenvironments.
Chapter8.ExtrasYouNeedtoKnowThischapterwillcoverhowtouseCheftobuildcustombootstrapscriptsforsystems,enhancedcommand-linetoolconcepts,leverageChefforautomation,integration,andsecurelystoresensitivedatainthesystem.Sometopicsthatwillbecoveredinthischapterinclude:
UsingChef-solowithVagrantInteractingwiththeChefshellDebuggingrecipesAdvancedcommand-lineusageAutomatingandintegratingwithChefMoretestingmethodologies
VagrantandChef-soloVagrantisaveryusefultooltobuilddevelopmentenvironments,whereitprovidestoolstobuildvirtualmachinesthatcontaineverythingyouneedtogetstartedwithbuildingsoftware.Consider,foramoment,workingonateamthatbuildssoftwareandreliesonaservice-orientedarchitecture(SOA),andthissoftwareiscomposedofanumberofdifferentservices.Inorderforittowork,youmayberequiredtoinstallandconfigureallofthedependentservicestoevenbeginworkingonapartofthesystem;thiscouldbeatime-consuminganderror-proneexerciseforevenseasoneddevelopers.Nowimaginethatallyouhadtodowasdownloadaconfigurationfileandexecutevagranttodoitforyou—thisistheworldofVagrant.
OneoftheinterestingfacetsofVagrantisthatithassupporttoprovisionnewinstancesusinganumberofdifferentmechanisms.Currently,thislistincludes10orsodifferenttools,butthemostinterestingtwoareChef-soloandChefclient.Bynow,youshouldbecomfortablewithhowyoumightprovisionavirtualmachineusingtheChefclient;it’snotmuchdifferentthanprovisioninganEC2instanceoradedicatedserver.However,wehaven’tdiscussedusingChef-solomuchyet,sothisisagoodtimetolearnmoreaboutit.
InstallingVagrantHistorically,VagrantwasinstalledviaRubyGems;thisisnolongerthecase,andifyouhaveanolderversioninstalledasagem,itisrecommendedthatyouremoveitbeforeinstallingVagrant.Installersforallsupportedplatforms(OSX,Windows,andLinux)areavailableatthefollowingURL:
http://www.vagrantup.com/downloads
IfyouarenewtoVagrant,theninadditiontoinstallingVagrant,youwillwanttoinstallVirtualBoxforsimplicity,asVagranthasbuilt-insupportforVirtualBox.VagrantdoessupportotherproviderssuchasVMWareandAWS,butitrequirespluginsthatarenotdistributedwiththecoreVagrantinstallationinorderforthemtowork.
OnceyouhaveinstalledVagrantandVirtualBox,thenyoucancontinueonwiththefollowingexamples.
ProvisioninganewhostwithVagrantProvisioninganewvirtualinstancerequiresthatyoubuildaVagrantconfigurationfilecalledVagrantfile.Thisfileservestwopurposes:todenotethatthedirectoryisaVagrantproject(similartohowaMakefileindicatesaprojectthatisbuiltwithMake),andtodescribethevirtualmachinethatisbeingrun,includinghowtoprovisionit,whatoperatingsystemtouse,wheretofindthevirtualimage,andsoon.Becausethisisjustaplaintextfile,youcanincludeitalongwithanyauxiliaryfilesrequiredtobuildtheimagesuchascookbooks,recipes,JSONfiles,installers,andsoon,andcommitittothesourcecontrolforotherstouse.
Inordertobegin,youwillwanttocreateadirectorythatwillhouseyournewVagrantproject.OnUnix-likesystems,wewouldbootstrapourprojectsimilarlytothefollowingcommand:
mkdir-p~/vagrant/chef_solo
cd~/vagrant/chef_solo
Windowshostswillbethesameexceptfordifferentpathsandchangesinmethodsofdirectorycreation.Oncethisstepiscomplete,youwillneedtocreateaskeletonconfigurationlocatedin~/vagrant/chef_solo/Vagrantfile.Thisfilecanbegeneratedusingvagrantinit,butwewouldnotwanttousethecontentsofthegeneratedfile;so,wewillskipthatstepandmanuallyconstructourVagrantfileinstead(withasimpleone-lineconfigurationthatusesabaseimageofUbuntu13.10).YourVagrantfileshouldlooklikethefollowingcode:
Vagrant.configure("2")do|config|
config.vm.box="ubuntu/trusty64"
end
Here,"2"istheAPIversion,whichiscurrentlyVersion2.0asofthiswriting,andtheconfiguredbaseimage(orbox)istheUbuntuTrusty(14.04)64-bitimage.Beforeyouusethisbaseimage,itneedstobedownloadedtoyourlocalmachine;youcanaddittoVagrantusingtheboxaddcommand:
vagrantboxaddubuntu/trusty64
TipThisstepwilltakeafewminutesonafastconnection,sobepreparedtowaitwhilethiscompletesifyouarefollowingalonginteractively.Alsonotethatifyouskipthisstep,thebaseimagewillautomaticallybedownloadedwhilerunningvagrantuptostartyourvirtualmachine.
Forfuturereferences,ifyouwanttofindalternativeOSimagestouseforyourVagrantmachines,youshouldlookatVagrantCloud(https://vagrantcloud.com/),whereyoucanfindanumberofotherfreelyavailablebaseimagestodownloadforusewithVagrant.
BootingyourVagrantimageOnceyourbaseimagehascompleteddownloading,youwillusethevagrantupcommandtobootupanewvirtualmachine.Bydoingthis,youwillinstructVagranttoreadtheVagrantfileandbootanewinstanceofthebaseimage:
Bringingmachine'default'upwith'virtualbox'provider…
==>default:Importingbasebox'ubuntu/trusty64'...
==>default:MatchingMACaddressforNATnetworking…
==>default:Checkingifbox'ubuntu/trusty64'isuptodate…
==>default:SettingthenameoftheVM:
chef_solo_default_1402875519251_51266
==>default:Clearinganypreviouslysetforwardedports…
==>default:Clearinganypreviouslysetnetworkinterfaces…
==>default:Preparingnetworkinterfacesbasedonconfiguration…
default:Adapter1:nat
==>default:Forwardingports…
default:22=>2222(adapter1)
==>default:BootingVM…
==>default:Waitingformachinetoboot.Thismaytakeafewminutes…
default:SSHaddress:127.0.0.1:2222
default:SSHusername:vagrant
default:SSHauthmethod:privatekey
default:Warning:Connectiontimeout.Retrying…
==>default:Machinebootedandready!
==>default:CheckingforguestadditionsinVM…
==>default:Mountingsharedfolders…
default:/vagrant=>/Users/jewart/Temp/vagrant/chef_solo
Asyoucanseefromtheoutput,Vagrantperformedthefollowingthings:
Usedthebaseimageubuntu/trusty64ConfiguredVirtualBoxtouseaNATadapter,mappingport22to2222StartedtheVMinheadlessmode(suchthatyoudon’tseetheVirtualBoxGUI)Createdauser,vagrant,withaprivatekeyforauthenticationMountedasharedfoldermapping/vagrantontheguesttotheVagrantworkspaceonthehost
Nowthatyouhavearunningguest,youcancontrolitbyrunningvagrantcommandsfrominsideofthevagrantworkspace(~/vagrant/chef_solo);forexample,youcanSSHintoitusingthefollowingcommand:
vagrantssh
Andyoucandestroytherunninginstancewiththefollowingcommand:
vagrantdestroy
GoaheadandSSHintoyournewguestandpokearoundalittlebit—youwillnoticethatitlooksjustlikeanyotherUbuntu14.04host.Onceyouaredone,usedestroytodestroyitsothatyoucanlookathowtoprovisionyourVagrantimageusingChef-solo.It’simportanttoknowthatifyouusedestroyonyourguest,changestoyourVagrantimagearenotpersisted;so,anychangesyouhavemadeinsideitwillnotbesavedandwillnot
existthenexttimeyouusevagrantuptostarttheVM.
CombiningVagrantwithChef-soloInourpreviousexample,ourVagrantfilesimplydeclaredthatourguestreliedontheubuntu/trusty64imageasthebaseimageviatheconfig.vm.boxproperty.Next,wewilllookathowtoextendourconfigurationfiletousetheChef-soloprovisionertoinstallsomesoftwareonourguesthost.Here,wewilluseChef-solotoinstallPostgreSQL,Python,andawebapplicationinsideoftheguest.
YouwillprobablynoticethattheconfigurationsectionsintheVagrantfilelooksortoflikeresourcesinChef—thisisbecausetheybothleverageRubyblockstoconfiguretheirresources.SowithVagrant,inordertospecifytheprovisioningmechanismbeingused,theconfig.vm.provisionoptionissettothedesiredtool.Here,wewilluseChef-solo,whichisnamed"chef_solo";so,wewillextendourVagrantfiletoindicatethis:
Vagrant.configure("2")do|config|
config.vm.box="ubuntu/trusty64"
config.vm.provision"chef_solo"do|chef|
#...Chefspecificsettingsblock
end
end
UnderstandingthelimitationsofChef-soloForthemostpart,Chef-solooperatesalotlikethetraditionalclient-servermodeofchef-client.TheprimarydifferencesresultfromthefactthatChef-solodoesnotinteractwithacentralChefserverandthereforelackssupportforthefollowing:
NodedatastorageSearchindexesCentralizeddistributionofcookbooksAcentralizedAPIthatinteractswithandintegratesinfrastructurecomponentsAuthenticationorauthorizationPersistentattributes
Asaresult,ifyouarewritingrecipestobeusedwithChef-solo,youwillbeunabletorelyonsearchfornodes,roles,orotherdataandmayneedtomodifythewayyoufinddataforyourrecipes.Youcanstillloaddatafromdatabagsforcomplexdata,buttheywillnotbecentrallylocated;rather,theywillbelocatedinanumberofJSONfilesthatcontaintherequireddata.
ConfiguringChef-soloThereareanumberofoptionsavailablefortheChef-soloprovisionerinVagrant.Forthemostup-to-datedocumentationofVagrant,besuretovisittheofficialVagrantdocumentationsiteathttp://docs.vagrantup.com/v2/.
MostoftheconfigurationoptionsarewaystoprovidepathstovariousChefresourcessuchascookbooks,databags,environments,recipes,androles.AnypathsspecifiedarerelativetotheVagrantworkspaceroot(wheretheVagrantfileislocated);thisisbecausethesearemountedintheguestunder/vagrantandaretheonlywaytogetdataintothe
hostduringthebootstrapphase.Theoneswewillbeusingare:
cookbooks_path:Thisconsistsofasinglestringoranarrayofpathstothelocationwherecookbooksarestored.Thedefaultlocationiscookbooks.data_bags_path:Thisconsistsofapathtodatabags’JSONfiles.Thedefaultpathisempty.roles_path:ThisconsistsofanarrayorasinglestringofpathswhererolesaredefinedinJSON.Thedefaultvalueisempty.
Inourcase,wewillbereusingourexamplecookbooksfromtheearlierchapter.YoucanfetchthemfromGitHubathttp://github.com/johnewart/chef_cookbook_files;eitherdownloadtheZIPfileorclonethemusingGitlocally.Onceyouhavedonethat,copycookbooks,roles,anddata_bagsfromthearchivetoyourVagrantworkspace.ThesewillbetheresourcesthatyouwilluseforyourVagrantimageaswell.InordertotellVagrant’sChef-soloproviderhowtofindthese,wewillupdateourVagrantfileagaintoincludethefollowingconfiguration:
Vagrant.configure("2")do|config|
config.vm.box="ubuntu/trusty64"
config.vm.provision"chef_solo"do|chef|
chef.cookbooks_path="cookbooks"
chef.roles_path="roles"
chef.data_bags_path="data_bags"
end
end
TellingChef-solowhattorunInsideoftheprovisionblock,wehaveaChefobjectthateffectivelyrepresentsaChefclientrun.Thisobjecthasanumberofmethods(suchasthepathsettingswealreadysaw),oneofwhichistheadd_recipemethod.Thisallowsustomanuallybuildourrunlistwithoutrequiringrolesordatabagsandcanbeused,asshowninthefollowingexample,toinstallthePostgreSQLserverwithnospecialconfiguration:
Vagrant.configure("2")do|config|
config.vm.box="ubuntu/trusty64"
config.vm.provision"chef_solo"do|chef|
chef.cookbooks_path="cookbooks"
chef.roles_path="roles"
chef.data_bags_path="data_bags"
#Buildrunlist
chef.add_recipe"postgresql::server"
end
end
ThiswilltellVagrantthatwewanttouseourdefineddirectoriestoloadourresources,andwewanttoaddthepostgresql::serverrecipetoourrunlist.Becausecookbooksarebydefaultexpectedtobein[vagrantroot]/cookbooks,wecanshortenthisexampleasshowninthefollowingcode,aswearenotyetusingrolesordatabags:
Vagrant.configure("2")do|config|
config.vm.box="ubuntu/trusty64"
config.vm.provision"chef_solo"do|chef|
chef.add_recipe"postgresql::server"
end
end
UsingrolesanddatabagswithChef-soloAsyouarealreadyawarebynow,wemaywanttoperformmorecomplexconfigurationofourhosts.Let’stakealookathowtousebothrolesanddatabagsaswellasourcookbookstodeployourPythonwebapplicationintoourVagrantguestsimilartohowwedeployedittoEC2:
Vagrant.configure("2")do|config|
config.vm.box="ubuntu/trusty64"
config.vm.provision"chef_solo"do|chef|
chef.cookbooks_path="cookbooks"
chef.roles_path="roles"
chef.data_bags_path="data_bags"
#Buildrunlist
chef.add_role("base_server")
chef.add_role("postgresql_server")
chef.add_role("web_server")
end
end
Justlikethecookbookspath,therolespathisrelativetotheprojectrootifarelativepathisgiven.
InjectingcustomJSONdata
AdditionalconfigurationdataforChefattributescanbepassedintoChef-solo.ThisisdonebysettingthejsonpropertywithaRubyhash(dictionary-likeobject),whichisconvertedtoJSONandpassedintoChef:
Vagrant.configure("2")do|config|
config.vm.provision"chef_solo"do|chef|
#...
chef.json={
"apache"=>{
"listen_address"=>"0.0.0.0"
}
}
end
end
Hashes,arrays,andsooncanbeusedwiththeJSONconfigurationobject.Basically,anythingthatcanbeturnedcleanlyintoJSONworks.
Providingacustomnodename
Youcanspecifyacustomnodenamebysettingthenode_nameproperty.Thisisusefulforcookbooksthatmaydependonthisbeingsettosomesortofvalue.Forexample:
Vagrant.configure("2")do|config|
config.vm.provision"chef_solo"do|chef|
chef.node_name="db00"
end
end
GettingtoknowtheChefshellTheChefshell,previouslycalledshef,providesaninteractivetoolorread-eval-print-loop(REPL)toworkwithChefresources.MuchinthesamewayIRBoranyotherlanguage’sREPLshellworks,chef-shellisawaytointeractwithknife.Thisishandyforexperimentingwithresourceswhilewritingrecipessothatyoucanseewhathappensinteractivelyratherthanhavingtouploadyourcookbooktoaserverandthenexecutingthechef-clientonatargetnode.Additionally,theChefshellprovidesaresourcetoaddbreakpointstorecipeexecutionsothatitcanbeusedtodebugrecipeexecution,whichisaveryhandyfeature.
UsingtheChefshellAsof11.x,shefhasbeenreplacedwithchef-shellandcanbeusedinthreedifferentmodes:standalone,solo,andclientmode.Eachofthesehasaslightlydifferentsetoffunctionalitiesandexpectedusecases.
ThestandalonemodeThestandalonemodeisusedtorunChefinaninteractivemodewithnothingloaded;thisisalmostlikerunninganREPLsuchasirborpythononthecommandline.Thisisalsothedefaultbehaviorofchef-shellifnothingisspecified.
ThesolomodeThesolomodeisinvokedusingthe-sor--solocommand-lineflagandisawaytousechef-shellasachef-soloclient.Itwillloadanycookbooksusingthesamemechanismthatchef-solouserswould,anditwilluseanychef-soloJSONfileprovidedtoitusingthe-jcommand-lineoption.
Thefollowingareexamplesofusingthesolomode:
chef-shell-s
chef-shell-s-j/home/myuser/chef/chef-solo.json
TheclientmodeTheclientmodeisenabledwiththe-zor--clientcommand-lineflag;thismodecauseschef-shelltoactasthoughyouinvokedchef-clientonthehost.Theshellwillreadthelocalclientconfigurationandperformthenormaldutiesofchef-client:connectingtoyourChefserveranddownloadinganyrequiredrunlists,attributes,andcookbooks.However,itwillallowforinteractiveexecutionsothatitispossibletodebugordiagnoseissueswithrecipesontheendhost.Whenusingtheclientmode,youcanuseanalternateconfigurationfilewiththe-ccommand-lineoption,orspecifyadifferentChefserverURLviathe-scommand-lineoption.
Theexampleusesthefollowing:
chef-shell--client-c/etc/chef/alternate.conf
chef-shell--client-shttp://test.server.url:8080/
InteractingwiththeChefserverusingtheshellTheChefshellprovidesyouwiththeabilitytointeractwiththeserverquicklyinthesamewayyouwoulduseknife,butwithouttheoverheadoftypingknifesearchnode…orknifenodelist,andsoon.ItisaveryconvenientwaytoquerythedatastoredintheChefserverinteractively.Inordertointeractwiththeserverfromyourworkstation,youneedtomakesurethatyourshell’sconfigurationfile,locatedin~/.chef/chef_shell.rb,isconfiguredproperly.Ifyouareconnectingwithchef-shellfromanode,thentheconfigurationin/etc/chef/client.rb(orsimilaronWindows)willbeusedinstead.
Thisfile,similartotheknife.rborclient.rbfile,containstherequiredcertificatedataandconfigurationdatatoconnecttotheChefserver.Anexampleconfigurationfilewillresemblethefollowing,withpaths,organization,andclientnamesupdatedaccordingly:
node_name'myorg'
client_keyFile.expand_path('~/.chef/client.pem')
validation_keyFile.expand_path('~/.chef/validator.pem')
validation_client_name"myorg-validator"
chef_server_url'https://api.opscode.com/organizations/myorg'
Allofthesefilesarepresentifyourknifeinstallationisoperational,andtheconfigurationfilecloselyresemblesthatofknife.rb—ifyouneedvaluesfortheseonyourworkstation,takealookatthe~/.chef/knife.rbfile.Onceyouhaveconfiguredyourshell,youcanpassthe-zcommand-lineflagtoconnectasthechef-clientwould:
[jewart]%chef-shell-z
loadingconfiguration:/Users/jewart/.chef/chef_shell.rb
Sessiontype:client
Loading…...resolvingcookbooksforrunlist:[]
SynchronizingCookbooks:
done.
Thisisthechef-shell.
ChefVersion:11.12.8
http://www.opscode.com/chef
http://docs.opscode.com/
run'help'forhelp,'exit'or^Dtoquit.
Ohai2ujewart@!
chef>
InteractingwithdataFromhere,youcaninteractwiththeChefserverinavarietyofways,includingsearching,modifying,anddisplayinganydataelements(roles,nodes,databags,environments,cookbooks,andclients),performingaclientrun(includingsteppingthroughit,onestepatatime),assumingtheidentityofanothernode,andprintingtheattributesofthelocalnode.Forexample,listingtherolesontheChefservercanbeperformedwiththeroles.allmethod,shownasfollows:
chef>roles.all
=>[role[umbraco_cms],role[umbraco],role[base_server],role[web_server],
role[postgresql_server]]
SearchingyourdataSearchingthedataelementsisalsosupported,aseachdatatypehasafindmethodattachedtoit.Thefindmethodtakesamapoftheattributeandpatterntolookforandreturnstheresults.Forexample,youcanfindallrolesontheChefserverthatbeginwith"um"withthefollowingcommand:
chef>umbraco_roles=roles.find(:name=>"um*")
=>[role[umbraco_cms],role[umbraco]]
EditingyourdataAnyobjectinyourChefservercanbeediteddirectlyfromtheChefshellusingtheeditcommandfrominsidetheshell.ThiswillinvokeyourfavoriteeditortoedittherawJSONoftheobjectinquestion,whichprovidesamoredirectmechanismoverusingknife(node|role|databag)editonthecommandline,asyoucanquicklymanipulateanumberofrecordsalotmoreeasily.Forexample,toeditalloftherolesthatcontainthename"apache"andsavetheresults,youcanusethefollowingRubycode:
chef>apache_roles=roles.find(:name=>"*apache*")
>[...somelist…]
chef>apache_roles.eachdo|r|
chef>updated=editr
chef>updated.save
chef>end
Thiswillfindallroleswhosenamecontains"apache".Thenforeachrecord,edittheJSON,storingtheresultsinvariablenamedupdated,andthensavethatrecordbacktotheChefserver.
TransformingdataInthisway,youcaninteractwithanyoftheresourcesthatareavailabletoyou,allowingyoutoquicklyfindandmanipulateanydatastoredinChefdirectlyusingtheRubycode.Forexample,tofindallclientswithagivenstringintheirnameanddisabletheiradministrativeaccess,youcanusethefollowingcode:
clients.transform("*:*")do|client|
ifclient.name=~/bad_user/i
client.admin(false)
true
else
nil
end
end
TipUsecautionwhentransformingyourdatafromtheChefshell;itisanincrediblypowerfultool,butitseffectsaredestructive.Thesechangesarenotreversible(atleastnotwithoutforethoughtorbackups)andcoulddamageyourdataifyouarenotcareful.Forexample,
ifthepreviouscodewastranscribedincorrectly,itcouldpotentiallyrenderallusersunabletoadministerthesystem.
ExecutingrecipeswithChefshellTwogreatfeaturesofchef-shellaretheabilitytorewindarun(tostepbackwards)andtobeabletostepforwardintherunoneresourceatatime.Asanexample,let’slookathowtodefineasimplerecipeinchef-shellinteractivelyandthenrunit,startitover,andstepthroughit.
First,let’sfireupchef-shellwiththefollowingcommand:
[jewart]%chef-shell
loadingconfiguration:none(standalonesession)
Sessiontype:standalone
Loading…...done.
Thisisthechef-shell.
ChefVersion:11.12.8
http://www.opscode.com/chef
http://docs.opscode.com/
run'help'forhelp,'exit'or^Dtoquit.
Ohai2ujewart@!
chef>
Thechef-shellpromptwillchangebasedonthestateyouarein.Ifyouareworkingwitharecipe,thepromptwillchangetobechef:recipe>.
CreatingarecipeintheshellTheChefshellhasanumberofmodes—recipemodeandattributemode.Recipemodeisactivatedwhenworkingwithrecipesandwillbewhatweusehere.Inordertoactivateit,typerecipe_modeattheprompt:
chef>recipe_mode
chef:recipe>
Here,wewillcreateresourcestocreateafileinthecurrentdirectoryinteractivelyusingafileresourcewithnoassociatedconfigurationblock,onlythename:
chef:recipe>file"foo.txt"
=><file[foo.txt]@name:"foo.txt"@noop:nil@before:nil@params:{}
@provider:Chef::Provider::File@allowed_actions:[:nothing,:create,
:delete,:touch,:create_if_missing]@action:"create"@updated:false
@updated_by_last_action:false@supports:{}@ignore_failure:false
@retries:0@retry_delay:2@source_line:"(irb#1):1:in'irb_binding'"
@guard_interpreter::default@elapsed_time:0@resource_name::file@path:
"foo.txt"@backup:5@atomic_update:true@force_unlink:false
@manage_symlink_source:nil@diff:nil@sensitive:false@cookbook_name:
nil@recipe_name:nil>
TipOnethingtonotehereisthattheshellwillprintouttheresultsofthelastoperationexecutedintheshell.ThisispartofanREPLshell’simplicitbehavior;itistheprintpart
ofREPL:inputisreadandevaluated,thentheresultsareprintedout,andtheshellloopstowaitformoreinputfromtheuser.Thiscanbecontrolledbyenablingordisablingtheechostate;echooffwillpreventtheprintedoutputandechoonwillturnitbackon.
Itiscriticaltonotethat,atthispoint,nothinghasbeenexecuted;wehaveonlydescribedafileresourcethatwillbeacteduponiftherecipeisrun.Youcanverifythisbymakingsurethatthereisnofilenamedfoo.txtinthedirectoryyouexecutedchef-shellfrom.Therecipecanberunbyissuingtherun_chefcommand,whichwillexecuteallofthestepsintherecipefromstarttofinish.Hereisanexampleofthis:
chef:recipe>run_chef
INFO:Processingfile[foo.txt]actioncreate((irb#1)line1)
DEBUG:touchingfoo.txttocreateit
INFO:file[foo.txt]createdfilefoo.txt
DEBUG:foundcurrent_mode==nil,sowearecreatinganewfile,updating
mode
DEBUG:foundcurrent_mode==nil,sowearecreatinganewfile,updating
mode
DEBUG:foundcurrent_uid==nil,sowearecreatinganewfile,updating
owner
DEBUG:foundcurrent_gid==nil,sowearecreatinganewfile,updating
group
DEBUG:foundcurrent_uid==nil,sowearecreatinganewfile,updating
owner
INFO:file[foo.txt]ownerchangedto501
DEBUG:foundcurrent_gid==nil,sowearecreatinganewfile,updating
group
INFO:file[foo.txt]groupchangedto20
DEBUG:foundcurrent_mode==nil,sowearecreatinganewfile,updating
mode
INFO:file[foo.txt]modechangedto644
DEBUG:selinuxutilitiescannotbefound.Skippingselinuxpermission
fixup.
DefiningnodeattributesJustasinanyrecipe,attributescanbeusedintherecipesdefinedintheshell.However,inthestandalonemode,therewillbenoattributesdefinedinitially;soloandclientmodeswilllikelyhaveattributesdefinedbytheirJSONfileortheChefserver,respectively.Inordertointeractwiththecurrentlydefinedattributes,wemustswitchbetweentherecipemodeandattributemode.Thisisachievedusingtheattributes_modecommandasshowninthefollowingcode:
chef:recipe>attributes_mode
chef:attributes>
Herewecanperformtwoprimaryoperations:gettingandsettingnodeattributes.ThesearewaysofmodifyingthevaluesthatareaccessedfrominsidethenodeMashinarecipe.
TipRememberthatthenode’sattributesareaccessedasaMash,akey-insensitivehashthatallowsyoutointerchangestringkeyswithsymbolkeys.TheMashclassisnotabuilt-in
structureinRuby—itisprovidedbyChefforconveniencesothathashkeyscanbeeithersymbolsorstringsandhavethesameeffect.
Settingattributes
Settingattributesisachievedusingthesetcommand,whichhasthefollowingform:
set[:key]=value
Here,:keycanbeasingle-levelkeyoramultilevelkeysimilartoanyentryintheattributes/default.rbfile.Asanexample,wecanconstructanapplicationconfigurationusingthefollowing:
set[:webapp][:path]="/opt/webapp"
set[:webapp][:db][:username]="dbuser"
set[:webapp][:db][:password]="topsecret"
set[:webapp][:user]="webuser"
set[:postgresql][:config][:listen]="0.0.0.0"
Anyparentkeysthatarenon-existentareimplicitlycreatedonthefly,soyoudonotneedtodosomethinglikethefollowing:
set[:webapp]={}
set[:webapp][:path]="/opt/webapp"
Accessingattributes
Inordertodisplayanattributewhenintheattributesmode,simplytypeinthenameofthekeyyouareinterestedin.Forexample,ifyouhadexecutedthesetcommandslistedpreviously,thenaskingforthewebapphashisassimpleastypingwebapp,asfollows:
chef:attributes>webapp
=>{"path"=>"/opt/webapp","db"=>{"username"=>"dbuser",
"password"=>"topsecret"},"user"=>"webuser"}
However,ifyouwishtoaccessthesewhenintherecipemode,theyareaccessedthroughthenodehash,asshownhere:
chef:attributes>recipe_mode
=>:attributes
chef:recipe>node[:webapp]
=>{"path"=>"/opt/webapp","db"=>{"username"=>"dbuser",
"password"=>"topsecret"},"user"=>"webuser"}
Theycanbeusedviathenodehashinjustthesamewayyouwouldusetheminarecipe.Ifyouwanttoconstructafileblockthatcreatedafoo.txtfilelocatedintheinstallpathofourwebapphash,youcaneasilyusethefollowingexampleinsideyourshell:
file"#{node[:webapp][:path]}/foo.txt"
Thismakeswritingrecipesusingtheinteractiveshellfeelexactlythesameaswritingrecipefiles.
UsingconfigurationblocksAresourceinarecipefilecanhaveaRubyblockwithattributes,andyoucandothisin
chef-shellinexactlythesamefashion.Simplyinsertdoaftertheresourcenameandtheshellwillbehaveasamultilineeditor,allowingyoutocompletetheblock.Thefollowingexampledemonstratesprovidingacontentattributetoafileresourceinthismanner:
chef:recipe>file"not_empty.txt"do
chef:recipe>content"Notempty!"
chef:recipe?>end
=><file[not_empty.txt]@name:"not_empty.txt"@noop:nil@before:nil
@params:{}@provider:Chef::Provider::File@allowed_actions:[:nothing,
:create,:delete,:touch,:create_if_missing]@action:"create"@updated:
false@updated_by_last_action:false@supports:{}@ignore_failure:false
@retries:0@retry_delay:2@source_line:"(irb#1):2:in'irb_binding'"
@guard_interpreter::default@elapsed_time:0@resource_name::file@path:
"not_empty.txt"@backup:5@atomic_update:true@force_unlink:false
@manage_symlink_source:nil@diff:nil@sensitive:false@cookbook_name:
nil@recipe_name:nil@content:"Notempty!">
Notethatwhentheshellprintedoutthepreviousfileresource,@contentwasnotpresent.Here,everythingbutthenameremainsthesame,andthereisanadditionalpropertyinsidetheobject,@content,asspecifiedinourattributesblock.
InteractivelyexecutingrecipesRunningarecipestepbystepisagoodwayofslowingdowntheexecutionofarecipesothatthestateofthesystemcanbeinspectedbeforeproceedingwiththenextresource.Thiscanbeincrediblyusefulbothfordebugging(aswillbediscussedlater)andfordevelopingandexploringresources.Itgivesyouachancetoseewhathashappenedandwhatsideeffectsyourrecipehasastherecipeisexecuted.Toachievethis,theChefshellallowsyoutorewindyourrecipetothestartandrunfromthebeginning,executeyourrecipeonestepatatime,andresumeexecutionfromthecurrentpointtotheend.
RestartingourChefshell,let’stakealookathowwecanusethis:
recipe_mode
echooff
file"foo.txt"
file"foo.txt"do
action:delete
end
file"foo.txt"do
content"Foocontent"
end
Hereourrecipeisquitesimple—createanemptyfile,foo.txt,removeit,andthenrecreateitwith“Foocontent”.Ifweexecuteourrecipeusingrun_chef,theshellwillperformalltheoperationsinonepasswithoutstoppingandwillnotallowustocheckwhetherthedeleteactionoccurred.Instead,wecanrunourrecipeandthenrewindandusethechef_run.stepmethodtointeractivelywalkthroughourrecipe:
chef:recipe>run_chef
...executionoutput…
chef:recipe>echoon
=>true
chef:recipe>chef_run.rewind
=>0
chef:recipe>chef_run.step
INFO:Processingfile[foo.txt]actioncreate((irb#1)line3)
=>1
chef:recipe>chef_run.step
INFO:Processingfile[foo.txt]actiondelete((irb#1)line4)
INFO:file[foo.txt]backedupto/var/chef/backup/foo.txt.chef-
20140615175124.279917
file[foo.txt]deletedfileatfoo.txt
=>2
chef:recipe>chef_run.step
INFO:Processingfile[foo.txt]actioncreate((irb#1)line7)
INFO:file[foo.txt]createdfilefoo.txt
INFO:file[foo.txt]updatedfilecontentsfoo.txt
INFO:file[foo.txt]ownerchangedto501
INFO:file[foo.txt]groupchangedto20
INFO:file[foo.txt]modechangedto644
=>3
Asyoucansee,herewewereabletorewindourrecipebacktothefirstinstruction(position0,astheresultofchef_run.rewindindicates),andthenwalkthrougheachresourcestepbystepusingchef_run.stepandseewhathappened.Duringthisrun,youcaneasilyopenaterminalafteryourewindtherecipe,deletethefoo.txtfilefromthepreviousrun,andcheckthatinitiallythereisnofoo.txtfile,thenstepthroughthenextcommandintherecipe,validatethatthereisanemptyfoo.txtfile,andsoon.Thisisaverygoodwaytolearnhowresourcesworkandtoseewhattheydowithouthavingtoformalizeyourrecipeinacookbook,provisionandbootstrapahost,andsoon.
DebuggingwiththeChefshellDebuggingisachievedintwodifferentwaysusingchef-shell:steppinginteractivelythrougharecipeorusingaspecialbreakpointresourcethatisonlyusedbychef-shell.Runningrecipesinteractivelystepbystepisgoodtobuildrecipeslocally;experimentwithresourcestodeterminetheeffectofcertainattributes,actionsandnotifications;ortoinspectthestateofthesystemaftereachresourcehasbeenactedupon.Thebreakpointsallowyoutoinjectveryspecificstoppingpointsintotheclientrunsothattheworldcanbeinspectedbeforecontinuing.Typically,onceabreakpointisencountered,youwillwanttostepthroughtheexecutionofyourscript(atleastforawhile)sothatthesearenotmutuallyexclusivetechniques.
UsingthebreakpointresourceThebreakpointresourceisstructuredjustlikeanyotherChefresource.Theresource’snameattributeisthelocationwhereyouwanttoinsertthebreakpoint,andithasonlyoneaction,:break,whichsignalschef-shelltointerruptexecutionofthecurrentrecipeandprovideaninteractiveshell.Anybreakpointresourcesinrecipesareignoredbythechef-client.Thatway,iftheyareforgottenaboutandleftinarecipe,theywillnotcausehavocinproduction.Thatbeingsaid,theyshouldonlybeusedwhenactivelydebugginganissueandremovedbeforereleasingyourrecipesintoyourproductionenvironment.
Thenameattributehasthefollowingstructure:
whenresourceresource_name
Here,whenhasthevalueof“before”or“after”,toindicatewhetherthebreakpointshouldstopbeforeorafterexecution,respectivelyandresourceisthetypeofresourcethatwhencombinedwithresource_nameistheuniqueidentifierthatwilltriggerthebreakpoint.Forexample:
beforefile'/tmp/foo.txt'
Thiswouldcausetheshelltointerruptexecutionoftherecipesimmediatelybeforeanyfileresourcethatwasmanipulating/tmp/foo.txt.Anotherexample,wherewewanttostopexecutionafterinstallingthegitpackage,wouldlooklikethefollowing:
afterpackage'git'
Usingthis,wewilltellchef-shellthatexecutionwastobepausedoncethegitpackagewasmodified.Let’slookathowwecanformasimplerecipecompletewithbreakpointresourcesthatwouldusetheseexamples:
breakpoint"beforefile'/tmp/foo.txt'"do
action:break
end
breakpoint"afterpackage'git'"do
action:break
end
file'/tmp/foo.txt'do
action:create
end
package'git'do
action:remove
end
Forthosewhohaveusedgdboranyotherdebugger,thiswillbeeasytounderstand;ifyouhavenotusedaninteractivedebugger,thentryafewoftheinteractiveexamples,andyouwillgetthehangofitinnotimeatall.
Chefshellprovidesacomprehensivewaytointeractwithyourrecipes.Nowthatyouseehowtotestoutanddebugyourwork,let’stakealookathowwecangoonestepfurtherinourtestingtoperformfullend-to-endintegrationtestingofourinfrastructure.
IntegrationtestingWithintegrationtesting,yourtestsmoveintotestingbeyondyourcode.WithChefSpec,welookedathowtoverifythatarecipewasexecutingstatementsasexpected.However,unittestinghasitslimits;integrationtestingcompletesthepicturebytestingcookbooksinconjunctionwithrealhoststhatrunthedesiredoperatingsystem(s):
Thismeanstestingcookbooksfromtheoutsideratherthanfromtheinside,whereaunittestprovidesverynarrowtestingofcodeinsideofcookbooks,integrationtestsperformdeploymentofyourcookbooksinatest-specificenvironmentandthenexecuteprobestovalidatethatthesystemisinthedesiredstate.OneofthemostpopulartoolsforthisisTestKitchen,whichwewilltakeabrieftourof.
UsingTestKitchenTestKitchenisanopensourcetoolthathelpstoautomateintegrationtestingofChefcookbooks.WhereaChefSpectestwouldvalidatethatafileresourcewascalledduringanexecution,asimilarlytaskedTestKitchentestwouldspinupanewinstance,executeyourrecipe(s),andthenvalidatethatthefilewasactuallycreatedonallplatformsthatyouexpecttherecipetoworkon.OneofthegreatthingsaboutTestKitchenisthatitsupportsanumberofdifferentdriverpluginstomanageyourtargethosts,includingVagrant(thedefault),EC2,OpenStack,Docker,andRackspaceCloudamongothers.ThisenablesyoutotestyourcookbooksnotonlyonlocalvirtualmachinesusingVagrant,buttoverifythattheyworkcorrectlyonacloudserviceaswell.Theabilitytoperformintegrationtestsondifferenttypesofhostsbringsyourteststhatmuchclosertomatchingaproductionenvironment,therebyincreasingyourconfidenceinthechangesyouaremaking.
InstallingTestKitchenCurrently,TestKitchenisdistributedasaRubygem,soinstallationisquitestraightforward:
geminstalltest-kitchen
Byinstallingthisgem,youarealsoinstallingacommand-linetool,kitchen,whichisusedtointeractwithTestKitchen.SimilartoVagrant,Bundler,andothertools,TestKitchenusesaconfigurationfiletostoreinformationaboutwhattotest,wheretotestit(onwhatvirtualmachines),andhowtotestit.
TestingwithTestKitchenAsmentionedbefore,TestKitchenfocusesonintegrationtestingofyourChefcomponents.Thismeansthatitneedstobeabletoexecuteyourrecipesonahost(orasetofhosts)andtheninvokeasetofteststovalidatethattheexpectedbehavioroccurredontheendhost(s).TestKitchenisresponsibleforthefollowing:
ProvisioningacleanhostfortestingInstallingChefontothenewhostExecutingourrecipesonthehostValidatingthebehavioroftherecipesonthehost
So,let’sgetstartedwithsomeactualtesting!
Buildingasimplecookbook
InordertodemonstratehowtouseTestKitchen,wewillneedtowriteacookbookthatwecanrunonourtesthostsandwriteteststovalidatethebehavior.InordertofocusonhowtouseTestKitchen,wewilltakealookataverysimplecookbookthatjustcreatesafileonthehostfilesystemsothatitisguaranteedthatitwillworkonbothUbuntuandCentOSplatforms.
First,createaplacetodoyourwork:
mkdir-ptestfile-cookbook/recipes
Then,addaverysimplemetadata.rbinyourtestfile-cookbookdirectorywiththefollowingcontentsinside:
name"testfile"
version"1.0"
Oncethatiscomplete,addadefaultrecipe,recipes/default.rb,withthesamplerecipecodeasfollows:
file"/tmp/myfile.txt"do
content"Myawesomefile!"
end
Nowthatwehaveacomplete(albeitsimple)cookbook,let’stakealookathowtotestitusingTestKitchen.
Preparingyourcookbookforthekitchen
InordertostartusingTestKitchen,youwillneedtoprepareyourtestenvironment.TestKitchenreliesonaconfigurationfile,.kitchen.yml,totellitwhattodo.Youcangenerateitbyhand,oryoucouldusetheinitcommandaspartofthekitchentool:
kitcheninit
Thiswilldoafewthingsforyou:first,itwillcreatea.kitchen.ymlfilewithsomesanedefaultsinthecurrentdirectory.Then,itwillcreateatest/integration/defaultdirectoryforyourintegrationtests,andthenitwillinstalltheVagrantdriverforTestKitchensothatitcaninteractwithVagrantvirtualmachines(ifithasnotalreadybeeninstalled).
Ifyoulookatthe.kitchen.ymlfile,youwillseethattheinitialfilecontainsthefollowingYAMLcode:
---
driver:
name:vagrant
provisioner:
name:chef_solo
platforms:
-name:ubuntu-12.04
-name:centos-6.4
suites:
-name:default
run_list:
-recipe[testfile::default]
attributes:
ThisconfigurationfileinstructsTestKitchentouseVagranttomanagethetargetinstancesandtouseChef-soloforprovisioning,anditshouldexecutethedefaultsuiteoftestsonbothUbuntu12.04andCentOS6.4.Ofcourse,youcanalwaysmodifyorextendthislistifyouhaveothersystemsthatyouwanttoruntestson,butthisisareasonabledefaultlist
fornow.
Noticethatwedon’thaveanyattributesspecifiedasourdefaultrecipeanditdoesnotuseattributes.Ifyouneedtoprovideattributestotestrecipes,thiswouldbetheplacetodoit,whichislaidoutasadictionaryinYAML.Eachtestsuitehasitsownrunlistanddefinedattributesthatallowyoutocheckthebehaviorofavarietyofconfigurationdataandrecipecombinations.
Testingyournewcookbook
TestingacookbookwithTestKitchenisoutlinedinthefollowingthreesteps:
1. Provisioningahostifneeded.2. Convergingthehostsothatitisuptodate.3. Executingtests.
Let’stakealookathowwewillperformthesewithoursimplecookbooktocheckthatitworksproperly.Provisioningtheinstance
Beforeyoucantest,youwillneedtopreparetheinstancesfortesting—thisisdoneusingthekitchencreate<instancename>command.Onlyyoudon’tknowwhatinstancetobringupjustyet.Togetthelistofinstancesthatcanberun,wewillusethelistsubcommand:
[jewart]$kitchenlist
InstanceDriverProvisionerLastAction
default-ubuntu-1204VagrantChefSolo<NotCreated>
default-centos-64VagrantChefSolo<NotCreated>
Youwillseethatthislistisgeneratedbyacombinationoftheplatformsandthesuiteslistedinthe.kitchen.ymlfile.Ifyouweretodefineanewsuite,namedserver,thenyourlistwouldincludetwoadditionalinstances,server-ubuntu-1204andserver-centos-64.
Onceyouhaveseenthislist,youcancreateanUbuntu12.04instancewiththefollowingcommand:
kitchencreatedefault-ubuntu-1204
ThiswilluseVagrantandVirtualBoxtoprovisionanewheadlessUbuntu12.04hostandbootitupforyoutostarttestingwith.Ifyoudon’talreadyhaveanUbuntu12.04Vagrantimagedownloaded,thenitwillbedownloadedforyouautomatically(thisisalargeimagesoitmaytakeawhiletocompletethisoperation,dependingonyourconnectionspeed,ifyouarefollowingalong).
ThiswilllookfamiliartothosethatusedVagrantatthebeginningofthechapter:
[jewart]%kitchencreatedefault-ubuntu-1204
----->StartingKitchen(v1.2.1)
----->Creating<default-ubuntu-1204>...
Provisioninghappens…
Finishedcreating<default-ubuntu-1204>(4m2.59s).
----->Kitchenisfinished.(4m2.83s)
However,thisonlybuildsthevirtualmachineforourtests;itdoesnotrunourrecipesorourtestsinthenewlyconstructedhost.Beforewemoveontowritingatest,let’stakealookathowtorunourrecipeinsideourinstance.Convergingthenewlycreatedinstance
Thenextstepistoexecuteourrunlist(“converge”inChefparlance)onthenewinstance.ThisisdonewithTestKitchen’sconvergecommandandcanbeusedforalloronespecificinstance.InordertoconvergeourUbuntu12.04instance,thefollowingcommandisused:
kitchenconvergedefault-ubuntu-1204
Whatthiswilldoistransferanyrequireddatafiles,installChefasneeded,andthenexecutetherunlistforthespecifiedsuite(defaultinthiscase)ontheinstance.Hereisasamplerunofconverge(withsomepartsremoved):
[jewart]%kitchenconvergedefault-ubuntu-1204
----->StartingKitchen(v1.2.1)
----->Converging<default-ubuntu-1204>...
Preparingfilesfortransfer
Preparingcurrentprojectdirectoryasacookbook
Removingnon-cookbookfilesbeforetransfer
----->InstallingChefOmnibus(true)
InstallationofChefhappens…
CompilingCookbooks…
Converging1resources
Recipe:testfile::default
AndthenyouwillseetheoutputofanormalChefrunafterthat—atthispoint,therunlistiscompleteandtheinstancehasbeenconvergedtothelateststate.Nowthatit’sbeenconverged,wewillcontinuetowriteaverysimpletesttoverifythatourrecipedidtherightthing.Writingasimpletest
TestsarestoredinadirectorywhosestructureissimilartootherChefcomponents—thedirectorywecreatedpreviouslywithkitcheninit,tests/integration/default,allowsustokeepourintegrationtestsseparatefromspectestsorothertypesoftests.Theintegrationdirectorywillcontainonedirectorypersuitesothattestfilesaregroupedtogetherbasedontheparticularcomponentoraspectofyourcookbookthatisbeingtested.Additionally,dependingonthetypeofthetestframeworkbeingused,yourtestswillbecontainedinanotherchilddirectoryforthegivensuite.Inthiscase,wewilltakealookattheBASHAutomatedTestingSystem(BATS),soourtestwillbeplacedinthetests/integration/default/bats/file_created.batsfileandwilllooklikethefollowingcode:
#!/usr/bin/envbats
@test"myfile.txtexistsin/tmp"{
[-f"/tmp/myfile.txt"]
}
Thisallowsustousethesimple-fBASHtest(whichreturnstrueifthespecifiedvalueexists)toguaranteethatthefilewascreatedontheinstance.
Next,wecanrunthistestwithkitchenverifydefault-ubuntu-1204andseethattheBATSpluginwasinstalledandthatourtestwasexecutedandpassed:
[jewart]%kitchenverifydefault-ubuntu-1204
----->StartingKitchen(v1.2.1)
----->Settingup<default-ubuntu-1204>...
Fetching:thor-0.19.0.gem(100%)
Fetching:busser-0.6.2.gem(100%)
Successfullyinstalledthor-0.19.0
Successfullyinstalledbusser-0.6.2
2gemsinstalled
----->SettingupBusser
CreatingBUSSER_ROOTin/tmp/busser
Creatingbusserbinstub
Pluginbatsinstalled(version0.2.0)
----->Runningpostinstallforbatsplugin
InstalledBatsto/tmp/busser/vendor/bats/bin/bats
Finishedsettingup<default-ubuntu-1204>(0m8.94s).
----->Verifying<default-ubuntu-1204>...
Suitepathdirectory/tmp/busser/suitesdoesnotexist,skipping.
Uploading/tmp/busser/suites/bats/file_created.bats(mode=0644)
----->Runningbatstestsuite
√myfile.txtexistsin/tmp
1test,0failures
Finishedverifying<default-ubuntu-1204>(0m0.67s).
----->Kitchenisfinished.(0m9.86s)
Todemonstratewhathappensifthefiledoesnotexist,wecancloneourtesttocreateasecondsimpletestthatvalidatestheexistenceof/tmp/myotherfile.txt,andrunourverifycommandagainwithoutmakingacorrespondingchangetoourrecipe.TheoutputfromTestKitchenwilltellusthatourtestfailedandwhy:
----->Runningbatstestsuite
√myfile.txtexistsin/tmp
x
myotherfile.txtexistsin/tmp
(intestfile/tmp/busser/suites/bats/other_file.bats,line4)
2tests,1failure
Command[/tmp/busser/vendor/bats/bin/bats/tmp/busser/suites/bats]exit
codewas1
>>>>>>Verifyfailedoninstance<default-ubuntu-1204>.
Combiningallthesteps
Fortunately,thefinefolksthatcreatedTestKitchenrealizedthatitwouldbetedioustorunallthreestepseverytimeyouwantedtorunsometests.Asaresult,thereisthekitchentestcommandthatwillprovisionaninstance,executetherunlist,verifytheresults,andthenteardowntheinstancewithonlyonecommand.Inthiscase,youcanreplacethemwiththefollowingsinglecommand:
kitchentestdefault-ubuntu-1204
ThiscoversthebasicsoftestingyourcookbookswithTestKitchen.ThereareotherthingsthatcanbedonewithTestKitchen,includingusingothertestingmechanisms,testingcookbookdependencies,validatingwhetherservicesarerunning,fullyautomatingTestKitchenaspartofyourreleaseprocess,andplentymore.Formoreinformation,visittheprojectathttp://kitchen.ci/.
ExtendingChefChefisdevelopedintheopenwithflexibilityandextensibilityinmind.Mostofthetoolsarearchitectedtosupportloadingcustompluginstosupportdevelopmentofadd-onsfornewfunctionality.Asyousawearlier,Knife’scloudservicesupportisprovidedbyplugins,oneforeachcloudservice,includingEC2,Azure,andRackspacecloud.WewilllookathowthathappenssothatyoucanexplorewritingyourownpluginsforKnife,Ohai,andotherChefcomponentsshouldtheneedarise.
InadditiontoextendingChef’scorecomponentsdirectly,itispossibletoextendthefunctionalityofyourChefecosystembybuildingenhancementstoexistingtoolsthatcanleverageChef’sdataAPIstoprovidedataaboutyourinfrastructure.
WritinganOhaipluginOhai(aplayonthephraseoh,hi!)isatoolthatisusedtodetectattributesonanode,andprovidetheseattributestothechef-clientatthestartofeverychef-clientrun.WithoutOhai,thechef-clientwillnotfunction,andtherefore,itmustbepresentonanodeinorderforCheftowork.Thedatathatiscollectedisauthoritative—ithasthehighestlevelofprecedencewhencomputingattributedataforclientrunsonanode.
ThetypesofattributesthatOhaimightbeusedforinclude:
PlatformdetailsNetworkusageMemoryusageProcessorusageKerneldataHostnamesInformationaboutthenetworktopologyCloud-specificinformation
Ohaiimplementsanextensiblearchitecturethroughpluginsthatallowsenduserstowritecustomextensionstoreportinformationthatiscollectedaboutanode.Forexample,therearepluginsforEC2cloudhoststhatuseEC2-specificmechanismstodetermineinformationaboutthehost,includingitsinternalIPaddressandotherbitsofinformation.
ThisisincrediblyusefulforintegratingChefwithanexistinginfrastructure,asyoucanautomaticallyprobeforlocalconfigurationdataandgenerateattributesfromthatdata.OncethisdataisstoredinChef,itcanbeusedinsearchqueries,recipes,andeverywhereelsethatyoucouldotherwiseusenodeattributes.
EveryOhaipluginfollowsapattern:itregistersitselfasprovidingacertainclassofattributedataandcontainsacombinationofgeneralpurposeanddatacollectionmethodstogatherinformationaboutthelocalstateofthesystemandreportitbacktotheChefserver.Ohaialreadyhasbuilt-incollectorsforanumberofplatforms,includingLinux,Windows,andBSD.
AsampleOhaipluginwouldlooklikethefollowing:
Ohai.plugin(:Region)do
provides"region"
definit
regionMash.new
end
collect_data(:default)do
#Runsonallhostswhoseplatformisnotspecificallyhandled
init
region[:name]="unknown"
end
collect_data(:linux,:freebsd)do
#RunonlyonLinuxandFreeBSDhosts
init
region[:name]=discover_region_unix
end
collect_data(:windows)do
#RunonWindowshosts
init
region[:public_ip]=discover_region_windows
end
end
Hereweregisterourpluginasprovidingaregiondata,thereisaninitmethodthatcreatesaMashforourregiondata,andafewdatacollectioncallbacks.Eachdatacollectioncallbackisregisteredasablockthatiscalledforthespecifiedplatform(s).Inourcase,therearethreecallbacksregistered,oneforWindowssystems,oneforLinuxandFreeBSDsystems,andthenafallbackthatwillbecalledforanyplatformnotexplicitlyhandled.
AnoteaboutwritingOhaipluginsThewaywedeclaretheregionMashinourinitmethodisalittlebitdifferentthannormalvariableassignmentinRuby.Inourplugin,wedefinetheplugin’sregionpropertywiththefollowingcode:
regionMash.new
Onthesurface,thismightlooklikesomeoneforgotanequalssign,asinthefollowingRubycode:
region=Mash.new
However,iftherewereanequalssign,thepluginwouldnotworkasintended.Inthiscase,thereisnoequalssignmissingandthecodeiscorrect.ThisisbecauseOhai’sPluginclassleveragesaspecialRubymechanismforinterceptingcallstononexistentmethodsanddynamicallyhandlingthem.Thismechanismisrecognizablebythepresenceofaspecialmethodnamedmethod_missinginthecode.Inthiscase,themethod_missinghandlerwillcallaspecialOhaipluginmethod,get_attribute,ifnoargumentsarepassed,oritwillcalltheset_attributemethodifargumentsarepassed.
Todemonstratewhythisisused,ifyouwantedtohavethesameeffectwithoutthemethod_missingmechanism,thentheplugin’sinitmethodcouldbewrittenas:
definit
set_attribute"region",Mash.new
end
Wereyoutodothis,thenoursubsequentcollectdatamethodswouldneedtoberewrittenaswell.Hereisanexampleofwhattheymightlooklike:
collect_data(:platform)do
init
region=get_attribute"region"
region[:name]=get_data_mechanism
set_attribute"region",region
end
Youcanseethatthemethod_missingmechanismmakeswritingpluginsmorenatural,thoughittakesalittlebitofextraworktounderstandhowtowritethematfirst.
ChefwithCapistranoOneexampleofextendingChefthroughexternaltoolsisthecapistrano-chefgemthatextendsthepopulardeploymenttool,Capistrano.WritteninRuby,Capistranowasdesignedtodeployapplicationsandperformlightsystemsadministration.IfyouhaveexistingapplicationsthatarebeingdeployedusingCapistrano,thisisanexampleofhowtoleverageyourconfigurationdatastoredinCheftomakeintegrationasseamlessaspossible.
IfyouhaveanexistingapplicationthatusesCapistrano,youwillhaveadeploy.rbfilethatdefinesthevariousapplicationroles.EachtoolhasanarrayofIPaddressestohoststhatprovidethatroleandmightlooksomethinglikethis:
role:web,'10.0.0.2','10.0.0.3'
role:db,'10.0.0.2',:primary=>true
Byusingcapistrano-chef,youcandothis:
require'capistrano/chef'
chef_role:web,'roles:web'
chef_role:db,'roles:database_master',
:primary=>true,
:attribute=>:private_ip,
:limit=>1
NoticethatherewehaveusedasimplesearchquerytodeterminethehoststhatshouldbeincludedineachCapistranorole.Inthiscase,the:webrolehasbeenreplacedwithaChefsearchqueryforallnodesthathavethewebChefroleassociatedwiththem.ThisallowsyoutomodelyourdatainChef,butstilluseCapistranotodeployyourapplicationstack,increasingtheeaseofintegration.
ThisworksbecauseallofChef’sdataisavailableviaanHTTPAPImakingintegrationassimpleasmakingHTTPcallsandparsingsomeJSONresults(which,comparedtosomeotherintegrationmechanisms,isincrediblyeasy).
AutomationandintegrationOneofthebestpartsaboutChefisthatyourinfrastructureandsoftwaremodelisconsistentwithwhatisdeployed.Whatthismeansforyouisthatwhenchef-clientrunsonanendhost,thathost’sstateisupdatedtomatchyourmodeledenvironment.Forexample,considerascenariowhereyouhave10EC2databasehosts,andallofthemhaveaspecialrole,database_server,appliedtothem.Thisrole’sattributesindicatethatPostgreSQL9.1istobeinstalledanditsdatashouldbestoredin/opt/postgresql/data.Byexecutingchef-clientonalltennodes,theywillhavePostgreSQL9.1installedandstoringdatain/opt/postgresql/data.NowconsiderthatallofournodesneedtohaveanewEBSstoragedeviceattachedtoeachofthem,andPostgreSQLneedstobepointedtoournewEBSdevice.UpdatingourmodeltoincludearecipethatmountstheEBSdevicegracefullyshutsdownPostgreSQL,movesthedatabasedata,reconfiguresPostgreSQL,andstartsitupagain.Wecanautomateandrolloutthisconfigurationtoourfleetoftendatabasehosts.Youcaneasilyimaginetenhostsgrowingtohundredsoreventhousands.Thisiswhatthepowerofautomationisallabout.
AutomatedupdatesanddeploymentsIfyouhaveconfidenceinyourmodelandyourcookbooks,thenyoucantakethisautomationonestepfurther.Byautomatingtheexecutionofchef-clientonaperiodicschedule,youcanfullyautomateupdateswithoutneedingtoSSHintothehoststorunchef-client.However,thislevelofautomationrequiresahighlevelofconfidenceinthecorrectnessofyourcookbooks.Achievingthisrequirescontinuousandin-depthtestingofnotonlythecodeinthecookbooksdevelopedbutalsoofthedependenciesthatareneededtomakeyourcookbookswork.Tothatend,comprehensiveintegrationtestscanhelptobuildtheconfidenceneededtomoveintoafullyautomatedworld.
SummaryBynowyouhavebeenexposedtoalotofwhatChefhastooffertheDevOpscommunity.YouhaveseenwhatChefdoes,howtoinstallit,andhowitworks.Throughoutthisbook,youhavebeenintroducedtosomenewwaysofthinkingabouthowtomodelinfrastructureanduseautomatedtoolstomanageit.
Atthispoint,youhopefullyunderstandhowtomodelyourinfrastructurewithChefaswellasinstallthevariouscomponentsrelatedtoChef,rangingfromtheservertotheclient.Fromhere,youcantakewhatyou’velearnedaboutthevariouscomponentsofChefandusethatinformationtobuildmoreadvancedcookbookstodeployyoursoftwareandmanageyourinfrastructure,rangingfromcloudhoststophysicalon-sitehardwareandevenvirtualmachinesusingVagrant.Onceyouhavegottenthingsworking,youcanautomateyourconfigurationtoolsandensurethereliabilityofyourcookbooksthroughunitandintegrationtestsaswell.
Now,itisyourturntotakethisinformationandyournewskillstoautomateyoursystemsinfrastructureinordertobuildexcitingnewthings!
IndexA
AmazonEC2about/AmazonEC2workingwith/AmazonEC2knifeplugin,installing/InstallingtheEC2knifepluginauthentication,settingup/SettingupEC2authenticationinstance,provisioning/Provisioninganinstanceinstance,bootstrapping/Bootstrappingtheinstanceinstance,terminating/TerminatingtheinstanceChefnode,removing/RemovingtheChefnode
AmazonEC2authenticationsettingup/SettingupEC2authentication
AMIIDURL/Provisioninganinstance
ApacheSolrdocumentationURL/Queryingyourdata
applicationconfiguring/Configuringyourapplication
applicationdeploymentcookbookviewing/Lookingatyourapplicationdeploymentcookbook
applicationrunmaintaining/Keepingyourapplicationrunning
applicationtemplatedefining/Definingafullapplicationtemplate
attributes/GettingtoknowChefabout/Attributesconfigurationvalues/Attributesmultiplefiles/Multipleattributefilesusing/Usingattributes
authorizedkeys,SSHkeystemplating/Templatingtheauthorizedkeys
automation,Chef/Automationandintegration
BBase64encoding/Secretkeysbaselinerole/ModelingasimplePythonapplicationbaseserverrole
creating/CreatingthebaseserverroleBASHAutomatedTestingSystem(BATS)/Writingasimpletestbootstrap/GettingtoknowChefbootstrapscript/GettingtoknowChefbreakpointresource,Chefshell
using/UsingthebreakpointresourceBundler,ChefSpec/InstallingChefSpec
CCapistran
Chef,usingwith/ChefwithCapistranoChef
terminology/Terminologyworkingwith/WorkingwithChefbenefits/WorkingwithChef,Large-scaleinfrastructurechef-solo,installing/Installingchef-soloRubygemmechanism/TheRubygemgems,managing/Managinggemschef-solo,verifying/Verifyingthatchef-soloworksabout/GettingtoknowChefnode/GettingtoknowChefworkstation/GettingtoknowChefbootstrap/GettingtoknowChefbootstrapscript/GettingtoknowChefrecipe/GettingtoknowChefcookbook/GettingtoknowChefattributes/GettingtoknowChefrole/GettingtoknowChefrunList/GettingtoknowChefresource/GettingtoknowChefprovider/GettingtoknowChefdatabags/GettingtoknowChefenvironment/GettingtoknowChefused,forsoftwaredeployment/DeployingsoftwarewithChefdependencies,managing/ManagingdependenciesinChefextending/ExtendingChefOhaiplugin,writing/WritinganOhaipluginusing,withCapistran/ChefwithCapistranoautomation/Automationandintegrationintegration/Automationandintegrationupdates,automating/Automatedupdatesanddeployments
chef-server-ctl,Chefserverworking/Understandinghowchef-server-ctlworks
Chef-soloabout/VagrantandChef-soloVagrant,combiningwith/CombiningVagrantwithChef-solo,ConfiguringChef-solo,TellingChef-solowhattorunlimitations/UnderstandingthelimitationsofChef-soloconfiguring/ConfiguringChef-solocookbooks_path/ConfiguringChef-solodata_bags_path/ConfiguringChef-solo
roles_path/ConfiguringChef-solorundirections,providing/TellingChef-solowhattorunroles,usingwith/UsingrolesanddatabagswithChef-solodatabags,usingwith/UsingrolesanddatabagswithChef-solo
chef-soloinstalling/Installingchef-soloverifying/Verifyingthatchef-soloworks
ChefcommunityURL/Managingthecookbooks
ChefLWRP,componentresource/Buildingaresourceprovider/Buildingaresource
Chefnode,AmazonEC2removing/RemovingtheChefnode
Chefnode,RackspaceCloudremoving/RemovingtheChefnode
Chefserverinstalling/InstallingaChefserverURL/Gettingtheinstallerconfiguring/ConfiguringaChefserverinteracting,Chefshellused/InteractingwiththeChefserverusingtheshell
Chefserver,installingonRedHatEnterpriseLinuxpackage,downloading/Downloadingthepackagepackage,downloading,URL/Downloadingthepackage
Chefserver,installingonUbuntuabout/InstallingonUbuntupackage,downloading/Downloadingthepackagepackage,downloading,URL/Downloadingthepackagepackage,installing/Installingthepackage
Chefserverconfigurationabout/ConfiguringaChefserverchef-server-ctl,working/Understandinghowchef-server-ctlworkshostserveractions,monitoring/What’shappeningonmyserver?services,verifying/Verifyingthattheservicesarerunning
Chefserverinteraction,withChefshelldata,interactingwith/Interactingwithdatadata,searching/Searchingyourdatadata,editing/Editingyourdatadata,transforming/Transformingdata
Chefserviceabout/Terminologyvalidating/Validatingthatyourserviceisworkingknifeconfiguration/Ensuringthatyourknifeconfigurationworks
Chefshell
about/GettingtoknowtheChefshellusing/UsingtheChefshellstandalonemode/Thestandalonemodesolomode/Thesolomodeclientmode/Theclientmodeused,forinteractingwithChefserver/InteractingwiththeChefserverusingtheshellused,forexecutingrecipes/ExecutingrecipeswithChefshellrecipe,creatingin/Creatingarecipeintheshellused,fordebugging/DebuggingwiththeChefshellbreakpointresource,using/Usingthebreakpointresource
ChefSpecabout/TestingrecipesandRSpec/RSpecandChefSpectestingbasics/Testingbasicsusing/UsingChefSpecoverview/GettingstartedwithChefSpecinstalling/InstallingChefSpecdependencies,lockinginRuby/LockingyourdependenciesinRubysimplerecipe,creating/CreatingasimplerecipeandamatchingChefSpectest
ChefSpectestwriting/WritingaChefSpectestexpanding/Expandingyourtestsmultipleexamples/Multipleexamplesinaspectesttesting,formultipleplatforms/Testingformultipleplatforms
ChefSupermarketURL/Determiningwhichrecipesyouneed
clientmode,Chefshell/Theclientmodecloud
leveraging/Leveragingthecloudcloudplatformproviders
AmazonEC2/AmazonEC2RackspaceCloud/RackspaceCloud
components,forChefserverinstallation/Whatyouwillbeinstallingconfigurationblocks
using/Usingconfigurationblocksconfigurationdata
organizing/Organizingyourconfigurationdatasources/Organizingyourconfigurationdataattributedataexample/Exampleattributedatadatabags/Databags
cookbook/Terminologyabout/GettingtoknowChef,Installingacookbookinstalling/Installingacookbook
cookbook,TestKitcheninstance,provisioning/Provisioningtheinstancecreatedinstance,converging/Convergingthenewlycreatedinstancetest,writing/Writingasimpletestsummarizing/Combiningallthesteps
cookbooksmanaging/ManagingthecookbooksURL/Managingthecookbooksdownloading/DownloadingcookbooksURL,fordownloading/Downloadingcookbooksdatabaserecipe,viewing/Lookingatthedatabaserecipeapplicationdeploymentcookbook,viewing/Lookingatyourapplicationdeploymentcookbookdirectories,preparing/PreparingthedirectoriesPythonvirtualenvironment,constructing/ConstructingyourPythonvirtualenvironmentsourcecode,checkingout/Checkingthesourcecodedependencies,installing/InstallinganyextradependenciesPython’srequirementsfile,using/UsingPython’srequirementsfileapplication,configuring/Configuringyourapplicationapplicationrun,maintaining/Keepingyourapplicationrunning
customdefinition,developingabout/Developingacustomdefinitioncode,organizing/Organizingyourcodedefinition,writingforusingPIP/WritingadefinitionforusingPIPapplicationtemplate,defining/Definingafullapplicationtemplate
customextensionswriting/Writingcustomextensionsresource,building/Buildingaresource
Ddata
storing,indatabags/Storingdataindatabagssearching/Searchingfordata,Searchingyourdataencrypting/Encryptingyourdatadecrypting/Decryptingyourdataquerying/Queryingyourdata
data,Chefserverinteracting/Interactingwithdatasearching/Searchingyourdataediting/Editingyourdatatransforming/Transformingdata
data,searchingdatabags,searchingwithknifetool/Searchingyourdatabagswithknifedatabags,searchingfromrecipes/Searchingyourdatabagsfromarecipedata,querying/Queryingyourdata
databagsabout/GettingtoknowChef,Databagsusing/Knowingwhentousedatabagsdata,storingin/Storingdataindatabagscreating,forusers/Creatingadatabagforusersworkingwith/Workingwithdatabagssecuring/Securingyourdatabagsdata,searching/Searchingyourdatabagswithknifemultiplemachines,managingwithsearchqueries/Managingmultiplemachineswithsearchqueries
databagssearching,withknifetool/Searchingyourdatabagswithknifesearching,fromrecipes/Searchingyourdatabagsfromarecipe
databags,securingabout/Securingyourdatabagssecretkeys/Secretkeysdata,encrypting/Encryptingyourdatadata,decrypting/Decryptingyourdatakeys,storingonnodes/Storingkeysonnodes
databags,usingwithChef-soloabout/UsingrolesanddatabagswithChef-solocustomJSONdata,injecting/InjectingcustomJSONdatacustomnodename,providing/Providingacustomnodename
databasehostconfiguring/Configuringthedatabasehost
databaserecipeviewing/Lookingatthedatabaserecipe
databaseserverrolecreating/Creatingthedatabaseserverrole
defaultaction/Overridingadefaultbehaviordefaultbehavior,resource
overriding/Overridingadefaultbehaviordefinition/Developingacustomdefinitiondefinitions
about/Definitionsdependencies
installing/Installinganyextradependenciesmanaging,inChef/ManagingdependenciesinChefmanaging/Managingdependencieselsewhere
deploymentkeys,SSHkeysadding/Addingdeploymentkeys
deployusers/Addingdeploymentkeysdirectories
preparing/Preparingthedirectoriesdriverplugins/UsingTestKitchendry-runmode/Implementingtheprovider
EEC2instances
provisioning/ProvisioningEC2instancesdatabasehost,configuring/Configuringthedatabasehost
echooffstate/Creatingarecipeintheshellechoonstate/Creatingarecipeintheshellencryptionkey
storing,onnodes/Storingkeysonnodesenvironment
about/GettingtoknowChefexample/Environments
equalsign/VariablereplacementERBprimer
about/AquickERBprimerURL/AquickERBprimerRubycode,executing/ExecutingRubyRubycodeexecution,variablereplacement/Variablereplacement
Ffetch_from_s3method/Implementingtheprovider
Ggems
managing,withRVM/Managinggemsgemset/Managinggems
HHostedChef
URL/WorkingwithChef
IImage-O-Rama/Determiningwhichrecipesyouneedimage-processingrole
defining/Animage-processingroleimagesearchrole
defining/Animagesearchroleinfrastructuremodeling
steps/Modelingyourinfrastructureabout/ModelingyourinfrastructureChef,using/Modelingyourinfrastructureservice-orientedwebapplication,services/Modelingyourinfrastructureservice-orientedarchitecture,advantages/Modelingyourinfrastructurerole/Rolesrole,implementing/Implementingarolerecipes,determining/Determiningwhichrecipesyouneedrecipes,applyingtorole/Applyingrecipestorolesrole,mappingtonode/Mappingyourrolestonodesrole,mappingtonodes/Mappingyourrolestonodesnodes,converging/Converginganodeenvironment/Environments
installationVagrant/InstallingVagrant
installation,Chefserverabout/InstallingaChefserverrequisites/Requirementsandrecentchanges,Installationrequirementscomponents/Whatyouwillbeinstallingomnibusinstaller,obtaining/GettingtheinstalleronUbuntu/InstallingonUbuntuonRedHatEnterpriseLinux/InstallingonRedHatEnterpriseLinux
instance,AmazonEC2provisioning/Provisioninganinstancebootstrapping/Bootstrappingtheinstanceterminating/Terminatingtheinstance
instance,RackspaceCloudprovisioning/Provisioninganinstanceterminating/Terminatinganinstance
integration,Chef/Automationandintegrationintegrationtesting
about/IntegrationtestingTestKitchen,using/UsingTestKitchen
Kknife-rackspaceplugin/Provisioninganinstanceknifenodelist/InteractingwiththeChefserverusingtheshellknifeplugin,AmazonEC2
installing/InstallingtheEC2knifepluginknifesearchnode…/InteractingwiththeChefserverusingtheshellknifetool/Validatingthatyourserviceisworking
used,forsearchingdatabags/Searchingyourdatabagswithknife
Llarge-scaleinfrastructure/Large-scaleinfrastructureload_current_resourcemethod/Loadinganexistingresourcelocalenvironment
configuring/Configuringyourlocalenvironment
MMash,Chef/UsingattributesmatchingChefSpectest
creating/CreatingasimplerecipeandamatchingChefSpectestmetadata
about/Metadatamockmethods/RSpecandChefSpecmultipleattributefiles
about/Multipleattributefilesmultipleplatforms,supporting/Supportingmultipleplatformsexternalattributes,loading/Loadingexternalattributes
multiplemachinesmanaging,withsearchqueries/Managingmultiplemachineswithsearchqueries
mysql/Lookingatthedatabaserecipe
N*nameparameter/WritingadefinitionforusingPIPnewhost
provisioning,withVagrant/ProvisioninganewhostwithVagrantnode/Terminology
about/GettingtoknowChefrole,mappingto/Mappingyourrolestonodesconverging/Converginganode
nodeattributesdefining/Definingnodeattributessetting/Settingattributesaccessing/Accessingattributes
nodedeletecommand/RemovingtheChefnodenodehash/Usingattributes
OOhaiplugin
writing/WritinganOhaiplugin,AnoteaboutwritingOhaipluginsattributes/WritinganOhaiplugin
omnibusinstallationpackage,Chef/Requirementsandrecentchangesomnibusinstaller,Chefserver
obtaining/Gettingtheinstalleroutline/Installationoutlinesteps/Installationoutline
OpenSSHserviceroledefining/AnOpenSSHservicerole
PPIP
used,forwritingdefinition/WritingadefinitionforusingPIPpostgresql/LookingatthedatabaserecipePostgreSQLservicerole
defining/APostgreSQLserviceroleprovider/GettingtoknowChef
about/Buildingaresourceimplementing/Implementingtheprovider
Pythonapplicationmodelling/ModelingasimplePythonapplication
Pythonrequirementsfileusing/UsingPython’srequirementsfile
Pythonvirtualenvironmentconstructing/ConstructingyourPythonvirtualenvironment
Python’srequirementsfileusing/UsingPython’srequirementsfile
RRackspaceCloud
about/RackspaceCloudinstance,provisioning/Provisioninganinstanceinstance,terminating/TerminatinganinstanceChefnode,removing/RemovingtheChefnode
read-eval-print-loop(REPL)/GettingtoknowtheChefshellrecipe/Terminology
about/GettingtoknowChefdetermining/Determiningwhichrecipesyouneedneedfor/Determiningwhichrecipesyouneedcookbook,installing/Installingacookbookapplying,torole/Applyingrecipestorolesexecuting/Startingoutsmalltesting/Testingrecipestesting,withChefSpec/Testingrecipesbuilding/Buildingyourrecipetests,executing/Executingtestsfailures/Understandingfailurescreating,inChefshell/Creatingarecipeintheshell
recipesabout/Recipes,Recipesstartingstates/Recipesdeveloping/Developingrecipeswriting/Writingrecipessimpleservice,installing/Installingasimpleservicecomplicatedrecipe,actions/Gettingmoreadvancedenhancing,withsearchmethod/Searchinginsiderecipesdatabags,searchingfrom/Searchingyourdatabagsfromarecipe
recipes,executingwithChefshellabout/ExecutingrecipeswithChefshell,Interactivelyexecutingrecipesrecipemode/Creatingarecipeintheshellattributemode/Creatingarecipeintheshellnodeattributes,defining/Definingnodeattributesattributes,setting/Settingattributesattributes,accessing/Accessingattributesconfigurationblocks,using/Usingconfigurationblocks
RedHatEnterpriseLinuxChefserver,installingon/InstallingonRedHatEnterpriseLinux
resource/GettingtoknowChefbuilding/Buildingaresourcepackageresource/Buildingaresourcedefining/Definingtheresource
provider,implementing/Implementingtheprovidermodifying/Modifyingresourcesexistingresource,loading/Loadinganexistingresourceupdatedstatus,declaring/Declaringthataresourcewasupdated
resourcesabout/Resourcesbuilt-inresources/Resourcesusing/Usingresourcesdefaultbehavior,overriding/Overridingadefaultbehavior
reusableresources,defininginChefbenefits/Definingafullapplicationtemplate
roleabout/GettingtoknowChef,Rolesdefining/Definingrolesimplementing/Implementingaroleapplying,torecipes/Applyingrecipestorolesmapping,tonode/Mappingyourrolestonodes
role,definingabout/Definingroleswebapplicationservicerole/Awebapplicationserviceroleimage-processingrole/Animage-processingroleimagesearchrole/AnimagesearchrolePostgreSQLservicerole/APostgreSQLserviceroleSolrservicerole/ASolrserviceroleOpenSSHservicerole/AnOpenSSHservicerole
rolesdefining/Definingrolesbaseserverrole,creating/Creatingthebaseserverroledatabaseserverrole,creating/Creatingthedatabaseserverrolewebserverrole,creating/Creatingthewebserverrole
roles,usingwithChef-soloabout/UsingrolesanddatabagswithChef-solocustomJSONdata,injecting/InjectingcustomJSONdatacustomnodename,providing/Providingacustomnodename
RSpecabout/RSpecandChefSpec/RSpecandChefSpectestinglevels/RSpecandChefSpeccomparing,withtestinglibraries/ComparingRSpecwithothertestinglibrariesfailures/Understandingfailures
Rubydependencies,locking/LockingyourdependenciesinRuby
Rubygemmechanism/TheRubygemRubyVersionManager(RVM)
about/TheRubygemURL/Managinggems
runlist/GettingtoknowChefrunner/Testingformultipleplatforms
Ssearchmethod
used,forenhancingrecipes/Searchinginsiderecipessearchqueries
used,formanagingmultiplemachines/Managingmultiplemachineswithsearchqueries
secretkeys,databagsmanaging,withsearchqueries/Secretkeys
serverlistsubcommand/Terminatinganinstanceservice-orientedarchitecture(SOA)/VagrantandChef-solosetup
describing/Describingthesetupshelluserrecipe
evolution/Evolutionofashelluserrecipesoftware
deploying,withChef/DeployingsoftwarewithChefdeploying/Deployingyoursoftware
softwaredeploymentupdates,deployingmanually/Manuallydeployingupdatesautomating/Automatingdeployment
solomode,Chefshell/ThesolomodeSolrservicerole
defining/ASolrservicerolesourcecode
checking/CheckingthesourcecodeSSHkeys
distributing/DistributingSSHkeysauthorizedkeys,templating/Templatingtheauthorizedkeysdeploymentkeys,adding/Addingdeploymentkeys
standalonemode,Chefshell/Thestandalonemodesupervisordservice
URL/Keepingyourapplicationrunning
Ttemplates
about/Templatesneedfor/Whyusetemplates?ERBprimer/AquickERBprimertemplateresource/Thetemplateresourcevariables/Thetemplatevariablesvariables,passingto/Passingvariablestoatemplatecomputedconfiguration,accessing/Accessingcomputedconfigurationssearchingfor/Searchingfortemplatessearchorder/Searchingfortemplates
terminology,Chefnode/TerminologyChefservice/Terminologyworkstation/Terminologyrecipe/Terminologycookbook/Terminology
test-driven-development(TDD)/TestingbasicsTestKitchen
using/UsingTestKitcheninstalling/InstallingTestKitchenused,fortesting/TestingwithTestKitchentasks/TestingwithTestKitchencookbook,building/Buildingasimplecookbookcookbook,preparingfor/Preparingyourcookbookforthekitchencookbook,testing/Testingyournewcookbook
UUbuntu
Chefserver,installingon/InstallingonUbuntuupdated_by_last_actionmethod/Declaringthataresourcewasupdatedusercookbook
enhancing/Enhancingyourusercookbookusermanagement
Chef/Managingusersshelluserrecipe,evolution/Evolutionofashelluserrecipedata,storingindatabags/Storingdataindatabagsrecipe,enhancingwithsearchmethod/Searchinginsiderecipesusercookbook,enhancing/EnhancingyourusercookbookSSHkeys,distributing/DistributingSSHkeys
usersadding/Addingusersmanaging/Managingusers
VVagrant
about/VagrantandChef-soloinstalling/InstallingVagrantURL/InstallingVagrantused,forprovisioningnewhost/ProvisioninganewhostwithVagrantimage,booting/BootingyourVagrantimagecombining,withChef-solo/CombiningVagrantwithChef-solo,UnderstandingthelimitationsofChef-solo,TellingChef-solowhattorun
VagrantCloudURL/ProvisioninganewhostwithVagrant
Vagrantimagebooting/BootingyourVagrantimage
Wwebapplicationservicerole
defining/Awebapplicationservicerolewebserver
configuring/Configuringthewebserverwebserverrole
creating/Creatingthewebserverrolewhy-runmechanism/Implementingtheproviderworkstation/Terminology,GettingtoknowChef
YYAML/DeployingsoftwarewithChef