SlideShare a Scribd company logo
1 of 203
Automated testing for:
✓ terraform
✓ docker
✓ packer
✓ kubernetes
✓ and more
Passed: 5. Failed: 0. Skipped: 0.
Test run successful.
How to
test
infrastructure
code
InfoQ.com: News & Community Site
• Over 1,000,000 software developers, architects and CTOs read the site world-
wide every month
• 250,000 senior developers subscribe to our weekly newsletter
• Published in 4 languages (English, Chinese, Japanese and Brazilian
Portuguese)
• Post content from our QCon conferences
• 2 dedicated podcast channels: The InfoQ Podcast, with a focus on
Architecture and The Engineering Culture Podcast, with a focus on building
• 96 deep dives on innovative topics packed as downloadable emags and
minibooks
• Over 40 new content items per week
Watch the video with slide
synchronization on InfoQ.com!
https://www.infoq.com/presentations/
automated-testing-terraform-docker-
packer/
Purpose of QCon
- to empower software development by facilitating the spread of
knowledge and innovation
Strategy
- practitioner-driven conference designed for YOU: influencers of
change and innovation in your teams
- speakers and topics driving the evolution and innovation
- connecting and catalyzing the influencers and innovators
Highlights
- attended by more than 12,000 delegates since 2007
- held in 9 cities worldwide
Presented at QCon San Francisco
www.qconsf.com
The DevOps world is full of
Fear
Fear of outages
Fear of security breaches
Fear of data loss
Fear of change
“Fear leads to
anger. Anger
leads to hate.
Hate leads to
suffering.”
Scrum Master Yoda
And you all know what
suffering leads to, right?
Credit: Daniele Polencic
Many DevOps teams deal
with this fear in two ways:
1) Heavy drinking and smoking
2) Deploying less frequently
Sadly, both of these just make
the problem worse!
There’s a better way to deal
with this fear:
Automated tests
Automated tests give you the
confidence to make changes
Fight fear with confidence
We know how to write automated
tests for application code…
resource "aws_lambda_function" "web_app" {
function_name = var.name
role = aws_iam_role.lambda.arn
# ...
}
resource "aws_api_gateway_integration" "proxy" {
type = "AWS_PROXY"
uri = aws_lambda_function.web_app.invoke_arn
# ...
}
But how do you test your Terraform code
deploys infrastructure that works?
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-app-deployment
spec:
selector:
matchLabels:
app: hello-world-app
replicas: 1
spec:
containers:
- name: hello-world-app
image: gruntwork-io/hello-world-app:v1
ports:
- containerPort: 8080
How do you test your Kubernetes code
configures your services correctly?
This talk is about how to write
tests for your infrastructure code.
I’m
Yevgeniy
Brikman
ybrikman.com
Co-founder of
Gruntwork
gruntwork.io
Author
1. Static analysis
2. Unit tests
3. Integration tests
4. End-to-end tests
5. Conclusion
Outline
1. Static analysis
2. Unit tests
3. Integration tests
4. End-to-end tests
5. Conclusion
Outline
Static analysis: test your code
without deploying it.
Static analysis
1. Compiler / parser / interpreter
2. Linter
3. Dry run
Static analysis
1. Compiler / parser / interpreter
2. Linter
3. Dry run
Statically check your code for
syntactic and structural issues
Tool Command
Terraform terraform validate
Packer packer validate <template>
Kubernetes kubectl apply -f <file> --dry-run --validate=true
Examples:
Static analysis
1. Compiler / parser / interpreter
2. Linter
3. Dry run
Statically validate your code to
catch common errors
Tool Linters
Terraform
1. conftest
2. terraform_validate
3. tflint
Docker
1. dockerfile_lint
2. hadolint
3. dockerfilelint
Kubernetes
1. kube-score
2. kube-lint
3. yamllint
Examples:
Static analysis
1. Compiler / parser / interpreter
2. Linter
3. Dry run
Partially execute the code and
validate the “plan”, but don’t
actually deploy
Tool Dry run options
Terraform
1. terraform plan
2. HashiCorp Sentinel
3. terraform-compliance
Kubernetes kubectl apply -f <file> --server-dry-run
Examples:
1. Static analysis
2. Unit tests
3. Integration tests
4. End-to-end tests
5. Conclusion
Outline
Unit tests: test a single “unit”
works in isolation.
Unit tests
1. Unit testing basics
2. Example: Terraform unit tests
3. Example: Docker/Kubernetes unit tests
4. Cleaning up after tests
Unit tests
1. Unit testing basics
2. Example: Terraform unit tests
3. Example: Docker/Kubernetes unit tests
4. Cleaning up after tests
You can’t “unit test” an entire end-
to-end architecture
Instead, break your infra code into
small modules and unit test those!
module
module
module
module
module
module
module
module
module
module
module
module
module module
module
With app code, you can test units
in isolation from the outside world
resource "aws_lambda_function" "web_app" {
function_name = var.name
role = aws_iam_role.lambda.arn
# ...
}
resource "aws_api_gateway_integration" "proxy" {
type = "AWS_PROXY"
uri = aws_lambda_function.web_app.invoke_arn
# ...
}
But 99% of infrastructure code is about
talking to the outside world…
resource "aws_lambda_function" "web_app" {
function_name = var.name
role = aws_iam_role.lambda.arn
# ...
}
resource "aws_api_gateway_integration" "proxy" {
type = "AWS_PROXY"
uri = aws_lambda_function.web_app.invoke_arn
# ...
}
If you try to isolate a unit from the
outside world, you’re left with nothing!
So you can only test infra code by
deploying to a real environment
Key takeaway: there’s no pure
unit testing for infrastructure
code.
Therefore, the test strategy is:
1. Deploy real infrastructure
2. Validate it works
(e.g., via HTTP requests, API calls, SSH commands, etc.)
3. Undeploy the infrastructure
(So it’s really integration testing of a single unit!)
Tool
Deploy /
Undeploy
Validate Works with
Terratest Yes Yes
Terraform, Kubernetes, Packer,
Docker, Servers, Cloud APIs, etc.
kitchen-terraform Yes Yes Terraform
Inspec No Yes Servers, Cloud APIs
Serverspec No Yes Servers
Goss No Yes Servers
Tools that help with this strategy:
Tool
Deploy /
Undeploy
Validate Works with
Terratest Yes Yes
Terraform, Kubernetes, Packer,
Docker, Servers, Cloud APIs, etc.
kitchen-terraform Yes Yes Terraform
Inspec No Yes Servers, Cloud APIs
Serverspec No Yes Servers
Goss No Yes Servers
In this talk, we’ll use Terratest:
Unit tests
1. Unit testing basics
2. Example: Terraform unit tests
3. Example: Docker/Kubernetes unit tests
4. Cleaning up after tests
Sample code for this talk is at:
github.com/gruntwork-io/infrastructure-as-code-testing-talk
An example of a Terraform
module you may want to test:
infrastructure-as-code-testing-talk
└ examples
└ hello-world-app
└ main.tf
└ outputs.tf
└ variables.tf
└ modules
└ test
└ README.md
hello-world-app: deploy a “Hello,
World” web service
resource "aws_lambda_function" "web_app" {
function_name = var.name
role = aws_iam_role.lambda.arn
# ...
}
resource "aws_api_gateway_integration" "proxy" {
type = "AWS_PROXY"
uri = aws_lambda_function.web_app.invoke_arn
# ...
}
Under the hood, this example runs on
top of AWS Lambda & API Gateway
$ terraform apply
Outputs:
url = ruvvwv3sh1.execute-api.us-east-2.amazonaws.com
$ curl ruvvwv3sh1.execute-api.us-east-2.amazonaws.com
Hello, World!
When you run terraform apply, it
deploys and outputs the URL
Let’s write a unit test for
hello-world-app with Terratest
infrastructure-as-code-testing-talk
└ examples
└ modules
└ test
└ hello_world_app_test.go
└ README.md
Create hello_world_app_test.go
func TestHelloWorldAppUnit(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/hello-world-app",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
validate(t, terraformOptions)
}
The basic test structure
func TestHelloWorldAppUnit(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/hello-world-app",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
validate(t, terraformOptions)
}
1. Tell Terratest where your Terraform
code lives
func TestHelloWorldAppUnit(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/hello-world-app",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
validate(t, terraformOptions)
}
2. Run terraform init and terraform
apply to deploy your module
func TestHelloWorldAppUnit(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/hello-world-app",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
validate(t, terraformOptions)
}
3. Validate the infrastructure works.
We’ll come back to this shortly.
func TestHelloWorldAppUnit(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/hello-world-app",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
validate(t, terraformOptions)
}
4. Run terraform destroy at the end of
the test to undeploy everything
func validate(t *testing.T, opts *terraform.Options) {
url := terraform.Output(t, opts, "url")
http_helper.HttpGetWithRetry(t,
url, // URL to test
200, // Expected status code
"Hello, World!", // Expected body
10, // Max retries
3 * time.Second // Time between retries
)
}
The validate function
func validate(t *testing.T, opts *terraform.Options) {
url := terraform.Output(t, opts, "url")
http_helper.HttpGetWithRetry(t,
url, // URL to test
200, // Expected status code
"Hello, World!", // Expected body
10, // Max retries
3 * time.Second // Time between retries
)
}
1. Run terraform output to get the web
service URL
func validate(t *testing.T, opts *terraform.Options) {
url := terraform.Output(t, opts, "url")
http_helper.HttpGetWithRetry(t,
url, // URL to test
200, // Expected status code
"Hello, World!", // Expected body
10, // Max retries
3 * time.Second // Time between retries
)
}
2. Make HTTP requests to the URL
func validate(t *testing.T, opts *terraform.Options) {
url := terraform.Output(t, opts, "url")
http_helper.HttpGetWithRetry(t,
url, // URL to test
200, // Expected status code
"Hello, World!", // Expected body
10, // Max retries
3 * time.Second // Time between retries
)
}
3. Check the response for an expected
status and body
func validate(t *testing.T, opts *terraform.Options) {
url := terraform.Output(t, opts, "url")
http_helper.HttpGetWithRetry(t,
url, // URL to test
200, // Expected status code
"Hello, World!", // Expected body
10, // Max retries
3 * time.Second // Time between retries
)
}
4. Retry the request up to 10 times, as
deployment is asynchronous
Note: since we’re testing a
web service, we use HTTP
requests to validate it.
Infrastructure Example Validate with… Example
Web service Dockerized web app HTTP requests Terratest http_helper package
Server EC2 instance SSH commands Terratest ssh package
Cloud service SQS Cloud APIs Terratest aws or gcp packages
Database MySQL SQL queries MySQL driver for Go
Examples of other ways to validate:
$ export AWS_ACCESS_KEY_ID=xxxx
$ export AWS_SECRET_ACCESS_KEY=xxxxx
To run the test, first authenticate to
AWS
$ go test -v -timeout 15m -run TestHelloWorldAppUnit
…
--- PASS: TestHelloWorldAppUnit (31.57s)
Then run go test. You now have a unit
test you can run after every commit!
Unit tests
1. Unit testing basics
2. Example: Terraform unit tests
3. Example: Docker/Kubernetes unit tests
4. Cleaning up after tests
What about other tools, such
as Docker + Kubernetes?
infrastructure-as-code-testing-talk
└ examples
└ hello-world-app
└ docker-kubernetes
└ Dockerfile
└ deployment.yml
└ modules
└ test
└ README.md
docker-kubernetes: deploy a “Hello,
World” web service to Kubernetes
FROM ubuntu:18.04
EXPOSE 8080
RUN DEBIAN_FRONTEND=noninteractive apt-get update && 
apt-get install -y busybox
RUN echo 'Hello, World!' > index.html
CMD ["busybox", "httpd", "-f", "-p", "8080"]
Dockerfile: Dockerize a simple “Hello,
World!” web service
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-app-deployment
spec:
selector:
matchLabels:
app: hello-world-app
replicas: 1
spec:
containers:
- name: hello-world-app
image: gruntwork-io/hello-world-app:v1
ports:
- containerPort: 8080
deployment.yml: define how to deploy a
Docker container in Kubernetes
$ cd examples/docker-kubernetes
$ docker build -t gruntwork-io/hello-world-app:v1 .
Successfully tagged gruntwork-io/hello-world-app:v1
$ kubectl apply -f deployment.yml
deployment.apps/hello-world-app-deployment created
service/hello-world-app-service created
$ curl localhost:8080
Hello, World!
Build the Docker image, deploy to
Kubernetes, and check URL
Let’s write a unit test for this
code.
infrastructure-as-code-testing-talk
└ examples
└ modules
└ test
└ hello_world_app_test.go
└ docker_kubernetes_test.go
└ README.md
Create docker_kubernetes_test.go
func TestDockerKubernetes(t *testing.T) {
buildDockerImage(t)
path := "../examples/docker-kubernetes/deployment.yml"
options := k8s.NewKubectlOptions("", "", "")
defer k8s.KubectlDelete(t, options, path)
k8s.KubectlApply(t, options, path)
validate(t, options)
}
The basic test structure
func TestDockerKubernetes(t *testing.T) {
buildDockerImage(t)
path := "../examples/docker-kubernetes/deployment.yml"
options := k8s.NewKubectlOptions("", "", "")
defer k8s.KubectlDelete(t, options, path)
k8s.KubectlApply(t, options, path)
validate(t, options)
}
1. Build the Docker image. You’ll see
the buildDockerImage method shortly.
func TestDockerKubernetes(t *testing.T) {
buildDockerImage(t)
path := "../examples/docker-kubernetes/deployment.yml"
options := k8s.NewKubectlOptions("", "", "")
defer k8s.KubectlDelete(t, options, path)
k8s.KubectlApply(t, options, path)
validate(t, options)
}
2. Tell Terratest where your Kubernetes
deployment is defined
func TestDockerKubernetes(t *testing.T) {
buildDockerImage(t)
path := "../examples/docker-kubernetes/deployment.yml"
options := k8s.NewKubectlOptions("", "", "")
defer k8s.KubectlDelete(t, options, path)
k8s.KubectlApply(t, options, path)
validate(t, options)
}
3. Configure kubectl options to
authenticate to Kubernetes
func TestDockerKubernetes(t *testing.T) {
buildDockerImage(t)
path := "../examples/docker-kubernetes/deployment.yml"
options := k8s.NewKubectlOptions("", "", "")
defer k8s.KubectlDelete(t, options, path)
k8s.KubectlApply(t, options, path)
validate(t, options)
}
4. Run kubectl apply to deploy the web
app to Kubernetes
func TestDockerKubernetes(t *testing.T) {
buildDockerImage(t)
path := "../examples/docker-kubernetes/deployment.yml"
options := k8s.NewKubectlOptions("", "", "")
defer k8s.KubectlDelete(t, options, path)
k8s.KubectlApply(t, options, path)
validate(t, options)
}
5. Check the app is working. You’ll see
the validate method shortly.
func TestDockerKubernetes(t *testing.T) {
buildDockerImage(t)
path := "../examples/docker-kubernetes/deployment.yml"
options := k8s.NewKubectlOptions("", "", "")
defer k8s.KubectlDelete(t, options, path)
k8s.KubectlApply(t, options, path)
validate(t, options)
}
6. At the end of the test, remove all
Kubernetes resources you deployed
func buildDockerImage(t *testing.T) {
options := &docker.BuildOptions{
Tags: []string{"gruntwork-io/hello-world-app:v1"},
}
path := "../examples/docker-kubernetes"
docker.Build(t, path, options)
}
The buildDockerImage method
func validate(t *testing.T, opts *k8s.KubectlOptions) {
k8s.WaitUntilServiceAvailable(t, opts, "hello-world-
app-service", 10, 1*time.Second)
http_helper.HttpGetWithRetry(t,
serviceUrl(t, opts), // URL to test
200, // Expected status code
"Hello, World!", // Expected body
10, // Max retries
3*time.Second // Time between retries
)
}
The validate method
func validate(t *testing.T, opts *k8s.KubectlOptions) {
k8s.WaitUntilServiceAvailable(t, opts, "hello-world-
app-service", 10, 1*time.Second)
http_helper.HttpGetWithRetry(t,
serviceUrl(t, opts), // URL to test
200, // Expected status code
"Hello, World!", // Expected body
10, // Max retries
3*time.Second // Time between retries
)
}
1. Wait until the service is deployed
func validate(t *testing.T, opts *k8s.KubectlOptions) {
k8s.WaitUntilServiceAvailable(t, opts, "hello-world-
app-service", 10, 1*time.Second)
http_helper.HttpGetWithRetry(t,
serviceUrl(t, opts), // URL to test
200, // Expected status code
"Hello, World!", // Expected body
10, // Max retries
3*time.Second // Time between retries
)
}
2. Make HTTP requests
func validate(t *testing.T, opts *k8s.KubectlOptions) {
k8s.WaitUntilServiceAvailable(t, opts, "hello-world-
app-service", 10, 1*time.Second)
http_helper.HttpGetWithRetry(t,
serviceUrl(t, opts), // URL to test
200, // Expected status code
"Hello, World!", // Expected body
10, // Max retries
3*time.Second // Time between retries
)
}
3. Use serviceUrl method to get URL
func serviceUrl(t *testing.T, opts *k8s.KubectlOptions) string {
service := k8s.GetService(t, options, "hello-world-app-service")
endpoint := k8s.GetServiceEndpoint(t, options, service, 8080)
return fmt.Sprintf("http://%s", endpoint)
}
The serviceUrl method
$ kubectl config set-credentials …
To run the test, first authenticate to a
Kubernetes cluster.
Note: Kubernetes is now part of
Docker Desktop. Test 100% locally!
$ go test -v -timeout 15m -run TestDockerKubernetes
…
--- PASS: TestDockerKubernetes (5.69s)
Run go test. You can validate your
config after every commit in seconds!
Unit tests
1. Unit testing basics
2. Example: Terraform unit tests
3. Example: Docker/Kubernetes unit tests
4. Cleaning up after tests
Note: tests create and destroy
many resources!
Pro tip #1: run tests in completely
separate “sandbox” accounts
Tool Clouds Features
cloud-nuke AWS (GCP planned)
Delete all resources older than a certain
date; in a certain region; of a certain type.
Janitor Monkey AWS
Configurable rules of what to delete.
Notify owners of pending deletions.
aws-nuke AWS
Specify specific AWS accounts and
resource types to target.
Azure Powershell Azure
Includes native commands to delete
Resource Groups
Pro tip #2: run these tools in cron jobs
to clean up left-over resources
1. Static analysis
2. Unit tests
3. Integration tests
4. End-to-end tests
5. Conclusion
Outline
Integration tests: test multiple
“units” work together.
Integration tests
1. Example: Terraform integration tests
2. Test parallelism
3. Test stages
4. Test retries
Integration tests
1. Example: Terraform integration tests
2. Test parallelism
3. Test stages
4. Test retries
infrastructure-as-code-testing-talk
└ examples
└ hello-world-app
└ docker-kubernetes
└ proxy-app
└ web-service
└ modules
└ test
└ README.md
Let’s say you have two Terraform
modules you want to test together:
infrastructure-as-code-testing-talk
└ examples
└ hello-world-app
└ docker-kubernetes
└ proxy-app
└ web-service
└ modules
└ test
└ README.md
proxy-app: an app that acts as an HTTP
proxy for other web services.
infrastructure-as-code-testing-talk
└ examples
└ hello-world-app
└ docker-kubernetes
└ proxy-app
└ web-service
└ modules
└ test
└ README.md
web-service: a web service that you
want proxied.
variable "url_to_proxy" {
description = "The URL to proxy."
type = string
}
proxy-app takes in the URL to proxy via
an input variable
output "url" {
value = module.web_service.url
}
web-service exposes its URL via an
output variable
infrastructure-as-code-testing-talk
└ examples
└ modules
└ test
└ hello_world_app_test.go
└ docker_kubernetes_test.go
└ proxy_app_test.go
└ README.md
Create proxy_app_test.go
func TestProxyApp(t *testing.T) {
webServiceOpts := configWebService(t)
defer terraform.Destroy(t, webServiceOpts)
terraform.InitAndApply(t, webServiceOpts)
proxyAppOpts := configProxyApp(t, webServiceOpts)
defer terraform.Destroy(t, proxyAppOpts)
terraform.InitAndApply(t, proxyAppOpts)
validate(t, proxyAppOpts)
}
The basic test structure
func TestProxyApp(t *testing.T) {
webServiceOpts := configWebService(t)
defer terraform.Destroy(t, webServiceOpts)
terraform.InitAndApply(t, webServiceOpts)
proxyAppOpts := configProxyApp(t, webServiceOpts)
defer terraform.Destroy(t, proxyAppOpts)
terraform.InitAndApply(t, proxyAppOpts)
validate(t, proxyAppOpts)
}
1. Configure options for the web
service
func TestProxyApp(t *testing.T) {
webServiceOpts := configWebService(t)
defer terraform.Destroy(t, webServiceOpts)
terraform.InitAndApply(t, webServiceOpts)
proxyAppOpts := configProxyApp(t, webServiceOpts)
defer terraform.Destroy(t, proxyAppOpts)
terraform.InitAndApply(t, proxyAppOpts)
validate(t, proxyAppOpts)
}
2. Deploy the web service
func TestProxyApp(t *testing.T) {
webServiceOpts := configWebService(t)
defer terraform.Destroy(t, webServiceOpts)
terraform.InitAndApply(t, webServiceOpts)
proxyAppOpts := configProxyApp(t, webServiceOpts)
defer terraform.Destroy(t, proxyAppOpts)
terraform.InitAndApply(t, proxyAppOpts)
validate(t, proxyAppOpts)
}
3. Configure options for the proxy app
(passing it the web service options)
func TestProxyApp(t *testing.T) {
webServiceOpts := configWebService(t)
defer terraform.Destroy(t, webServiceOpts)
terraform.InitAndApply(t, webServiceOpts)
proxyAppOpts := configProxyApp(t, webServiceOpts)
defer terraform.Destroy(t, proxyAppOpts)
terraform.InitAndApply(t, proxyAppOpts)
validate(t, proxyAppOpts)
}
4. Deploy the proxy app
func TestProxyApp(t *testing.T) {
webServiceOpts := configWebService(t)
defer terraform.Destroy(t, webServiceOpts)
terraform.InitAndApply(t, webServiceOpts)
proxyAppOpts := configProxyApp(t, webServiceOpts)
defer terraform.Destroy(t, proxyAppOpts)
terraform.InitAndApply(t, proxyAppOpts)
validate(t, proxyAppOpts)
}
5. Validate the proxy app works
func TestProxyApp(t *testing.T) {
webServiceOpts := configWebService(t)
defer terraform.Destroy(t, webServiceOpts)
terraform.InitAndApply(t, webServiceOpts)
proxyAppOpts := configProxyApp(t, webServiceOpts)
defer terraform.Destroy(t, proxyAppOpts)
terraform.InitAndApply(t, proxyAppOpts)
validate(t, proxyAppOpts)
}
6. At the end of the test, undeploy the
proxy app and the web service
func configWebService(t *testing.T) *terraform.Options {
return &terraform.Options{
TerraformDir: "../examples/web-service",
}
}
The configWebService method
func configProxyApp(t *testing.T, webServiceOpts
*terraform.Options) *terraform.Options {
url := terraform.Output(t, webServiceOpts, "url")
return &terraform.Options{
TerraformDir: "../examples/proxy-app",
Vars: map[string]interface{}{
"url_to_proxy": url,
},
}
}
The configProxyApp method
func configProxyApp(t *testing.T, webServiceOpts
*terraform.Options) *terraform.Options {
url := terraform.Output(t, webServiceOpts, "url")
return &terraform.Options{
TerraformDir: "../examples/proxy-app",
Vars: map[string]interface{}{
"url_to_proxy": url,
},
}
}
1. Read the url output from the web-
service module
func configProxyApp(t *testing.T, webServiceOpts
*terraform.Options) *terraform.Options {
url := terraform.Output(t, webServiceOpts, "url")
return &terraform.Options{
TerraformDir: "../examples/proxy-app",
Vars: map[string]interface{}{
"url_to_proxy": url,
},
}
}
2. Pass it in as the url_to_proxy input to
the proxy-app module
func validate(t *testing.T, opts *terraform.Options) {
url := terraform.Output(t, opts, "url")
http_helper.HttpGetWithRetry(t,
url, // URL to test
200, // Expected status code
`{"text":"Hello, World!"}`, // Expected body
10, // Max retries
3 * time.Second // Time between retries
)
}
The validate method
$ go test -v -timeout 15m -run TestProxyApp
…
--- PASS: TestProxyApp (182.44s)
Run go test. You’re now testing
multiple modules together!
$ go test -v -timeout 15m -run TestProxyApp
…
--- PASS: TestProxyApp (182.44s)
But integration tests can take (many)
minutes to run…
Integration tests
1. Example: Terraform integration tests
2. Test parallelism
3. Test stages
4. Test retries
Infrastructure tests can take a
long time to run
One way to save time: run
tests in parallel
func TestProxyApp(t *testing.T) {
t.Parallel()
// The rest of the test code
}
func TestHelloWorldAppUnit(t *testing.T) {
t.Parallel()
// The rest of the test code
}
Enable test parallelism in Go by adding
t.Parallel() as the 1st line of each test.
$ go test -v -timeout 15m
=== RUN TestHelloWorldApp
=== RUN TestDockerKubernetes
=== RUN TestProxyApp
Now, if you run go test, all the tests
with t.Parallel() will run in parallel
But there’s a gotcha:
resource conflicts
resource "aws_iam_role" "role_example" {
name = "example-iam-role"
}
resource "aws_security_group" "sg_example" {
name = "security-group-example"
}
Example: module with hard-coded IAM
Role and Security Group names
resource "aws_iam_role" "role_example" {
name = "example-iam-role"
}
resource "aws_security_group" "sg_example" {
name = "security-group-example"
}
If two tests tried to deploy this module
in parallel, the names would conflict!
Key takeaway: you must
namespace all your resources
resource "aws_iam_role" "role_example" {
name = var.name
}
resource "aws_security_group" "sg_example" {
name = var.name
}
Example: use variables in all resource
names…
uniqueId := random.UniqueId()
return &terraform.Options{
TerraformDir: "../examples/proxy-app",
Vars: map[string]interface{}{
"name": fmt.Sprintf("text-proxy-app-%s", uniqueId)
},
}
At test time, set the variables to a
randomized value to avoid conflicts
Integration tests
1. Example: Terraform integration tests
2. Test parallelism
3. Test stages
4. Test retries
Consider the structure of the
proxy-app integration test:
1. Deploy web-service
2. Deploy proxy-app
3. Validate proxy-app
4. Undeploy proxy-app
5. Undeploy web-service
1. Deploy web-service
2. Deploy proxy-app
3. Validate proxy-app
4. Undeploy proxy-app
5. Undeploy web-service
When iterating locally, you sometimes
want to re-run just one of these steps.
1. Deploy web-service
2. Deploy proxy-app
3. Validate proxy-app
4. Undeploy proxy-app
5. Undeploy web-service
But as the code is written now, you
have to run all steps on each test run.
1. Deploy web-service
2. Deploy proxy-app
3. Validate proxy-app
4. Undeploy proxy-app
5. Undeploy web-service
And that can add up to a lot of
overhead.
(~3 min)
(~2 min)
(~30 seconds)
(~1 min)
(~2 min)
Key takeaway: break your
tests into independent test
stages
webServiceOpts := configWebService(t)
defer terraform.Destroy(t, webServiceOpts)
terraform.InitAndApply(t, webServiceOpts)
proxyAppOpts := configProxyApp(t, webServiceOpts)
defer terraform.Destroy(t, proxyAppOpts)
terraform.InitAndApply(t, proxyAppOpts)
validate(t, proxyAppOpts)
The original test structure
stage := test_structure.RunTestStage
defer stage(t, "cleanup_web_service", cleanupWebService)
stage(t, "deploy_web_service", deployWebService)
defer stage(t, "cleanup_proxy_app", cleanupProxyApp)
stage(t, "deploy_proxy_app", deployProxyApp)
stage(t, "validate", validate)
The test structure with test stages
stage := test_structure.RunTestStage
defer stage(t, "cleanup_web_service", cleanupWebService)
stage(t, "deploy_web_service", deployWebService)
defer stage(t, "cleanup_proxy_app", cleanupProxyApp)
stage(t, "deploy_proxy_app", deployProxyApp)
stage(t, "validate", validate)
1. RunTestStage is a helper function
from Terratest.
stage := test_structure.RunTestStage
defer stage(t, "cleanup_web_service", cleanupWebService)
stage(t, "deploy_web_service", deployWebService)
defer stage(t, "cleanup_proxy_app", cleanupProxyApp)
stage(t, "deploy_proxy_app", deployProxyApp)
stage(t, "validate", validate)
2. Wrap each stage of your test with a
call to RunTestStage
stage := test_structure.RunTestStage
defer stage(t, "cleanup_web_service", cleanupWebService)
stage(t, "deploy_web_service", deployWebService)
defer stage(t, "cleanup_proxy_app", cleanupProxyApp)
stage(t, "deploy_proxy_app", deployProxyApp)
stage(t, "validate", validate)
3. Define each stage in a function
(you’ll see this code shortly).
stage := test_structure.RunTestStage
defer stage(t, "cleanup_web_service", cleanupWebService)
stage(t, "deploy_web_service", deployWebService)
defer stage(t, "cleanup_proxy_app", cleanupProxyApp)
stage(t, "deploy_proxy_app", deployProxyApp)
stage(t, "validate", validate)
4. Give each stage a unique name
stage := test_structure.RunTestStage
defer stage(t, "cleanup_web_service", cleanupWebService)
stage(t, "deploy_web_service", deployWebService)
defer stage(t, "cleanup_proxy_app", cleanupProxyApp)
stage(t, "deploy_proxy_app", deployProxyApp)
stage(t, "validate", validate)
Any stage foo can be skipped by
setting the env var SKIP_foo=true
$ SKIP_cleanup_web_service=true
$ SKIP_cleanup_proxy_app=true
Example: on the very first test run, skip
the cleanup stages.
$ go test -v -timeout 15m -run TestProxyApp
Running stage 'deploy_web_service'…
Running stage 'deploy_proxy_app'…
Running stage 'validate'…
Skipping stage 'cleanup_proxy_app'…
Skipping stage 'cleanup_web_service'…
--- PASS: TestProxyApp (105.73s)
That way, after the test finishes, the
infrastructure will still be running.
$ SKIP_deploy_web_service=true
$ SKIP_deploy_proxy_app=true
Now, on the next several test runs, you
can skip the deploy stages too.
$ go test -v -timeout 15m -run TestProxyApp
Skipping stage 'deploy_web_service’…
Skipping stage 'deploy_proxy_app'…
Running stage 'validate'…
Skipping stage 'cleanup_proxy_app'…
Skipping stage 'cleanup_web_service'…
--- PASS: TestProxyApp (14.22s)
This allows you to iterate on solely the
validate stage…
$ go test -v -timeout 15m -run TestProxyApp
Skipping stage 'deploy_web_service’…
Skipping stage 'deploy_proxy_app'…
Running stage 'validate'…
Skipping stage 'cleanup_proxy_app'…
Skipping stage 'cleanup_web_service'…
--- PASS: TestProxyApp (14.22s)
Which dramatically speeds up your
iteration / feedback cycle!
$ SKIP_validate=true
$ unset SKIP_cleanup_web_service
$ unset SKIP_cleanup_proxy_app
When you’re done iterating, skip
validate and re-enable cleanup
$ go test -v -timeout 15m -run TestProxyApp
Skipping stage 'deploy_web_service’…
Skipping stage 'deploy_proxy_app’…
Skipping stage 'validate’…
Running stage 'cleanup_proxy_app’…
Running stage 'cleanup_web_service'…
--- PASS: TestProxyApp (59.61s)
This cleans up everything that was left
running.
func deployWebService(t *testing.T) {
opts := configWebServiceOpts(t)
test_structure.SaveTerraformOptions(t, "/tmp", opts)
terraform.InitAndApply(t, opts)
}
func cleanupWebService(t *testing.T) {
opts := test_structure.LoadTerraformOptions(t, "/tmp")
terraform.Destroy(t, opts)
}
Note: each time you run test stages via
go test, it’s a separate OS process.
func deployWebService(t *testing.T) {
opts := configWebServiceOpts(t)
test_structure.SaveTerraformOptions(t, "/tmp", opts)
terraform.InitAndApply(t, opts)
}
func cleanupWebService(t *testing.T) {
opts := test_structure.LoadTerraformOptions(t, "/tmp")
terraform.Destroy(t, opts)
}
So to pass data between stages, one
stage needs to write the data to disk…
func deployWebService(t *testing.T) {
opts := configWebServiceOpts(t)
test_structure.SaveTerraformOptions(t, "/tmp", opts)
terraform.InitAndApply(t, opts)
}
func cleanupWebService(t *testing.T) {
opts := test_structure.LoadTerraformOptions(t, "/tmp")
terraform.Destroy(t, opts)
}
And the other stages need to read that
data from disk.
Integration tests
1. Example: Terraform integration tests
2. Test parallelism
3. Test stages
4. Test retries
Real infrastructure can fail for
intermittent reasons
(e.g., bad EC2 instance, Apt downtime, Terraform bug)
To avoid “flaky” tests, add
retries for known errors.
&terraform.Options{
TerraformDir: "../examples/proxy-app",
RetryableTerraformErrors: map[string]string{
"net/http: TLS handshake timeout": "Terraform bug",
},
MaxRetries: 3,
TimeBetweenRetries: 3*time.Second,
}
Example: retry up to 3 times on a
known TLS error in Terraform.
1. Static analysis
2. Unit tests
3. Integration tests
4. End-to-end tests
5. Conclusion
Outline
End-to-end tests: test your
entire infrastructure works
together.
How do you test this entire thing?
You could use the same strategy…
1. Deploy all the infrastructure
2. Validate it works
(e.g., via HTTP requests, API calls, SSH commands, etc.)
3. Undeploy all the infrastructure
But it’s rare to write end-to-
end tests this way. Here’s why:
e2e
Tests
Test pyramid
Integration Tests
Unit Tests
Static analysis
e2e
Tests
Integration Tests
Unit Tests
Static analysis
Cost,
brittleness,
run time
e2e
Tests
Integration Tests
Unit Tests
Static analysis
60 – 240+
minutes
5 – 60
minutes
1 – 20
minutes
1 – 60
seconds
e2e
Tests
Integration Tests
Unit Tests
Static analysis
E2E tests are too slow to be useful
60 – 240+
minutes
5 – 60
minutes
1 – 20
minutes
1 – 60
seconds
Another problem with E2E
tests: brittleness.
Let’s do some math:
Assume a single resource (e.g.,
EC2 instance) has a 1/1000
(0.1%) chance of failure.
Test type # of resources Chance of failure
Unit tests 10 1%
Integration tests 50 5%
End-to-end tests 500+ 40%+
The more resources your tests deploy,
the flakier they will be.
Test type # of resources Chance of failure
Unit tests 10 1%
Integration tests 50 5%
End-to-end tests 500+ 40%+
You can work around the failure rate
for unit & integration tests with retries
Test type # of resources Chance of failure
Unit tests 10 1%
Integration tests 50 5%
End-to-end tests 500+ 40%+
You can work around the failure rate
for unit & integration tests with retries
Key takeaway: E2E tests from
scratch are too slow and too
brittle to be useful
Instead, you can do
incremental E2E testing!
module
module
module
module
module
module
module
module
module
module
module
module
module module
module
1. Deploy a persistent test
environment and leave it running.
module
module
module
module
module
module
module
module
module
module
module
module
module module
module
2. Each time you update a module,
deploy & validate just that module
module
module
module
module
module
module
module
module
module
module
module
module
module module
module
3. Bonus: test your deployment
process is zero-downtime too!
1. Static analysis
2. Unit tests
3. Integration tests
4. End-to-end tests
5. Conclusion
Outline
Testing techniques compared:
Technique Strengths Weaknesses
Static analysis
1. Fast
2. Stable
3. No need to deploy real resources
4. Easy to use
1. Very limited in errors you can catch
2. You don’t get much confidence in your
code solely from static analysis
Unit tests
1. Fast enough (1 – 10 min)
2. Mostly stable (with retry logic)
3. High level of confidence in individual units
1. Need to deploy real resources
2. Requires writing non-trivial code
Integration tests
1. Mostly stable (with retry logic)
2. High level of confidence in multiple units
working together
1. Need to deploy real resources
2. Requires writing non-trivial code
3. Slow (10 – 30 min)
End-to-end tests
1. Build confidence in your entire
architecture
1. Need to deploy real resources
2. Requires writing non-trivial code
3. Very slow (60 min – 240+ min)*
4. Can be brittle (even with retry logic)*
So which should you use?
All of them!
They all catch different types of bugs.
e2e
Tests
Keep in mind the test pyramid
Integration Tests
Unit Tests
Static analysis
e2e
Tests
Lots of unit tests + static analysis
Integration Tests
Unit Tests
Static analysis
e2e
Tests
Fewer integration tests
Integration Tests
Unit Tests
Static analysis
e2e
Tests
A handful of high-value e2e tests
Integration Tests
Unit Tests
Static analysis
Infrastructure code
without tests is scary
Fight the fear & build confidence in
your code with automated tests
Questions?
info@gruntwork.io
Watch the video with slide
synchronization on InfoQ.com!
https://www.infoq.com/presentations/a
utomated-testing-terraform-docker-
packer/

More Related Content

What's hot

[KubeCon NA 2020] containerd: Rootless Containers 2020
[KubeCon NA 2020] containerd: Rootless Containers 2020[KubeCon NA 2020] containerd: Rootless Containers 2020
[KubeCon NA 2020] containerd: Rootless Containers 2020Akihiro Suda
 
[오픈소스컨설팅] Open Stack Ceph, Neutron, HA, Multi-Region
[오픈소스컨설팅] Open Stack Ceph, Neutron, HA, Multi-Region[오픈소스컨설팅] Open Stack Ceph, Neutron, HA, Multi-Region
[오픈소스컨설팅] Open Stack Ceph, Neutron, HA, Multi-RegionJi-Woong Choi
 
How VXLAN works on Linux
How VXLAN works on LinuxHow VXLAN works on Linux
How VXLAN works on LinuxEtsuji Nakai
 
Service Discovery in Prometheus
Service Discovery in PrometheusService Discovery in Prometheus
Service Discovery in PrometheusOliver Moser
 
Kamailio :: A Quick Introduction
Kamailio :: A Quick IntroductionKamailio :: A Quick Introduction
Kamailio :: A Quick IntroductionOlle E Johansson
 
ONIC-Japan-2019-OVN public
ONIC-Japan-2019-OVN publicONIC-Japan-2019-OVN public
ONIC-Japan-2019-OVN publicManabu Ori
 
DevOps Meetup ansible
DevOps Meetup   ansibleDevOps Meetup   ansible
DevOps Meetup ansiblesriram_rajan
 
Webinar "Introduction to OpenStack"
Webinar "Introduction to OpenStack"Webinar "Introduction to OpenStack"
Webinar "Introduction to OpenStack"CREATE-NET
 
OpenShift Overview
OpenShift OverviewOpenShift Overview
OpenShift Overviewroundman
 
A Hands-on Introduction on Terraform Best Concepts and Best Practices
A Hands-on Introduction on Terraform Best Concepts and Best Practices A Hands-on Introduction on Terraform Best Concepts and Best Practices
A Hands-on Introduction on Terraform Best Concepts and Best Practices Nebulaworks
 
OpenShift 4 installation
OpenShift 4 installationOpenShift 4 installation
OpenShift 4 installationRobert Bohne
 
Terraform -- Infrastructure as Code
Terraform -- Infrastructure as CodeTerraform -- Infrastructure as Code
Terraform -- Infrastructure as CodeMartin Schütte
 
오픈스택 기반 클라우드 서비스 구축 방안 및 사례
오픈스택 기반 클라우드 서비스 구축 방안 및 사례오픈스택 기반 클라우드 서비스 구축 방안 및 사례
오픈스택 기반 클라우드 서비스 구축 방안 및 사례SONG INSEOB
 
Infrastructure-as-Code (IaC) using Terraform
Infrastructure-as-Code (IaC) using TerraformInfrastructure-as-Code (IaC) using Terraform
Infrastructure-as-Code (IaC) using TerraformAdin Ermie
 
Cloud adoption fails - 5 ways deployments go wrong and 5 solutions
Cloud adoption fails - 5 ways deployments go wrong and 5 solutionsCloud adoption fails - 5 ways deployments go wrong and 5 solutions
Cloud adoption fails - 5 ways deployments go wrong and 5 solutionsYevgeniy Brikman
 

What's hot (20)

[KubeCon NA 2020] containerd: Rootless Containers 2020
[KubeCon NA 2020] containerd: Rootless Containers 2020[KubeCon NA 2020] containerd: Rootless Containers 2020
[KubeCon NA 2020] containerd: Rootless Containers 2020
 
Introduce to Terraform
Introduce to TerraformIntroduce to Terraform
Introduce to Terraform
 
[오픈소스컨설팅] Open Stack Ceph, Neutron, HA, Multi-Region
[오픈소스컨설팅] Open Stack Ceph, Neutron, HA, Multi-Region[오픈소스컨설팅] Open Stack Ceph, Neutron, HA, Multi-Region
[오픈소스컨설팅] Open Stack Ceph, Neutron, HA, Multi-Region
 
Terraform
TerraformTerraform
Terraform
 
How VXLAN works on Linux
How VXLAN works on LinuxHow VXLAN works on Linux
How VXLAN works on Linux
 
Service Discovery in Prometheus
Service Discovery in PrometheusService Discovery in Prometheus
Service Discovery in Prometheus
 
Kamailio :: A Quick Introduction
Kamailio :: A Quick IntroductionKamailio :: A Quick Introduction
Kamailio :: A Quick Introduction
 
ONIC-Japan-2019-OVN public
ONIC-Japan-2019-OVN publicONIC-Japan-2019-OVN public
ONIC-Japan-2019-OVN public
 
Docker introduction
Docker introductionDocker introduction
Docker introduction
 
DevOps Meetup ansible
DevOps Meetup   ansibleDevOps Meetup   ansible
DevOps Meetup ansible
 
Webinar "Introduction to OpenStack"
Webinar "Introduction to OpenStack"Webinar "Introduction to OpenStack"
Webinar "Introduction to OpenStack"
 
OpenShift Overview
OpenShift OverviewOpenShift Overview
OpenShift Overview
 
A Hands-on Introduction on Terraform Best Concepts and Best Practices
A Hands-on Introduction on Terraform Best Concepts and Best Practices A Hands-on Introduction on Terraform Best Concepts and Best Practices
A Hands-on Introduction on Terraform Best Concepts and Best Practices
 
OpenShift 4 installation
OpenShift 4 installationOpenShift 4 installation
OpenShift 4 installation
 
Terraform -- Infrastructure as Code
Terraform -- Infrastructure as CodeTerraform -- Infrastructure as Code
Terraform -- Infrastructure as Code
 
오픈스택 기반 클라우드 서비스 구축 방안 및 사례
오픈스택 기반 클라우드 서비스 구축 방안 및 사례오픈스택 기반 클라우드 서비스 구축 방안 및 사례
오픈스택 기반 클라우드 서비스 구축 방안 및 사례
 
Openshift
Openshift Openshift
Openshift
 
Infrastructure-as-Code (IaC) using Terraform
Infrastructure-as-Code (IaC) using TerraformInfrastructure-as-Code (IaC) using Terraform
Infrastructure-as-Code (IaC) using Terraform
 
Terraform
TerraformTerraform
Terraform
 
Cloud adoption fails - 5 ways deployments go wrong and 5 solutions
Cloud adoption fails - 5 ways deployments go wrong and 5 solutionsCloud adoption fails - 5 ways deployments go wrong and 5 solutions
Cloud adoption fails - 5 ways deployments go wrong and 5 solutions
 

Similar to Automated Testing for Terraform, Docker, Packer, Kubernetes, and More

Infrastructure as code, using Terraform
Infrastructure as code, using TerraformInfrastructure as code, using Terraform
Infrastructure as code, using TerraformHarkamal Singh
 
AWS Lambda from the trenches
AWS Lambda from the trenchesAWS Lambda from the trenches
AWS Lambda from the trenchesYan Cui
 
Serverless in production, an experience report (JeffConf)
Serverless in production, an experience report (JeffConf)Serverless in production, an experience report (JeffConf)
Serverless in production, an experience report (JeffConf)Yan Cui
 
quickguide-einnovator-4-cloudfoundry
quickguide-einnovator-4-cloudfoundryquickguide-einnovator-4-cloudfoundry
quickguide-einnovator-4-cloudfoundryjorgesimao71
 
Serverless in production, an experience report (linuxing in london)
Serverless in production, an experience report (linuxing in london)Serverless in production, an experience report (linuxing in london)
Serverless in production, an experience report (linuxing in london)Yan Cui
 
RichFaces - Testing on Mobile Devices
RichFaces - Testing on Mobile DevicesRichFaces - Testing on Mobile Devices
RichFaces - Testing on Mobile DevicesPavol Pitoňák
 
Serverless Beyond Functions - CTO Club Made in JLM
Serverless Beyond Functions - CTO Club Made in JLMServerless Beyond Functions - CTO Club Made in JLM
Serverless Beyond Functions - CTO Club Made in JLMBoaz Ziniman
 
Serverless in production, an experience report (CoDe-Conf)
Serverless in production, an experience report (CoDe-Conf)Serverless in production, an experience report (CoDe-Conf)
Serverless in production, an experience report (CoDe-Conf)Yan Cui
 
Application Lifecycle Management in a Serverless World
Application Lifecycle Management in a Serverless WorldApplication Lifecycle Management in a Serverless World
Application Lifecycle Management in a Serverless WorldAmazon Web Services
 
Serverless in production, an experience report (Going Serverless)
Serverless in production, an experience report (Going Serverless)Serverless in production, an experience report (Going Serverless)
Serverless in production, an experience report (Going Serverless)Yan Cui
 
Containers as a Service with Docker
Containers as a Service with DockerContainers as a Service with Docker
Containers as a Service with DockerDocker, Inc.
 
Docker Container As A Service - March 2016
Docker Container As A Service - March 2016Docker Container As A Service - March 2016
Docker Container As A Service - March 2016Patrick Chanezon
 
Alfresco Development Framework Basic
Alfresco Development Framework BasicAlfresco Development Framework Basic
Alfresco Development Framework BasicMario Romano
 
Devfest 2023 - Service Weaver Introduction - Taipei.pdf
Devfest 2023 - Service Weaver Introduction - Taipei.pdfDevfest 2023 - Service Weaver Introduction - Taipei.pdf
Devfest 2023 - Service Weaver Introduction - Taipei.pdfKAI CHU CHUNG
 
Test cloud application deployments locally and in CI without staging environm...
Test cloud application deployments locally and in CI without staging environm...Test cloud application deployments locally and in CI without staging environm...
Test cloud application deployments locally and in CI without staging environm...Thomas Rausch
 
AWS Sydney Summit 2013 - Continuous Deployment Practices, with Production, Te...
AWS Sydney Summit 2013 - Continuous Deployment Practices, with Production, Te...AWS Sydney Summit 2013 - Continuous Deployment Practices, with Production, Te...
AWS Sydney Summit 2013 - Continuous Deployment Practices, with Production, Te...Amazon Web Services
 
PHP Buildpacks in the Cloud on Bluemix
PHP Buildpacks in the Cloud on BluemixPHP Buildpacks in the Cloud on Bluemix
PHP Buildpacks in the Cloud on BluemixIBM
 
Cloud Foundry for PHP developers
Cloud Foundry for PHP developersCloud Foundry for PHP developers
Cloud Foundry for PHP developersDaniel Krook
 
AWS CodeDeploy - basic intro
AWS CodeDeploy - basic introAWS CodeDeploy - basic intro
AWS CodeDeploy - basic introAnton Babenko
 
Node Interactive: Node.js Performance and Highly Scalable Micro-Services
Node Interactive: Node.js Performance and Highly Scalable Micro-ServicesNode Interactive: Node.js Performance and Highly Scalable Micro-Services
Node Interactive: Node.js Performance and Highly Scalable Micro-ServicesChris Bailey
 

Similar to Automated Testing for Terraform, Docker, Packer, Kubernetes, and More (20)

Infrastructure as code, using Terraform
Infrastructure as code, using TerraformInfrastructure as code, using Terraform
Infrastructure as code, using Terraform
 
AWS Lambda from the trenches
AWS Lambda from the trenchesAWS Lambda from the trenches
AWS Lambda from the trenches
 
Serverless in production, an experience report (JeffConf)
Serverless in production, an experience report (JeffConf)Serverless in production, an experience report (JeffConf)
Serverless in production, an experience report (JeffConf)
 
quickguide-einnovator-4-cloudfoundry
quickguide-einnovator-4-cloudfoundryquickguide-einnovator-4-cloudfoundry
quickguide-einnovator-4-cloudfoundry
 
Serverless in production, an experience report (linuxing in london)
Serverless in production, an experience report (linuxing in london)Serverless in production, an experience report (linuxing in london)
Serverless in production, an experience report (linuxing in london)
 
RichFaces - Testing on Mobile Devices
RichFaces - Testing on Mobile DevicesRichFaces - Testing on Mobile Devices
RichFaces - Testing on Mobile Devices
 
Serverless Beyond Functions - CTO Club Made in JLM
Serverless Beyond Functions - CTO Club Made in JLMServerless Beyond Functions - CTO Club Made in JLM
Serverless Beyond Functions - CTO Club Made in JLM
 
Serverless in production, an experience report (CoDe-Conf)
Serverless in production, an experience report (CoDe-Conf)Serverless in production, an experience report (CoDe-Conf)
Serverless in production, an experience report (CoDe-Conf)
 
Application Lifecycle Management in a Serverless World
Application Lifecycle Management in a Serverless WorldApplication Lifecycle Management in a Serverless World
Application Lifecycle Management in a Serverless World
 
Serverless in production, an experience report (Going Serverless)
Serverless in production, an experience report (Going Serverless)Serverless in production, an experience report (Going Serverless)
Serverless in production, an experience report (Going Serverless)
 
Containers as a Service with Docker
Containers as a Service with DockerContainers as a Service with Docker
Containers as a Service with Docker
 
Docker Container As A Service - March 2016
Docker Container As A Service - March 2016Docker Container As A Service - March 2016
Docker Container As A Service - March 2016
 
Alfresco Development Framework Basic
Alfresco Development Framework BasicAlfresco Development Framework Basic
Alfresco Development Framework Basic
 
Devfest 2023 - Service Weaver Introduction - Taipei.pdf
Devfest 2023 - Service Weaver Introduction - Taipei.pdfDevfest 2023 - Service Weaver Introduction - Taipei.pdf
Devfest 2023 - Service Weaver Introduction - Taipei.pdf
 
Test cloud application deployments locally and in CI without staging environm...
Test cloud application deployments locally and in CI without staging environm...Test cloud application deployments locally and in CI without staging environm...
Test cloud application deployments locally and in CI without staging environm...
 
AWS Sydney Summit 2013 - Continuous Deployment Practices, with Production, Te...
AWS Sydney Summit 2013 - Continuous Deployment Practices, with Production, Te...AWS Sydney Summit 2013 - Continuous Deployment Practices, with Production, Te...
AWS Sydney Summit 2013 - Continuous Deployment Practices, with Production, Te...
 
PHP Buildpacks in the Cloud on Bluemix
PHP Buildpacks in the Cloud on BluemixPHP Buildpacks in the Cloud on Bluemix
PHP Buildpacks in the Cloud on Bluemix
 
Cloud Foundry for PHP developers
Cloud Foundry for PHP developersCloud Foundry for PHP developers
Cloud Foundry for PHP developers
 
AWS CodeDeploy - basic intro
AWS CodeDeploy - basic introAWS CodeDeploy - basic intro
AWS CodeDeploy - basic intro
 
Node Interactive: Node.js Performance and Highly Scalable Micro-Services
Node Interactive: Node.js Performance and Highly Scalable Micro-ServicesNode Interactive: Node.js Performance and Highly Scalable Micro-Services
Node Interactive: Node.js Performance and Highly Scalable Micro-Services
 

More from C4Media

Streaming a Million Likes/Second: Real-Time Interactions on Live Video
Streaming a Million Likes/Second: Real-Time Interactions on Live VideoStreaming a Million Likes/Second: Real-Time Interactions on Live Video
Streaming a Million Likes/Second: Real-Time Interactions on Live VideoC4Media
 
Next Generation Client APIs in Envoy Mobile
Next Generation Client APIs in Envoy MobileNext Generation Client APIs in Envoy Mobile
Next Generation Client APIs in Envoy MobileC4Media
 
Software Teams and Teamwork Trends Report Q1 2020
Software Teams and Teamwork Trends Report Q1 2020Software Teams and Teamwork Trends Report Q1 2020
Software Teams and Teamwork Trends Report Q1 2020C4Media
 
Understand the Trade-offs Using Compilers for Java Applications
Understand the Trade-offs Using Compilers for Java ApplicationsUnderstand the Trade-offs Using Compilers for Java Applications
Understand the Trade-offs Using Compilers for Java ApplicationsC4Media
 
Kafka Needs No Keeper
Kafka Needs No KeeperKafka Needs No Keeper
Kafka Needs No KeeperC4Media
 
High Performing Teams Act Like Owners
High Performing Teams Act Like OwnersHigh Performing Teams Act Like Owners
High Performing Teams Act Like OwnersC4Media
 
Does Java Need Inline Types? What Project Valhalla Can Bring to Java
Does Java Need Inline Types? What Project Valhalla Can Bring to JavaDoes Java Need Inline Types? What Project Valhalla Can Bring to Java
Does Java Need Inline Types? What Project Valhalla Can Bring to JavaC4Media
 
Service Meshes- The Ultimate Guide
Service Meshes- The Ultimate GuideService Meshes- The Ultimate Guide
Service Meshes- The Ultimate GuideC4Media
 
Shifting Left with Cloud Native CI/CD
Shifting Left with Cloud Native CI/CDShifting Left with Cloud Native CI/CD
Shifting Left with Cloud Native CI/CDC4Media
 
CI/CD for Machine Learning
CI/CD for Machine LearningCI/CD for Machine Learning
CI/CD for Machine LearningC4Media
 
Fault Tolerance at Speed
Fault Tolerance at SpeedFault Tolerance at Speed
Fault Tolerance at SpeedC4Media
 
Architectures That Scale Deep - Regaining Control in Deep Systems
Architectures That Scale Deep - Regaining Control in Deep SystemsArchitectures That Scale Deep - Regaining Control in Deep Systems
Architectures That Scale Deep - Regaining Control in Deep SystemsC4Media
 
ML in the Browser: Interactive Experiences with Tensorflow.js
ML in the Browser: Interactive Experiences with Tensorflow.jsML in the Browser: Interactive Experiences with Tensorflow.js
ML in the Browser: Interactive Experiences with Tensorflow.jsC4Media
 
Build Your Own WebAssembly Compiler
Build Your Own WebAssembly CompilerBuild Your Own WebAssembly Compiler
Build Your Own WebAssembly CompilerC4Media
 
User & Device Identity for Microservices @ Netflix Scale
User & Device Identity for Microservices @ Netflix ScaleUser & Device Identity for Microservices @ Netflix Scale
User & Device Identity for Microservices @ Netflix ScaleC4Media
 
Scaling Patterns for Netflix's Edge
Scaling Patterns for Netflix's EdgeScaling Patterns for Netflix's Edge
Scaling Patterns for Netflix's EdgeC4Media
 
Make Your Electron App Feel at Home Everywhere
Make Your Electron App Feel at Home EverywhereMake Your Electron App Feel at Home Everywhere
Make Your Electron App Feel at Home EverywhereC4Media
 
The Talk You've Been Await-ing For
The Talk You've Been Await-ing ForThe Talk You've Been Await-ing For
The Talk You've Been Await-ing ForC4Media
 
Future of Data Engineering
Future of Data EngineeringFuture of Data Engineering
Future of Data EngineeringC4Media
 
Navigating Complexity: High-performance Delivery and Discovery Teams
Navigating Complexity: High-performance Delivery and Discovery TeamsNavigating Complexity: High-performance Delivery and Discovery Teams
Navigating Complexity: High-performance Delivery and Discovery TeamsC4Media
 

More from C4Media (20)

Streaming a Million Likes/Second: Real-Time Interactions on Live Video
Streaming a Million Likes/Second: Real-Time Interactions on Live VideoStreaming a Million Likes/Second: Real-Time Interactions on Live Video
Streaming a Million Likes/Second: Real-Time Interactions on Live Video
 
Next Generation Client APIs in Envoy Mobile
Next Generation Client APIs in Envoy MobileNext Generation Client APIs in Envoy Mobile
Next Generation Client APIs in Envoy Mobile
 
Software Teams and Teamwork Trends Report Q1 2020
Software Teams and Teamwork Trends Report Q1 2020Software Teams and Teamwork Trends Report Q1 2020
Software Teams and Teamwork Trends Report Q1 2020
 
Understand the Trade-offs Using Compilers for Java Applications
Understand the Trade-offs Using Compilers for Java ApplicationsUnderstand the Trade-offs Using Compilers for Java Applications
Understand the Trade-offs Using Compilers for Java Applications
 
Kafka Needs No Keeper
Kafka Needs No KeeperKafka Needs No Keeper
Kafka Needs No Keeper
 
High Performing Teams Act Like Owners
High Performing Teams Act Like OwnersHigh Performing Teams Act Like Owners
High Performing Teams Act Like Owners
 
Does Java Need Inline Types? What Project Valhalla Can Bring to Java
Does Java Need Inline Types? What Project Valhalla Can Bring to JavaDoes Java Need Inline Types? What Project Valhalla Can Bring to Java
Does Java Need Inline Types? What Project Valhalla Can Bring to Java
 
Service Meshes- The Ultimate Guide
Service Meshes- The Ultimate GuideService Meshes- The Ultimate Guide
Service Meshes- The Ultimate Guide
 
Shifting Left with Cloud Native CI/CD
Shifting Left with Cloud Native CI/CDShifting Left with Cloud Native CI/CD
Shifting Left with Cloud Native CI/CD
 
CI/CD for Machine Learning
CI/CD for Machine LearningCI/CD for Machine Learning
CI/CD for Machine Learning
 
Fault Tolerance at Speed
Fault Tolerance at SpeedFault Tolerance at Speed
Fault Tolerance at Speed
 
Architectures That Scale Deep - Regaining Control in Deep Systems
Architectures That Scale Deep - Regaining Control in Deep SystemsArchitectures That Scale Deep - Regaining Control in Deep Systems
Architectures That Scale Deep - Regaining Control in Deep Systems
 
ML in the Browser: Interactive Experiences with Tensorflow.js
ML in the Browser: Interactive Experiences with Tensorflow.jsML in the Browser: Interactive Experiences with Tensorflow.js
ML in the Browser: Interactive Experiences with Tensorflow.js
 
Build Your Own WebAssembly Compiler
Build Your Own WebAssembly CompilerBuild Your Own WebAssembly Compiler
Build Your Own WebAssembly Compiler
 
User & Device Identity for Microservices @ Netflix Scale
User & Device Identity for Microservices @ Netflix ScaleUser & Device Identity for Microservices @ Netflix Scale
User & Device Identity for Microservices @ Netflix Scale
 
Scaling Patterns for Netflix's Edge
Scaling Patterns for Netflix's EdgeScaling Patterns for Netflix's Edge
Scaling Patterns for Netflix's Edge
 
Make Your Electron App Feel at Home Everywhere
Make Your Electron App Feel at Home EverywhereMake Your Electron App Feel at Home Everywhere
Make Your Electron App Feel at Home Everywhere
 
The Talk You've Been Await-ing For
The Talk You've Been Await-ing ForThe Talk You've Been Await-ing For
The Talk You've Been Await-ing For
 
Future of Data Engineering
Future of Data EngineeringFuture of Data Engineering
Future of Data Engineering
 
Navigating Complexity: High-performance Delivery and Discovery Teams
Navigating Complexity: High-performance Delivery and Discovery TeamsNavigating Complexity: High-performance Delivery and Discovery Teams
Navigating Complexity: High-performance Delivery and Discovery Teams
 

Recently uploaded

A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersThousandEyes
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...HostedbyConfluent
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...gurkirankumar98700
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGSujit Pal
 

Recently uploaded (20)

A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAG
 

Automated Testing for Terraform, Docker, Packer, Kubernetes, and More

  • 1. Automated testing for: ✓ terraform ✓ docker ✓ packer ✓ kubernetes ✓ and more Passed: 5. Failed: 0. Skipped: 0. Test run successful. How to test infrastructure code
  • 2. InfoQ.com: News & Community Site • Over 1,000,000 software developers, architects and CTOs read the site world- wide every month • 250,000 senior developers subscribe to our weekly newsletter • Published in 4 languages (English, Chinese, Japanese and Brazilian Portuguese) • Post content from our QCon conferences • 2 dedicated podcast channels: The InfoQ Podcast, with a focus on Architecture and The Engineering Culture Podcast, with a focus on building • 96 deep dives on innovative topics packed as downloadable emags and minibooks • Over 40 new content items per week Watch the video with slide synchronization on InfoQ.com! https://www.infoq.com/presentations/ automated-testing-terraform-docker- packer/
  • 3. Purpose of QCon - to empower software development by facilitating the spread of knowledge and innovation Strategy - practitioner-driven conference designed for YOU: influencers of change and innovation in your teams - speakers and topics driving the evolution and innovation - connecting and catalyzing the influencers and innovators Highlights - attended by more than 12,000 delegates since 2007 - held in 9 cities worldwide Presented at QCon San Francisco www.qconsf.com
  • 4. The DevOps world is full of Fear
  • 6. Fear of security breaches
  • 9. “Fear leads to anger. Anger leads to hate. Hate leads to suffering.” Scrum Master Yoda
  • 10. And you all know what suffering leads to, right?
  • 11.
  • 13.
  • 14.
  • 15. Many DevOps teams deal with this fear in two ways:
  • 16. 1) Heavy drinking and smoking
  • 17. 2) Deploying less frequently
  • 18. Sadly, both of these just make the problem worse!
  • 19.
  • 20. There’s a better way to deal with this fear:
  • 22. Automated tests give you the confidence to make changes
  • 23. Fight fear with confidence
  • 24. We know how to write automated tests for application code…
  • 25. resource "aws_lambda_function" "web_app" { function_name = var.name role = aws_iam_role.lambda.arn # ... } resource "aws_api_gateway_integration" "proxy" { type = "AWS_PROXY" uri = aws_lambda_function.web_app.invoke_arn # ... } But how do you test your Terraform code deploys infrastructure that works?
  • 26. apiVersion: apps/v1 kind: Deployment metadata: name: hello-world-app-deployment spec: selector: matchLabels: app: hello-world-app replicas: 1 spec: containers: - name: hello-world-app image: gruntwork-io/hello-world-app:v1 ports: - containerPort: 8080 How do you test your Kubernetes code configures your services correctly?
  • 27. This talk is about how to write tests for your infrastructure code.
  • 31. 1. Static analysis 2. Unit tests 3. Integration tests 4. End-to-end tests 5. Conclusion Outline
  • 32. 1. Static analysis 2. Unit tests 3. Integration tests 4. End-to-end tests 5. Conclusion Outline
  • 33. Static analysis: test your code without deploying it.
  • 34. Static analysis 1. Compiler / parser / interpreter 2. Linter 3. Dry run
  • 35. Static analysis 1. Compiler / parser / interpreter 2. Linter 3. Dry run
  • 36. Statically check your code for syntactic and structural issues
  • 37. Tool Command Terraform terraform validate Packer packer validate <template> Kubernetes kubectl apply -f <file> --dry-run --validate=true Examples:
  • 38. Static analysis 1. Compiler / parser / interpreter 2. Linter 3. Dry run
  • 39. Statically validate your code to catch common errors
  • 40. Tool Linters Terraform 1. conftest 2. terraform_validate 3. tflint Docker 1. dockerfile_lint 2. hadolint 3. dockerfilelint Kubernetes 1. kube-score 2. kube-lint 3. yamllint Examples:
  • 41. Static analysis 1. Compiler / parser / interpreter 2. Linter 3. Dry run
  • 42. Partially execute the code and validate the “plan”, but don’t actually deploy
  • 43. Tool Dry run options Terraform 1. terraform plan 2. HashiCorp Sentinel 3. terraform-compliance Kubernetes kubectl apply -f <file> --server-dry-run Examples:
  • 44. 1. Static analysis 2. Unit tests 3. Integration tests 4. End-to-end tests 5. Conclusion Outline
  • 45. Unit tests: test a single “unit” works in isolation.
  • 46. Unit tests 1. Unit testing basics 2. Example: Terraform unit tests 3. Example: Docker/Kubernetes unit tests 4. Cleaning up after tests
  • 47. Unit tests 1. Unit testing basics 2. Example: Terraform unit tests 3. Example: Docker/Kubernetes unit tests 4. Cleaning up after tests
  • 48. You can’t “unit test” an entire end- to-end architecture
  • 49. Instead, break your infra code into small modules and unit test those! module module module module module module module module module module module module module module module
  • 50. With app code, you can test units in isolation from the outside world
  • 51. resource "aws_lambda_function" "web_app" { function_name = var.name role = aws_iam_role.lambda.arn # ... } resource "aws_api_gateway_integration" "proxy" { type = "AWS_PROXY" uri = aws_lambda_function.web_app.invoke_arn # ... } But 99% of infrastructure code is about talking to the outside world…
  • 52. resource "aws_lambda_function" "web_app" { function_name = var.name role = aws_iam_role.lambda.arn # ... } resource "aws_api_gateway_integration" "proxy" { type = "AWS_PROXY" uri = aws_lambda_function.web_app.invoke_arn # ... } If you try to isolate a unit from the outside world, you’re left with nothing!
  • 53. So you can only test infra code by deploying to a real environment
  • 54. Key takeaway: there’s no pure unit testing for infrastructure code.
  • 55. Therefore, the test strategy is: 1. Deploy real infrastructure 2. Validate it works (e.g., via HTTP requests, API calls, SSH commands, etc.) 3. Undeploy the infrastructure (So it’s really integration testing of a single unit!)
  • 56. Tool Deploy / Undeploy Validate Works with Terratest Yes Yes Terraform, Kubernetes, Packer, Docker, Servers, Cloud APIs, etc. kitchen-terraform Yes Yes Terraform Inspec No Yes Servers, Cloud APIs Serverspec No Yes Servers Goss No Yes Servers Tools that help with this strategy:
  • 57. Tool Deploy / Undeploy Validate Works with Terratest Yes Yes Terraform, Kubernetes, Packer, Docker, Servers, Cloud APIs, etc. kitchen-terraform Yes Yes Terraform Inspec No Yes Servers, Cloud APIs Serverspec No Yes Servers Goss No Yes Servers In this talk, we’ll use Terratest:
  • 58. Unit tests 1. Unit testing basics 2. Example: Terraform unit tests 3. Example: Docker/Kubernetes unit tests 4. Cleaning up after tests
  • 59. Sample code for this talk is at: github.com/gruntwork-io/infrastructure-as-code-testing-talk
  • 60. An example of a Terraform module you may want to test:
  • 61. infrastructure-as-code-testing-talk └ examples └ hello-world-app └ main.tf └ outputs.tf └ variables.tf └ modules └ test └ README.md hello-world-app: deploy a “Hello, World” web service
  • 62. resource "aws_lambda_function" "web_app" { function_name = var.name role = aws_iam_role.lambda.arn # ... } resource "aws_api_gateway_integration" "proxy" { type = "AWS_PROXY" uri = aws_lambda_function.web_app.invoke_arn # ... } Under the hood, this example runs on top of AWS Lambda & API Gateway
  • 63. $ terraform apply Outputs: url = ruvvwv3sh1.execute-api.us-east-2.amazonaws.com $ curl ruvvwv3sh1.execute-api.us-east-2.amazonaws.com Hello, World! When you run terraform apply, it deploys and outputs the URL
  • 64. Let’s write a unit test for hello-world-app with Terratest
  • 65. infrastructure-as-code-testing-talk └ examples └ modules └ test └ hello_world_app_test.go └ README.md Create hello_world_app_test.go
  • 66. func TestHelloWorldAppUnit(t *testing.T) { terraformOptions := &terraform.Options{ TerraformDir: "../examples/hello-world-app", } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) validate(t, terraformOptions) } The basic test structure
  • 67. func TestHelloWorldAppUnit(t *testing.T) { terraformOptions := &terraform.Options{ TerraformDir: "../examples/hello-world-app", } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) validate(t, terraformOptions) } 1. Tell Terratest where your Terraform code lives
  • 68. func TestHelloWorldAppUnit(t *testing.T) { terraformOptions := &terraform.Options{ TerraformDir: "../examples/hello-world-app", } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) validate(t, terraformOptions) } 2. Run terraform init and terraform apply to deploy your module
  • 69. func TestHelloWorldAppUnit(t *testing.T) { terraformOptions := &terraform.Options{ TerraformDir: "../examples/hello-world-app", } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) validate(t, terraformOptions) } 3. Validate the infrastructure works. We’ll come back to this shortly.
  • 70. func TestHelloWorldAppUnit(t *testing.T) { terraformOptions := &terraform.Options{ TerraformDir: "../examples/hello-world-app", } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) validate(t, terraformOptions) } 4. Run terraform destroy at the end of the test to undeploy everything
  • 71. func validate(t *testing.T, opts *terraform.Options) { url := terraform.Output(t, opts, "url") http_helper.HttpGetWithRetry(t, url, // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3 * time.Second // Time between retries ) } The validate function
  • 72. func validate(t *testing.T, opts *terraform.Options) { url := terraform.Output(t, opts, "url") http_helper.HttpGetWithRetry(t, url, // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3 * time.Second // Time between retries ) } 1. Run terraform output to get the web service URL
  • 73. func validate(t *testing.T, opts *terraform.Options) { url := terraform.Output(t, opts, "url") http_helper.HttpGetWithRetry(t, url, // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3 * time.Second // Time between retries ) } 2. Make HTTP requests to the URL
  • 74. func validate(t *testing.T, opts *terraform.Options) { url := terraform.Output(t, opts, "url") http_helper.HttpGetWithRetry(t, url, // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3 * time.Second // Time between retries ) } 3. Check the response for an expected status and body
  • 75. func validate(t *testing.T, opts *terraform.Options) { url := terraform.Output(t, opts, "url") http_helper.HttpGetWithRetry(t, url, // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3 * time.Second // Time between retries ) } 4. Retry the request up to 10 times, as deployment is asynchronous
  • 76. Note: since we’re testing a web service, we use HTTP requests to validate it.
  • 77. Infrastructure Example Validate with… Example Web service Dockerized web app HTTP requests Terratest http_helper package Server EC2 instance SSH commands Terratest ssh package Cloud service SQS Cloud APIs Terratest aws or gcp packages Database MySQL SQL queries MySQL driver for Go Examples of other ways to validate:
  • 78. $ export AWS_ACCESS_KEY_ID=xxxx $ export AWS_SECRET_ACCESS_KEY=xxxxx To run the test, first authenticate to AWS
  • 79. $ go test -v -timeout 15m -run TestHelloWorldAppUnit … --- PASS: TestHelloWorldAppUnit (31.57s) Then run go test. You now have a unit test you can run after every commit!
  • 80. Unit tests 1. Unit testing basics 2. Example: Terraform unit tests 3. Example: Docker/Kubernetes unit tests 4. Cleaning up after tests
  • 81. What about other tools, such as Docker + Kubernetes?
  • 82. infrastructure-as-code-testing-talk └ examples └ hello-world-app └ docker-kubernetes └ Dockerfile └ deployment.yml └ modules └ test └ README.md docker-kubernetes: deploy a “Hello, World” web service to Kubernetes
  • 83. FROM ubuntu:18.04 EXPOSE 8080 RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y busybox RUN echo 'Hello, World!' > index.html CMD ["busybox", "httpd", "-f", "-p", "8080"] Dockerfile: Dockerize a simple “Hello, World!” web service
  • 84. apiVersion: apps/v1 kind: Deployment metadata: name: hello-world-app-deployment spec: selector: matchLabels: app: hello-world-app replicas: 1 spec: containers: - name: hello-world-app image: gruntwork-io/hello-world-app:v1 ports: - containerPort: 8080 deployment.yml: define how to deploy a Docker container in Kubernetes
  • 85. $ cd examples/docker-kubernetes $ docker build -t gruntwork-io/hello-world-app:v1 . Successfully tagged gruntwork-io/hello-world-app:v1 $ kubectl apply -f deployment.yml deployment.apps/hello-world-app-deployment created service/hello-world-app-service created $ curl localhost:8080 Hello, World! Build the Docker image, deploy to Kubernetes, and check URL
  • 86. Let’s write a unit test for this code.
  • 87. infrastructure-as-code-testing-talk └ examples └ modules └ test └ hello_world_app_test.go └ docker_kubernetes_test.go └ README.md Create docker_kubernetes_test.go
  • 88. func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } The basic test structure
  • 89. func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 1. Build the Docker image. You’ll see the buildDockerImage method shortly.
  • 90. func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 2. Tell Terratest where your Kubernetes deployment is defined
  • 91. func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 3. Configure kubectl options to authenticate to Kubernetes
  • 92. func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 4. Run kubectl apply to deploy the web app to Kubernetes
  • 93. func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 5. Check the app is working. You’ll see the validate method shortly.
  • 94. func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 6. At the end of the test, remove all Kubernetes resources you deployed
  • 95. func buildDockerImage(t *testing.T) { options := &docker.BuildOptions{ Tags: []string{"gruntwork-io/hello-world-app:v1"}, } path := "../examples/docker-kubernetes" docker.Build(t, path, options) } The buildDockerImage method
  • 96. func validate(t *testing.T, opts *k8s.KubectlOptions) { k8s.WaitUntilServiceAvailable(t, opts, "hello-world- app-service", 10, 1*time.Second) http_helper.HttpGetWithRetry(t, serviceUrl(t, opts), // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3*time.Second // Time between retries ) } The validate method
  • 97. func validate(t *testing.T, opts *k8s.KubectlOptions) { k8s.WaitUntilServiceAvailable(t, opts, "hello-world- app-service", 10, 1*time.Second) http_helper.HttpGetWithRetry(t, serviceUrl(t, opts), // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3*time.Second // Time between retries ) } 1. Wait until the service is deployed
  • 98. func validate(t *testing.T, opts *k8s.KubectlOptions) { k8s.WaitUntilServiceAvailable(t, opts, "hello-world- app-service", 10, 1*time.Second) http_helper.HttpGetWithRetry(t, serviceUrl(t, opts), // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3*time.Second // Time between retries ) } 2. Make HTTP requests
  • 99. func validate(t *testing.T, opts *k8s.KubectlOptions) { k8s.WaitUntilServiceAvailable(t, opts, "hello-world- app-service", 10, 1*time.Second) http_helper.HttpGetWithRetry(t, serviceUrl(t, opts), // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3*time.Second // Time between retries ) } 3. Use serviceUrl method to get URL
  • 100. func serviceUrl(t *testing.T, opts *k8s.KubectlOptions) string { service := k8s.GetService(t, options, "hello-world-app-service") endpoint := k8s.GetServiceEndpoint(t, options, service, 8080) return fmt.Sprintf("http://%s", endpoint) } The serviceUrl method
  • 101. $ kubectl config set-credentials … To run the test, first authenticate to a Kubernetes cluster.
  • 102. Note: Kubernetes is now part of Docker Desktop. Test 100% locally!
  • 103. $ go test -v -timeout 15m -run TestDockerKubernetes … --- PASS: TestDockerKubernetes (5.69s) Run go test. You can validate your config after every commit in seconds!
  • 104. Unit tests 1. Unit testing basics 2. Example: Terraform unit tests 3. Example: Docker/Kubernetes unit tests 4. Cleaning up after tests
  • 105. Note: tests create and destroy many resources!
  • 106. Pro tip #1: run tests in completely separate “sandbox” accounts
  • 107. Tool Clouds Features cloud-nuke AWS (GCP planned) Delete all resources older than a certain date; in a certain region; of a certain type. Janitor Monkey AWS Configurable rules of what to delete. Notify owners of pending deletions. aws-nuke AWS Specify specific AWS accounts and resource types to target. Azure Powershell Azure Includes native commands to delete Resource Groups Pro tip #2: run these tools in cron jobs to clean up left-over resources
  • 108. 1. Static analysis 2. Unit tests 3. Integration tests 4. End-to-end tests 5. Conclusion Outline
  • 109. Integration tests: test multiple “units” work together.
  • 110. Integration tests 1. Example: Terraform integration tests 2. Test parallelism 3. Test stages 4. Test retries
  • 111. Integration tests 1. Example: Terraform integration tests 2. Test parallelism 3. Test stages 4. Test retries
  • 112. infrastructure-as-code-testing-talk └ examples └ hello-world-app └ docker-kubernetes └ proxy-app └ web-service └ modules └ test └ README.md Let’s say you have two Terraform modules you want to test together:
  • 113. infrastructure-as-code-testing-talk └ examples └ hello-world-app └ docker-kubernetes └ proxy-app └ web-service └ modules └ test └ README.md proxy-app: an app that acts as an HTTP proxy for other web services.
  • 114. infrastructure-as-code-testing-talk └ examples └ hello-world-app └ docker-kubernetes └ proxy-app └ web-service └ modules └ test └ README.md web-service: a web service that you want proxied.
  • 115. variable "url_to_proxy" { description = "The URL to proxy." type = string } proxy-app takes in the URL to proxy via an input variable
  • 116. output "url" { value = module.web_service.url } web-service exposes its URL via an output variable
  • 117. infrastructure-as-code-testing-talk └ examples └ modules └ test └ hello_world_app_test.go └ docker_kubernetes_test.go └ proxy_app_test.go └ README.md Create proxy_app_test.go
  • 118. func TestProxyApp(t *testing.T) { webServiceOpts := configWebService(t) defer terraform.Destroy(t, webServiceOpts) terraform.InitAndApply(t, webServiceOpts) proxyAppOpts := configProxyApp(t, webServiceOpts) defer terraform.Destroy(t, proxyAppOpts) terraform.InitAndApply(t, proxyAppOpts) validate(t, proxyAppOpts) } The basic test structure
  • 119. func TestProxyApp(t *testing.T) { webServiceOpts := configWebService(t) defer terraform.Destroy(t, webServiceOpts) terraform.InitAndApply(t, webServiceOpts) proxyAppOpts := configProxyApp(t, webServiceOpts) defer terraform.Destroy(t, proxyAppOpts) terraform.InitAndApply(t, proxyAppOpts) validate(t, proxyAppOpts) } 1. Configure options for the web service
  • 120. func TestProxyApp(t *testing.T) { webServiceOpts := configWebService(t) defer terraform.Destroy(t, webServiceOpts) terraform.InitAndApply(t, webServiceOpts) proxyAppOpts := configProxyApp(t, webServiceOpts) defer terraform.Destroy(t, proxyAppOpts) terraform.InitAndApply(t, proxyAppOpts) validate(t, proxyAppOpts) } 2. Deploy the web service
  • 121. func TestProxyApp(t *testing.T) { webServiceOpts := configWebService(t) defer terraform.Destroy(t, webServiceOpts) terraform.InitAndApply(t, webServiceOpts) proxyAppOpts := configProxyApp(t, webServiceOpts) defer terraform.Destroy(t, proxyAppOpts) terraform.InitAndApply(t, proxyAppOpts) validate(t, proxyAppOpts) } 3. Configure options for the proxy app (passing it the web service options)
  • 122. func TestProxyApp(t *testing.T) { webServiceOpts := configWebService(t) defer terraform.Destroy(t, webServiceOpts) terraform.InitAndApply(t, webServiceOpts) proxyAppOpts := configProxyApp(t, webServiceOpts) defer terraform.Destroy(t, proxyAppOpts) terraform.InitAndApply(t, proxyAppOpts) validate(t, proxyAppOpts) } 4. Deploy the proxy app
  • 123. func TestProxyApp(t *testing.T) { webServiceOpts := configWebService(t) defer terraform.Destroy(t, webServiceOpts) terraform.InitAndApply(t, webServiceOpts) proxyAppOpts := configProxyApp(t, webServiceOpts) defer terraform.Destroy(t, proxyAppOpts) terraform.InitAndApply(t, proxyAppOpts) validate(t, proxyAppOpts) } 5. Validate the proxy app works
  • 124. func TestProxyApp(t *testing.T) { webServiceOpts := configWebService(t) defer terraform.Destroy(t, webServiceOpts) terraform.InitAndApply(t, webServiceOpts) proxyAppOpts := configProxyApp(t, webServiceOpts) defer terraform.Destroy(t, proxyAppOpts) terraform.InitAndApply(t, proxyAppOpts) validate(t, proxyAppOpts) } 6. At the end of the test, undeploy the proxy app and the web service
  • 125. func configWebService(t *testing.T) *terraform.Options { return &terraform.Options{ TerraformDir: "../examples/web-service", } } The configWebService method
  • 126. func configProxyApp(t *testing.T, webServiceOpts *terraform.Options) *terraform.Options { url := terraform.Output(t, webServiceOpts, "url") return &terraform.Options{ TerraformDir: "../examples/proxy-app", Vars: map[string]interface{}{ "url_to_proxy": url, }, } } The configProxyApp method
  • 127. func configProxyApp(t *testing.T, webServiceOpts *terraform.Options) *terraform.Options { url := terraform.Output(t, webServiceOpts, "url") return &terraform.Options{ TerraformDir: "../examples/proxy-app", Vars: map[string]interface{}{ "url_to_proxy": url, }, } } 1. Read the url output from the web- service module
  • 128. func configProxyApp(t *testing.T, webServiceOpts *terraform.Options) *terraform.Options { url := terraform.Output(t, webServiceOpts, "url") return &terraform.Options{ TerraformDir: "../examples/proxy-app", Vars: map[string]interface{}{ "url_to_proxy": url, }, } } 2. Pass it in as the url_to_proxy input to the proxy-app module
  • 129. func validate(t *testing.T, opts *terraform.Options) { url := terraform.Output(t, opts, "url") http_helper.HttpGetWithRetry(t, url, // URL to test 200, // Expected status code `{"text":"Hello, World!"}`, // Expected body 10, // Max retries 3 * time.Second // Time between retries ) } The validate method
  • 130. $ go test -v -timeout 15m -run TestProxyApp … --- PASS: TestProxyApp (182.44s) Run go test. You’re now testing multiple modules together!
  • 131. $ go test -v -timeout 15m -run TestProxyApp … --- PASS: TestProxyApp (182.44s) But integration tests can take (many) minutes to run…
  • 132. Integration tests 1. Example: Terraform integration tests 2. Test parallelism 3. Test stages 4. Test retries
  • 133. Infrastructure tests can take a long time to run
  • 134. One way to save time: run tests in parallel
  • 135. func TestProxyApp(t *testing.T) { t.Parallel() // The rest of the test code } func TestHelloWorldAppUnit(t *testing.T) { t.Parallel() // The rest of the test code } Enable test parallelism in Go by adding t.Parallel() as the 1st line of each test.
  • 136. $ go test -v -timeout 15m === RUN TestHelloWorldApp === RUN TestDockerKubernetes === RUN TestProxyApp Now, if you run go test, all the tests with t.Parallel() will run in parallel
  • 137. But there’s a gotcha: resource conflicts
  • 138. resource "aws_iam_role" "role_example" { name = "example-iam-role" } resource "aws_security_group" "sg_example" { name = "security-group-example" } Example: module with hard-coded IAM Role and Security Group names
  • 139. resource "aws_iam_role" "role_example" { name = "example-iam-role" } resource "aws_security_group" "sg_example" { name = "security-group-example" } If two tests tried to deploy this module in parallel, the names would conflict!
  • 140. Key takeaway: you must namespace all your resources
  • 141. resource "aws_iam_role" "role_example" { name = var.name } resource "aws_security_group" "sg_example" { name = var.name } Example: use variables in all resource names…
  • 142. uniqueId := random.UniqueId() return &terraform.Options{ TerraformDir: "../examples/proxy-app", Vars: map[string]interface{}{ "name": fmt.Sprintf("text-proxy-app-%s", uniqueId) }, } At test time, set the variables to a randomized value to avoid conflicts
  • 143. Integration tests 1. Example: Terraform integration tests 2. Test parallelism 3. Test stages 4. Test retries
  • 144. Consider the structure of the proxy-app integration test:
  • 145. 1. Deploy web-service 2. Deploy proxy-app 3. Validate proxy-app 4. Undeploy proxy-app 5. Undeploy web-service
  • 146. 1. Deploy web-service 2. Deploy proxy-app 3. Validate proxy-app 4. Undeploy proxy-app 5. Undeploy web-service When iterating locally, you sometimes want to re-run just one of these steps.
  • 147. 1. Deploy web-service 2. Deploy proxy-app 3. Validate proxy-app 4. Undeploy proxy-app 5. Undeploy web-service But as the code is written now, you have to run all steps on each test run.
  • 148. 1. Deploy web-service 2. Deploy proxy-app 3. Validate proxy-app 4. Undeploy proxy-app 5. Undeploy web-service And that can add up to a lot of overhead. (~3 min) (~2 min) (~30 seconds) (~1 min) (~2 min)
  • 149. Key takeaway: break your tests into independent test stages
  • 150. webServiceOpts := configWebService(t) defer terraform.Destroy(t, webServiceOpts) terraform.InitAndApply(t, webServiceOpts) proxyAppOpts := configProxyApp(t, webServiceOpts) defer terraform.Destroy(t, proxyAppOpts) terraform.InitAndApply(t, proxyAppOpts) validate(t, proxyAppOpts) The original test structure
  • 151. stage := test_structure.RunTestStage defer stage(t, "cleanup_web_service", cleanupWebService) stage(t, "deploy_web_service", deployWebService) defer stage(t, "cleanup_proxy_app", cleanupProxyApp) stage(t, "deploy_proxy_app", deployProxyApp) stage(t, "validate", validate) The test structure with test stages
  • 152. stage := test_structure.RunTestStage defer stage(t, "cleanup_web_service", cleanupWebService) stage(t, "deploy_web_service", deployWebService) defer stage(t, "cleanup_proxy_app", cleanupProxyApp) stage(t, "deploy_proxy_app", deployProxyApp) stage(t, "validate", validate) 1. RunTestStage is a helper function from Terratest.
  • 153. stage := test_structure.RunTestStage defer stage(t, "cleanup_web_service", cleanupWebService) stage(t, "deploy_web_service", deployWebService) defer stage(t, "cleanup_proxy_app", cleanupProxyApp) stage(t, "deploy_proxy_app", deployProxyApp) stage(t, "validate", validate) 2. Wrap each stage of your test with a call to RunTestStage
  • 154. stage := test_structure.RunTestStage defer stage(t, "cleanup_web_service", cleanupWebService) stage(t, "deploy_web_service", deployWebService) defer stage(t, "cleanup_proxy_app", cleanupProxyApp) stage(t, "deploy_proxy_app", deployProxyApp) stage(t, "validate", validate) 3. Define each stage in a function (you’ll see this code shortly).
  • 155. stage := test_structure.RunTestStage defer stage(t, "cleanup_web_service", cleanupWebService) stage(t, "deploy_web_service", deployWebService) defer stage(t, "cleanup_proxy_app", cleanupProxyApp) stage(t, "deploy_proxy_app", deployProxyApp) stage(t, "validate", validate) 4. Give each stage a unique name
  • 156. stage := test_structure.RunTestStage defer stage(t, "cleanup_web_service", cleanupWebService) stage(t, "deploy_web_service", deployWebService) defer stage(t, "cleanup_proxy_app", cleanupProxyApp) stage(t, "deploy_proxy_app", deployProxyApp) stage(t, "validate", validate) Any stage foo can be skipped by setting the env var SKIP_foo=true
  • 157. $ SKIP_cleanup_web_service=true $ SKIP_cleanup_proxy_app=true Example: on the very first test run, skip the cleanup stages.
  • 158. $ go test -v -timeout 15m -run TestProxyApp Running stage 'deploy_web_service'… Running stage 'deploy_proxy_app'… Running stage 'validate'… Skipping stage 'cleanup_proxy_app'… Skipping stage 'cleanup_web_service'… --- PASS: TestProxyApp (105.73s) That way, after the test finishes, the infrastructure will still be running.
  • 159. $ SKIP_deploy_web_service=true $ SKIP_deploy_proxy_app=true Now, on the next several test runs, you can skip the deploy stages too.
  • 160. $ go test -v -timeout 15m -run TestProxyApp Skipping stage 'deploy_web_service’… Skipping stage 'deploy_proxy_app'… Running stage 'validate'… Skipping stage 'cleanup_proxy_app'… Skipping stage 'cleanup_web_service'… --- PASS: TestProxyApp (14.22s) This allows you to iterate on solely the validate stage…
  • 161. $ go test -v -timeout 15m -run TestProxyApp Skipping stage 'deploy_web_service’… Skipping stage 'deploy_proxy_app'… Running stage 'validate'… Skipping stage 'cleanup_proxy_app'… Skipping stage 'cleanup_web_service'… --- PASS: TestProxyApp (14.22s) Which dramatically speeds up your iteration / feedback cycle!
  • 162. $ SKIP_validate=true $ unset SKIP_cleanup_web_service $ unset SKIP_cleanup_proxy_app When you’re done iterating, skip validate and re-enable cleanup
  • 163. $ go test -v -timeout 15m -run TestProxyApp Skipping stage 'deploy_web_service’… Skipping stage 'deploy_proxy_app’… Skipping stage 'validate’… Running stage 'cleanup_proxy_app’… Running stage 'cleanup_web_service'… --- PASS: TestProxyApp (59.61s) This cleans up everything that was left running.
  • 164. func deployWebService(t *testing.T) { opts := configWebServiceOpts(t) test_structure.SaveTerraformOptions(t, "/tmp", opts) terraform.InitAndApply(t, opts) } func cleanupWebService(t *testing.T) { opts := test_structure.LoadTerraformOptions(t, "/tmp") terraform.Destroy(t, opts) } Note: each time you run test stages via go test, it’s a separate OS process.
  • 165. func deployWebService(t *testing.T) { opts := configWebServiceOpts(t) test_structure.SaveTerraformOptions(t, "/tmp", opts) terraform.InitAndApply(t, opts) } func cleanupWebService(t *testing.T) { opts := test_structure.LoadTerraformOptions(t, "/tmp") terraform.Destroy(t, opts) } So to pass data between stages, one stage needs to write the data to disk…
  • 166. func deployWebService(t *testing.T) { opts := configWebServiceOpts(t) test_structure.SaveTerraformOptions(t, "/tmp", opts) terraform.InitAndApply(t, opts) } func cleanupWebService(t *testing.T) { opts := test_structure.LoadTerraformOptions(t, "/tmp") terraform.Destroy(t, opts) } And the other stages need to read that data from disk.
  • 167. Integration tests 1. Example: Terraform integration tests 2. Test parallelism 3. Test stages 4. Test retries
  • 168. Real infrastructure can fail for intermittent reasons (e.g., bad EC2 instance, Apt downtime, Terraform bug)
  • 169. To avoid “flaky” tests, add retries for known errors.
  • 170. &terraform.Options{ TerraformDir: "../examples/proxy-app", RetryableTerraformErrors: map[string]string{ "net/http: TLS handshake timeout": "Terraform bug", }, MaxRetries: 3, TimeBetweenRetries: 3*time.Second, } Example: retry up to 3 times on a known TLS error in Terraform.
  • 171. 1. Static analysis 2. Unit tests 3. Integration tests 4. End-to-end tests 5. Conclusion Outline
  • 172. End-to-end tests: test your entire infrastructure works together.
  • 173. How do you test this entire thing?
  • 174. You could use the same strategy… 1. Deploy all the infrastructure 2. Validate it works (e.g., via HTTP requests, API calls, SSH commands, etc.) 3. Undeploy all the infrastructure
  • 175. But it’s rare to write end-to- end tests this way. Here’s why:
  • 177. e2e Tests Integration Tests Unit Tests Static analysis Cost, brittleness, run time
  • 178. e2e Tests Integration Tests Unit Tests Static analysis 60 – 240+ minutes 5 – 60 minutes 1 – 20 minutes 1 – 60 seconds
  • 179. e2e Tests Integration Tests Unit Tests Static analysis E2E tests are too slow to be useful 60 – 240+ minutes 5 – 60 minutes 1 – 20 minutes 1 – 60 seconds
  • 180. Another problem with E2E tests: brittleness.
  • 181. Let’s do some math:
  • 182. Assume a single resource (e.g., EC2 instance) has a 1/1000 (0.1%) chance of failure.
  • 183. Test type # of resources Chance of failure Unit tests 10 1% Integration tests 50 5% End-to-end tests 500+ 40%+ The more resources your tests deploy, the flakier they will be.
  • 184. Test type # of resources Chance of failure Unit tests 10 1% Integration tests 50 5% End-to-end tests 500+ 40%+ You can work around the failure rate for unit & integration tests with retries
  • 185. Test type # of resources Chance of failure Unit tests 10 1% Integration tests 50 5% End-to-end tests 500+ 40%+ You can work around the failure rate for unit & integration tests with retries
  • 186. Key takeaway: E2E tests from scratch are too slow and too brittle to be useful
  • 187. Instead, you can do incremental E2E testing!
  • 191. 1. Static analysis 2. Unit tests 3. Integration tests 4. End-to-end tests 5. Conclusion Outline
  • 193. Technique Strengths Weaknesses Static analysis 1. Fast 2. Stable 3. No need to deploy real resources 4. Easy to use 1. Very limited in errors you can catch 2. You don’t get much confidence in your code solely from static analysis Unit tests 1. Fast enough (1 – 10 min) 2. Mostly stable (with retry logic) 3. High level of confidence in individual units 1. Need to deploy real resources 2. Requires writing non-trivial code Integration tests 1. Mostly stable (with retry logic) 2. High level of confidence in multiple units working together 1. Need to deploy real resources 2. Requires writing non-trivial code 3. Slow (10 – 30 min) End-to-end tests 1. Build confidence in your entire architecture 1. Need to deploy real resources 2. Requires writing non-trivial code 3. Very slow (60 min – 240+ min)* 4. Can be brittle (even with retry logic)*
  • 194. So which should you use?
  • 195. All of them! They all catch different types of bugs.
  • 196. e2e Tests Keep in mind the test pyramid Integration Tests Unit Tests Static analysis
  • 197. e2e Tests Lots of unit tests + static analysis Integration Tests Unit Tests Static analysis
  • 198. e2e Tests Fewer integration tests Integration Tests Unit Tests Static analysis
  • 199. e2e Tests A handful of high-value e2e tests Integration Tests Unit Tests Static analysis
  • 201. Fight the fear & build confidence in your code with automated tests
  • 203. Watch the video with slide synchronization on InfoQ.com! https://www.infoq.com/presentations/a utomated-testing-terraform-docker- packer/