0% found this document useful (0 votes)
26 views

1.introduction Infra As A Code

The document describes the challenges of manually configuring and deploying infrastructure and applications. It introduces infrastructure as code using Docker containers and Terraform to automate configuration. Specifically, it will show configuring and deploying two microservices - a Sinatra backend returning "Hello World" and a Rails frontend calling the backend - on Amazon ECS using Docker and Terraform. This addresses issues like dependency and environment mismatches that arise from manual configuration.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
26 views

1.introduction Infra As A Code

The document describes the challenges of manually configuring and deploying infrastructure and applications. It introduces infrastructure as code using Docker containers and Terraform to automate configuration. Specifically, it will show configuring and deploying two microservices - a Sinatra backend returning "Hello World" and a Rails frontend calling the backend - on Amazon ECS using Docker and Terraform. This addresses issues like dependency and environment mismatches that arise from manual configuration.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 107

INFRASTRUCTURE

as CODE
Why infrastructure-as-code
matters: a short story.
You are starting a
new project
I know, I’ll use Ruby on Rails!
> gem i n s tal l r a i l s
> gem i n s t a l l r a i l s
Fetching: i18n-0.7.0.gem (100%)
Fetching: json-1.8.3.gem (100%)
B u i ldin g nat i v e exten sions. T h is coul d ta k e a wh i l e . . .
ERROR: Er r o r i n s t a l l i n g r a i l s :
ERROR: Failed t o b u i l d gem n a ti ve extension.

/ usr/ b i n / r uby1. 9 . 1 extco nf . r b


c r e a ti n g Makefile

make
s h : 1 : make: not found
Ah, I just need to install make
> sudo a p t- get i n s t a l l make
...
Success!
> gem i n s tal l r a i l s
> gem i n s t a l l r a i l s
Fetching: nokogiri-1.6.7.2.gem (100%)
B u i l d i n g nat i v e exten si o n s . T h is could tak e a w h i le . . .
ERROR: Er r o r i n s t a l l i n g r a i l s :
ERROR: Failed t o b u i l d gem n a ti ve extension.

/usr/bin/ruby1.9.1 extconf.rb
checking i f the C compileraccepts. . . yes
B u i l d i n g nok o g i r i u s ingpackaged l i b r a r i es.
Using m i n i _ p o r t i l e version2 . 0 . 0 . r c 2
checking f o r gzdopen() i n - l z . . . no
z l i b i s missing; necessary f o r b u i l d i n g l i b xm l 2
* * * extconf.rb f a i l e d * * *
Hmm. Time to visit StackOverflow.
> sudo a p t- g e t i n s t a l l zlib1g-dev
...
Success!
> gem i n s tal l r a i l s
> gem i n s t a l l r a i l s
B u i l d i n g nat i v e exten si o n s . T h is could tak e a w h i le . . .
ERROR: Er r o r i n s t a l l i n g r a i l s :
ERROR: Failed t o b u i l d gem n a t i v e extension.

/usr/bin/ruby1.9.1 extconf.rb
checking i f the C compileraccepts. . . yes
B u i l d i n g nok o g i r i u s ingpackaged l i b r a r i es.
Using m i n i _ p o r t i l e version2 . 0 . 0 . r c 2
checking f o r gzdopen() i n - l z . . . yes
checking f o r i c o n v. . . yes

E x t r a c ti n g l i b x m l 2 - 2 . 9 . 2 . t a r. g z i n t o tmp/x86_64-pc-linux-
g n u / p o r t s / l i b x m l 2 / 2 . 9 . 2 . . . OK
* * * extconf.rb f a i l e d * * *
y u never install correctly?
(Spend 2 hours trying random
StackOverflow suggestions)
> gem i n s tal l r a i l s
> gem i n s tal l r a i l s
...
Success!
Finally!
> r a i l s new my- proj e c t
> cd my-project
> rails start
> r a i l s new my-project
> cd my-project
> rails start

/source/my- p r o j e c t/ b i n/ s p r i n g :11: i n ` < to p ( r equired) > ':


undefined method `path_separator' f o r Gem:Module
(NoMethodError)
from b i n / r a i l s : 3 : i n ` l o a d '
from b i n /ra i l s : 3 : i n `< main>'
Eventually, you get it working
Now you have to deploy your
Rails app in production
You use the AWS Console to
deploy an EC2 instance
> ssh [email protected]

| |_ )
_| ( / Amazon Linu x AMI
|\ | |

[ec2- user@i p- 172- 31- 61- 204 ~ ]$ gem i n s t a l l r a i l s


> ssh [email protected]

| |_ )
_| ( / Amazon Linu x AMI
|\ | |

[ec2- user@i p- 172- 31- 61- 204 ~ ]$ gem i n s t a l l r a i l s


ERROR: Er r o r i n s t a l l i n g r a i l s :
ERROR: Failed t o b u i l d gem n a ti ve extension.

/usr/bin/ruby1.9.1 extconf.rb
Eventually you get it working
Now you urgently have to update
all your Rails installs
> bundle update ra i l s
> bundle update r a i l s
B u i l d i n g nat i v e exten si o n s . T h is could tak e a w h i le . . .
ERROR: Er r o r i n s t a l l i n g r a i l s :
ERROR: Failed t o b u i l d gem n a t i v e extension.

/usr/bin/ruby1.9.1 extconf.rb
checking i f the C compileraccepts. . . yes
B u i l d i n g nok o g i r i u s ingpackaged l i b r a r i es.
Using m i n i _ p o r t i l e version2 . 0 . 0 . r c 2
checking f o r gzdopen() i n - l z . . . yes
checking f o r i c o n v. . . yes

E x t r a c ti n g l i b x m l 2 - 2 . 9 . 2 . t a r. g z i n t o tmp/x86_64-pc-linux-
g n u / p o r t s / l i b x m l 2 / 2 . 9 . 2 . . . OK
* * * extconf.rb f a i l e d * * *
The problem isn’t Rails
> ssh [email protected]

| |_ )
_| ( / Amazon Linu x AMI
|\ | |

[ec2- user@i p- 172- 31- 61- 204 ~ ]$ gem i n s t a l l r a i l s

The problem is that you’re


configuring servers manually
And that you’re deploying
infrastructure manually
A better alternative: infrastructure-
as-code
In this talk, we’ll go through a
real-world example:
We’ll configure & deploy two
microservices on Amazon ECS
TERRAFORM

With two infrastructure-as-code


tools: Docker and Terraform
Outline
1. Microservices
2. Docker
3. Terraform
4. Recap
Outline
1. Microservices
2. Docker
3. Terraform
4. ECS
5. Recap
Code is the enemy: the more you
have, the slower you go
Project Size Bug Density
Lines of code Bugs per thousand lines
of code

< 2K 0 – 25

2K – 6K 0 – 40

16K – 64K 0.5 – 50

64K – 512K 2 – 70

> 512K 4 – 100


As the code grows, the number of
bugs grows even faster
“Software
development doesn't
happen in a chart, an
IDE, or a design tool;
it happens in your
head.”
The mind can only handle so
much complexity at once
One solution is to break the code
into microservices
moduleA.func()

moduleB.func() moduleC.func() moduleD.func()

moduleE.func()

In a monolith, you use function


calls within one process
http://service.a

http://service.b http://service.c http://service.d

http://service.e

With services, you pass messages


between processes
Advantages of services:

1. Isolation
2. Technology agnostic
3. Scalability
Disadvantages of services:

1. Operational overhead
2. Performance overhead
3. I/O, error handling
4. Backwards compatibility
5. Global changes, transactions,
referential integrity all very hard
For more info, see: Splitting Up a
Codebase into Microservices and
Artifacts
For this talk, we’ll use two
example microservices
require ' s i n a t r a '

get " / " do


" H e l l o , World!"
end

A sinatra backend that returns


“Hello, World”
class A p p l i c a t i o n C o n t r o l l e r <ActionController::Base
def index
u r l = URI.parse(backend_addr)
req = Net::HTTP::Get.new(url.to_s)
res = Ne t : : HTTP. s t a r t ( u r l .host , ur l . port ) { | h t t p|
http.request(req)
}
@te x t = res.body
end
end

A rails frontend that calls the


sinatra backend
<h1>Rails Frontend</h1>
<p>
Response from the backend: <strong><%= @text %></strong>
</p>

And renders the response as


HTML
Outline
1. Microservices
2. Docker
3. Terraform
4. ECS
5. Recap
Docker allows you to build and
run code in containers
Containers are like lightweight
Virtual Machines (VMs)
VM VM VM

App App App

Guest User Guest User Guest User


Space Space Space

Guest OS Guest OS Guest OS

Virtualized Virtualized Virtualized


hardware hardware hardware

Virtual Machine

Host User Space

Host OS

Hardware

VMs virtualize the hardware and run an


entire guest OS on top of the host OS
VM VM VM

App App App

Guest User Guest User Guest User


Space Space Space

Guest OS Guest OS Guest OS

Virtualized Virtualized Virtualized


hardware hardware hardware

Virtual Machine

Host User Space

Host OS

Hardware

This provides good isolation, but lots of


CPU, memory, disk, & startup overhead
VM VM VM

App App App

Guest User Guest User Guest User


Container Container Container
Space Space Space

Guest OS Guest OS Guest OS App App App

Virtualized Virtualized Virtualized Virtualized Virtualized Virtualized


hardware hardware hardware User Space User Space User Space

Virtual Machine Container Engine

Host User Space Host User Space

Host OS Host OS

Hardware Hardware

Containers virtualize User Space (shared


memory, processes, mount, network)
VM VM VM

App App App

Guest User Guest User Guest User


Container Container Container
Space Space Space

Guest OS Guest OS Guest OS App App App

Virtualized Virtualized Virtualized Virtualized Virtualized Virtualized


hardware hardware hardware User Space User Space User Space

Virtual Machine Container Engine

Host User Space Host User Space

Host OS Host OS

Hardware Hardware

Isolation isn’t as good but much less CPU,


memory, disk, startup overhead
> docker r un –i t ubunt u bash

root@12345:/# echo " I ' m i n $ ( c a t / e t c / i s s u e ) ”

I ' m i n Ubuntu 14.04.4 LTS

Running Ubuntu in a Docker


container
> time docke r r un ubunt u echo "Hel l o , Wor l d "
H e l l o , World

r e a l 0m0.183s
user 0m0.009s
sys 0m0.014s

Containers boot very quickly.


Easily run a dozen at once.
You can define a Docker image
as code in a Dockerfile
FROM g l i d e r l a b s / a l p i n e : 3 . 3

RUN apk --no-cache add ruby ruby-dev


RUN gem in st a l l s i n a tr a - - no- r i - - no- rdoc

RUN mkdi r - p / us r / src / app


COPY . / u s r / s r c / a p p
WORKDIR / u s r / s r c / a p p

EXPOSE 4567
CMD [ " r u b y " , " a p p . r b " ]

Here is the Dockerfile for the


Sinatra backend
FROM g l i d e r l a b s / a l p i n e : 3 . 3

RUN apk --no-cache add ruby ruby-dev


RUN gem in st a l l s i n a tr a - - no- r i - - no- rdoc

RUN mkdi r - p / us r / src / app


COPY . / u s r / s r c / a p p
WORKDIR / u s r / s r c / a p p

EXPOSE 4567
CMD [ " r u b y " , " a p p . r b " ]

It specifies dependencies, code,


config, and how to run the app
> docker bui l d - t b r i ki s 9 8 /s i n a tr a- backend .
Step 0 : FROM g l i d e r l a b s / a l p i n e : 3 . 3
- - - > 0a7e169bce21

(...)

Step 8 : CMD ruby app.rb


- - - > 2e243eba30ed

Successf u l l y bui l t 2e243eba30ed

Build the Docker image


> docker run - i t - p 4567:4567 brikis98/sinatra -backend
INFO WEBrick 1 .3 .1
INFO ruby 2 . 2 . 4 (2015-12-16) [x86_64-linux-musl]
== Sina t r a ( v 1 .4.7) has ta ken th e sta ge on 4567 f o r
development w i t h backup from WEBrick
INFO WEBrick::HTTPServer#start: pid=1 port=4567

Run the Docker image


> docker push brikis98/sinatra -backend
The push r ef e r s t o a r eposito r y [ docker.i o / br i k i s 9 8 / s i na tr a -
backend] ( l e n : 1 )
2e243eba30ed: Image s u c ce ssfu lly pushed
7e2e0c53e246: Image s u c ce ssfu lly pushed
919d9a73b500: Image s u c ce ssfu l l y pushed

(...)

v1: d i g e s t : sha256:09f 48ed773966ec7fe4558 si ze: 14319

You can share your images by


pushing them to Docker Hub
Now you can reuse the same
image in dev, stg, prod, etc
> docker pul l r a i l s:4 . 2.6

And you can reuse images created


by others.
FROM ra i l s : 4 .2 .6

RUN mkdi r - p / us r / src / app


COPY . / u s r / s r c / a p p
WORKDIR / u s r / s r c / a p p

RUN bundle i n s t a l l

EXPOSE 3000
CMD [ " r a i l s" , " sta r t " ]

The rails-frontend is built on top of


the official rails Docker image
No more insane install procedures!
rails_frontend:
image: br i k i s 9 8 /r a i l s- f r o n tend
ports:
- "3000:3000"
links:
- sinatra_backend:sinatra_backend

sinatra_backend:
image: br i k i s 9 8 /s i n at r a - backend
ports:
- "4567:4567"

Define your entire dev stack as


code with docker-compose
rails_frontend:
image: br i k i s 9 8 /r a i l s- f r o n tend
ports:
- "3000:3000"
links:
- sinatra_backend

sinatra_backend:
image: br i k i s 9 8 /s i n at r a - backend
ports:
- "4567:4567"

Docker links provide a simple


service discovery mechanism
> docker-compose up
S t a r t i n g infrastructureascodetalk_sinatra_backend_1
Recreating in fr a structurea scode ta l k_rai l s _fro ntend _1

sinatra_backend_1 | INFO WEBrick 1 . 3 . 1


sinatra_backend_1 | INFO ruby 2 .2 .4 (2015-12-16)
sinatra_backend_1 | Sinatra has taken the stage on 4567

r a ils _ fro ntend _1 | INFO WEBrick 1 . 3 . 1


r a ils _ fro ntend _1 | INFO ruby 2 .3 .0 (2015-12-25)
r a ils _ fro ntend _1 | INFO WEBrick::HTTPServer#start: port=3000

Run your entire dev stack with one


command
Advantages of Docker:

1. Easy to create & share images


2. Images run the same way in all
environments (dev, test, prod)
3. Easily run the entire stack in dev
4. Minimal overhead
5. Better resource utilization
Disadvantages of Docker:

1. Maturity. Ecosystem developing


very fast, but still a ways to go
2. Tricky to manage persistent data in
a container
3. Tricky to pass secrets to containers
Outline
1. Microservices
2. Docker
3. Terraform
4. ECS
5. Recap
Terraform is a tool for
provisioning infrastructure
Terraform supports many
providers (cloud agnostic)
And many resources for each
provider
You define infrastructure as code
in Terraform templates
provider "aws" {
region = "us- east - 1"
}

resource " aws_instance" "e xample " {


ami = "ami-408c7f28"
instance_type = " t 2 . m i c r o "
}

This template creates a single EC2


instance in AWS
> te r r afo rm plan
+ aws_instance.example
ami: "" => "ami-408c7f28"
instance_type: "" => "t2.micro"
key_name: "" => "<computed>"
private_ip: "" => "<computed>"
public_ip: "" => "<computed>"

Plan: 1 t o add, 0 t o change, 0 t o destroy.

Use the plan command to see


what you’re about to deploy
> te r r afo rm apply
aws_instan ce.example : Creatin g . . .
ami: " " => "ami-408c7f28"
i n s tan ce_type: " " => " t 2 . m i c r o "
key_name: " " => "<comput ed>"
private_ip: " " => "<comput ed>"
public_ip: " " => "<comput ed>”
aws_instance.example: Creation complete

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Use the apply command to apply


the changes
Now our EC2 instance is running!
resource " aws_instance" "e xample " {
ami ="ami-408c7f28"
instance_type = " t 2 . m i c r o "
tags{
Name = "terraform-example"
}
}

Let’s give the EC2 instance a tag


with a readable name
> te r r afo rm plan
~ aws_instance.example
tag s.# : " 0 " => " 1 "
tags.Name: " " => "terraform-example"

Plan: 0 t o add, 1 t o change, 0 t o destroy.

Use the plan command again to


verify your changes
> te r r afo rm apply
aws_instan ce.example : Refreshi n g s t a t e . . .
aws_instance.example: M o d i f y i n g . . .
tags.#: " 0 " => " 1 "
tags.Name: " " => "terraform-example"
aws_instan ce.example : Modifi c a t i ons compl e t e

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

Use the apply command again to


deploy those changes
Now our EC2 instance has a tag!
resource " aws_elb" "e xample" {
name = "example"
avai l a bi l i t y _ z ones = [ " us- east - 1a" , " us- east- 1b" ]
instances=["${aws_instance.example.id}" ]
listener {
l b _ p o r t = 80
lb _protocol = " h t t p"
in stance_po r t = "$ { var . i nstance_port } "
instance_protocol = " h t t p ”
}
}

Let’s add an Elastic Load Balancer


(ELB).
resource " aws_elb" "e xample" {
name = "example"
avai l a bi l i t y _ z ones = [ " us- east - 1a" , " us- east- 1b" ]
instances=["${aws_instance.example.id}" ]
listener {
l b _ p o r t = 80
lb _protocol = " h t t p"
in stance_po r t = "$ { var . i nstance_port } "
instance_protocol = " h t t p ”
}
}

Terraform supports variables,


such as var.instance_port
resource " aws_elb" "e xample" {
name = "example"
avai l a bi l i t y _ z ones = [ " us- east - 1a" , " us- east- 1b" ]
instances=["${aws_instance.example.id}" ]
listener {
l b _ p o r t = 80
lb _protocol = " h t t p"
in stance_po r t = "$ { var . i nstance_port } "
instance_protocol = " h t t p "
}
}

As well as dependencies like


aws_instance.example.id
resource " aws_elb" "e xample" {
name = "example"
avai l a bi l i t y _ z ones = [ " us- east - 1a" , " us- east- 1b" ]
instances=["${aws_instance.example.id}" ]
listener {
l b _ p o r t = 80
lb _protocol = " h t t p"
in stance_po r t = "$ { var . i nstance_port } "
instance_protocol = " h t t p "
}
}

It builds a dependency graph and


applies it in parallel.
After running apply, we have an ELB!
> te r r afo rm dest r o y
aws_instan ce.example : Refreshi n g s t a t e . . . ( I D: i - f3 d58c70)
aws_elb.example: Refreshing s t a t e . . . ( I D : example)
aws_elb.example: D e s t r o y i n g . . .
aws_elb.example: Destruction complete
aws_instance.example: D e s t r o y i n g . . .
aws_instan ce.example : Destruct i o n complet e

Apply complete! Resources: 0 added, 0 changed, 2 destroyed.

Use the destroy command to


delete all your resources
Advantages of Terraform:

1. Concise, readable syntax


2. Reusable code: inputs, outputs,
modules
3. Plan command!
4. Cloud agnostic
5. Very active development
Disadvantages of Terraform:

1. Maturity
2. Collaboration on Terraform state is
hard (but terragrunt makes it easier)
3. No rollback
4. Poor secrets management
Outline
1. Microservices
2. Docker
3. Terraform
4. Recap
Benefits of infrastructure-as-code:
1. Reuse
2. Automation
3. Version control
4. Code review
5. Testing
6. Documentation
Questions?

You might also like