Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Build, provision & deploy in the Cloud with Packer, Ansible & Terraform

Ca901ddcea38854b9783781c91fc87c9?s=47 Thijs Feryn
October 27, 2017

Build, provision & deploy in the Cloud with Packer, Ansible & Terraform

A Cloud Automation talk I did at Symfony Live Berlin 2017.

More details at https://feryn.eu/speaking/build-provision-deploy-cloud-packer-ansible-terraform

Ca901ddcea38854b9783781c91fc87c9?s=128

Thijs Feryn

October 27, 2017
Tweet

Transcript

  1. By Thijs Feryn Build, provision & deploy in the Cloud

    with: Packer, Ansible & Terraform
  2. I’m so sick of Cloud!

  3. None
  4. None
  5. SaaS PaaS Iaas Private Public Hybrid

  6. Abstraction

  7. Flexibility

  8. Result in stability

  9. None
  10. Addressing the needs of today’s internet

  11. Technical point of view

  12. Developers Agile

  13. Sysadmins DevOps

  14. Virtualization

  15. Web 2.0: everyone participates

  16. Always online

  17. Online Everywhere

  18. Deployment

  19. Scalability

  20. Servers can go down any second

  21. Automation

  22. Many platforms

  23. None
  24. None
  25. ✓ Less lock-in ✓ Faster deployments ✓ Better scalability ✓

    Better reproducibility ✓ Less human error ✓ Lower cost Goals
  26. Hi, I’m Thijs

  27. I’m @ThijsFeryn on Twitter

  28. I’m an Evangelist At

  29. None
  30. Git repo Application Code Build server Servers Commit Build Deploy

    Git repo Infra scripts Build server Commit Build Orchestrate
  31. Git repo Application Code Build server Servers Commit Build Push

    Infra scripts VM image Cloud registry Build & provision Orchestrate
  32. Application code + Stack = VM image

  33. None
  34. None
  35. VM image + Orchestration = Infrastructure

  36. None
  37. Disposable stacks

  38. Test = Staging = Production

  39. Deployment A Deployment B Internet Floating IP Atomic switch from

    deployment A to deployment B
  40. Deployment A Deployment B Internet Floating IP Atomic switch from

    deployment A to deployment B
  41. None
  42. ✓ Alicloud ECS ✓ Amazon EC2 ✓ Azure ✓ CloudStack

    ✓ DigitalOcean ✓ Docker ✓ File ✓ Google Cloud ✓ Hyper-V ✓ LXC ✓ LXD ✓ Null ✓ 1&1 ✓ OpenStack ✓ Oracle OCI ✓ Parallels ✓ ProfitBricks ✓ QEMU ✓ Triton ✓ VirtualBox ✓ VMware ✓ Custom Packer builders
  43. None
  44. { "builders": [ { "type": "openstack", "identity_endpoint": "https://osp.combell.com:5000/v3", "tenant_name": "",

    "username": "", "password": "", "image_name": "MyBuild-{{isotime \"2006-01-02 03:04:05\"}}", "source_image": "1b9fe43e-4cf2-4986-b7f2-51f5706b120b", "ssh_username": "debian", "domain_name" : "default", "flavor": "m1.medium", "networks": ["a1175c54-cb12-436d-ac7b-d327499dc39b"], "floating_ip_pool" : "public" } ] }
  45. $ packer build packer.json

  46. Packer build Openstack API Boot VM using source image Take

    VM snapshot Image registry Store VM snapshot as new image Tear down VM
  47. $ openstack image list +--------------------------------------+----------------------------------+--------+ | ID | Name |

    Status | +--------------------------------------+----------------------------------+--------+ | ef90ad1c-ed64-4ae0-8283-e446e95c33e6 | MyBuild-2017-10-26 02:31:24 | active | | aa0a5244-0d57-477d-b985-653d6b881292 | Windows-2012-R2 | active | | 31e84bbb-abbb-4d5a-b620-ecebf4697d16 | centos-6-64bit | active | | 9eac74f9-6e6c-45a9-bd93-915d3e390687 | centos-7-64bit | active | | 3a69a110-775f-41ad-9d63-32079db57203 | cirros-0.3.4-64bit | active | | 244cbbde-77b6-4f1e-837f-9250520ee78b | coreos-stable | active | | 1b9fe43e-4cf2-4986-b7f2-51f5706b120b | debian-jessie-64bit | active | | 690b063b-f239-4032-ad23-9ae6337f248f | debian-stretch-64bit | active | | d8034a10-9b15-46b1-9c68-bd98f92d0ffb | ubuntu-server-precise-lts-64-bit | active | | 040f9d1d-bb18-4466-abd7-9f2172e6db70 | ubuntu-server-trusty-lts-64-bit | active | | 5be2b652-a965-4ecb-a2b4-b9f83d7779e6 | ubuntu-server-xenial-lts-64-bit | active | +--------------------------------------+----------------------------------+--------+
  48. None
  49. Configuration management system

  50. None
  51. ✓ Written in Python ✓ Agentless ✓ Standalone ✓ SSH-based

    ✓ Runs playbook (yml files) ✓ Groups playbook in roles ✓ Install software ✓ Configure your server Ansible
  52. --- - hosts: all sudo: true tasks: - name: update

    apt cache apt: update_cache=yes cache_valid_time=86400 - name: install required packages apt: name={{ item }} state=present with_items: - vim - curl - nginx - php-fpm - name: create /var/www/html folder file: path: /var/www/html state: directory mode: 0755 owner: www-data - name: configure index.php template: src: files/index.php playbook.yml
  53. - name: configure index.php template: src: files/index.php dest: /var/www/html/index.php owner:

    www-data group: www-data mode: 0644 force: yes - name: configure nginx vhost copy: src: files/nginx.conf dest: /etc/nginx/sites-enabled/default owner: www-data group: www-data mode: 0644 force: yes notify: - reload nginx handlers: - name: reload nginx service: name=nginx state=reloaded playbook.yml
  54. Organize your playbooks

  55. . !"" files # $"" db.sql !"" host_vars # $""

    default.yml !"" group_vars # $"" all.yml !"" inventory !"" playbook.yml !"" roles # $"" my-role # !"" defaults # # $"" main.yml # !"" handlers # # $"" main.yml # !"" meta # # $"" main.yml # !"" tasks # # $"" main.yml # !"" templates # # $"" template.j2 # $"" vars # $"" vars.yml !"" tasks # !"" task1.yml # !"" task2.yml $"" templates $"" template.j2
  56. - hosts: default sudo: true tasks: - include: tasks/logfiles.yml -

    include: tasks/hosts.yml - include: tasks/packages.yml - include: tasks/mysql.yml post_tasks: - include: tasks/nginx-vhost.yml handlers: - name: Import MySQL mysql_db: name=my_database state=import target=/path/to/files/db.sql roles: - nginx - percona-server - php - php-fpm More organized version of playbook.yml
  57. . !"" files # $"" db.sql !"" host_vars # $""

    default.yml !"" group_vars # $"" all.yml !"" inventory !"" playbook.yml !"" roles # $"" my-role # !"" defaults # # $"" main.yml # !"" handlers # # $"" main.yml # !"" meta # # $"" main.yml # !"" tasks # # $"" main.yml # !"" templates # # $"" template.j2 # $"" vars # $"" vars.yml !"" tasks # !"" task1.yml # !"" task2.yml $"" templates $"" template.j2
  58. [eu-webservers] web01.srv.my-company.com web02.srv.my-company.com [us-webservers] web03.srv.my-company.com web04.srv.my-company.com [eu-dbservers] db01.srv.my-company.com db02.srv.my-company.com [us-dbservers]

    db03.srv.my-company.com db04.srv.my-company.com [webservers:children] eu-webservers us-webservers [dbservers:children] eu-dbservers us-dbservers [eu:children] eu-webservers eu-dbservers [us:children] eu-webservers us-dbservers Inventory file
  59. server { index index.php; server_name {{webserver_hostname}}; root {{homedirectory}}/public; listen 80;

    location ~ \.php { fastcgi_param SCRIPT_FILENAME $request_filename; fastcgi_param APPLICATION_ENV {{application_env}}; fastcgi_pass unix:/var/run/php7.0-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_split_path_info ^(.+\.php)(\/.+)$; } location ~ /\.ht { deny all; } location / { sendfile on; try_files $uri $uri/ /index.php; } } Template file in Jinja2 format
  60. . !"" files # $"" db.sql !"" host_vars # $""

    default.yml !"" group_vars # $"" all.yml !"" inventory !"" playbook.yml !"" roles # $"" my-role # !"" defaults # # $"" main.yml # !"" handlers # # $"" main.yml # !"" meta # # $"" main.yml # !"" tasks # # $"" main.yml # !"" templates # # $"" template.j2 # $"" vars # $"" vars.yml !"" tasks # !"" task1.yml # !"" task2.yml $"" templates $"" template.j2
  61. --- php_display_errors: "Off" php_enable_webserver: false php_enable_php_fpm: true php_date_timezone: "Europe/Brussels" create_app_db:

    true db_name: "my_db" db_collation: "utf8_general_ci" db_user: "username" db_user_password: "" db_host: "%" db_dump_file: "files/db.sql" nginx_install_method: "package" webserver_hostname: "web001.srv.my-company.com" application_env: "production" homedirectory: "/var/www/html" nginx_repo: "ppa:nginx/stable" php_enable_webserver: false php_enable_php_fpm: true php_date_timezone: "Europe/Brussels" root_password: “" Host or group variables
  62. . !"" files # $"" db.sql !"" host_vars # $""

    default.yml !"" group_vars # $"" all.yml !"" inventory !"" playbook.yml !"" roles # $"" my-role # !"" defaults # # $"" main.yml # !"" handlers # # $"" main.yml # !"" meta # # $"" main.yml # !"" tasks # # $"" main.yml # !"" templates # # $"" template.j2 # $"" vars # $"" vars.yml !"" tasks # !"" task1.yml # !"" task2.yml $"" templates $"" template.j2
  63. $ ansible-playbook playbook.yml

  64. { "type": "openstack", "identity_endpoint": "https://osp.combell.com:5000/v3", "tenant_name": "18324-1", "username": "18324", "password":

    "", "image_name": "MyBuild-{{isotime \"2006-01-02 03:04:05\"}}", "source_image": "1b9fe43e-4cf2-4986-b7f2-51f5706b120b", "ssh_username": "debian", "domain_name" : "default", "flavor": "m1.medium", "networks": ["a1175c54-cb12-436d-ac7b-d327499dc39b"], "floating_ip_pool" : "public" } ], "provisioners": [ { "type": "ansible", "playbook_file": "./ansible/playbook.yml" } ] }
  65. $ ansible-playbook --extra-vars packer_build_name=openstack packer_builder_type=openstack -i /var/folders/j7/ kpxjpxq92vs_t97gg6tqtttm0000gn/T/packer-provisioner- ansible972172074 /Users/thijsferyn/Dropbox/Sites/openstack/

    packer-ansible-terraform/ansible/playbook.yml --private-key / var/folders/j7/kpxjpxq92vs_t97gg6tqtttm0000gn/T/ansible- key333617975
  66. Packer build Openstack API Boot VM using source image Take

    VM snapshot Image registry Store VM snapshot as new image Ansible provisioning run Tear down VM
  67. Packer build Openstack API Boot VM using source image Take

    VM snapshot Image registry Store VM snapshot as new image Ansible provisioning run Store build results manifest.json Tear down VM
  68. "image_name": "MyBuild-{{isotime \"2006-01-02 03:04:05\"}}", "source_image": "1b9fe43e-4cf2-4986-b7f2-51f5706b120b", "ssh_username": "debian", "domain_name" :

    "default", "flavor": "m1.medium", "networks": ["a1175c54-cb12-436d-ac7b-d327499dc39b"], "floating_ip_pool" : "public" } ], "provisioners": [ { "type": "ansible", "playbook_file": "./ansible/playbook.yml" } ], "post-processors": [ { "type": "manifest", "output": "manifest.json" } ] }
  69. { "builds": [ { "name": "openstack", "builder_type": "openstack", "build_time": 1508775293,

    "files": null, "artifact_id": "d534f1a9-1578-41ab-a071-c7a67bc9c24d", "packer_run_uuid": "cd0c6312-dd90-329f-60d4-c5445144ebec" }, { "name": "openstack", "builder_type": "openstack", "build_time": 1509053610, "files": null, "artifact_id": "ef90ad1c-ed64-4ae0-8283-e446e95c33e6", "packer_run_uuid": "b6000539-f3e6-85dd-2733-9fb3ae443e9f" } ], "last_run_uuid": "b6000539-f3e6-85dd-2733-9fb3ae443e9f" }
  70. $ openstack image list +--------------------------------------+----------------------------------+--------+ | ID | Name |

    Status | +--------------------------------------+----------------------------------+--------+ | ef90ad1c-ed64-4ae0-8283-e446e95c33e6 | MyBuild-2017-10-26 02:31:24 | active | | aa0a5244-0d57-477d-b985-653d6b881292 | Windows-2012-R2 | active | | 31e84bbb-abbb-4d5a-b620-ecebf4697d16 | centos-6-64bit | active | | 9eac74f9-6e6c-45a9-bd93-915d3e390687 | centos-7-64bit | active | | 3a69a110-775f-41ad-9d63-32079db57203 | cirros-0.3.4-64bit | active | | 244cbbde-77b6-4f1e-837f-9250520ee78b | coreos-stable | active | | 1b9fe43e-4cf2-4986-b7f2-51f5706b120b | debian-jessie-64bit | active | | 690b063b-f239-4032-ad23-9ae6337f248f | debian-stretch-64bit | active | | d8034a10-9b15-46b1-9c68-bd98f92d0ffb | ubuntu-server-precise-lts-64-bit | active | | 040f9d1d-bb18-4466-abd7-9f2172e6db70 | ubuntu-server-trusty-lts-64-bit | active | | 5be2b652-a965-4ecb-a2b4-b9f83d7779e6 | ubuntu-server-xenial-lts-64-bit | active | +--------------------------------------+----------------------------------+--------+
  71. None
  72. Deploy the VM image

  73. Orchestration

  74. ✓ Boot up webservers ✓ Configure firewalls ✓ Assign loadbalancers

    ✓ Link floating IP ✓ Assign SSH keys Orchestration
  75. ✓ CloudFormation (AWS) ✓ Heat (OpenStack) ✓ Resource manager (Azure)

    Vendor-specific orchestration tools
  76. None
  77. ✓ Infrastructure As Code ✓ Plan, graph, execute ✓ terraform.tf

    file ✓ Multiple providers ✓ Incremental changes Terraform
  78. ✓ Alicloud ✓ Archive ✓ Arukas ✓ AWS ✓ Bitbucket

    ✓ CenturyLinkCloud ✓ Chef ✓ Circonus ✓ Cloudflare ✓ CloudStack ✓ Cobbler ✓ Consul ✓ Datadog ✓ DigitalOcean ✓ DNS ✓ DNSMadeEasy ✓ DNSimple ✓ Docker ✓ Dyn ✓ External ✓ Fastly ✓ GitHub ✓ Gitlab ✓ Google Cloud ✓ Grafana ✓ Heroku ✓ HTTP ✓ Icinga2 ✓ Ignition ✓ InfluxDB ✓ Kubernetes ✓ Librato ✓ Local ✓ Logentries ✓ LogicMonitor ✓ Mailgun ✓ Microsoft Azure ✓ Microsoft Azure (Legacy ASM) ✓ MySQL ✓ New Relic ✓ Nomad ✓ NS1 ✓ 1&1 ✓ Oracle Public Cloud ✓ OpenStack ✓ OpsGenie ✓ OVH ✓ Packet ✓ PagerDuty ✓ PostgreSQL ✓ PowerDNS ✓ ProfitBricks ✓ RabbitMQ ✓ Rancher ✓ Random ✓ Rundeck ✓ Scaleway ✓ SoftLayer ✓ StatusCake ✓ Spotinst ✓ Template ✓ Terraform ✓ Terraform Enterprise ✓ TLS ✓ Triton ✓ UltraDNS ✓ Vault ✓ VMware vCloud Director ✓ VMware vSphere Terraform providers
  79. None
  80. terraform.tf

  81. variable image_id { default = "aa528a22-8bdf-47f3-ab3c-3513b1f19252" } variable flavor_id {

    default = "abadcff2-7c20-4e31-8234-a8dcb19de72a" } variable network_id { default = "a1175c54-cb12-436d-ac7b-d327499dc39b" } variable subnet_id { default = "2c380e2a-92cc-464e-b0b7-9142b4a88111" } variable public_key { default = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/ gBW3E+AYITjeme6NRz5QdqTEox8WJxfq0/Hsdg8kzJB1bY3wpFV0ug0CMbAQpknpv/Ajma/ ptSlX0dQmqWBCTztV5inKFtYEnfbUNEAEaoE+ZzCDrcow+1m02XpMhqvwHcFUm4NJ8NwvKtiMqz6cIeg QD1bKThyy03LKO7lJgo3xoit+2ladj7x9HAoGk/ epRp4GmuJc1QiR2PgosrGKor8JVY2qUO0QqjFDi5jF3oB+RlTdiWzTSgzRxZW1tO1N/ jsW9FpgR0OVAVF3lp/ YSfqKgFZOKaeF5wxbTOyPf1aiVe9bqwFjaC5UIJVe+TojXCUR8cpU0egT6DphA6E9 thijs@combellgroup.com" }
  82. provider "openstack" { user_name = "aaa" tenant_name = "aaa" password

    = "aaa" auth_url = "https://osp.combell.com:5000/v3" domain_name = "default" } resource "openstack_compute_keypair_v2" "key" { name = "key_${terraform.workspace}" public_key = "${var.public_key}" }
  83. resource "openstack_compute_secgroup_v2" "secgroup" { name = "secgroup_${terraform.workspace}" description = "Security

    group that allows access to ports 80 and 22" rule { from_port = 22 to_port = 22 ip_protocol = "tcp" cidr = "0.0.0.0/0" } rule { from_port = 80 to_port = 80 ip_protocol = "tcp" cidr = "0.0.0.0/0" } } resource "openstack_compute_instance_v2" "web_1" { name = "web_1_${terraform.workspace}" image_id = "${var.image_id}" flavor_id = "${var.flavor_id}" key_pair = "${openstack_compute_keypair_v2.key.name}" security_groups = ["${openstack_compute_secgroup_v2.secgroup.name}"] network { uuid = "${var.network_id}" name = "private" } }
  84. resource "openstack_compute_instance_v2" "web_2" { name = "web_2_${terraform.workspace}" image_id = "${var.image_id}"

    flavor_id = "${var.flavor_id}" key_pair = "${openstack_compute_keypair_v2.key.name}" security_groups = ["${openstack_compute_secgroup_v2.secgroup.name}"] network { uuid = "${var.network_id}" name = "private" } } resource "openstack_lb_loadbalancer_v2" "lb" { name = “lb_${terraform.workspace}" vip_subnet_id = "${var.subnet_id}" } resource "openstack_networking_floatingip_v2" "floatingip" { pool = "public" port_id = "${openstack_lb_loadbalancer_v2.lb.vip_port_id}" } output "address" { value = "${openstack_networking_floatingip_v2.floatingip.address}" }
  85. resource "openstack_lb_listener_v2" "listener" { name = "listener_${terraform.workspace}" protocol = "HTTP"

    protocol_port = 80 loadbalancer_id = "${openstack_lb_loadbalancer_v2.lb.id}" } resource "openstack_lb_pool_v2" "pool" { name = "pool_${terraform.workspace}" protocol = "HTTP" lb_method = "ROUND_ROBIN" listener_id = "${openstack_lb_listener_v2.listener.id}" } resource "openstack_lb_monitor_v2" "monitor" { name = "monitor_${terraform.workspace}" pool_id = "${openstack_lb_pool_v2.pool.id}" type = "HTTP" url_path = "/" expected_codes = "200" http_method = "GET" delay = 20 timeout = 10 max_retries = 5 depends_on = ["openstack_compute_instance_v2.web_1","openstack_compute_instance_v2.web_2"] }
  86. resource "openstack_lb_member_v2" "web_1_member" { address = "${openstack_compute_instance_v2.web_1.access_ip_v4}" protocol_port = 80

    pool_id = "${openstack_lb_pool_v2.pool.id}" subnet_id = "${var.subnet_id}" depends_on = ["openstack_compute_instance_v2.web_1"] } resource "openstack_lb_member_v2" "web_2_member" { address = "${openstack_compute_instance_v2.web_2.access_ip_v4}" protocol_port = 80 pool_id = "${openstack_lb_pool_v2.pool.id}" subnet_id = "${var.subnet_id}" depends_on = ["openstack_compute_instance_v2.web_2"] }
  87. $ terraform init Loads provider plugins for project

  88. $ terraform providers . $"" provider.openstack

  89. $ terraform plan

  90. None
  91. $ terraform graph | dot -Tpng > graph.png

  92. None
  93. $ terraform apply

  94. delay: "" => "20" expected_codes: "" => "200" http_method: ""

    => "GET" max_retries: "" => "5" name: "" => "monitor_default" pool_id: "" => "36e94ffb-39c9-4741-9fdc-ef2f76f3dc38" region: "" => "<computed>" tenant_id: "" => "<computed>" timeout: "" => "10" type: "" => "HTTP" url_path: "" => "/" openstack_lb_member_v2.web_1_member: Still creating... (10s elapsed) openstack_lb_monitor_v2.monitor: Still creating... (10s elapsed) openstack_lb_member_v2.web_2_member: Still creating... (10s elapsed) openstack_lb_member_v2.web_1_member: Creation complete after 13s (ID: 526c0998- db35-48cc-86e0-012cc315144a) openstack_lb_member_v2.web_2_member: Creation complete after 13s (ID: 9dfd6de0- e731-4209-b134-024df2f246d4) openstack_lb_monitor_v2.monitor: Creation complete after 14s (ID: decbf0fe-30f5-4996-9af9-0e4b7dba2aae) Apply complete! Resources: 5 added, 0 changed, 0 destroyed. Outputs: address = 185.115.216.125
  95. ✓ vars.tf ✓ output.tf ✓ secgroup.tf ✓ keypair.tf ✓ compute.tf

    ✓ network.tf ✓ provider.tf Organize Terraform files
  96. Multiple resources in 1 resource definition

  97. resource "openstack_compute_instance_v2" "master" { count = "${var.servercount}" name = "${format("my-webserver-%02d",

    count.index+1)}" image_name = "${var.image}" flavor_name = "${var.flavor}" network { name = "${var.network}" } security_groups = ["${openstack_networking_secgroup_v2.web.id}"] key_pair = "my_keypair" }
  98. variable "servercount" { default = "3" } variable "image" {

    default = "debian-stretch" } variable "flavor" { default = "m1.medium" } variable "network" { default = "private" } variable "float-pool" { default = "public" }
  99. Remote state

  100. None
  101. Similar to etcd & zookeeper

  102. terraform { backend "consul" { address = "127.0.0.1:8500" path =

    "example/terraform_state" } }
  103. $ terraform init $ terraform workspace new wsp1 $ terraform

    workspace new wsp2 $ terraform workspace select wsp1 $ terraform plan $ terraform apply $ terraform workspace select wsp2 $ terraform plan $ terraform apply
  104. None
  105. Blue/green deployments

  106. Build terraform script to relink floating IP

  107. Or to change the DNS record AWS route53 DYN PowerDNS

    DNSimple DNSMadeEasy UltraDNS Custom DNS service
  108. $ terraform workspace select wsp1 $ terraform destroy

  109. There are way more features in Terraform for AWS, Azure

    & GCP
  110. Hybrid solutions

  111. https://feryn.eu https://twitter.com/ThijsFeryn https://instagram.com/ThijsFeryn

  112. None