-
Notifications
You must be signed in to change notification settings - Fork 0
/
ch02-playbooks-a-beginning.html
1318 lines (758 loc) · 52 KB
/
ch02-playbooks-a-beginning.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<section data-type="chapter" id="playbooks_a_beginning">
<h1>Playbooks: A Beginning</h1>
<p>Most of your time in Ansible will be spent writing playbooks. A <em>playbook</em> is
the term that Ansible uses for a configuration management script. <a data-type="indexterm" id="ix_playbk" data-primary="playbooks"/>Let’s look at
an example: installing the Nginx web server and configuring it for secure
communication.</p>
<p>If you’re following along in this chapter, you should end up with the files listed here:</p>
<ul>
<li>
<p><em>playbooks/ansible.cfg</em></p>
</li>
<li>
<p><em>playbooks/hosts</em></p>
</li>
<li>
<p><em>playbooks/Vagrantfile</em></p>
</li>
<li>
<p><em>playbooks/web-notls.yml</em></p>
</li>
<li>
<p><em>playbooks/web-tls.yml</em></p>
</li>
<li>
<p><em>playbooks/files/nginx.key</em></p>
</li>
<li>
<p><em>playbooks/files/nginx.crt</em></p>
</li>
<li>
<p><em>playbooks/files/nginx.conf</em></p>
</li>
<li>
<p><em>playbooks/templates/index.html.j2</em></p>
</li>
<li>
<p><em>playbooks/templates/nginx.conf.j2</em></p>
</li>
</ul>
<section data-type="sect1">
<h1>Some Preliminaries</h1>
<p>Before we can run this playbook against our Vagrant machine, we need
to expose ports 80 and 443, so we can access them. <a data-type="indexterm" data-primary="Nginx" data-secondary="configuring host to run" data-tertiary="exposing ports on Vagrant machine"/><a data-type="indexterm" data-primary="Vagrant" data-secondary="exposing ports on"/>As shown in
<a data-type="xref" href="#vagrant_ports_figure"/>, we are going to configure Vagrant so that requests to
ports 8080 and 8443 on our local machine are forwarded to ports 80 and 443 on
the Vagrant machine. This will allow us to access the web server running inside Vagrant at
<a href="http://localhost:8080"><em class="hyperlink">http://localhost:8080</em></a> and <a href="https://localhost:8443"><em class="hyperlink">https://localhost:8443</em></a>.</p>
<figure id="vagrant_ports_figure">
<img src="images/aur2_0201.png" alt="Vagrant port forwarding"/>
<figcaption>Exposing ports on Vagrant machine</figcaption>
</figure>
<p>Modify your <em>Vagrantfile</em> so it looks like this:</p>
<pre data-type="programlisting" data-code-language="ruby">VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 443, host: 8443
end</pre>
<p>This maps port 8080 on your local machine to port 80 of the Vagrant machine, and
port 8443 on your local machine to port 443 on the Vagrant machine. After you
make the changes, tell Vagrant to have them go into effect by running this command:</p>
<pre data-type="programlisting" data-code-language="console">$ vagrant reload</pre>
<p>You should see output that includes the following:</p>
<pre data-type="programlisting">==> default: Forwarding ports...
default: 80 => 8080 (adapter 1)
default: 443 => 8443 (adapter 1)
default: 22 => 2222 (adapter 1)</pre>
</section>
<section data-type="sect1">
<h1>A Very Simple Playbook</h1>
<p>For our first example playbook, we’ll configure a host to run an Nginx web
server.<a data-type="indexterm" id="ix_Nginxconf" data-primary="Nginx" data-secondary="configuring host to run"/><a data-type="indexterm" id="ix_playbksimex" data-primary="playbooks" data-secondary="simple example, configuring host to run Nginx"/> For this example, we won’t configure the web server to support TLS encryption. This will make setting up
the web server simpler. However, a proper website should have Transport Layer Security (TLS) encryption
enabled, and we’ll cover how to do that later in this chapter.</p>
<aside data-type="sidebar" class="pagebreak-before less_space">
<h5>TLS versus SSL</h5>
<p>You might be familiar with the term <em>SSL</em> rather than <em>TLS</em> in the context of secure web servers.<a data-type="indexterm" data-primary="SSL (Secure Sockets Layer)" data-seealso="TLS"/><a data-type="indexterm" data-primary="TLS (Transport Layer Security)"/> SSL is an older protocol that was used to secure communications between browsers and web servers, and it has been superseded by a newer protocol named TLS. Although many continue to use the term <em>SSL</em> to refer to the current secure protocol, in this book, I use the more accurate <em>TLS</em>.</p>
</aside>
<p>First, we’ll see what happens when we run the playbook in <a data-type="xref" href="#WEB_NOTLS_PLAYBOOK"/>, and then we’ll go over the contents of the playbook in detail.</p>
<div id="WEB_NOTLS_PLAYBOOK" data-type="example">
<h5>web-notls.yml</h5>
<pre data-type="programlisting"></pre></div>
<aside id="YAML_TRUTHY" data-type="sidebar">
<h5>Why Do You Use <em>True</em> in One Place and <em>Yes</em> in Another?</h5>
<p>Sharp-eyed readers might have noticed that <a data-type="xref" href="#WEB_NOTLS_PLAYBOOK"/> uses <code>True</code> in
one spot in the playbook (to enable <code>sudo</code>) and <code>yes</code> in another spot in the
playbook (to update the apt cache).</p>
<p>Ansible is pretty flexible in how you represent truthy and falsey values in
playbooks.<a data-type="indexterm" data-primary="truthy and falsey values in playbooks"/> Strictly speaking, module arguments (for example, <code>update_cache=yes</code>) are treated
differently from values elsewhere in playbooks (for example, <code>sudo: True</code>). Values elsewhere
are handled by the YAML parser and so use the YAML conventions of truthiness:</p>
<dl>
<dt>YAML truthy</dt>
<dd>
<p><code>true</code>, <code>True</code>, <code>TRUE</code>, <code>yes</code>, <code>Yes</code>, <code>YES</code>, <code>on</code>, <code>On</code>, <code>ON</code>, <code>y</code>, <code>Y</code></p>
</dd>
<dt>YAML falsey</dt>
<dd>
<p><code>false</code>, <code>False</code>, <code>FALSE</code>, <code>no</code>, <code>No</code>, <code>NO</code>, <code>off</code>, <code>Off</code>, <code>OFF</code>, <code>n</code>, <code>N</code></p>
</dd>
</dl>
<p>Module arguments are passed as strings and use Ansible’s internal
conventions:</p>
<dl>
<dt>module arg truthy</dt>
<dd>
<p><code>yes</code>, <code>on</code>, <code>1</code>, <code>true</code></p>
</dd>
<dt>module arg falsey</dt>
<dd>
<p><code>no</code>, <code>off</code>, <code>0</code>, <code>false</code></p>
</dd>
</dl>
<p>I tend to follow the examples in the official Ansible documentation. These
typically use <code>yes</code> and <code>no</code> when passing arguments to modules (since that’s
consistent with the module documentation), and <code>True</code> and <code>False</code> elsewhere in
playbooks.</p>
</aside>
<section data-type="sect2">
<h2>Specifying an Nginx Config File</h2>
<p>This playbook requires two additional files before we can run it. First, we need
to define an Nginx configuration file.<a data-type="indexterm" data-primary="playbooks" data-secondary="simple example, configuring host to run Nginx" data-tertiary="specifying Nginx config file"/><a data-type="indexterm" data-primary="Nginx" data-secondary="configuring host to run" data-tertiary="specifying config file"/></p>
<p>Nginx ships with a configuration file that works out of the box if you just want
to serve static files. But you’ll almost always need to customize this, so we’ll
overwrite the default configuration file with our own as part
of this playbook. As you’ll see later, we’ll need to modify this configuration
file to support TLS. <a data-type="xref" href="#NGINX_CONF_NOTLS"/> shows a basic Nginx config file. Put it in
<em>playbooks/files/nginx.conf</em>.<span data-type="footnote">Note that while we call this file <em>nginx.conf</em>, it replaces the <em>sites-enabled/default</em> Nginx server block config file, not the main <em>/etc/nginx.conf</em> config file.</span></p>
<div data-type="note">
<p>An Ansible convention is to keep files in a subdirectory named <em>files</em>, and
Jinja2 templates in a subdirectory named <em>templates</em>. I follow this
convention throughout the book.<a data-type="indexterm" data-primary="templates subdirectory"/><a data-type="indexterm" data-primary="files subdirectory"/></p>
</div>
<div id="NGINX_CONF_NOTLS" data-type="example">
<h5>files/nginx.conf</h5>
<pre data-type="programlisting" data-code-language="nginx"></pre></div>
</section>
<section data-type="sect2">
<h2>Creating a Custom Home Page</h2>
<p>Let’s add a custom home page. We’re going to use Ansible’s template functionality
so that Ansible will generate the file from a template.<a data-type="indexterm" data-primary="templates" data-secondary="home page"/><a data-type="indexterm" data-primary="playbooks" data-secondary="simple example, configuring host to run Nginx" data-tertiary="creating custom home page"/> Put the content shown in
<a data-type="xref" href="#HOME_PAGE_TEMPLATE"/> in <em>playbooks/templates/index.html.j2</em>.</p>
<div id="HOME_PAGE_TEMPLATE" data-type="example">
<h5>playbooks/templates/index.html.j2</h5>
<pre data-type="programlisting" data-code-language="html"><html>
<head>
<title>Welcome to ansible</title>
</head>
<body>
<h1>nginx, configured by Ansible</h1>
<p>If you can see this, Ansible successfully installed nginx.</p>
<p>{{ ansible_managed }}</p>
</body>
</html></pre></div>
<p>This template references a special Ansible variable named <code>ansible_managed</code>.
When Ansible renders this template,<a data-type="indexterm" data-primary="ansible_managed variable"/> it will replace this variable with
information about when the template file was generated.
<a data-type="xref" href="#web_page_screenshot"/> shows a web browser displaying the
generated HTML.</p>
<figure id="web_page_screenshot">
<img src="images/aur2_0202.png" alt="Welcome page"/>
<figcaption>Rendered HTML</figcaption>
</figure>
</section>
<section data-type="sect2">
<h2>Creating a Webservers Group</h2>
<p>Let’s create a <code>webservers</code> group in our inventory file so that we can refer to
this group in our playbook. <a data-type="indexterm" data-primary="webservers group, creating"/><a data-type="indexterm" data-primary="Nginx" data-secondary="configuring host to run" data-tertiary="creating webservers group"/><a data-type="indexterm" data-primary="playbooks" data-secondary="simple example, configuring host to run Nginx" data-tertiary="creating webservers group"/>For now, this group will contain our test server.</p>
<p>Inventory files are in the <em>.ini</em> file format. We’ll go into this format in detail later in the book. Edit your <em>playbooks/hosts</em> file to put a <code>[webservers]</code> line above the <code>testserver</code> line, as shown in <a data-type="xref" href="#example-2-5"/>. This indicates that <code>testserver</code> is in the <code>webservers</code> group.</p>
<div id="example-2-5" data-type="example">
<h5>playbooks/hosts</h5>
<pre data-type="programlisting">[webservers]
testserver ansible_host=127.0.0.1 ansible_port=2222</pre></div>
<p>You should now be able to ping the <code>webservers</code> group<a data-type="indexterm" data-primary="ping module, invoking" data-secondary="pinging webservers group"/> by using the <code>ansible</code>
command-line tool:</p>
<pre data-type="programlisting" data-code-language="console">$ ansible webservers -m ping</pre>
<p>The output should look like this:</p>
<pre data-type="programlisting">testserver | success >> {
"changed": false,
"ping": "pong"
}</pre>
</section>
</section>
<section data-type="sect1">
<h1>Running the Playbook</h1>
<p>The <code>ansible-playbook</code> command executes playbooks.<a data-type="indexterm" data-primary="playbooks" data-secondary="simple example, configuring host to run Nginx" data-tertiary="running the playbook" data-startref="ix_playbkrun"/><a data-type="indexterm" data-primary="ansible-playbook command"/> To run the playbook, use this command:</p>
<pre data-type="programlisting" data-code-language="console">$ ansible-playbook web-notls.yml</pre>
<p><a data-type="xref" href="#output_ansible_playbook"/> shows what the output should look like.</p>
<div id="output_ansible_playbook" data-type="example">
<h5>Output of ansible-playbook</h5>
<pre data-type="programlisting">PLAY [Configure webserver with nginx] *********************************
GATHERING FACTS ***************************************************************
ok: [testserver]
TASK: [install nginx] *********************************************************
changed: [testserver]
TASK: [copy nginx config file] ************************************************
changed: [testserver]
TASK: [enable configuration] **************************************************
ok: [testserver]
TASK: [copy index.html] *******************************************************
changed: [testserver]
TASK: [restart nginx] *********************************************************
changed: [testserver]
PLAY RECAP ********************************************************************
testserver : ok=6 changed=4 unreachable=0 failed=0</pre></div>
<aside data-type="sidebar">
<h5>Cowsay</h5>
<p>If you have the <em>cowsay</em> program installed on your local machine, <a data-type="indexterm" data-primary="cowsay program, disabling"/>Ansible output will look like this instead:</p>
<pre data-type="programlisting"> _______________________________________
< PLAY [Configure webserver with nginx] >
---------------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||</pre>
<p>If you<a data-type="indexterm" data-primary="ANSIBLE_NOCOWS environment variable"/> don’t want to see the cows, you can disable cowsay by
setting the <span class="keep-together"><code>ANSIBLE_NOCOWS</code></span> environment variable like this:</p>
<pre data-type="programlisting" data-code-language="console">$ export ANSIBLE_NOCOWS=1</pre>
<p>You can also disable cowsay by adding the following to your <em>ansible.cfg</em> file:</p>
<pre data-type="programlisting">[defaults]
nocows = 1</pre>
</aside>
<p>If you didn’t get any errors,<span data-type="footnote">If you encountered an error, you might want to skip to <a data-type="xref" href="#DEBUGGING"/> for assistance on debugging.</span> you should be able to point
your browser to <a href="http://localhost:8080"><em class="hyperlink">http://localhost:8080</em></a> and see the custom HTML page, as shown in
<a data-type="xref" href="#web_page_screenshot"/>.</p>
<div data-type="tip">
<p>If your playbook file is marked as executable<a data-type="indexterm" data-primary="shebangs"/><a data-type="indexterm" data-primary="#! (shebang), lines beginning with"/> and starts with a line that looks like
this:<span data-type="footnote">Colloquially referred to as a <em>shebang</em>.</span></p>
<pre data-type="programlisting">#!/usr/bin/env ansible-playbook</pre>
<p>then you can execute it by invoking it directly, like this:</p>
<pre data-type="programlisting" data-code-language="console">$ ./web-notls.yml</pre>
</div>
</section>
<section data-type="sect1">
<h1>Playbooks Are YAML</h1>
<p>Ansible playbooks are written in YAML syntax. <em>YAML</em> is a file format similar in<a data-type="indexterm" data-primary="playbooks" data-secondary="simple example, configuring host to run Nginx" data-tertiary="running the playbook" data-startref="ix_playbkrun"/><a data-type="indexterm" data-primary="Nginx" data-secondary="configuring host to run" data-startref="ix_Nginxconf"/><a data-type="indexterm" data-primary="playbooks" data-secondary="simple example, configuring host to run Nginx" data-startref="ix_playbksimex"/>
intent to JSON, but generally easier for humans to read and write. Before we go
over the playbook, let’s cover the concepts of YAML that are
most important for writing playbooks.<a data-type="indexterm" id="ix_YAML" data-primary="YAML"/><a data-type="indexterm" id="ix_playbkYAML" data-primary="playbooks" data-secondary="YAML syntax"/></p>
<section data-type="sect2">
<h2>Start of File</h2>
<p>YAML files are supposed to start with three dashes to <a data-type="indexterm" data-primary="YAML" data-secondary="start of file"/><a data-type="indexterm" data-primary="start of file (YAML)"/>indicate the beginning of
the document:</p>
<pre data-type="programlisting" data-code-language="yaml"></pre>
<p>However, if you forget to put those three dashes at the top of your playbook
files, Ansible won’t complain.</p>
</section>
<section data-type="sect2">
<h2>Comments</h2>
<p>Comments start with a number sign and apply to the end of the line, the same as in<a data-type="indexterm" data-primary="comments in YAML"/><a data-type="indexterm" data-primary="YAML" data-secondary="comments"/>
shell scripts, Python, and Ruby:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja"># This is a YAML comment</pre>
</section>
<section data-type="sect2">
<h2>Strings</h2>
<p>In general, YAML strings don’t have to be quoted, although you can quote them if
you prefer.<a data-type="indexterm" data-primary="strings" data-secondary="in YAML"/><a data-type="indexterm" data-primary="YAML" data-secondary="strings"/> Even if there are spaces, you don’t need to quote them. For example,
this is a string in YAML:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">this is a lovely sentence</pre>
<p>The JSON equivalent is as follows:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">"this is a lovely sentence"</pre>
<p>In some scenarios in Ansible, you will need to quote strings. These typically involve the use of <code>{{ braces }}</code> for variable substitution. We’ll get to those later.<a data-type="indexterm" data-primary="{{ }} (braces notation)" data-secondary="variable substitution"/></p>
</section>
<section data-type="sect2">
<h2>Booleans</h2>
<p>YAML has a native Boolean type, and provides you with a wide variety of strings<a data-type="indexterm" data-primary="Boolean type in YAML"/><a data-type="indexterm" data-primary="YAML" data-secondary="Boolean type"/>
that can be interpreted as true or false, which we covered in <a data-type="xref" href="#YAML_TRUTHY"/>. Personally, I always use <code>True</code> and <code>False</code> in my Ansible playbooks.</p>
<p>For example, this is a Boolean in YAML:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">True</pre>
<p>The JSON equivalent is this:</p>
<pre data-type="programlisting" data-code-language="json">true</pre>
</section>
<section data-type="sect2">
<h2>Lists</h2>
<p>YAML lists are like arrays in JSON and Ruby, or lists in Python.<a data-type="indexterm" data-primary="YAML" data-secondary="lists (or sequences)"/><a data-type="indexterm" data-primary="sequences in YAML"/><a data-type="indexterm" data-primary="lists" data-secondary="in YAML"/> Technically,
these are called <em>sequences</em> in YAML, but I call them <em>lists</em> here to be
consistent with the official Ansible documentation.</p>
<p>They are delimited with hyphens, like this:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">- My Fair Lady
- Oklahoma
- The Pirates of Penzance</pre>
<p>The JSON equivalent is shown here:</p>
<pre data-type="programlisting" data-code-language="json">[
"My Fair Lady",
"Oklahoma",
"The Pirates of Penzance"
]</pre>
<p>(Note again that we don’t have to quote the strings in YAML, even though they have spaces in them.)</p>
<p>YAML also supports an<a data-type="indexterm" data-primary="inline lists format (YAML)"/> inline format for lists, which looks like this:</p>
<pre data-type="programlisting">[My Fair Lady, Oklahoma, The Pirates of Penzance]</pre>
</section>
<section data-type="sect2">
<h2>Dictionaries</h2>
<p>YAML <em>dictionaries</em> are like objects in JSON, dictionaries in Python, or hashes in
Ruby.<a data-type="indexterm" data-primary="dictionaries" data-secondary="YAML"/><a data-type="indexterm" data-primary="YAML" data-secondary="dictionaries"/> Technically, these are called <em>mappings</em> in YAML, but I call them
<em>dictionaries</em> here to be consistent with the official Ansible documentation.</p>
<p>They look like this:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">address: 742 Evergreen Terrace
city: Springfield
state: North Takoma</pre>
<p>The JSON equivalent is shown here:</p>
<pre data-type="programlisting" data-code-language="json">{
"address": "742 Evergreen Terrace",
"city": "Springfield",
"state": "North Takoma"
}</pre>
<p>YAML also supports an inline format for dictionaries, which looks like this:</p>
<pre data-type="programlisting">{address: 742 Evergreen Terrace, city: Springfield, state: North Takoma}</pre>
</section>
<section data-type="sect2" id="line_folding">
<h2>Line Folding</h2>
<p>When writing playbooks, you’ll often encounter situations where you’re passing
many arguments to a module.<a data-type="indexterm" data-primary="YAML" data-secondary="line folding"/> For aesthetics, you might want to break this up across
multiple lines in your file, but you want Ansible to treat the string as if it
were a single line.</p>
<p>You can do this with YAML by using line folding with the greater than (<code>></code>)
character.<a data-type="indexterm" data-primary=">, line folding in YAML"/><a data-type="indexterm" data-primary="line folding in YAML"/> The YAML parser will replace line breaks with spaces. For example:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">address: >
Department of Computer Science,
A.V. Williams Building,
University of Maryland
city: College Park
state: Maryland</pre>
<p>The JSON equivalent is as follows:</p>
<pre data-type="programlisting" data-code-language="json">{
"address": "Department of Computer Science, A.V. Williams Building,
University of Maryland",
"city": "College Park",
"state": "Maryland"
}</pre>
</section>
</section>
<section data-type="sect1">
<h1>Anatomy of a Playbook</h1>
<p>Let’s take a look at our playbook from the perspective of a YAML file.<a data-type="indexterm" data-primary="YAML" data-startref="ix_YAML"/><a data-type="indexterm" data-primary="playbooks" data-secondary="YAML syntax" data-startref="ix_playbkYAML"/><a data-type="indexterm" id="ix_playbkanat" data-primary="playbooks" data-secondary="anatomy of"/> Here it is again, in <a data-type="xref" href="#example2-7"/>.</p>
<div id="example2-7" data-type="example">
<h5>web-notls.yml</h5>
<pre data-type="programlisting" data-code-language="yaml+jinja"></pre></div>
<p>In <a data-type="xref" href="#example2-8"/>, we see the JSON equivalent of this file.</p>
<div id="example2-8" data-type="example">
<h5>JSON equivalent of web-notls.yml</h5>
<pre data-type="programlisting" data-code-language="json"></pre></div>
<div data-type="note">
<p>A valid JSON file is also a valid YAML file.<a data-type="indexterm" data-primary="JSON" data-secondary="valid file"/><a data-type="indexterm" data-primary="YAML" data-secondary="valid file, JSON and"/> This is because YAML allows strings to be quoted, considers <code>true</code> and <code>false</code> to be valid Booleans, and has inline lists and dictionary syntaxes that are the same as JSON arrays and objects. But don’t write your playbooks as JSON—the whole point of YAML is that it’s easier for people to read.</p>
</div>
<section data-type="sect2">
<h2>Plays</h2>
<p>Looking at either the YAML or JSON representation, it should be clear that a
playbook is a list of dictionaries.<a data-type="indexterm" data-primary="playbooks" data-secondary="anatomy of" data-tertiary="plays"/> Specifically, a playbook is a list of
<em>plays</em>.<a data-type="indexterm" data-primary="plays"/></p>
<p>Here’s the play<span data-type="footnote">Actually, it’s a list that contains a single play.</span> from our example:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">- name: Configure webserver with nginx
hosts: webservers
become: True
tasks:
- name: install nginx
apt: name=nginx update_cache=yes
- name: copy nginx config file
copy: src=files/nginx.conf dest=/etc/nginx/sites-available/default
- name: enable configuration
file: >
dest=/etc/nginx/sites-enabled/default
src=/etc/nginx/sites-available/default
state=link
- name: copy index.html
template: src=templates/index.html.j2
dest=/usr/share/nginx/html/index.html mode=0644
- name: restart nginx
service: name=nginx state=restarted</pre>
<p>Every play must contain the following:</p>
<ul>
<li>
<p>A set of <em>hosts</em> to configure</p>
</li>
<li>
<p>A list of <em>tasks</em> to be executed on those hosts</p>
</li>
</ul>
<p>Think of a play as the thing that connects hosts to tasks.<a data-type="indexterm" data-primary="hosts" data-secondary="in plays"/><a data-type="indexterm" data-primary="tasks" data-secondary="in plays"/></p>
<p>In addition to specifying hosts and tasks, plays also support optional settings. We’ll get into those later, but here are three common ones:</p>
<dl>
<dt><code>name</code></dt>
<dd>
<p> A comment that describes what the play is about. Ansible prints this out
when the play starts to run.<a data-type="indexterm" data-primary="name setting (in plays)"/></p>
</dd>
<dt><code>become</code></dt>
<dd>
<p> If true, Ansible will run every task by becoming (by default) the root
user.<a data-type="indexterm" data-primary="become setting (in plays)"/><a data-type="indexterm" data-primary="root user"/> This is useful when managing Ubuntu servers, since by default you cannot SSH as the root user.</p>
</dd>
<dt><code>vars</code></dt>
<dd>
<p> A list of variables and values.<a data-type="indexterm" data-primary="variables" data-secondary="vars setting in plays"/> You’ll see this in action later in this
chapter.</p>
</dd>
</dl>
</section>
<section data-type="sect2">
<h2>Tasks</h2>
<p>Our example playbook contains one play that has five tasks.<a data-type="indexterm" data-primary="tasks" data-secondary="in example playbook"/><a data-type="indexterm" data-primary="playbooks" data-secondary="anatomy of" data-tertiary="tasks"/> Here’s the first
task of that play:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">- name: install nginx
apt: name=nginx update_cache=yes</pre>
<p>The <code>name</code> is optional, so it’s perfectly <a data-type="indexterm" data-primary="names" data-secondary="name setting for tasks"/>valid to write a task like this:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">- apt: name=nginx update_cache=yes</pre>
<p>Even though names are optional, I recommend you use them because they serve as good reminders for the intent of the task. (Names will be very useful when somebody else is trying to understand your playbook, including yourself in six months.) As you’ve seen, Ansible will print out the name of a task when it runs. Finally, as you’ll see in
<a data-type="xref" href="#DEBUGGING"/>, you can use the <code>--start-at-task <task name></code> flag to tell <code>ansible-playbook</code> to start a playbook in the middle of a play, but you need to reference the task by name.</p>
<p>Every task must contain a key with the name of a module and a value with the
arguments to that module.<a data-type="indexterm" data-primary="modules" data-secondary="name and arguments in tasks"/><a data-type="indexterm" data-primary="apt module" data-secondary="and arguments in a task"/> In the preceding example, the module name is <code>apt</code> and the
arguments are <code>name=nginx update_cache=yes</code>.</p>
<p>These arguments tell the <code>apt</code> module to install the package named <em>nginx</em> and to
update the package cache (the equivalent of doing an <code>apt-get update</code>) before
installing the package.</p>
<p>It’s important to understand that, from the point of the view of the YAML parser used by the Ansible frontend, the arguments are treated as a string, not as a dictionary.<a data-type="indexterm" data-primary="YAML" data-secondary="line folding" data-tertiary="for module name and arguments in playbook tasks"/> This means that if you want to break arguments into multiple lines, you need to use the YAML folding syntax, like this:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">- name: install nginx
apt: >
name=nginx
update_cache=yes</pre>
<p>Ansible also supports a task syntax that will let you specify module arguments as
a YAML dictionary, which is helpful when using modules that support complex
<span class="keep-together">arguments</span>. We’ll cover that in <a data-type="xref" href="#COMPLEX_ARGS"/>.</p>
<p>Ansible also supports an older syntax that uses <code>action</code> as the key and puts the name of the module in the value. The preceding example also can be written as follows:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">- name: install nginx
action: apt name=nginx update_cache=yes</pre>
</section>
<section data-type="sect2">
<h2>Modules</h2>
<p><em>Modules</em> are scripts that come packaged with Ansible and perform some kind of action on a host.<span data-type="footnote">The modules that ship with Ansible all are written in Python, but modules can be written in any language.</span> Admittedly, that’s a pretty generic description, but there’s enormous variety across Ansible modules.<a data-type="indexterm" data-primary="modules"/><a data-type="indexterm" data-primary="playbooks" data-secondary="anatomy of" data-tertiary="modules"/> The modules we use in this chapter are as follows:</p>
<dl>
<dt><code>apt</code></dt>
<dd>
<p>Installs or removes packages by using the apt package manager</p>
</dd>
<dt><code>copy</code></dt>
<dd>
<p>Copies a file from local machine to the hosts</p>
</dd>
<dt><code>file</code></dt>
<dd>
<p>Sets the attribute of a file, symlink, or directory</p>
</dd>
<dt><code>service</code></dt>
<dd>
<p>Starts, stops, or restarts a service</p>
</dd>
<dt><code>template</code></dt>
<dd>
<p>Generates a file from a template and copies it to the hosts</p>
</dd>
</dl>
<aside data-type="sidebar">
<h5>Viewing Ansible Module Documentation</h5>
<p>Ansible ships with the <code>ansible-doc</code> command-line tool, which shows documentation about modules.<a data-type="indexterm" data-primary="modules" data-secondary="documentation for"/><a data-type="indexterm" data-primary="documentation" data-secondary="for Ansible modules"/><a data-type="indexterm" data-primary="ansible-doc command-line tool"/> Think of it as man pages for Ansible modules. For example, to show the documentation for the <code>service</code> module, run this:</p>
<pre data-type="programlisting" data-code-language="console">$ ansible-doc service</pre>
<p>If you use macOS, there’s a wonderful documentation viewer called
<a href="http://kapeli.com/dash">Dash</a> that has support for Ansible. Dash indexes all of the Ansible module documentation.<a data-type="indexterm" data-primary="Dash"/><a data-type="indexterm" data-primary="macOS" data-secondary="Dash documentation viewer"/> It’s a commercial tool ($24.99 as of this writing), but I find it invaluable.</p>
</aside>
<p>Recall from the first chapter that Ansible executes a task on a host by
generating a custom script based on the module name and arguments, and then copies this script to the host and runs it.</p>
<p>More than 200 modules ship with Ansible, and this number grows with every release. You can also find third-party Ansible modules out there, or write your own.</p>
</section>
<section data-type="sect2">
<h2>Putting It All Together</h2>
<p>To sum up, a playbook contains one or more plays. A play associates an unordered set of hosts with an ordered list of tasks. Each task is associated with exactly one module.<a data-type="indexterm" data-primary="playbooks" data-secondary="anatomy of" data-tertiary="summary of contents"/></p>
<p><a data-type="xref" href="#erd_figure"/> is an entity-relationship diagram that depicts this
relationship between playbooks, plays, hosts, tasks, and modules.</p>
<figure id="erd_figure">
<img src="images/aur2_0203.png" alt="Ansible entities"/>
<figcaption>Entity-relationship diagram</figcaption>
</figure>
</section>
</section>
<section data-type="sect1">
<h1>Did Anything Change? Tracking Host State</h1>
<p>When you run <code>ansible-playbook</code>, Ansible outputs status information for each task it executes in the play.<a data-type="indexterm" data-primary="playbooks" data-secondary="tracking host state"/><a data-type="indexterm" data-primary="hosts" data-secondary="tracking host state"/></p>
<p>Looking back at <a data-type="xref" href="#output_ansible_playbook"/>, notice that the status for some of the tasks is <code>changed</code>, and the status for some others is <code>ok</code>. For example, the <code>install nginx</code> task <a data-type="indexterm" data-primary="tasks" data-secondary="changed status"/>has status <code>changed</code>, which appears as yellow on my terminal:</p>
<pre data-type="programlisting">TASK: [install nginx] *********************************************************
changed: [testserver]</pre>
<p>The <code>enable configuration</code>, on the other hand,<a data-type="indexterm" data-primary="tasks" data-secondary="ok status"/> has status <code>ok</code>, which appears as green on my terminal:</p>
<pre data-type="programlisting">TASK: [enable configuration] **************************************************
ok: [testserver]</pre>
<p>Any Ansible task that runs has the potential to change the state of the host in
some way. Ansible modules will first check to see whether the state of the host needs
to be changed before taking any action. If the state of the host matches the
arguments of the module, Ansible takes no action on the host and responds
with a state of <code>ok</code>.</p>
<p>On the other hand, if there is a difference between the state of the host and
the arguments to the module, Ansible will change the state of the host and
return <code>changed</code>.</p>
<p>In the example output just shown, the <code>install nginx</code> task was changed,
which meant that before I ran the playbook, the <em>nginx</em> package had not
previously been installed on the host. The <code>enable configuration</code> task was
unchanged, which meant that there was already a configuration file on the
server that was identical to the file I was copying over. The reason for this is
that the <em>nginx.conf</em> file I used in my playbook is the same as the <em>nginx.conf</em>
file that gets installed by the <em>nginx</em> package on Ubuntu.</p>
<p>As you’ll see later in this chapter, Ansible’s detection of state change can
be used to trigger additional actions through the use of <em>handlers</em>.<a data-type="indexterm" data-primary="handlers"/> But, even
without using handlers, it is still a useful form of feedback to see whether
your hosts are changing state as the playbook runs.<a data-type="indexterm" data-primary="playbooks" data-secondary="anatomy of" data-startref="ix_playbkanat"/></p>
</section>
<section data-type="sect1">
<h1>Getting Fancier: TLS Support</h1>
<p>Let’s move on to a more complex example: we’re going to modify the previous playbook so that our web servers support TLS.<a data-type="indexterm" id="ix_TLS" data-primary="TLS (Transport Layer Security)"/><a data-type="indexterm" id="ix_playbkTLS" data-primary="playbooks" data-secondary="modifying simple example to add TLS support"/> The new features here are as follows:</p>
<ul>
<li>
<p>Variables</p>
</li>
<li>
<p>Handlers</p>
</li>
</ul>
<p><a data-type="xref" href="#WEB_TLS_PLAYBOOK"/> shows what our playbook looks like with TLS support.</p>
<div id="WEB_TLS_PLAYBOOK" data-type="example">
<h5>web-tls.yml</h5>
<pre data-type="programlisting" data-code-language="yaml+jinja"></pre></div>
<section data-type="sect2">
<h2>Generating a TLS Certificate</h2>
<p>We need to manually generate a TLS certificate. <a data-type="indexterm" data-primary="TLS (Transport Layer Security)" data-secondary="generating a TLS certificate"/><a data-type="indexterm" data-primary="certificates" data-secondary="generating a TLS certificate"/>In a
production environment, you’d purchase your TLS certificate from a certificate
authority, or use a free service such as Let’s Encrypt, which Ansible supports via
the <code>letsencrypt</code> module. We’ll use a self-signed certificate, since we can generate those for
free.</p>
<p>Create a <em>files</em> subdirectory of your <em>playbooks</em> directory, and then generate the TLS certificate and key:</p>
<pre data-type="programlisting" data-code-language="console">$ mkdir files
$ openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-subj /CN=localhost \
-keyout files/nginx.key -out files/nginx.crt</pre>
<p>This should generate the files <em>nginx.key</em> and <em>nginx.crt</em> in the <em>files</em>
directory. The certificate has an expiration date of 10 years (3,650 days) from the day you created it.</p>
</section>
<section data-type="sect2">
<h2>Variables</h2>
<p>The play in our playbook now has a section called <code>vars</code>:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">vars:
key_file: /etc/nginx/ssl/nginx.key
cert_file: /etc/nginx/ssl/nginx.crt
conf_file: /etc/nginx/sites-available/default
server_name: localhost</pre>
<p>This section defines four <a data-type="indexterm" data-primary="variables" data-secondary="in playbook with TLS support"/>variables and assigns a value to each variable.</p>
<p>In our example, each value is a string (e.g., <code>/etc/nginx/ssl/nginx.key</code>),
but any valid YAML can be used as the value of a variable. You can use lists
and dictionaries in addition to strings and Booleans.</p>
<p>Variables can be used in tasks, as well as in template files.<a data-type="indexterm" data-primary="tasks" data-secondary="variables in"/> You reference
variables by using the <code>{{ braces }}</code> notation. <a data-type="indexterm" data-primary="{{ }} (braces notation)" data-secondary="referencing variables"/>Ansible replaces these braces
with the value of the variable.</p>
<p>Consider this task in the playbook:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">- name: copy TLS key
copy: src=files/nginx.key dest={{ key_file }} owner=root mode=0600</pre>
<p>Ansible will substitute <code>{{ key_file }}</code> with <code>/etc/nginx/ssl/nginx.key</code> when it
executes this task.</p>
<aside data-type="sidebar">
<h5>When Quoting Is Necessary</h5>
<p>If you reference a variable right after specifying the module, the YAML parser will misinterpret the variable reference as the beginning of an inline dictionary.<a data-type="indexterm" data-primary="strings" data-secondary="quoting in arguments"/> Consider the following example:</p>
<pre data-type="programlisting" data-code-language="yaml+jinja">- name: perform some task
command: {{ myapp }} -a foo</pre>
<p>Ansible will try to parse the first part of <code>{{ myapp }} -a foo</code> as a dictionary
instead of a string, and will return an error. In this case, you must quote the