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

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

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

Thijs Feryn

October 27, 2017
Tweet

More Decks by Thijs Feryn

Other Decks in Technology

Transcript

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

    with: Packer, Ansible & Terraform
  2. ✓ Less lock-in ✓ Faster deployments ✓ Better scalability ✓

    Better reproducibility ✓ Less human error ✓ Lower cost Goals
  3. Git repo Application Code Build server Servers Commit Build Deploy

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

    Infra scripts VM image Cloud registry Build & provision Orchestrate
  5. ✓ 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
  6. { "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" } ] }
  7. Packer build Openstack API Boot VM using source image Take

    VM snapshot Image registry Store VM snapshot as new image Tear down VM
  8. $ 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 | +--------------------------------------+----------------------------------+--------+
  9. ✓ Written in Python ✓ Agentless ✓ Standalone ✓ SSH-based

    ✓ Runs playbook (yml files) ✓ Groups playbook in roles ✓ Install software ✓ Configure your server Ansible
  10. --- - 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
  11. - 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
  12. . !"" 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
  13. - 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
  14. . !"" 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
  15. [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
  16. 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
  17. . !"" 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
  18. --- 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
  19. . !"" 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
  20. { "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" } ] }
  21. 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
  22. 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
  23. "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" } ] }
  24. { "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" }
  25. $ 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 | +--------------------------------------+----------------------------------+--------+
  26. ✓ Boot up webservers ✓ Configure firewalls ✓ Assign loadbalancers

    ✓ Link floating IP ✓ Assign SSH keys Orchestration
  27. ✓ Infrastructure As Code ✓ Plan, graph, execute ✓ terraform.tf

    file ✓ Multiple providers ✓ Incremental changes Terraform
  28. ✓ 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
  29. 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 [email protected]" }
  30. 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}" }
  31. 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" } }
  32. 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}" }
  33. 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"] }
  34. 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"] }
  35. 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
  36. ✓ vars.tf ✓ output.tf ✓ secgroup.tf ✓ keypair.tf ✓ compute.tf

    ✓ network.tf ✓ provider.tf Organize Terraform files
  37. 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" }
  38. variable "servercount" { default = "3" } variable "image" {

    default = "debian-stretch" } variable "flavor" { default = "m1.medium" } variable "network" { default = "private" } variable "float-pool" { default = "public" }
  39. $ 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
  40. Or to change the DNS record AWS route53 DYN PowerDNS

    DNSimple DNSMadeEasy UltraDNS Custom DNS service