Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Infrastructure as Code with Terraform

98 views

Published on

Infrastructure as Code with Terraform by Mitchell Hashimoto

Terraform enables you to describe almost anything with an API as code. From infrastructure to high level services, Terraform can help construct everything required to run your applications. In this talk, we’ll explore using Terraform to manage both traditional and non-traditional resources and dive into how to write best practices Terraform for reusability within teams.


Published in: Technology
  • Be the first to comment

Infrastructure as Code with Terraform

  1. 1. Copyright © 2018 HashiCorp HashiCorp Infrastructure as Code With Terraform
  2. 2. Copyright © 2018 HashiCorp HashiCorp Everything as Code With Terraform
  3. 3. Copyright © 2018 HashiCorp HashiCorp Infrastructure as Code With Terraform
  4. 4. Copyright © 2018 HashiCorp HashiCorp Infrastructure as Code With Terraform
  5. 5. Maturity
  6. 6. Manual Everything
  7. 7. Basic Automation #!/bin/bash
  8. 8. Machine Virtualization
  9. 9. *aaS
  10. 10. Datacenter-as-Computer
  11. 11. Maturity
  12. 12. Capability Complexity
  13. 13. 475 def update 476 return update_api if api_request? 477 478 if authorized_action(@account, @current_user, :manage_account_settings) 479 respond_to do |format| 480 481 custom_help_links = params[:account].delete :custom_help_links 482 if custom_help_links 483 @account.settings[:custom_help_links] = custom_help_links.select{|k, h| h['state'] != 'delete 484 hash = index_with_hash[1] 485 hash.delete('state') 486 hash.assert_valid_keys ["text", "subtext", "url", "available_to"] 487 hash 488 end 489 end 490 491 params[:account][:turnitin_host] = validated_turnitin_host(params[:account][:turnitin_host]) 492 enable_user_notes = params[:account].delete :enable_user_notes 493 allow_sis_import = params[:account].delete :allow_sis_import 494 params[:account].delete :default_user_storage_quota_mb unless @account.root_account? && !@accou 495 unless @account.grants_right? @current_user, :manage_storage_quotas 496 [:storage_quota, :default_storage_quota, :default_storage_quota_mb, 497 :default_user_storage_quota, :default_user_storage_quota_mb, 498 :default_group_storage_quota, :default_group_storage_quota_mb].each { |key| params[:account] 499 end 500 if params[:account][:services] 501 params[:account][:services].slice(*Account.services_exposed_to_ui_hash(nil, @current_user, @a 502 @account.set_service_availability(key, value == '1') 503 end 504 params[:account].delete :services 505 end 506 if @account.grants_right?(@current_user, :manage_site_settings) 507 # If the setting is present (update is called from 2 different settings forms, one for notifi 508 if params[:account][:settings] && params[:account][:settings][:outgoing_email_default_name_op 509 # If set to default, remove the custom name so it doesn't get saved 510 params[:account][:settings][:outgoing_email_default_name] = '' if params[:account][:setting 511 end 512 513 google_docs_domain = params[:account][:settings].try(:delete, :google_docs_domain) 514 if @account.feature_enabled?(:google_docs_domain_restriction) && 515 @account.root_account? && 516 !@account.site_admin? 517 @account.settings[:google_docs_domain] = google_docs_domain.present? ? google_docs_domain : 518 end 519 520 @account.enable_user_notes = enable_user_notes if enable_user_notes 521 @account.allow_sis_import = allow_sis_import if allow_sis_import && @account.root_account? 522 if @account.site_admin? && params[:account][:settings] 523 # these shouldn't get set for the site admin account 524 params[:account][:settings].delete(:enable_alerts) 525 params[:account][:settings].delete(:enable_eportfolios) 526 end 527 else 528 # must have :manage_site_settings to update these 529 [ :admins_can_change_passwords, 530 :admins_can_view_notifications, 531 :enable_alerts, 532 :enable_eportfolios, 533 :enable_profiles, 534 :show_scheduler, 535 :global_includes, 536 :gmail_domain 537 ].each do |key| 538 params[:account][:settings].try(:delete, key)
  14. 14. 475 def update 476 return update_api if api_request? 477 478 if authorized_action(@account, @current_user, :manage_account_settings) 479 respond_to do |format| 480 481 custom_help_links = params[:account].delete :custom_help_links 482 if custom_help_links 483 @account.settings[:custom_help_links] = custom_help_links.select{|k, h| h['state'] != 'delete 484 hash = index_with_hash[1] 485 hash.delete('state') 486 hash.assert_valid_keys ["text", "subtext", "url", "available_to"] 487 hash 488 end 489 end 490 491 params[:account][:turnitin_host] = validated_turnitin_host(params[:account][:turnitin_host]) 492 enable_user_notes = params[:account].delete :enable_user_notes 493 allow_sis_import = params[:account].delete :allow_sis_import 494 params[:account].delete :default_user_storage_quota_mb unless @account.root_account? && !@accou 495 unless @account.grants_right? @current_user, :manage_storage_quotas 496 [:storage_quota, :default_storage_quota, :default_storage_quota_mb, 497 :default_user_storage_quota, :default_user_storage_quota_mb, 498 :default_group_storage_quota, :default_group_storage_quota_mb].each { |key| params[:account] 499 end 500 if params[:account][:services] 501 params[:account][:services].slice(*Account.services_exposed_to_ui_hash(nil, @current_user, @a 502 @account.set_service_availability(key, value == '1') 503 end 504 params[:account].delete :services 505 end 506 if @account.grants_right?(@current_user, :manage_site_settings) 507 # If the setting is present (update is called from 2 different settings forms, one for notifi 508 if params[:account][:settings] && params[:account][:settings][:outgoing_email_default_name_op 509 # If set to default, remove the custom name so it doesn't get saved 510 params[:account][:settings][:outgoing_email_default_name] = '' if params[:account][:setting 511 end 512 513 google_docs_domain = params[:account][:settings].try(:delete, :google_docs_domain) 514 if @account.feature_enabled?(:google_docs_domain_restriction) && 515 @account.root_account? && 516 !@account.site_admin? 517 @account.settings[:google_docs_domain] = google_docs_domain.present? ? google_docs_domain : 518 end 519 520 @account.enable_user_notes = enable_user_notes if enable_user_notes 521 @account.allow_sis_import = allow_sis_import if allow_sis_import && @account.root_account? 522 if @account.site_admin? && params[:account][:settings] 523 # these shouldn't get set for the site admin account 524 params[:account][:settings].delete(:enable_alerts) 525 params[:account][:settings].delete(:enable_eportfolios) 526 end 527 else 528 # must have :manage_site_settings to update these 529 [ :admins_can_change_passwords, 530 :admins_can_view_notifications, 531 :enable_alerts, 532 :enable_eportfolios, 533 :enable_profiles, 534 :show_scheduler, 535 :global_includes, 536 :gmail_domain 537 ].each do |key| 538 params[:account][:settings].try(:delete, key) Infrastructure as Code
  15. 15. 475 def update 476 return update_api if api_request? 477 478 if authorized_action(@account, @current_user, :manage_account_settings) 479 respond_to do |format| 480 481 custom_help_links = params[:account].delete :custom_help_links 482 if custom_help_links 483 @account.settings[:custom_help_links] = custom_help_links.select{|k, h| h['state'] != 'delete 484 hash = index_with_hash[1] 485 hash.delete('state') 486 hash.assert_valid_keys ["text", "subtext", "url", "available_to"] 487 hash 488 end 489 end 490 491 params[:account][:turnitin_host] = validated_turnitin_host(params[:account][:turnitin_host]) 492 enable_user_notes = params[:account].delete :enable_user_notes 493 allow_sis_import = params[:account].delete :allow_sis_import 494 params[:account].delete :default_user_storage_quota_mb unless @account.root_account? && !@accou 495 unless @account.grants_right? @current_user, :manage_storage_quotas 496 [:storage_quota, :default_storage_quota, :default_storage_quota_mb, 497 :default_user_storage_quota, :default_user_storage_quota_mb, 498 :default_group_storage_quota, :default_group_storage_quota_mb].each { |key| params[:account] 499 end 500 if params[:account][:services] 501 params[:account][:services].slice(*Account.services_exposed_to_ui_hash(nil, @current_user, @a 502 @account.set_service_availability(key, value == '1') 503 end 504 params[:account].delete :services 505 end 506 if @account.grants_right?(@current_user, :manage_site_settings) 507 # If the setting is present (update is called from 2 different settings forms, one for notifi 508 if params[:account][:settings] && params[:account][:settings][:outgoing_email_default_name_op 509 # If set to default, remove the custom name so it doesn't get saved 510 params[:account][:settings][:outgoing_email_default_name] = '' if params[:account][:setting 511 end 512 513 google_docs_domain = params[:account][:settings].try(:delete, :google_docs_domain) 514 if @account.feature_enabled?(:google_docs_domain_restriction) && 515 @account.root_account? && 516 !@account.site_admin? 517 @account.settings[:google_docs_domain] = google_docs_domain.present? ? google_docs_domain : 518 end 519 520 @account.enable_user_notes = enable_user_notes if enable_user_notes 521 @account.allow_sis_import = allow_sis_import if allow_sis_import && @account.root_account? 522 if @account.site_admin? && params[:account][:settings] 523 # these shouldn't get set for the site admin account 524 params[:account][:settings].delete(:enable_alerts) 525 params[:account][:settings].delete(:enable_eportfolios) 526 end 527 else 528 # must have :manage_site_settings to update these 529 [ :admins_can_change_passwords, 530 :admins_can_view_notifications, 531 :enable_alerts, 532 :enable_eportfolios, 533 :enable_profiles, 534 :show_scheduler, 535 :global_includes, 536 :gmail_domain 537 ].each do |key| 538 params[:account][:settings].try(:delete, key)
  16. 16. Infrastructure as Code Source of knowledge Supports versioning, collaboration Executable
  17. 17. 475 def update 476 return update_api if api_request? 477 478 if authorized_action(@account, @current_user, :manage_account_settings) 479 respond_to do |format| 480 481 custom_help_links = params[:account].delete :custom_help_links 482 if custom_help_links 483 @account.settings[:custom_help_links] = custom_help_links.select{|k, h| h['state'] != 'delete 484 hash = index_with_hash[1] 485 hash.delete('state') 486 hash.assert_valid_keys ["text", "subtext", "url", "available_to"] 487 hash 488 end 489 end 490 491 params[:account][:turnitin_host] = validated_turnitin_host(params[:account][:turnitin_host]) 492 enable_user_notes = params[:account].delete :enable_user_notes 493 allow_sis_import = params[:account].delete :allow_sis_import 494 params[:account].delete :default_user_storage_quota_mb unless @account.root_account? && !@accou 495 unless @account.grants_right? @current_user, :manage_storage_quotas 496 [:storage_quota, :default_storage_quota, :default_storage_quota_mb, 497 :default_user_storage_quota, :default_user_storage_quota_mb, 498 :default_group_storage_quota, :default_group_storage_quota_mb].each { |key| params[:account] 499 end 500 if params[:account][:services] 501 params[:account][:services].slice(*Account.services_exposed_to_ui_hash(nil, @current_user, @a 502 @account.set_service_availability(key, value == '1') 503 end 504 params[:account].delete :services 505 end 506 if @account.grants_right?(@current_user, :manage_site_settings) 507 # If the setting is present (update is called from 2 different settings forms, one for notifi 508 if params[:account][:settings] && params[:account][:settings][:outgoing_email_default_name_op 509 # If set to default, remove the custom name so it doesn't get saved 510 params[:account][:settings][:outgoing_email_default_name] = '' if params[:account][:setting 511 end 512 513 google_docs_domain = params[:account][:settings].try(:delete, :google_docs_domain) 514 if @account.feature_enabled?(:google_docs_domain_restriction) && 515 @account.root_account? && 516 !@account.site_admin? 517 @account.settings[:google_docs_domain] = google_docs_domain.present? ? google_docs_domain : 518 end 519 520 @account.enable_user_notes = enable_user_notes if enable_user_notes 521 @account.allow_sis_import = allow_sis_import if allow_sis_import && @account.root_account? 522 if @account.site_admin? && params[:account][:settings] 523 # these shouldn't get set for the site admin account 524 params[:account][:settings].delete(:enable_alerts) 525 params[:account][:settings].delete(:enable_eportfolios) 526 end 527 else 528 # must have :manage_site_settings to update these 529 [ :admins_can_change_passwords, 530 :admins_can_view_notifications, 531 :enable_alerts, 532 :enable_eportfolios, 533 :enable_profiles, 534 :show_scheduler, 535 :global_includes, 536 :gmail_domain 537 ].each do |key| 538 params[:account][:settings].try(:delete, key)
  18. 18. Copyright © 2018 HashiCorp HashiCorp Infrastructure as Code With Terraform
  19. 19. Terraform Tool to manage anything with an API as code Open source (> 12,000 stars, > 1,000 contributors) Support for over 100 providers, including all major clouds: AWS, Azure, GCP, and more.
  20. 20. Goals Unify the view of resources using infra as code Expose a way for individuals and teams to safely and predictably change infrastructure Manage anything with an API
  21. 21. resource "aws_instance" "web" { ami = "ami-b123125" } resource "dnsimple_record" "hello" { domain = "example.com" name = "test" value = "${aws_instance.web.public_ip}" type = "A" } C O D E E D I T O R
  22. 22. resource "aws_instance" "web" { ami = "ami-b123125" } resource "dnsimple_record" "hello" { domain = "example.com" name = "test" value = "${aws_instance.web.public_ip}" type = "A" } C O D E E D I T O R
  23. 23. resource "aws_instance" "web" { ami = "ami-b123125" } resource "dnsimple_record" "hello" { domain = "example.com" name = "test" value = "${aws_instance.web.public_ip}" type = "A" } C O D E E D I T O R
  24. 24. resource "aws_instance" "web" { ami = "ami-b123125" } resource "dnsimple_record" "hello" { domain = "example.com" name = "test" value = "${aws_instance.web.public_ip}" type = "A" } C O D E E D I T O R
  25. 25. Configuration Declarative: what you want, vs how to do it Human-friendly, JSON compatible Versionable, VCS-friendly
  26. 26. resource "aws_instance" "web" { - ami = "ami-b123125" + ami = "ami-c345123" } resource "dnsimple_record" "hello" { domain = "example.com" name = "test" value = "${aws_instance.web.public_ip}" type = "A" } C O D E E D I T O R
  27. 27. Command: terraform plan The plan shows you what will happen You can save plans to guarantee what will happen Plans show reasons for certain actions (such as re- create) Prior to Terraform, users had to guess change ordering, parallelization, and rollout effect
  28. 28. Command: terraform plan + resource will be created - resource will be destroyed ~ resource will be updated in-place -/+ resources will be destroyed and re-created
  29. 29. T E R M I N A L $ terraform plan + aws_instance.web ami: "ami-db24d8b6" availability_zone: "<computed>" ebs_block_device.#: "<computed>" ephemeral_block_device.#: "<computed>" instance_state: "<computed>" instance_type: "t2.micro" key_name: "<computed>" network_interface_id: "<computed>" placement_group: "<computed>" private_dns: "<computed>" private_ip: "<computed>" public_dns: "<computed>" public_ip: "<computed>" # ...
  30. 30. Command: terraform apply Executes changes in order based on the resource graph Parallelizes changes when possible Handles and recovers transient errors Runs plan first unless given a serialized plan file
  31. 31. T E R M I N A L $ terraform apply aws_instance.web: Creating... ami: "" => "ami-db24d8b6" # ... source_dest_check: "" => "true" subnet_id: "" => "subnet-f6e6a5dc" aws_instance.web: Still creating... (10s elapsed) aws_instance.web: Still creating... (20s elapsed) aws_instance.web: Creation complete Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
  32. 32. A Workflow for Safe Infrastructure "terraform apply" The make things happen command (after confirm) Tell Terraform what you want, sit back and let Terraform make it a reality.
  33. 33. Modules Reusable packages of Terraform configuration Enable broader usage of Terraform Share and discover: https://registry.terraform.io/
  34. 34. module "consul" { source = "hashicorp/consul/aws" version = "~> 0.3.0" } output "servers" { value = "${module.consul.asg_name_servers} } C O D E E D I T O R
  35. 35. Copyright © 2018 HashiCorp HashiCorp Everything as Code With Terraform
  36. 36. Providers Integration point Expose resources, such as "aws_instance" Can be created for anything with an API Easy to write, great beginner Go project
  37. 37. resource "TYPE" "NAME" { CONFIG } C O D E E D I T O R
  38. 38. resource "TYPE" "NAME" { CONFIG } C O D E E D I T O R
  39. 39. Terraform's Internals Core Providers Upstream APIsPlugins
  40. 40. Terraform's Internals Core Providers Upstream APIsPlugins Diff() Apply() Refresh() Create() Read() Update() Delete()
  41. 41. Terraform's Internals Core Providers Upstream APIsPlugins Diff() Apply() Refresh() Create() Read() Update() Delete()
  42. 42. Terraform's Internals Config: Target Reality State: Current Reality Diff: {Config - State} Plan: Presents Diff Apply: Resolves Diff
  43. 43. FOR
 EVERY THING Example: PagerDuty On-call schedules as code Notifications as code Manage PagerDuty schedules, teams, and users
 as code!
  44. 44. resource "pagerduty_team" "engineering" { name = "Engineering" description = "All engineering" } resource "pagerduty_user" "earline" { name = "Earline Greenholt" email = "125.greenholt.earline@graham.name" teams = ["${pagerduty_team.engineering.id}"] } C O D E E D I T O R
  45. 45. resource "pagerduty_schedule" "foo" { name = "Daily Engineering Rotation" time_zone = "America/New_York" layer { name = "Night Shift" ... users = ["${pagerduty_user.earline.id}"] restriction { type = "daily_restriction" start_time_of_day = "08:00:00" duration_seconds = 32400 } } } C O D E E D I T O R
  46. 46. FOR
 EVERY THING Example: DataDog Metrics as code Notifications as code Manage DataDog users, monitors, and timeboards
 as code.
  47. 47. resource "datadog_timeboard" "redis" { title = "Redis Timeboard (created via Terraform)" read_only = true graph { title = "Redis latency (ms)" viz = "timeseries" request { q = "avg:redis.info.latency_ms{$host}" type = "bars" } } } C O D E E D I T O R
  48. 48. resource "datadog_monitor" "foo" { name = "Name for monitor foo" type = "metric alert" message = "Monitor triggered. Notify: @hipchat-channel" escalation_message = "Escalation message @pagerduty" query = "avg(last_1h):avg:cpu{host:foo} by {host} > 4" thresholds { ok = 0 warning = 2 warning_recovery = 1 critical = 4 critical_recovery = 3 } C O D E E D I T O R
  49. 49. FOR
 EVERY THING Example: GitHub Organization and teams as code Issue labels as code Etc. HashiCorp uses this to manage provider repositories!
  50. 50. variable "issue_labels" { default = { "custom-label" = "533D99" "documentation" = "FFB340" "waiting-reply" = "CC6A14" } } resource "github_issue_label" "test_repo" { repository = "${github_repository.example-repo.id}" count = "${length(var.issue_labels)}" name = "${element(keys(var.issue_labels), count.index)}" color = "${element(values(var.issue_labels), count.index)}" } C O D E E D I T O R
  51. 51. Learn More All Providers
 terraform.io/docs/providers Extending Terraform
 terraform.io/docs/extend Provider Development Program
 terraform.io/guides/
 terraform-provider-development-program.html
  52. 52. Thank You.

×