Diff plugin to the rescue!
If you are used to working with infrastructure as code for AWS you may be familiar with checking the changes you are about to apply to your infrastructure. Terraform has the “plan” command. AWS Cloudformation has the “changeset” feature. AWS CDK (which relies on Cloudformation stacks) has the “diff” command.
When I make changes to an environment I like to visualise what will be modified and how. So I usually add a simple user validation step in the deployment pipeline. It may not be useful in all environments, but I feel it is very convenient for production ones.
Below is a very simple example implemented with “make” and “bash” as deployment tools.
deploy.diff:
terraform plan -out=$(TFPLAN_FILE)
deploy.apply: deploy.diff
@read -p "Apply changes? (Y)es or (N)o " apply; \
if [ "$$apply" = "Y" ]; then \
echo "Applying changes"; \
terraform apply $(TFPLAN_FILE); \
else \
echo "Changes cancelled by user"; \
fi
Running the command `make deploy.apply` you get the following output:
id = "tf-demo"
~ tags = {vscode ➜ /workspaces/infra-diff-demo/terraform $ make deploy.apply
terraform plan -out=thebest.tfplan
aws_s3_bucket.demos3: Refreshing state... [id=tf-demo]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_s3_bucket.demos3 will be updated in-place
~ resource "aws_s3_bucket" "demos3" {
~ "Version" = "0.7.3" -> "0.7.4"
# (2 unchanged elements hidden)
}
~ tags_all = {
~ "Version" = "0.7.3" -> "0.7.4"
# (2 unchanged elements hidden)
}
# (9 unchanged attributes hidden)
# (2 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: thebest.tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "thebest.tfplan"
Apply changes? (Y)es or (N)o
As you see, it is just about adding a step with a user input in your deployment pipeline/workflow. It could be implemented in any of the most used CI/CD tools (e.g. Jenkins, Gitlab, Azure DevOps, etc…).
When I started using Helm I was very surprised to not find a built-in option allowing us to preview the changes to apply. I still wonder why this option does not exist. But then came the helm diff plugin to the rescue.
The installation is the same as any other plugin.
helm plugin install https://github.com/databus23/helm-diff
The new command “diff” appears in the helm help after installing the plugin.
$ helm --help
[...]
Available Commands:
completion generate autocompletion scripts for the specified shell
create create a new chart with the given name
dependency manage a chart's dependencies
diff Preview helm upgrade changes as a diff
env helm client environment information
[...]
“helm diff –help” shows all the commands and options available for the diff plugin. There are a lot but, for now, we will use it very simply to check the difference between the version of the chart currently deployed on the cluster and the version we are about to deploy. This is very similar to the standard helm command. We just need to add the “diff” word before the “upgrade” command:
helm diff upgrade test-diff . -f test-values.yaml
As for the previous example, it could be easily integrated in any deployment workflow to allow a human user to validate the changes to be applied. Below is an example with a very simple helm chart to deploy a nginx web server.
deploy.diff:
helm --kubeconfig ./kube-config diff upgrade diff-demo . -f values.yaml
deploy.apply: deploy.diff
@read -p "Apply changes? (Y)es or (N)o " apply; \
if [ "$$apply" = "Y" ]; then \
echo "Applying changes"; \
helm --kubeconfig ./kube-config upgrade -i diff upgrade diff-demo . -f values.yaml; \
else \
echo "Changes cancelled by user"; \
fi
With the following output:
vscode ➜ /workspaces/infra-diff-demo/helm/simple-nginx $ make deploy.apply
helm --kubeconfig ../kube-config diff --allow-unreleased upgrade diff-demo . -f values.yaml
default, diff-demo-simple-nginx, Deployment (apps) has changed:
# Source: simple-nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: diff-demo-simple-nginx
labels:
app: simple-nginx
chart: simple-nginx-0.1.0
release: diff-demo
heritage: Helm
spec:
selector:
matchLabels:
app: simple-nginx
release: diff-demo
replicas: 1
template:
metadata:
labels:
app: simple-nginx
release: diff-demo
spec:
containers:
- name: simple-nginx
- image: "nginx:stable"
+ image: "nginx:1.22.1"
imagePullPolicy: IfNotPresent
env:
- name: PORT
value: "5000"
ports:
- containerPort: 5000
livenessProbe:
httpGet:
path: /
port: 5000
readinessProbe:
httpGet:
path: /
port: 5000
resources:
{}
Apply changes? (Y)es or (N)o
We can see that we are going to deploy another version of the nginx image.
I did not really dive into it, but ansible seems to have a similar feature (the “dry-run” mode) with the `–check` and `–diff` options.
Rememb
er that it is different to compare the current code version with the previous one. Here we are comparing the current state of our infrastructure with the desired one. Be aware that the word “state” is ambiguous here. From the tools mentioned above, the “current state” of the infrastructure is a stored representation of what was previously deployed through these tools. It may be different from the actual/real state of your infrastructure because of some changes made outside the normal deployment workflow, i.e. without those tools. For instance, in response to a production incident. This leads to “drifts” between the “current state” and the “real state”. Detecting these ones and managing them is another story, and may be discussed in another article.
Want to learn more about Terraform?
Check out TechRadar by Devoteam 2023 to see what our experts say about it in the market.