191
Untangling Infrastructure Code Through Refactoring DevOpsDays Chicago @nellshamrell

Refactoring Infrastructure Code

Embed Size (px)

Citation preview

Page 1: Refactoring Infrastructure Code

Untangling InfrastructureCode Through Refactoring

DevOpsDays Chicago

@nellshamrell

Page 2: Refactoring Infrastructure Code

Who Am I?

Nell Shamrell-Harrington• Software Engineer at Chef

• @nellshamrell• [email protected]

• Former O’Fallon, IL resident (Southern IL ftw!)

Page 3: Refactoring Infrastructure Code

Why should I refactor my infrastructure code?

Page 4: Refactoring Infrastructure Code

Why Refactor?

• Add a feature

Page 5: Refactoring Infrastructure Code

• Add a feature • Fix a bug

Why Refactor?

Page 6: Refactoring Infrastructure Code

• Add a feature • Fix a bug • Improve the design

Why Refactor?

Page 7: Refactoring Infrastructure Code

• Add a feature • Fix a bug • Improve the design • Optimize resource usage

Why Refactor?

Page 8: Refactoring Infrastructure Code

Let’s go through an example!

Page 9: Refactoring Infrastructure Code

Refactoring a Terraform config

Page 10: Refactoring Infrastructure Code

Today, we will refactor supermarket-terraform

http://github.com/nellshamrell/supermarket-terraform

supermarket-terraform

Page 11: Refactoring Infrastructure Code

supermarket-cluster.tf

provider  “aws”  {      access_key  =  “${var.access_key}”      secret_key  =  “${var.secret_key}”  }

supermarket-terraform

Page 12: Refactoring Infrastructure Code

supermarket-cluster.tf

provider  “aws”  {      access_key  =  “${var.access_key}”      secret_key  =  “${var.secret_key}”  }

supermarket-terraform

Page 13: Refactoring Infrastructure Code

supermarket-cluster.tf

variables.tf

provider  “aws”  {      access_key  =  “${var.access_key}”      secret_key  =  “${var.secret_key}”  }

variable  “access_key”  =  {}    variable  “secret_key”  =  {}

supermarket-terraform

Page 14: Refactoring Infrastructure Code

supermarket-cluster.tf

variables.tf

terraform.tfvars

provider  “aws”  {      access_key  =  “${var.access_key}”      secret_key  =  “${var.secret_key}”  }

variable  “access_key”  =  {}    variable  “secret_key”  =  {}

access_key  =  “xxxxx”    secret_key  =  “xxxxx”

supermarket-terraform

Page 15: Refactoring Infrastructure Code

Wait…should you use a credentials file?

Page 16: Refactoring Infrastructure Code

You can…but for the sake of the example, let’s supply them inline

Page 17: Refactoring Infrastructure Code

supermarket-cluster.tf

provider  “aws”  {      access_key  =  “${var.access_key}”      secret_key  =  “${var.secret_key}”  }

supermarket-terraform

Page 18: Refactoring Infrastructure Code

$ terraform apply

supermarket-cluster.tf

Page 19: Refactoring Infrastructure Code

Security Group

$ terraform apply

supermarket-cluster.tf

Page 20: Refactoring Infrastructure Code

Security Group

Allow SSH

$ terraform apply

supermarket-cluster.tf

Page 21: Refactoring Infrastructure Code

Security Group

Security Group

Allow SSH

$ terraform apply

supermarket-cluster.tf

Page 22: Refactoring Infrastructure Code

Security Group

Security Group

Allow SSH Allow Out

$ terraform apply

supermarket-cluster.tf

Page 23: Refactoring Infrastructure Code

Security Group

Security Group

EC2

Allow SSH Allow Out

$ terraform apply

supermarket-cluster.tf

Page 24: Refactoring Infrastructure Code

Security Group

Security Group

EC2Chef Server

Allow SSH Allow Out

$ terraform apply

supermarket-cluster.tf

Page 25: Refactoring Infrastructure Code

EC2

Security Group

Security Group

EC2Chef Server

Allow OutAllow SSH

$ terraform apply

supermarket-cluster.tf

Page 26: Refactoring Infrastructure Code

EC2

Security Group

Security Group

EC2Chef Server Supermarket

Allow OutAllow SSH

$ terraform apply

supermarket-cluster.tf

Page 27: Refactoring Infrastructure Code

EC2

Security Group

Security Group

EC2Chef Server Supermarket

Allow OutAllow SSH

$ terraform apply

supermarket-cluster.tf

Page 28: Refactoring Infrastructure Code

EC2

Security Group

Security Group

EC2Chef Server Supermarket

Allow OutAllow SSH

$ terraform apply

supermarket-cluster.tf

Page 29: Refactoring Infrastructure Code

EC2

Security Group

Security Group

EC2Chef Server Supermarket

Allow OutAllow SSH

$ terraform apply

supermarket-cluster.tf

Page 30: Refactoring Infrastructure Code

EC2

Security Group

Security Group

EC2Chef Server Supermarket

Allow OutAllow SSH

$ terraform apply

supermarket-cluster.tf

Page 31: Refactoring Infrastructure Code

Hypothetical: We are using too many AWS Security Groups

Why Refactor?

Page 32: Refactoring Infrastructure Code

We need this config to create only

one security group

Why Refactor?

Page 33: Refactoring Infrastructure Code

Source: Working Effectively with Legacy Code

How to Refactor?Two Approaches

Page 34: Refactoring Infrastructure Code

Source: Working Effectively with Legacy Code

How to Refactor?

• Edit and PrayTwo Approaches

Page 35: Refactoring Infrastructure Code

• Edit and Pray• Cover and Modify

Two Approaches

Source: Working Effectively with Legacy Code

How to Refactor?

Page 36: Refactoring Infrastructure Code

Confidence in code without tests is

false confidence

Page 37: Refactoring Infrastructure Code

What code is intended to do is much less important

than what it actually does

Page 38: Refactoring Infrastructure Code

Do I have to add tests for the entire thing?

Page 39: Refactoring Infrastructure Code

No.

Page 40: Refactoring Infrastructure Code

The point of adding tests is to not make things worse

Page 41: Refactoring Infrastructure Code

And to start making the code better here and now

Page 42: Refactoring Infrastructure Code

How can we test Terraform?

Page 43: Refactoring Infrastructure Code

How Can We Test Terraform?• Test Kitchen (http://kitchen.ci)

Page 44: Refactoring Infrastructure Code

How Can We Test Terraform?• Test Kitchen (http://kitchen.ci)

• Kitchen Terraform (http://github.com/newcontext/ kitchen-terraform)

Page 45: Refactoring Infrastructure Code

driver:      name:  terraform  

provisioner:      name:  terraform      variable_files:  terraform.@vars  

transport:        name:  ssh      ssh_key:  ~/path/to/your/aws/key  

pla@orms:      -­‐  name:  ubuntu  

suites      -­‐  name:  default  

.kitchen.yml

Page 46: Refactoring Infrastructure Code

driver:      name:  terraform  

provisioner:      name:  terraform      variable_files:  terraform.@vars  

transport:        name:  ssh      ssh_key:  ~/path/to/your/aws/key  

pla@orms:      -­‐  name:  ubuntu  

suites      -­‐  name:  default  

.kitchen.yml

Page 47: Refactoring Infrastructure Code

driver:      name:  terraform  

provisioner:      name:  terraform      variable_files:  terraform.5vars  

transport:        name:  ssh      ssh_key:  ~/path/to/your/aws/key  

pla@orms:      -­‐  name:  ubuntu  

suites      -­‐  name:  default  

.kitchen.yml

Page 48: Refactoring Infrastructure Code

driver:      name:  terraform  

provisioner:      name:  terraform      variable_files:  terraform.5vars  

transport:        name:  ssh      ssh_key:  ~/path/to/your/aws/key  

pla@orms:      -­‐  name:  ubuntu  

suites      -­‐  name:  default  

.kitchen.yml

Page 49: Refactoring Infrastructure Code

driver:      name:  terraform  

provisioner:      name:  terraform      variable_files:  terraform.@vars  

transport:        name:  ssh      ssh_key:  ~/path/to/your/aws/key  

pla@orms:      -­‐  name:  ubuntu  

suites      -­‐  name:  default  

.kitchen.yml

Page 50: Refactoring Infrastructure Code

.kitchen.ymldriver:      name:  terraform  

provisioner:      name:  terraform      variable_files:  terraform.@vars  

transport:        name:  ssh      ssh_key:  ~/path/to/your/aws/key  

pla5orms:      -­‐  name:  ubuntu  

suites      -­‐  name:  default  

Test KitchenBoilerplate

Page 51: Refactoring Infrastructure Code

• Test Kitchen (http://kitchen.ci)

• Kitchen Terraform (http://github.com/newcontext/ kitchen-terraform)

• Inspec (http://chef.io/inspec)

How Can We Test Terraform?

Page 52: Refactoring Infrastructure Code

Now that we have our tools…

Page 53: Refactoring Infrastructure Code

Let’s start from a clean slate

Page 54: Refactoring Infrastructure Code

#provider  “aws”  {  #    access_key  =  “${var.access_key}”  #    secret_key  =  “${var.secret_key}”  #    region  =  “${var.region}”  #}  

#resource  “aws_security_group”  “allow-­‐ssh”  {  #    name  =  “${var.user_name}-­‐allow-­‐ssh”  #    tags  =  {  #        Name  =  “${var.user_name}  Allow  All  SSH”  #    }  …

supermarket-cluster.tf

Page 55: Refactoring Infrastructure Code

$ terraform apply

supermarket-cluster.tf

Page 56: Refactoring Infrastructure Code

$ terraform apply

Nothing happens!

supermarket-cluster.tf

Page 57: Refactoring Infrastructure Code

First, we need the provider

Page 58: Refactoring Infrastructure Code

provider  “aws”  {      access_key  =  “${var.access_key}”      secret_key  =  “${var.secret_key}”      region  =  “${var.region}”  }  

#resource  “aws_security_group”  “allow-­‐ssh”  {  #    name  =  “${var.user_name}-­‐allow-­‐ssh”  #    tags  =  {  #        Name  =  “${var.user_name}  Allow  All  SSH”  #    }  …

supermarket-cluster.tf

Page 59: Refactoring Infrastructure Code

We also need actual AWS instances

Page 60: Refactoring Infrastructure Code

Including both our Chef Server…

Page 61: Refactoring Infrastructure Code

…  #resource  “aws_instance”  “chef_server”  {  #    ami  =  “${var.ami}”  #    instance_type  =  “${var.instance_type}”  #    key_name  =  “${var.key_name}”  #    tags  {  #        Name  =  “dev-­‐chef-­‐server”  #    }  #    security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”,  #                                                                        “${aws_security_group.allow-­‐out.name}”]  #    (…)  #}  …

This is the Chef Server

supermarket-cluster.tf

Page 62: Refactoring Infrastructure Code

…  resource  “aws_instance”  “chef_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐chef-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”,                                                                          “${aws_security_group.allow-­‐out.name}”]      (…)  }  …

This is the Chef Server

supermarket-cluster.tf

Page 63: Refactoring Infrastructure Code

$ terraform apply

supermarket-cluster.tf

Page 64: Refactoring Infrastructure Code

EC2Chef Server $ terraform apply

supermarket-cluster.tf

Page 65: Refactoring Infrastructure Code

And our supermarketserver

Page 66: Refactoring Infrastructure Code

…  #resource  “aws_instance”  “supermarket_server”  {  #    ami  =  “${var.ami}”  #    instance_type  =  “${var.instance_type}”  #    key_name  =  “${var.key_name}”  #    tags  {  #        Name  =  “dev-­‐supermarket-­‐server”  #    }  #    security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”,  #                                                                        “${aws_security_group.allow-­‐out.name}”]  #    (…)  #}  …

This is the Supermarket

Server

supermarket-cluster.tf

Page 67: Refactoring Infrastructure Code

…  resource  “aws_instance”  “supermarket_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐supermarket-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”,                                                                          “${aws_security_group.allow-­‐out.name}”]      (…)  }  …

This is the Supermarket

Server

supermarket-cluster.tf

Page 68: Refactoring Infrastructure Code

EC2Chef Server $ terraform apply

EC2 Supermarket

supermarket-cluster.tf

Page 69: Refactoring Infrastructure Code

We also need at least one security group

Page 70: Refactoring Infrastructure Code

#resource  “aws_security_group”  “allow-­‐ssh”  {  #    name  =  “${var.user_name}-­‐allow-­‐ssh”  #    tags  =  {  #        Name  =  “${var.user_name}  Allow  all  ssh”  #    }  #}  

#resource  “aws_security_group_rule”  “allow-­‐ssh”  {  #    type  =  “ingress”  #    from_port  =  22  #    to_port  =  22      …  #}

supermarket-cluster.tf

Page 71: Refactoring Infrastructure Code

resource  “aws_security_group”  “allow-­‐ssh”  {      name  =  “${var.user_name}-­‐allow-­‐ssh”      tags  =  {          Name  =  “${var.user_name}  Allow  all  ssh”      }  }  

#resource  “aws_security_group_rule”  “allow-­‐ssh”  {  #    type  =  “ingress”  #    from_port  =  22  #    to_port  =  22      …  #}

supermarket-cluster.tf

Page 72: Refactoring Infrastructure Code

EC2Chef Server

supermarket-cluster.tf

$ terraform applyEC2

Supermarket

Security Group

Page 73: Refactoring Infrastructure Code

kitchen-terraform needs to be able to

ssh into our instances

Page 74: Refactoring Infrastructure Code

We need a security group rule

Page 75: Refactoring Infrastructure Code

resource  “aws_security_group”  “allow-­‐ssh”  {      name  =  “${var.user_name}-­‐allow-­‐ssh”      tags  =  {          Name  =  “${var.user_name}  Allow  all  ssh”      }  }  

#resource  “aws_security_group_rule”  “allow-­‐ssh”  {  #  type  =  “ingress”  #  from_port  =  22  #  to_port  =  22      …  #}

supermarket-cluster.tf

Page 76: Refactoring Infrastructure Code

resource  “aws_security_group”  “allow-­‐ssh”  {      name  =  “${var.user_name}-­‐allow-­‐ssh”      tags  =  {          Name  =  “${var.user_name}  Allow  all  ssh”      }  }  

resource  “aws_security_group_rule”  “allow-­‐ssh”  {    type  =  “ingress”    from_port  =  22    to_port  =  22      …  }

supermarket-cluster.tf

Page 77: Refactoring Infrastructure Code

EC2Chef Server $ terraform apply

EC2 Supermarket

Security Group

Allow SSH

supermarket-cluster.tf

Page 78: Refactoring Infrastructure Code

Now, let’s create our test cluster

Page 79: Refactoring Infrastructure Code

$ kitchen converge

Page 80: Refactoring Infrastructure Code

Like running terraform apply

$ kitchen converge

Page 81: Refactoring Infrastructure Code

$ kitchen converge (…) >>>>>> ------Exception------- >>>>>> Class: Kitchen::ActionFailed >>>>>> Message: 1 actions failed. >>>>>> Converge failed on instance <default-ubuntu>. >>>>>> Please see .kitchen/logs/kitchen.log for more details

Page 82: Refactoring Infrastructure Code

-­‐-­‐-­‐-­‐  Begin  output  of  terraform  validate    /root/supermarket-­‐terraform-­‐2  -­‐-­‐-­‐-­‐  STDOUT:  STDERR:  ^[[31mError  validaWng:  2  error(s)  occurred:  

* resource  'aws_instance.chef_server'  config:  unknown  resource    * 'aws_security_group.allow-­‐egress'  referenced  in    * variable  aws_security_group.allow-­‐egress.name  * resource  'aws_instance.supermarket_server'  config:    * unknown  resource  'aws_security_group.allow-­‐egress'    * referenced  in  variable  aws_security_group.allow-­‐egress.  * name

.kitchen/logs/default-ubuntu.log

Page 83: Refactoring Infrastructure Code

We need our EC2 resources to reference only one

security group

Page 84: Refactoring Infrastructure Code

Let’s look at the Chef Server

Page 85: Refactoring Infrastructure Code

…  resource  “aws_instance”  “chef_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐chef-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”,                                                                          “${aws_security_group.allow-­‐out.name}”]      (…)  }  …

This is the Chef Server

supermarket-cluster.tf

Page 86: Refactoring Infrastructure Code

…  resource  “aws_instance”  “chef_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐chef-­‐server”      }        security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”]      (…)  }  …

This is the Chef Server

supermarket-cluster.tf

Page 87: Refactoring Infrastructure Code

And the Supermarket Server

Page 88: Refactoring Infrastructure Code

…  resource  “aws_instance”  “supermarket_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐supermarket-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”,                                                                          “${aws_security_group.allow-­‐out.name}”]      (…)  }  …

This is the Supermarket

Server

supermarket-cluster.tf

Page 89: Refactoring Infrastructure Code

…  resource  “aws_instance”  “supermarket_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐supermarket-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”      

   (…)  }  …

This is the Supermarket

Server

supermarket-cluster.tf

Page 90: Refactoring Infrastructure Code

$ kitchen converge$ kitchen converge

Page 91: Refactoring Infrastructure Code

$ kitchen converge Apply complete! Resources: 2 added, 0 changed, 0 destroyed. (…) Finished converging <default-ubuntu> (0m7.10s).

Page 92: Refactoring Infrastructure Code

EC2Chef Server

EC2 Supermarket

Security Group

Allow SSH

supermarket-cluster.tf

$ terraform apply

Page 93: Refactoring Infrastructure Code

Now let’s write some tests

Page 94: Refactoring Infrastructure Code

First, let’s define a test group

Page 95: Refactoring Infrastructure Code

driver:      name:  terraform      (…)  

verifier:      name:  terraform      format:  doc      groups:      -­‐  name:  default          tests:                -­‐  security_groups          hostnames:  aws_hostnames          username:  ubuntu

.kitchen.yml

Page 96: Refactoring Infrastructure Code

driver:      name:  terraform      (…)  

verifier:      name:  terraform      format:  doc      groups:      -­‐  name:  default          tests:                -­‐  security_groups          hostnames:  aws_hostnames          username:  ubuntu

.kitchen.yml

Page 97: Refactoring Infrastructure Code

driver:      name:  terraform      (…)  

verifier:      name:  terraform      format:  doc      groups:      -­‐  name:  default          tests:                -­‐  security_groups          hostnames:  aws_hostnames          username:  ubuntu

.kitchen.yml

Page 98: Refactoring Infrastructure Code

driver:      name:  terraform      (…)  

verifier:      name:  terraform      format:  doc      groups:      -­‐  name:  default          tests:                -­‐  security_groups          hostnames:  aws_hostnames          username:  ubuntu

.kitchen.yml

Page 99: Refactoring Infrastructure Code

Output variable

driver:      name:  terraform      (…)  

verifier:      name:  terraform      format:  doc      groups:      -­‐  name:  default          tests:                -­‐  security_groups          hostnames:  aws_hostnames          username:  ubuntu

.kitchen.yml

Page 100: Refactoring Infrastructure Code

We need to create that output variable

Page 101: Refactoring Infrastructure Code

output  “aws_hostnames”  {  

}

outputs.tf

Page 102: Refactoring Infrastructure Code

output  “aws_hostnames”  {      value  =  “${aws_instance.chef_server.public_dns},                                      ${aws_instance.supermarket_server.public_dns}”  }

outputs.tf

Page 103: Refactoring Infrastructure Code

output  “aws_hostnames”  {      value  =  “${aws_instance.chef_server.public_dns},                                      ${aws_instance.supermarket_server.public_dns}”  }

outputs.tf

Page 104: Refactoring Infrastructure Code

Spins up AWS resources

Using Outputs

Page 105: Refactoring Infrastructure Code

Spins up AWS resources

Captures public_dns of EC2 instances

in aws_hostnames

Using Outputs

Page 106: Refactoring Infrastructure Code

Spins up AWS resources

Passes to kitchen-terraform

Captures public_dns of EC2 instances

in aws_hostnames

Using Outputs

Page 107: Refactoring Infrastructure Code

Spins up AWS resources

Captures public_dns of EC2 instances

in aws_hostnames

Passes to kitchen-terraform

kitchen-terraform uses aws_hostnames

to ssh into the instances

Using Outputs

Page 108: Refactoring Infrastructure Code

Spins up AWS resources

Runs Tests

kitchen-terraform uses aws_hostnames

to ssh into the instances

Captures public_dns of EC2 instances

in aws_hostnames

Passes to kitchen-terraform

Using Outputs

Page 109: Refactoring Infrastructure Code

$ kitchen destroy$ kitchen destroy

Page 110: Refactoring Infrastructure Code

$ kitchen destroy $ kitchen converge

$ kitchen destroy $ kitchen converge

Page 111: Refactoring Infrastructure Code

#resource  “aws_security_group”  “allow-­‐egress”  {  #    name  =  “${var.user_name}-­‐allow-­‐egress”  #    tags  =  {  #        Name  =  “${var.user_name}  Allow  connecLons  out”  #    }  #}  

#resource  “aws_security_group_rule”  “allow-­‐out”  {  #  type  =  “egress”  #  from_port  =  0  #  to_port  =  65535  #  cidr_blocks  =  ["0.0.0.0/0"]      …

supermarket-cluster.tf

Page 112: Refactoring Infrastructure Code

Let’s write a test

Page 113: Refactoring Infrastructure Code

describe  command(‘ping  -­‐c  1  google.com’)  do      end

security_groups_spec.rb

Page 114: Refactoring Infrastructure Code

describe  command(‘ping  -­‐c  1  google.com’)  do      its(‘stdout’)  {  should  match  /1  packets  transmiSed,  1  received/  }  end

security_groups_spec.rb

Page 115: Refactoring Infrastructure Code

$ kitchen verify$ kitchen verify

Page 116: Refactoring Infrastructure Code

$ kitchen verify Failure/Error: expected "PING google.com (216.58.218.238) 56(84) bytes of data.\n\n--- google.com ping statistics —\n 1 packets transmitted, 0 received, to match /1 packets transmitted, 1 received/ Diff: @@ -1,2 +1,5 @@ -/1 packets transmitted, 1 received/

Page 117: Refactoring Infrastructure Code

Good! We have a failure!

Page 118: Refactoring Infrastructure Code

Now let’s make it pass

Page 119: Refactoring Infrastructure Code

#resource  “aws_security_group”  “allow-­‐egress”  {  #    name  =  “${var.user_name}-­‐allow-­‐egress”  #    tags  =  {  #        Name  =  “${var.user_name}  Allow  connecLons  out”  #    }  #}  

#resource  “aws_security_group_rule”  “allow-­‐out”  {  #  type  =  “egress”  #  from_port  =  0  #  to_port  =  65535  #  cidr_blocks  =  ["0.0.0.0/0"]      …

supermarket-cluster.tf

Page 120: Refactoring Infrastructure Code

resource  “aws_security_group”  “allow-­‐egress”  {      name  =  “${var.user_name}-­‐allow-­‐egress”      tags  =  {          Name  =  “${var.user_name}  Allow  connecTons  out”      }  }  

resource  “aws_security_group_rule”  “allow-­‐out”  {    type  =  “egress”    from_port  =  0    to_port  =  65535    cidr_blocks  =  ["0.0.0.0/0"]      …  

supermarket-cluster.tf

Page 121: Refactoring Infrastructure Code

EC2Chef Server $ terraform apply

EC2 Supermarket

Security Group

Allow SSH

Security Group

Allow Out

supermarket-cluster.tf

Page 122: Refactoring Infrastructure Code

Now let’s call this security group from our

Chef Server

Page 123: Refactoring Infrastructure Code

…  resource  “aws_instance”  “chef_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐chef-­‐server”      }          security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”]      (…)  }  …

This is the Chef Server

supermarket-cluster.tf

Page 124: Refactoring Infrastructure Code

…  resource  “aws_instance”  “chef_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐chef-­‐server”      }          security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”,                                                                          “${aws_security_group.allow-­‐out.name}”]      (…)  }  …

This is the Chef Server

supermarket-cluster.tf

Page 125: Refactoring Infrastructure Code

And the Supermarket Server

Page 126: Refactoring Infrastructure Code

…  resource  “aws_instance”  “supermarket_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐supermarket-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”      

   (…)  }  …

This is the Supermarket

Server

supermarket-cluster.tf

Page 127: Refactoring Infrastructure Code

…  resource  “aws_instance”  “supermarket_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐supermarket-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”,                                                                          “${aws_security_group.allow-­‐out.name}”]      (…)  }  …

This is the Supermarket

Server

supermarket-cluster.tf

Page 128: Refactoring Infrastructure Code

$ kitchen destroy $ kitchen converge

$ kitchen destroy $ kitchen converge

Page 129: Refactoring Infrastructure Code

$ kitchen destroy $ kitchen converge $ kitchen verify

Page 130: Refactoring Infrastructure Code

$ kitchen verify$ kitchen verify

$ kitchen verify Command ping -c 1 google.com stdout should match /1 packets transmitted, 1 received/ 1 example, 0 failures

Page 131: Refactoring Infrastructure Code

It passes! Now let’s make a

change

Page 132: Refactoring Infrastructure Code

Let’s condense the two security groups

into one security group

Page 133: Refactoring Infrastructure Code

resource  “aws_security_group”  “allow-­‐egress”  {      name  =  “${var.user_name}-­‐allow-­‐egress”      tags  =  {          Name  =  “${var.user_name}  Allow  connecWons  out”      }  }  

resource  “aws_security_group_rule”  “allow-­‐out”  {    type  =  “egress”    from_port  =  0    to_port  =  65535    cidr_blocks  =  ["0.0.0.0/0"]      …  

supermarket-cluster.tf

Page 134: Refactoring Infrastructure Code

resource  “aws_security_group_rule”  “allow-­‐out”  {    type  =  “egress”    from_port  =  0    to_port  =  65535    cidr_blocks  =  ["0.0.0.0/0"]    security_group_id  “${aws_security_group.allow-­‐egress.id}”  }

supermarket-cluster.tf

Page 135: Refactoring Infrastructure Code

resource  “aws_security_group_rule”  “allow-­‐out”  {    type  =  “egress”    from_port  =  0    to_port  =  65535    cidr_blocks  =  ["0.0.0.0/0"]    security_group_id  “${aws_security_group.allow-­‐ssh.id}”  }

supermarket-cluster.tf

Page 136: Refactoring Infrastructure Code

Now our instances should only use the one security group

Page 137: Refactoring Infrastructure Code

…  resource  “aws_instance”  “chef_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐chef-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”,                                                                          “${aws_security_group.allow-­‐out.name}”]      (…)  }  …

This is the Chef Server

supermarket-cluster.tf

Page 138: Refactoring Infrastructure Code

…  resource  “aws_instance”  “chef_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐chef-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”]                                                                          (…)  }  …

This is the Chef Server

supermarket-cluster.tf

Page 139: Refactoring Infrastructure Code

…  resource  “aws_instance”  “supermarket_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐supermarket-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”,                                                                          “${aws_security_group.allow-­‐out.name}”]        (…)  }  …

This is the Supermarket

Server

supermarket-cluster.tf

Page 140: Refactoring Infrastructure Code

…  resource  “aws_instance”  “supermarket_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐supermarket-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”]      (…)  }  …

This is the Supermarket

Server

supermarket-cluster.tf

Page 141: Refactoring Infrastructure Code

EC2Chef Server

$ terraform apply EC2 Supermarket

Security Group

Allow SSHAllow Out

supermarket-cluster.tf

Page 142: Refactoring Infrastructure Code

$ kitchen destroy $ kitchen converge

Page 143: Refactoring Infrastructure Code

$ kitchen destroy $ kitchen converge $ kitchen verify

Page 144: Refactoring Infrastructure Code

$ kitchen verify$ kitchen verify Command ping -c 1 google.com stdout should match /1 packets transmitted, 1 received/

1 example, 0 failures

Page 145: Refactoring Infrastructure Code

Now let’s improve the design

Page 146: Refactoring Infrastructure Code

By moving the security group code into a module

Page 147: Refactoring Infrastructure Code

Source: Terraform Docs

Why Move into a Module?

• Self-contained package

Page 148: Refactoring Infrastructure Code

Source: Terraform Docs

Why Move into a Module?

• Self-contained package• Reusable component

Page 149: Refactoring Infrastructure Code

• Self-contained package• Reusable component • Improve organization

Source: Terraform Docs

Why Move into a Module?

Page 150: Refactoring Infrastructure Code

$ kitchen destroy

Page 151: Refactoring Infrastructure Code

$ mkdir security_groups

Page 152: Refactoring Infrastructure Code

security-groups/main.tf

Page 153: Refactoring Infrastructure Code

resource  “aws_security_group”  “allow-­‐ssh”  {    name  =  “${var.user_name}-­‐allow-­‐ssh”    tags  =  {        Name  =  “${var.user_name}  Allow  All  SSH”    }  

resource  “aws_security_group_rule”  “allow-­‐ssh”  {    type  =  “ingress”    from_port  =  22    to_port  =  22      …  }

security-groups/main.tf

Page 154: Refactoring Infrastructure Code

Now we need to connect to the module

from main config

Page 155: Refactoring Infrastructure Code

First, we need to know what variables the module needs

passed to it

Page 156: Refactoring Infrastructure Code

resource  “aws_security_group”  “allow-­‐ssh”  {    name  =  “${var.user_name}-­‐allow-­‐ssh”    tags  =  {        Name  =  “${var.user_name}  Allow  All  SSH”    }  

resource  “aws_security_group_rule”  “allow-­‐ssh”  {    type  =  “ingress”    from_port  =  22      …      security_group_id  =  “${aws_security_group.allow-­‐ssh.id}”  }

Needs to be passed to the module

security-groups/main.tf

Page 157: Refactoring Infrastructure Code

resource  “aws_security_group”  “allow-­‐ssh”  {    name  =  “${var.user_name}-­‐allow-­‐ssh”    tags  =  {        Name  =  “${var.user_name}  Allow  All  SSH”    }  

resource  “aws_security_group_rule”  “allow-­‐ssh”  {    type  =  “ingress”    from_port  =  22      …      security_group_id  =  “${aws_security_group.allow-­‐ssh.id}”  }

Does not needto be passed in

security-groups/main.tf

Page 158: Refactoring Infrastructure Code

Uses variables

supermarket- cluster.tf

Using the Module

Page 159: Refactoring Infrastructure Code

variables.tf

supermarket- cluster.tf

Definesvariablesused by config

Using the Module

Page 160: Refactoring Infrastructure Code

terraform.tfvars

variables.tf

supermarket- cluster.tf

Definesvalues

for variables

Using the Module

Page 161: Refactoring Infrastructure Code

terraform.tfvars

variables.tf

supermarket- cluster.tf

security-groups module

Definesvariables used

by module config

variables.tf

Using the Module

Page 162: Refactoring Infrastructure Code

variable  “user_name”  {}

security-groups/variables.tf

Page 163: Refactoring Infrastructure Code

terraform.tfvars

variables.tf

supermarket- cluster.tf

security-groups module

Usesvariables

variables.tf

main.tf

Using the Module

Page 164: Refactoring Infrastructure Code

terraform.tfvars

variables.tf

supermarket- cluster.tf

security-groups modulePassesvariables

to module

variables.tf

main.tf

Using the Module

Page 165: Refactoring Infrastructure Code

module  “security-­‐groups”  {      source  =  “./security-­‐groups”      user_name  =  “${var.user_name}”  }

supermarket-cluster.tf

Page 166: Refactoring Infrastructure Code

module  “security-­‐groups”  {      source  =  “./security-­‐groups”      user_name  =  “${var.user_name}”  }

supermarket-cluster.tf

Page 167: Refactoring Infrastructure Code

$ kitchen converge

Page 168: Refactoring Infrastructure Code

$ kitchen converge -----> Starting Kitchen (v1.10.2) -----> Converging <default-ubuntu>… (…) >>>>>> ------Exception------- >>>>>> Class: Kitchen::ActionFailed Message: 1 actions failed. >>>>>> Converge failed on instance <default-ubuntu>. Please see .kitchen/logs/default-ubuntu.log for more details

Page 169: Refactoring Infrastructure Code

$ kitchen converge -----> Starting Kitchen (v1.10.2) -----> Converging <default-ubuntu>… (…) >>>>>> ------Exception------- >>>>>> Class: Kitchen::ActionFailed Message: 1 actions failed. >>>>>> Converge failed on instance <default-ubuntu>. Please see .kitchen/logs/default-ubuntu.log for more details

Page 170: Refactoring Infrastructure Code

-­‐-­‐-­‐  Begin  output  of  terraform  validate  (…)  STDOUT:  STDERR:  Error  validaWng:  2  error(s)  occurred:  * resource  'aws_instance.supermarket_server'  config:    * unknown  resource  'aws_security_group.allow-­‐ssh'    * referenced  in  variable  aws_security_group.allow-­‐ssh.name  * resource  'aws_instance.chef_server'  config:    * unknown  resource  'aws_security_group.allow-­‐ssh'    * referenced  in  variable  aws_security_group.allow-­‐ssh.name

.kitchen/logs/default-ubuntu.log

Page 171: Refactoring Infrastructure Code

terraform.tfvars

variables.tf

supermarket- cluster.tf

security-groups module

variables.tf

main.tf

output.tf

Passesoutput

values to main

config

Workflow Into The Module

Page 172: Refactoring Infrastructure Code

output  “sg-­‐name”  {      }

security-groups/output.tf

Page 173: Refactoring Infrastructure Code

output  “sg-­‐name”  {      value  =  “${aws_security_group.allow-­‐ssh.name}”  }

security-groups/output.tf

Page 174: Refactoring Infrastructure Code

Now, we need to use this output in our

supermarket-cluster config

Page 175: Refactoring Infrastructure Code

…  resource  “aws_instance”  “chef_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐chef-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh.name}”]                                                                          (…)  }  …

This is the Chef Server

supermarket-cluster.tf

Page 176: Refactoring Infrastructure Code

…  resource  “aws_instance”  “chef_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐chef-­‐server”      }      security_groups  =  [“${module.security-­‐groups.sg-­‐name}”]            (…)  }  …

This is the Chef Server

supermarket-cluster.tf

Page 177: Refactoring Infrastructure Code

…  resource  “aws_instance”  “supermarket_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐supermarket-­‐server”      }      security_groups  =  [“${aws_security_group.allow-­‐ssh-­‐name}”]    

   (…)  }  …

This is the Supermarket

Server

supermarket-cluster.tf

Page 178: Refactoring Infrastructure Code

…  resource  “aws_instance”  “supermarket_server”  {      ami  =  “${var.ami}”      instance_type  =  “${var.instance_type}”      key_name  =  “${var.key_name}”      tags  {          Name  =  “dev-­‐supermarket-­‐server”      }      security_groups  =  [“${module.security-­‐groups.sg-­‐name}”]          (…)  }  …

This is the Supermarket

Server

supermarket-cluster.tf

Page 179: Refactoring Infrastructure Code

$ kitchen converge

Page 180: Refactoring Infrastructure Code

$ kitchen converge $ kitchen verify

Page 181: Refactoring Infrastructure Code

$ kitchen verify$ kitchen verify

$ kitchen verify Command ping -c 1 google.com stdout should match /1 packets transmitted, 1 received/

1 example, 0 failures

Page 182: Refactoring Infrastructure Code

So we have improved our resource usage

Page 183: Refactoring Infrastructure Code

And the code’s organization and

design

Page 184: Refactoring Infrastructure Code

With minimal risk

Page 185: Refactoring Infrastructure Code

Infrastructure codemust be maintained

and refactored just like application code

Page 186: Refactoring Infrastructure Code

Even more so because infrastructure code involves so many

moving pieces

Page 187: Refactoring Infrastructure Code

When refactoring, always cover with tests

Page 188: Refactoring Infrastructure Code

And refactor one smallpiece at a time

Page 189: Refactoring Infrastructure Code

Thank you

Page 190: Refactoring Infrastructure Code

Who Am I?

• Software Engineer at Chef

• @nellshamrell• [email protected]

• Former O’Fallon, IL resident (Southern IL ftw!)

Nell Shamrell-Harrington

Page 191: Refactoring Infrastructure Code

Who Am I?

Any Questions?

• Software Engineer at Chef

• @nellshamrell• [email protected]

• Former O’Fallon, IL resident (Southern IL ftw!)

Nell Shamrell-Harrington