summaryrefslogtreecommitdiff
path: root/src/talks/ansible-cloud.hbs
diff options
context:
space:
mode:
Diffstat (limited to 'src/talks/ansible-cloud.hbs')
-rw-r--r--src/talks/ansible-cloud.hbs711
1 files changed, 711 insertions, 0 deletions
diff --git a/src/talks/ansible-cloud.hbs b/src/talks/ansible-cloud.hbs
new file mode 100644
index 0000000..9f356d5
--- /dev/null
+++ b/src/talks/ansible-cloud.hbs
@@ -0,0 +1,711 @@
1<!doctype html>
2<html lang="en">
3
4 <head>
5 <meta charset="utf-8">
6
7 <title>NoOps with Ansible and Puppet</title>
8 </head>
9 <body>
10
11 <section id="who-am-i" class="slide level2">
12 <h1>Who am I?</h1>
13 <ul>
14 <li>Distinguished Technologist at HP</li>
15 <li>OpenStack Technical Committee</li>
16 <li>OpenStack Foundation Board of Directors</li>
17 <li>OpenStack Developer Infrastructure Core Team</li>
18 <li>Former Consultant for MySQL, Inc</li>
19 <li>Former Drizzle Core Developer</li>
20 </ul>
21 </section>
22
23 <section id="what-are-we-going-to-talk-about" class="slide level2">
24 <h1>What are we going to talk about?</h1>
25 <ul>
26 <li>NoOps</li>
27 <li>Cloud Applications</li>
28 <li>Puppet</li>
29 <li>Ansible</li>
30 </ul>
31 </section>
32
33 <section id="no-ops" class="slide level2">
34 <h1>NoOps</h1>
35 <p class="fragment">NoOps means developers can code and let a service deploy, manage and scale their code</p>
36 <p class="fragment">I don't want to "do ops"</p>
37 <p class="fragment">I want to change the system by landing commits</p>
38 <p class="fragment">If I have to use my root access, it's a bug</p>
39 </section>
40
41 <section id="cloud-native" class="slide level2">
42 <h1>Cloud Native</h1>
43 <ul>
44 <li>Ephemeral Compute</li>
45 <li>Data services</li>
46 <li>Design your applications to be resilient via scale out</li>
47 </ul>
48 </section>
49
50 <section id="cloud-scale-out" class="slide level2">
51 <h1>Cloud Scale Out</h1>
52 <ul>
53 <li>Forget HA of one system</li>
54 <li>Forget long-lived systems</li>
55 <li>Shared-nothing for EVERYTHING</li>
56 </ul>
57 </section>
58
59 <section id="cloud-scale-out-is-great-for-new-applications" class="slide level2">
60 <h1>Cloud Scale Out is great for new applications</h1>
61 </section>
62
63 <section id="what-about-existing-applications" class="slide level2">
64 <h1>What about existing applications?</h1>
65 </section>
66
67 <section>
68 <section id="openstack-infra" data-transition='zoom'>
69 <h1>OpenStack Infra</h1>
70 </section>
71
72 <section id="tooling-automation-and-ci-for-openstack-project" class="slide level2" data-transition='zoom'>
73 <h1>Tooling, Automation and CI for OpenStack Project</h1>
74 </section>
75
76 <section id="developers" class="slide level2" data-transition='zoom'>
77 <h1>2000 Developers</h1>
78 </section>
79
80 <section id="gated-commits" class="slide level2" data-transition='zoom'>
81 <h1>Gated Commits</h1>
82 <p>Every commit is fully integration tested (twice) before landing</p>
83 </section>
84
85 <section id="each-test-runs-on-a-single-use-cloud-slave" class="slide level2" data-transition='zoom'>
86 <h1>Each Test Runs on a Single Use Cloud Slave</h1>
87 <p>This is that "cloud scale out" part</p>
88 </section>
89
90 <section id="million-test-jobs-in-the-last-6-months" class="slide level2" data-transition='zoom'>
91 <h1>1.7 Million Test Jobs in the last 6 Months</h1>
92 </section>
93
94 <section id="15-million-tests-in-december" class="slide level2" data-transition='zoom'>
95 <h1>15 Million Tests in December</h1>
96 </section>
97
98 <section id="terabytes-of-log-data-in-six-months" class="slide level2" data-transition='zoom'>
99 <h1>18 Terabytes of Log Data in six months</h1>
100 </section>
101
102 <section id="we-have-no-servers" class="slide level2" data-transition='zoom'>
103 <h1>We have no servers</h1>
104 <p>It all runs across HP and Rackspace Public Clouds.</p>
105 </section>
106
107 </section>
108
109 <section id="architecture" class="slide level2">
110 <h1>Architecture</h1>
111 <p><img src="/images/infra_architecture.jpg" alt="image" /></p>
112 </section>
113
114 <section id="it-didnt-start-this-way" class="slide level2">
115 <h1>It didn't start this way</h1>
116 <p><img src="/images/original.jpg" alt="image" /></p>
117 </section>
118
119 <section id="step-one-puppet" class="titleslide slide level1">
120 <h1>Step One: Puppet</h1>
121 </section>
122
123 <section id="overview" class="slide level2">
124 <h1>Overview</h1>
125 <ul>
126 <li>Open Source Config Management System</li>
127 <li>Written in Ruby</li>
128 <li>Models intended state</li>
129 <li>Wants to own entire system</li>
130 </ul>
131 </section>
132
133 <section id="config-management-is-great" class="slide level2">
134 <h1>Config Management is Great</h1>
135 <ul>
136 <li>Repeatable and consistent machines</li>
137 <li>Code Review</li>
138 <li>Collaboration from non-root users</li>
139 <li>Less repetition for me</li>
140 <li>Open Source Infrastructure</li>
141 <li><a href="http://git.openstack.org/cgit/openstack-infra/system-config/">http://git.openstack.org/cgit/openstack-infra/system-config/</a></li>
142 <li><a href="http://puppetdb.openstack.org/">http://puppetdb.openstack.org/</a></li>
143 </ul>
144 </section>
145
146 <section id="ruby-dsl" class="slide level2">
147 <h1>Ruby DSL</h1>
148 <pre><code>
149package { 'git':
150 ensure =&gt; 'present',
151}
152 </code></pre>
153 </section>
154
155 <section id="leaky-abstraction" class="slide level2">
156 <h1>Leaky Abstraction</h1>
157 <pre><code>
158if !defined(Package['git']) {
159 package { 'git':
160 ensure =&gt; 'present',
161 }
162}
163 </code></pre>
164 </section>
165
166 <section id="three-ways-to-run" class="slide level2">
167 <h1>Three ways to run</h1>
168 <ul>
169 <li>puppet apply</li>
170 <li>puppetmaster + puppet agent daemons</li>
171 <li>puppetmaster + puppet agent non-daemon</li>
172 </ul>
173 </section>
174
175 <section id="managing-users-and-ssh-keys" class="slide level2">
176 <h1>Managing users and ssh keys</h1>
177 <pre><code>
178define user::virtual::localuser(
179 $realname,
180 $groups = [ 'sudo', 'admin', ],
181 $sshkeys = '',
182 $key_id = '',
183 $old_keys = [],
184 $shell = '/bin/bash',
185 $home = "/home/${title}",
186 $managehome = true
187) {
188
189 group { $title:
190 ensure =&gt; present,
191 }
192 </code></pre>
193 </section>
194
195 <section id="managing-users-and-ssh-keys-2" class="slide level2">
196 <h1>Managing users and ssh keys (cont.)</h1>
197 <pre><code>
198 user { $title:
199 ensure =&gt; present,
200 comment =&gt; $realname,
201 gid =&gt; $title,
202 groups =&gt; $groups,
203 home =&gt; $home,
204 managehome =&gt; $managehome,
205 membership =&gt; 'minimum',
206 shell =&gt; $shell,
207 require =&gt; Group[$title],
208 }
209 </code></pre>
210 </section>
211
212 <section id="managing-users-and-ssh-keys-3" class="slide level2">
213 <h1>Managing users and ssh keys (cont.)</h1>
214 <pre><code>
215 ssh_authorized_key { $key_id:
216 ensure =&gt; present,
217 key =&gt; $sshkeys,
218 user =&gt; $title,
219 type =&gt; 'ssh-rsa',
220 }
221
222 if ( $old_keys != [] ) {
223 ssh_authorized_key { $old_keys:
224 ensure =&gt; absent,
225 user =&gt; $title,
226 }
227 }
228}
229 </code></pre>
230 </section>
231
232 <section id="our-code-is-open-source-right-what-about-passwords-and-keys-..." class="slide level2">
233 <h1>Our code is Open Source, right? What about passwords and keys ...</h1>
234 </section>
235
236 <section id="hiera" class="slide level2">
237 <h1>hiera</h1>
238 <ul>
239 <li>Simple YAML database, sits on puppetmaster</li>
240 <li>Use it for secret data</li>
241 <li>puppet code is still complete</li>
242 </ul>
243 <pre><code>
244node default {
245 class { 'openstack_project::server':
246 sysadmins =&gt; hiera('sysadmins', []),
247 }
248}
249 </code></pre>
250 <p>Breaks ability to use simple puppet apply</p>
251 </section>
252
253 <section id="step-two-ansible-for-orchestration" class="titleslide slide level1">
254 <h1>Step Two: Ansible for Orchestration</h1>
255 </section>
256
257 <section id="about-ansible" class="slide level2">
258 <h1>About Ansible</h1>
259 <ul>
260 <li>Open Source System Management tool</li>
261 <li>Written in Python</li>
262 <li>Sequence of steps to perform</li>
263 <li>Works over SSH</li>
264 <li>Incremental Adoption</li>
265 </ul>
266 </section>
267
268 <section>
269 <h1>ad-hoc operation</h1>
270 <pre>
271ansible '*' -m shell -p uptime
272 </pre>
273 </section>
274
275 <section id="yaml-syntax" class="slide level2">
276 <h1>YAML Syntax</h1>
277 <pre><code>
278- hosts: '*.slave.openstack.org'
279 tasks:
280 - shell: 'rm -rf ~jenkins/workspace/*{{ project }}*'
281 </code></pre>
282 <p>That's executed:</p>
283 <pre>
284ansible-playbook -f 10 /etc/ansible/clean_workspaces.yaml --extra-vars "project=$PROJECTNAME"
285 </pre>
286 </section>
287
288 <section id="ansible-organization" class="slide level2">
289 <h1>Ansible Organization</h1>
290 <ul>
291 <li>modules</li>
292 <li>plays</li>
293 <li>playbooks</li>
294 <li>roles</li>
295 </ul>
296 </section>
297
298 <section id="use-ansible-to-run-puppet" class="slide level2">
299 <h1>Use Ansible to Run Puppet!</h1>
300 </section>
301
302 <section id="puppet-module" class="slide level2">
303 <h1>puppet module</h1>
304 <pre><code>def main():
305 module = AnsibleModule(argument_spec=dict(
306 timeout=dict(default="30m"),
307 puppetmaster=dict(required=True),
308 show_diff=dict(default=False, aliases=['show-diff'], type='bool'),
309 ))
310 p = module.params
311
312 puppet_cmd = module.get_bin_path("puppet", False)
313 if not puppet_cmd:
314 module.fail_json(msg="Could not find puppet. Please ensure it is installed.")
315 </code></pre>
316 </section>
317
318 <section id="puppet-module-2" class="slide level2">
319 <h1>puppet module (cont)</h1>
320 <pre><code class="python">
321 cmd = ("timeout -s 9 %(timeout)s %(puppet_cmd)s agent --onetime"
322 " --server %(puppetmaster)s"
323 " --ignorecache --no-daemonize --no-usecacheonfailure --no-splay"
324 " --detailed-exitcodes --verbose") % dict(
325 timeout=pipes.quote(p['timeout']), puppet_cmd=PUPPET_CMD,
326 puppetmaster=pipes.quote(p['puppetmaster']))
327 if p['show_diff']:
328 cmd += " --show-diff"
329 rc, stdout, stderr = module.run_command(cmd)
330 </code></pre>
331 </section>
332
333 <section id="puppet-module-3" class="slide level2">
334 Please. Everyone. Marvel at the following logic
335 <pre><code>
336 if rc == 0: # success
337 module.exit_json(rc=rc, changed=False, stdout=stdout)
338 elif rc == 1:
339 # rc==1 could be because it's disabled OR there was a compilation failure
340 disabled = "administratively disabled" in stdout
341 if disabled:
342 msg = "puppet is disabled"
343 else:
344 msg = "puppet compilation failed"
345 module.fail_json(rc=rc, disabled=disabled, msg=msg, stdout=stdout, stderr=stderr)
346 elif rc == 2: # success with changes
347 module.exit_json(changed=True)
348 elif rc == 124: # timeout
349 module.exit_json(rc=rc, msg="%s timed out" % cmd, stdout=stdout, stderr=stderr)
350 else: # failure
351 module.fail_json(rc=rc, msg="%s failed" % (cmd), stdout=stdout, stderr=stderr)
352 </code></pre>
353 </section>
354
355 <section id="puppet-play" class="slide level2">
356 <h1>puppet play</h1>
357 <pre><code>
358- name: run puppet
359 puppet:
360 puppetmaster: "{{puppetmaster}}"
361 </code></pre>
362 </section>
363
364 <section id="puppet-role" class="slide level2">
365 <h1>puppet role</h1>
366 <p>roles/puppet/tasks/main.yml</p>
367 </section>
368
369 <section id="remote-puppet-playbook" class="slide level2">
370 <h1>remote puppet playbook</h1>
371 <pre><code>
372- hosts: git0*
373 gather_facts: false
374 max_fail_percentage: 1
375 roles:
376 - { role: puppet, puppetmaster: puppetmaster.openstack.org }
377- hosts: review.openstack.org
378 gather_facts: false
379 roles:
380 - { role: puppet, puppetmaster: puppetmaster.openstack.org }
381- hosts: "!review.openstack.org:!git0*:!afs*"
382 gather_facts: false
383 roles:
384 - { role: puppet, puppetmaster: puppetmaster.openstack.org }
385 </pre></code>
386 </section>
387
388 <section id="ansible-inventory" class="slide level2">
389 <h1>ansible inventory</h1>
390 <ul>
391 <li>List of servers to operate on</li>
392 <li>Optionally variables associated with each server</li>
393 <li>Optional groups of servers</li>
394 <li>Simple yaml file in /etc/ansible/hosts</li>
395 <li>Dynamic executable that returns JSON</li>
396 </ul>
397 </section>
398
399 <section id="ansible-inventory-from-file" class="slide level2">
400 <h1>Simple inventory</h1>
401 <pre>
402review.openstack.org
403git01.openstack.org
404git02.openstack.org
405pypi.dfw.openstack.org
406pypi.iad.openstack.org
407
408[pypi]
409pypi.dfw.openstack.org
410pypi.iad.openstack.org
411
412[git]
413git01.openstack.org
414git02.openstack.org
415 </pre>
416 </section>
417
418 <section id="ansible-inventory-from-puppet" class="slide level2">
419 <h1>ansible inventory from puppet certs</h1>
420 <pre><code>
421import json
422import subprocess
423
424output = [
425 x.split()[1][1:-1] for x in subprocess.check_output(
426 ["puppet","cert","list","-a"]).split('\n')
427 if x.startswith('+')
428]
429
430data = {
431 '_meta': {'hostvars': dict()},
432 'ungrouped': output,
433}
434print json.dumps(data, sort_keys=True, indent=2)
435 </code></pre>
436 </section>
437
438 <section>
439 <h1>Step Three: Ansible for Cloud Management</h1>
440 </section>
441
442 <section>
443 <h1>ansible and OpenStack</h1>
444 <ul>
445 <li>Ansible modules are just python</li>
446 <li>playbooks are lists of steps to take</li>
447 <li>Have plays/roles that provision servers</li>
448 <li>Infrastructure as code - for real!</li>
449 </ul>
450 </section>
451
452 <section>
453 <h1>Consider this data</h1>
454 <pre><code>
455pypi.dfw.openstack.org:
456 image_name: Ubuntu 12.04.4
457 flavor_ram: 2048
458 region: DFW
459 cloud: rackspace
460 volumes:
461 - size: 200
462 mount: /srv
463pypi.region-b.geo-1.openstack.org:
464 image_name: Ubuntu 12.04.4
465 flavor_ram: 2048
466 region: region-b.geo-1
467 cloud: hp
468 volumes:
469 - size: 200
470 mount: /srv
471 </code></pre>
472 </section>
473
474 <section>
475 <h1>Further consider this data</h1>
476 <pre><code>
477pypi:
478 image_name: Ubuntu 12.04.4
479 flavor_ram: 2048
480 volumes:
481 - size: 200
482 mount: /srv
483 hosts:
484 pypi.dfw:
485 region: DFW
486 pypi.iad:
487 region: IAD
488 pypi.ord:
489 region: ORD
490 pypi.region-b.geo-1:
491 cloud: hp
492 </code></pre>
493 </section>
494
495 <section>
496 <h1>Steps to launch a node</h1>
497 <ol>
498 <li>Create a compute instance</li>
499 <li>Wait for instance to exist</li>
500 <li>Create a floating IP</li>
501 <li>Attach floating IP to instance</li>
502 <li>Create one or more volumes</li>
503 <li>Attach volumes to instance</li>
504 <li>Wait for SSH to work</li>
505 <li>On host, format each volume</li>
506 <li>On host, mount each volume</li>
507 <li>On host, install config management software</li>
508 <li>On host, run config management software</li>
509 </ol>
510 </section>
511
512 <section>
513 <h1>Launch a node</h1>
514 <pre><code>
515---
516- name: Launch Node
517 os_compute:
518 cloud: "{{ cloud }}"
519 region_name: "{{ region_name }}"
520 name: "{{ name }}"
521 image_name: "{{ image_name }}"
522 flavor_ram: "{{ flavor_ram }}"
523 flavor_include: "{{ flavor_include }}"
524 meta:
525 group: "{{ group }}"
526 key_name: "{{ launch_keypair }}"
527 register: node
528- name: Create volumes
529 os_volume:
530 cloud: "{{ cloud }}"
531 size: "{{ item.size }}"
532 display_name: "{{ item.display_name }}"
533 with_items: volumes
534- name: Attach volumes
535 os_compute_volume:
536 cloud: "{{ cloud }}"
537 server_id: "{{ node.id }}"
538 volume_name: "{{ item.display_name }}"
539 with_items: volumes
540 register: attached_volumes
541- debug: var=attached_volumes
542- name: Re-request server to get up to date metadata after the volume loop
543 os_compute_facts:
544 cloud: "{{ cloud }}"
545 name: "{{ name }}"
546 when: attached_volumes.changed
547- name: Wait for SSH to work
548 wait_for: host={{ node.openstack.interface_ip }} port=22
549 when: node.changed == True
550- name: Add SSH host key to known hosts
551 shell: ssh-keyscan "{{ node.openstack.interface_ip|quote }}" &gt;&gt; ~/.ssh/known_hosts
552 when: node.changed == True
553- name: Add all instance public IPs to host group
554 add_host:
555 name: "{{ node.openstack.interface_ip }}"
556 groups: "{{ provision_group }}"
557 openstack: "{{ node.openstack }}"
558 when: attached_volumes|length == 0
559- name: Add all instance public IPs to host and volumes group
560 add_host:
561 name: "{{ node.openstack.interface_ip }}"
562 groups: "{{ provision_group }},hasvolumes"
563 openstack: "{{ node.openstack }}"
564 when: attached_volumes|length != 0
565 </code></pre>
566 </section>
567
568 <section>
569 <h1> Cloud based inventory </h1>
570 <ul>
571 <li> Just ask the cloud for the inventory </li>
572 <li> All of the meta-data the cloud knows is available </li>
573 <li> No need for puppetmaster now </li>
574 </ul>
575 </section>
576
577 <section>
578 <pre><code>
579 "pypi.dfw.openstack.org": {
580 "ansible_ssh_host": "23.253.237.8",
581 "openstack": {
582 "HUMAN_ID": true,
583 "NAME_ATTR": "name",
584 "OS-DCF:diskConfig": "MANUAL",
585 "OS-EXT-STS:power_state": 1,
586 "OS-EXT-STS:task_state": null,
587 "OS-EXT-STS:vm_state": "active",
588 "accessIPv4": "23.253.237.8",
589 "accessIPv6": "2001:4800:7817:104:d256:7a33:5187:7e1b",
590 "addresses": {
591 "private": [
592 {
593 "addr": "10.208.195.50",
594 "version": 4
595 }
596 ],
597 "public": [
598 {
599 "addr": "23.253.237.8",
600 "version": 4
601 },
602 {
603 "addr": "2001:4800:7817:104:d256:7a33:5187:7e1b",
604 "version": 6
605 }
606 ]
607 },
608 "cloud": "rax",
609 "config_drive": "",
610 "created": "2014-09-05T15:32:14Z",
611 "flavor": {
612 "id": "performance1-4",
613 "links": [
614 {
615 "href": "https://dfw.servers.api.rackspacecloud.com/610275/flavors/performance1-4",
616 "rel": "bookmark"
617 }
618 ],
619 "name": "4 GB Performance"
620 },
621 "hostId": "adb603d4566efe0392756c76dab38ffcba22099368837c7973321e77",
622 "human_id": "pypidfwopenstackorg",
623 "id": "de672205-9245-46b6-b3df-489ccf9e0c17",
624 "image": {
625 "id": "928e709d-35f0-47eb-b296-d18e1b0a76b7",
626 "links": [
627 {
628 "href": "https://dfw.servers.api.rackspacecloud.com/610275/images/928e709d-35f0-47eb-b296-d18e1b0a76b7",
629 "rel": "bookmark"
630 }
631 ]
632 },
633 "interface_ip": "23.253.237.8",
634 "key_name": "launch-node-root",
635 "links": [
636 {
637 "href": "https://dfw.servers.api.rackspacecloud.com/v2/610275/servers/de672205-9245-46b6-b3df-489ccf9e0c17",
638 "rel": "self"
639 },
640 {
641 "href": "https://dfw.servers.api.rackspacecloud.com/610275/servers/de672205-9245-46b6-b3df-489ccf9e0c17",
642 "rel": "bookmark"
643 }
644 ],
645 "metadata": {},
646 "name": "pypi.dfw.openstack.org",
647 "networks": {
648 "private": [
649 "10.208.195.50"
650 ],
651 "public": [
652 "23.253.237.8",
653 "2001:4800:7817:104:d256:7a33:5187:7e1b"
654 ]
655 },
656 "progress": 100,
657 "region": "DFW",
658 "status": "ACTIVE",
659 "tenant_id": "610275",
660 "updated": "2014-09-05T15:32:49Z",
661 "user_id": "156284",
662 "volumes": [
663 {
664 "HUMAN_ID": false,
665 "NAME_ATTR": "name",
666 "attachments": [
667 {
668 "device": "/dev/xvdb",
669 "host_name": null,
670 "id": "c6f5229c-1cc0-47c4-aab7-60db1f6cf8e8",
671 "server_id": "de672205-9245-46b6-b3df-489ccf9e0c17",
672 "volume_id": "c6f5229c-1cc0-47c4-aab7-60db1f6cf8e8"
673 }
674 ],
675 "availability_zone": "nova",
676 "bootable": "false",
677 "created_at": "2014-09-05T14:37:42.000000",
678 "device": "/dev/xvdb",
679 "display_description": null,
680 "display_name": "pypi.dfw.openstack.org/main01",
681 "human_id": null,
682 "id": "c6f5229c-1cc0-47c4-aab7-60db1f6cf8e8",
683 "metadata": {
684 "readonly": "False",
685 "storage-node": "1845027a-5e07-47a1-9572-3eea4716f726"
686 },
687 "os-vol-tenant-attr:tenant_id": "610275",
688 "size": 200,
689 "snapshot_id": null,
690 "source_volid": null,
691 "status": "in-use",
692 "volume_type": "SATA"
693 }
694 ]
695 }
696 },
697 </code></pre>
698 </section>
699
700 <section>
701 <h1> Wait - what about secrets? </h1>
702 <p class="fragment">ansible can just pass secrets to puppet apply as parameters </p>
703 </section>
704
705 <section>
706 <h1> Step Four: Just get rid of puppet ... </h1>
707 <p class="fragment">but that's another talk</p>
708 </section>
709
710</body>
711</html>