WEBVTT 99:59:59.999 --> 99:59:59.999 Thank you everyone for coming. 99:59:59.999 --> 99:59:59.999 If you were expecting the Postgres talk, that was the one before, so 99:59:59.999 --> 99:59:59.999 you might need to watch the video stream. 99:59:59.999 --> 99:59:59.999 So, Ansible best practices, 99:59:59.999 --> 99:59:59.999 I thought about calling it "Ansible, my best practices", 99:59:59.999 --> 99:59:59.999 so, just warning ahead, this is things I stumbled on using Ansible 99:59:59.999 --> 99:59:59.999 for the last 2-3 years and 99:59:59.999 --> 99:59:59.999 those are very specific things I found that worked very well for me. 99:59:59.999 --> 99:59:59.999 About me, I do also freelance work, do a lot of Ansible in there, 99:59:59.999 --> 99:59:59.999 I'm also the Debian maintainer for Ansible with Harlan Lieberman-Berg 99:59:59.999 --> 99:59:59.999 If there are any bugs in the package, just report them. 99:59:59.999 --> 99:59:59.999 The talk will be roughly divided into 4 parts. 99:59:59.999 --> 99:59:59.999 The first part will be about why you actually want to use config management 99:59:59.999 --> 99:59:59.999 and why you specifically want to use Ansible. 99:59:59.999 --> 99:59:59.999 So, if you're still SSHing into machines and editing config files, 99:59:59.999 --> 99:59:59.999 you're probably a good candidate for using Ansible. 99:59:59.999 --> 99:59:59.999 Then, the second part will be about good role and playbook patterns 99:59:59.999 --> 99:59:59.999 that I have found that work really well for me. 99:59:59.999 --> 99:59:59.999 The third chapter will be about typical antipatterns I've stumbled upon, 99:59:59.999 --> 99:59:59.999 either in my work with other people using Ansible, 99:59:59.999 --> 99:59:59.999 or the IRC support channel, for example. 99:59:59.999 --> 99:59:59.999 The fourth part will be like advanced tips and tricks you can use 99:59:59.999 --> 99:59:59.999 like fun things you can do with Ansible. 99:59:59.999 --> 99:59:59.999 Quick elevator pitch, what makes config management good? 99:59:59.999 --> 99:59:59.999 It actually also serves as a documentation of changes on your servers over time 99:59:59.999 --> 99:59:59.999 so if you just put the whole config management in a git repo 99:59:59.999 --> 99:59:59.999 and just regularly commit, 99:59:59.999 --> 99:59:59.999 you will actually be able to say 99:59:59.999 --> 99:59:59.999 "Why doesn't this work? It used to work a year ago" 99:59:59.999 --> 99:59:59.999 You can actually check why. 99:59:59.999 --> 99:59:59.999 Also, most config management tools have a lot better error reporting than 99:59:59.999 --> 99:59:59.999 your self-written bash scripts that do whatever. 99:59:59.999 --> 99:59:59.999 And usually, you have a very good reproducibility with config management 99:59:59.999 --> 99:59:59.999 and also idempotency, meaning that if you run, for example, a playbook several times 99:59:59.999 --> 99:59:59.999 you will always get the same result. 99:59:59.999 --> 99:59:59.999 Also, it's great if you work in small team or you admin ??? in the company 99:59:59.999 --> 99:59:59.999 and you have some people working on a few things too. 99:59:59.999 --> 99:59:59.999 It makes team work a lot easier and you will save a lot of time actually 99:59:59.999 --> 99:59:59.999 debugging things when things break. 99:59:59.999 --> 99:59:59.999 What makes Ansible good? 99:59:59.999 --> 99:59:59.999 Comparing it to Chef or Puppet for example it's really easy to set up, 99:59:59.999 --> 99:59:59.999 you start with two config files, you have it installed and you're ready to go. 99:59:59.999 --> 99:59:59.999 It's also agentless, so whatever machines you actually want to control, 99:59:59.999 --> 99:59:59.999 the only thing you they really need to have is an SSH daemon and Python 2.6+ 99:59:59.999 --> 99:59:59.999 so that's virtually any Debian machine you have installed and 99:59:59.999 --> 99:59:59.999 that ??? 99:59:59.999 --> 99:59:59.999 Ansible also supports configuration of many things like 99:59:59.999 --> 99:59:59.999 networking equipment or even Windows machines, 99:59:59.999 --> 99:59:59.999 they don't need SSH but they use the WinRM 99:59:59.999 --> 99:59:59.999 but Ansible came a bit late to the game so Ansible's still not as good 99:59:59.999 --> 99:59:59.999 in coverage like for example Puppet, 99:59:59.999 --> 99:59:59.999 which literally, you can configure any machine on the planet with that, 99:59:59.999 --> 99:59:59.999 as long as it has a CPU. 99:59:59.999 --> 99:59:59.999 Next step, I will talk about good role patterns. 99:59:59.999 --> 99:59:59.999 If you've never worked with Ansible before, 99:59:59.999 --> 99:59:59.999 this is the point when you watch the video stream, 99:59:59.999 --> 99:59:59.999 that you pause it and start working a few weeks with it 99:59:59.999 --> 99:59:59.999 and then unpause the actual video. 99:59:59.999 --> 99:59:59.999 A good role should ideally have the following layout. 99:59:59.999 --> 99:59:59.999 So, in the "roles" directory, you have the name of the role and task/main.yml 99:59:59.999 --> 99:59:59.999 You have the following rough layout. 99:59:59.999 --> 99:59:59.999 At the beginning of the role, you check for various conditions, 99:59:59.999 --> 99:59:59.999 for example using the "assert" task to for example check that 99:59:59.999 --> 99:59:59.999 certain variables are defined, things are set, 99:59:59.999 --> 99:59:59.999 that it's maybe part of a group, things like that you actually want to check. 99:59:59.999 --> 99:59:59.999 Then, usually, you install packages, you can use apt on CentOS machines, 99:59:59.999 --> 99:59:59.999 yum, or you can do a git checkout or whatever, 99:59:59.999 --> 99:59:59.999 then usually you do some templating of files where you have certain abstraction 99:59:59.999 --> 99:59:59.999 and the variables are actually put into the template and 99:59:59.999 --> 99:59:59.999 make the actual config file. 99:59:59.999 --> 99:59:59.999 There's also good to point out that the template module actually has 99:59:59.999 --> 99:59:59.999 a "validate" parameter, 99:59:59.999 --> 99:59:59.999 that means you can actually use a command to check your config files for syntax errors 99:59:59.999 --> 99:59:59.999 and if that fails, your playbook will fail before actually deploying that config file 99:59:59.999 --> 99:59:59.999 so you can for example use Apache with the right parameters to actually do 99:59:59.999 --> 99:59:59.999 a check on the syntax of the file. 99:59:59.999 --> 99:59:59.999 That way, you never end up with a state where there's a broken config. 99:59:59.999 --> 99:59:59.999 In the end, you usually… 99:59:59.999 --> 99:59:59.999 When you change things, you trigger handlers to restart any ??? 99:59:59.999 --> 99:59:59.999 If you use variables, I recommend putting sensible defaults in 99:59:59.999 --> 99:59:59.999 defaults/main.yml 99:59:59.999 --> 99:59:59.999 and then you only have to override those variables on specific cases. 99:59:59.999 --> 99:59:59.999 Ideally, you should have sensible defaults you want to have to get whatever things 99:59:59.999 --> 99:59:59.999 you want to have running. 99:59:59.999 --> 99:59:59.999 When you start working with it and do that a bit more, 99:59:59.999 --> 99:59:59.999 you notice a few things and that is 99:59:59.999 --> 99:59:59.999 your role should ideally run in "check mode". 99:59:59.999 --> 99:59:59.999 "ansible-playbook" has --check that basically is just a dry run of 99:59:59.999 --> 99:59:59.999 your complete playbook 99:59:59.999 --> 99:59:59.999 and with --diff, it will actually show you for example file changes, 99:59:59.999 --> 99:59:59.999 or file mode changes, stuff like that 99:59:59.999 --> 99:59:59.999 and won't actually change anything. 99:59:59.999 --> 99:59:59.999 So if you end up editing a lot of stuff, you can use that as a check. 99:59:59.999 --> 99:59:59.999 I'll later get to some antipatterns that actually break that thing. 99:59:59.999 --> 99:59:59.999 And, ideally, the way you change files and configs and states, 99:59:59.999 --> 99:59:59.999 you should make sure that when the actual changes are deployed, 99:59:59.999 --> 99:59:59.999 and you run it a second time, 99:59:59.999 --> 99:59:59.999 that Ansible doesn't report any changes 99:59:59.999 --> 99:59:59.999 because if you end up writing your roles fairly sloppy, you end up having 99:59:59.999 --> 99:59:59.999 a lot of changes and then, 99:59:59.999 --> 99:59:59.999 in the end of the report, you have like 20 changes reported and 99:59:59.999 --> 99:59:59.999 you kind of then know those 18, they're always there 99:59:59.999 --> 99:59:59.999 and you kind of miss the 2 that are important, that actually broke your system 99:59:59.999 --> 99:59:59.999 If you want to do it really well, you make sure that it doesn't report any changes 99:59:59.999 --> 99:59:59.999 when you run it twice in a row. 99:59:59.999 --> 99:59:59.999 Also, a thing to consider is you can define variables in the "defaults" folder 99:59:59.999 --> 99:59:59.999 and also in the "vars" folder, 99:59:59.999 --> 99:59:59.999 but if you look up how variables get inherited, you'll notice that 99:59:59.999 --> 99:59:59.999 the "vars" folder is really hard to actually override, 99:59:59.999 --> 99:59:59.999 so you want to avoid that as much as possible. 99:59:59.999 --> 99:59:59.999 That much larger section will be about typical anti-patterns I've noticed 99:59:59.999 --> 99:59:59.999 and I'll come to the first one now. 99:59:59.999 --> 99:59:59.999 It's the shell or command module. 99:59:59.999 --> 99:59:59.999 When people start using Ansible, that's the first thing they go 99:59:59.999 --> 99:59:59.999 "Oh well, I know how to use wget or I know 'apt-get install' " 99:59:59.999 --> 99:59:59.999 and then they end up using the shell module to do just that. 99:59:59.999 --> 99:59:59.999 If you use the shell module or the command module, you usually don't want to use that 99:59:59.999 --> 99:59:59.999 and that's for several reasons. 99:59:59.999 --> 99:59:59.999 There's currently, I think, 1300 different modules in Ansible 99:59:59.999 --> 99:59:59.999 so there's likely a big chance that whatever you want to do, 99:59:59.999 --> 99:59:59.999 there's already a module for that, that just does that thing. 99:59:59.999 --> 99:59:59.999 But those two modules also have several problems and that is 99:59:59.999 --> 99:59:59.999 the shell module, of course, gets interpreted by your actual shell, 99:59:59.999 --> 99:59:59.999 so if you have any special variables in there, 99:59:59.999 --> 99:59:59.999 you'd actually also have to take care of any variables you interpret in the shell string. 99:59:59.999 --> 99:59:59.999 Then, one of the biggest problems is if you run your playbook in check mode, 99:59:59.999 --> 99:59:59.999 the shell and the command modules won't get run. 99:59:59.999 --> 99:59:59.999 So if you're actually doing anything with that, they just get skipped 99:59:59.999 --> 99:59:59.999 and that would cause that your actual check mode and the real mode, 99:59:59.999 --> 99:59:59.999 they will start diverging if you use a lot of shell module. 99:59:59.999 --> 99:59:59.999 The worst, also, bad part about this is that these two modules, 99:59:59.999 --> 99:59:59.999 they'll always ??? changed 99:59:59.999 --> 99:59:59.999 like, you run a command and it exits 0 99:59:59.999 --> 99:59:59.999 it's like "Oh, it changed" 99:59:59.999 --> 99:59:59.999 To get the reporting right on that module, you'd actually have to define for yourself 99:59:59.999 --> 99:59:59.999 when this is actually a change or not. 99:59:59.999 --> 99:59:59.999 So you'd have to probably get the output and then check, for example, 99:59:59.999 --> 99:59:59.999 if there's something on stderr or something to report an actual error or change. 99:59:59.999 --> 99:59:59.999 Then I'll get to the actual examples. 99:59:59.999 --> 99:59:59.999 The left is a bad example for using the shell module, 99:59:59.999 --> 99:59:59.999 I've seen that a lot, it's basically 99:59:59.999 --> 99:59:59.999 "Yeah, I actually want this file, so just use 'cat /path/file' and I'll use 99:59:59.999 --> 99:59:59.999 the register parameter to get the output". 99:59:59.999 --> 99:59:59.999 The actual output goes into the "shell_cmd" and then 99:59:59.999 --> 99:59:59.999 we want to copy it to some other file somewhere else and 99:59:59.999 --> 99:59:59.999 so we use the Jinja "{{ }}" to define the actual content of the file 99:59:59.999 --> 99:59:59.999 and then put it into that destination file 99:59:59.999 --> 99:59:59.999 That is problematic because, first of all if you run it in check mode, 99:59:59.999 --> 99:59:59.999 this gets skipped and then this variable is undefined and 99:59:59.999 --> 99:59:59.999 Ansible will fail with an error, so you won't be able to actually 99:59:59.999 --> 99:59:59.999 run that in check mode. 99:59:59.999 --> 99:59:59.999 The other problem is this will always ??? 99:59:59.999 --> 99:59:59.999 so you'd probably have to… 99:59:59.999 --> 99:59:59.999 the most sensible thing would probably be to say just "changed when false" 99:59:59.999 --> 99:59:59.999 and just acknowledge that that shell command won't change anything on this system 99:59:59.999 --> 99:59:59.999 The good example would be to use the actual "slurp" module that will 99:59:59.999 --> 99:59:59.999 just slurp the whole file and base64encode it 99:59:59.999 --> 99:59:59.999 and you can access the actual content with "path_file.contents" and you then just 99:59:59.999 --> 99:59:59.999 base64decode it and write in there. 99:59:59.999 --> 99:59:59.999 The nice thing is slurp will never return any change, so it won't say it changed 99:59:59.999 --> 99:59:59.999 and it also works great in check mode. 99:59:59.999 --> 99:59:59.999 Here's an other quick example. 99:59:59.999 --> 99:59:59.999 The example on the left, oh yeah wget. 99:59:59.999 --> 99:59:59.999 Here's the problem, every time your playbook runs, this file will get downloaded 99:59:59.999 --> 99:59:59.999 and of course if the file can't be retrieved from that URL 99:59:59.999 --> 99:59:59.999 it will throw an error and that will happen all the time. 99:59:59.999 --> 99:59:59.999 The right example is a more clean example using the uri module. 99:59:59.999 --> 99:59:59.999 You define a URL to retrieve a file from, you define where you want to write it to 99:59:59.999 --> 99:59:59.999 and you use the "creates" parameter to say 99:59:59.999 --> 99:59:59.999 "Just skip the whole thing if the file is already there". 99:59:59.999 --> 99:59:59.999 "set_facts", that's my pet peeve. 99:59:59.999 --> 99:59:59.999 set_facts is a module that allows you to define variables 99:59:59.999 --> 99:59:59.999 during your playbook run, so you can say set_facts and then 99:59:59.999 --> 99:59:59.999 this variable = that variable + a third variable or whatever 99:59:59.999 --> 99:59:59.999 you can do things with that. 99:59:59.999 --> 99:59:59.999 It's very problematic, though, because you end up having your variables 99:59:59.999 --> 99:59:59.999 changed during the playbook run 99:59:59.999 --> 99:59:59.999 and that is a problem when you use the "--start-at" parameter 99:59:59.999 --> 99:59:59.999 from ansible-playbook. 99:59:59.999 --> 99:59:59.999 Because this parameter allows you to skip forward to a certain task ??? 99:59:59.999 --> 99:59:59.999 so it skips everything until that point and then continues running there 99:59:59.999 --> 99:59:59.999 and that's really great for debugging 99:59:59.999 --> 99:59:59.999 but if you define a variable with set_facts and you skip over it, 99:59:59.999 --> 99:59:59.999 that variable would just not be defined. 99:59:59.999 --> 99:59:59.999 If you heavily use set_facts, that makes prototyping really horrible. 99:59:59.999 --> 99:59:59.999 Another point is that you can use 99:59:59.999 --> 99:59:59.999 "ansible -m setup" and then the hostname to check what variables are actually defined 99:59:59.999 --> 99:59:59.999 for a specific host and everything set with set_facts is just not there. 99:59:59.999 --> 99:59:59.999 In summary, avoid the shell module, avoid the command module, 99:59:59.999 --> 99:59:59.999 avoid set_facts as much as you can, 99:59:59.999 --> 99:59:59.999 and don't hide changes with "changed_when" 99:59:59.999 --> 99:59:59.999 so the clean approach is always to use one task to check something 99:59:59.999 --> 99:59:59.999 and then a second task to actually execute something for example. 99:59:59.999 --> 99:59:59.999 Also, a bad idea in my opinion is when people say 99:59:59.999 --> 99:59:59.999 "Oh well, it's not important if this ??? 99:59:59.999 --> 99:59:59.999 I'll just say 'fails when false'" 99:59:59.999 --> 99:59:59.999 That might work sometimes, but the problem there is, if something really breaks, 99:59:59.999 --> 99:59:59.999 you'll never find out. 99:59:59.999 --> 99:59:59.999 Advanced topics. 99:59:59.999 --> 99:59:59.999 This is about the templating. 99:59:59.999 --> 99:59:59.999 The usual approach, for example for postfix role, 99:59:59.999 --> 99:59:59.999 would be to do the following templating. 99:59:59.999 --> 99:59:59.999 You define certain variables in for example group_vars/postfix_servers 99:59:59.999 --> 99:59:59.999 so any host in that group would inherit these variables, 99:59:59.999 --> 99:59:59.999 so this is sort of a list of parameters for stmp recipient restrictions 99:59:59.999 --> 99:59:59.999 and this is just the smtp helo required. 99:59:59.999 --> 99:59:59.999 So the usual approach would be to define variables 99:59:59.999 --> 99:59:59.999 in the host_vars or group_vars, or even in the defaults 99:59:59.999 --> 99:59:59.999 and then you have a template where you just check every single variable 99:59:59.999 --> 99:59:59.999 If it exists, you actually sort of put the actual value there in place. 99:59:59.999 --> 99:59:59.999 Here, I check if this variable is set true and if yes, put the string there 99:59:59.999 --> 99:59:59.999 else, put this string there 99:59:59.999 --> 99:59:59.999 and for example, smtpd_recipient_restrictions I just iterate over this array 99:59:59.999 --> 99:59:59.999 and just output these values in order in that list. 99:59:59.999 --> 99:59:59.999 The problem here is that every time upstream defines a new variable 99:59:59.999 --> 99:59:59.999 you'll end up having to change the actual template file and touch the actual variables 99:59:59.999 --> 99:59:59.999 so, I thought, "Well, you actually have keys and values and strings and arrays 99:59:59.999 --> 99:59:59.999 and arrays on one side, and actually, a config file is nothing else than that, 99:59:59.999 --> 99:59:59.999 just in a different format". 99:59:59.999 --> 99:59:59.999 So I came up with… 99:59:59.999 --> 99:59:59.999 With Jinja2, you can also define functions 99:59:59.999 --> 99:59:59.999 I'll have to cut short a little bit on explaining it but 99:59:59.999 --> 99:59:59.999 basically, up here, a function is defined and it's called here in the bottom 99:59:59.999 --> 99:59:59.999 Basically, what it just does, it iterates over the whole dictionary defined here, 99:59:59.999 --> 99:59:59.999 "postfix.main", and it just goes… 99:59:59.999 --> 99:59:59.999 It iterates over all the keys and values and it goes… 99:59:59.999 --> 99:59:59.999 If the value is a string, I'll just put "key = value" and 99:59:59.999 --> 99:59:59.999 if it's an array, I just iterate over it and put it there in the format that 99:59:59.999 --> 99:59:59.999 postfix actually wants. 99:59:59.999 --> 99:59:59.999 Basically, you can do the same, for example, for haproxy and 99:59:59.999 --> 99:59:59.999 you can just deserialize all the variables you actually defined. 99:59:59.999 --> 99:59:59.999 The advantages of this is, 99:59:59.999 --> 99:59:59.999 your template file just stays the same and it doesn't get messy 99:59:59.999 --> 99:59:59.999 if you start adding things. 99:59:59.999 --> 99:59:59.999 You have complete whitespace control, usually if you edit stuff, 99:59:59.999 --> 99:59:59.999 you kind of get an extra space, a new line in there, and that changes 99:59:59.999 --> 99:59:59.999 the template files for all machines. 99:59:59.999 --> 99:59:59.999 You have all the settings in alphabetical order, so if you actually run it and 99:59:59.999 --> 99:59:59.999 you see the diff, you don't end up having things going back and forth. 99:59:59.999 --> 99:59:59.999 If you get the syntax on the template file right, you don't have to touch it after that 99:59:59.999 --> 99:59:59.999 and you also don't get any syntax errors by editing them. 99:59:59.999 --> 99:59:59.999 That follows to the next one. 99:59:59.999 --> 99:59:59.999 You can actually set a "hash_behaviour" merge in the Ansible config and 99:59:59.999 --> 99:59:59.999 that allows you to do the following. 99:59:59.999 --> 99:59:59.999 On the left here, you define for example a dictionary and this is, like, in a group 99:59:59.999 --> 99:59:59.999 and then in a specific machine, you define an other setting in this dictionary. 99:59:59.999 --> 99:59:59.999 If you wouldn't use merge, the second setting would just override the first one 99:59:59.999 --> 99:59:59.999 and you'd end up with that, but if you actually do the merge, 99:59:59.999 --> 99:59:59.999 it does a deep merge of the hash. 99:59:59.999 --> 99:59:59.999 So the previous thing I showed would actually benefit from that 99:59:59.999 --> 99:59:59.999 so the combination of both is really good. 99:59:59.999 --> 99:59:59.999 I'll skip that. 99:59:59.999 --> 99:59:59.999 For the resources, Ansible has just a really good documentation, 99:59:59.999 --> 99:59:59.999 there's the IRC and there's also debops which is a project that is 99:59:59.999 --> 99:59:59.999 specific to Debian and derivatives. 99:59:59.999 --> 99:59:59.999 That's it. 99:59:59.999 --> 99:59:59.999 [Applause] 99:59:59.999 --> 99:59:59.999 Thank you very much.