In this post we will use terraform to deploy a helm release to kubernetes.
Kubernetes
For this demonstration I will be using kind to deploy a local Kubernetes cluster to the operating system that I am running this on, which will be Ubuntu Linux. For a more in-depth tutorial on Kind, you can see my post on Kind for Local Kubernetes Clusters.
Installing the Pre-Requirements
We will be installing terraform, docker, kind and kubectl on Linux.
Now we can test if kubectl can communicate with the kubernetes api server:
1
kubectl get nodes
In my case it returns:
12
NAME STATUS ROLES AGE VERSION
rbkr-control-plane Ready control-plane 6m20s v1.24.0
Terraform
Now that our pre-requirements are sorted we can configure terraform to communicate with kubernetes. For that to happen, we need to consult the terraform kubernetes provider’s documentation.
As per their documentation they provide us with this snippet:
And from their main page, it gives us a couple of options to configure the provider and the easiest is probably to read the ~/.kube/config configuration file.
But in cases where you have multiple configurations in your kube config file, this might not be ideal, and I like to be precise, so I will extract the client certificate, client key and cluster ca certificate and endpoint from our ~/.kube/config file.
If we run cat ~/.kube/config we will see something like this:
First we will create a directory for our certificates:
1
mkdir ~/certs
I have truncated my kube config for readability, but for our first file certs/client-cert.pem we will copy the value of client-certificate-data:, which will look something like this:
Then we will copy the contents of client-key-data: into certs/client-key.pem and then lastly the content of certificate-authority-data: into certs/cluster-ca-cert.pem.
So then we should have the following files inside our certs/ directory:
Your host might look different to mine, but you can find your host endpoint in ~/.kube/config.
For a simple test we can list all our namespaces to ensure that our configuration is working. In a file called namespaces.tf, we can populate the following:
12345
data "kubernetes_all_namespaces""allns"{}output "all-ns"{value= data.kubernetes_all_namespaces.allns.namespaces
}
Now we need to initialize terraform so that it can download the providers:
1
terraform init
Then we can run a plan which will reveal our namespaces:
12345678910111213
terraform plan
data.kubernetes_all_namespaces.allns: Reading...
data.kubernetes_all_namespaces.allns: Read complete after 0s [id=a0ff7e83ffd7b2d9953abcac9f14370e842bdc8f126db1b65a18fd09faa3347b]Changes to Outputs:
+ all-ns =[ + "default",
+ "kube-node-lease",
+ "kube-public",
+ "kube-system",
+ "local-path-storage",
]
We can now remove our namespaces.tf as our test worked:
1
rm namespaces.tf
Helm Releases with Terraform
We will need two things, we need to consult the terraform helm release provider documentation and we also need to consult the helm chart documentation which we are interested in.
As we are working with helm releases, we need to configure the helm provider, I will just extend my configuration from my previous provider config in providers.tf:
In our main.tf I will use two ways to override values in our values.yaml using set and templatefile. The reason for the templatefile, is when we want to fetch a value and want to replace the content with our values file, it could be used when we retrieve a value from a data source as an example. In my example im just using a variable.
variable "release_name" {type = stringdefault = "nginx"description = "The name of our release."}variable "chart_repository_url" {type = stringdefault = "https://charts.bitnami.com/bitnami"description = "The chart repository url."}variable "chart_name" {type = stringdefault = "nginx"description = "The name of of our chart that we want to install from the repository."}variable "chart_version" {type = stringdefault = "13.2.20"description = "The version of our chart."}variable "namespace" {type = stringdefault = "apps"description = "The namespace where our release should be deployed into."}variable "create_namespace" {type = booldefault = truedescription = "If it should create the namespace if it doesnt exist."}variable "atomic" {type = booldefault = falsedescription = "If it should wait until release is deployed."}
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# helm_release.nginx will be created + resource "helm_release""nginx"{ + atomic=false + chart="nginx" + cleanup_on_fail=false + create_namespace=true + dependency_update=false + disable_crd_hooks=false + disable_openapi_validation=false + disable_webhooks=false + force_update=false + id=(known after apply) + lint=false + manifest=(known after apply) + max_history= 0
+ metadata=(known after apply) + name="nginx" + namespace="apps" + pass_credentials=false + recreate_pods=false + render_subchart_notes=true + replace=false + repository="https://charts.bitnami.com/bitnami" + reset_values=false + reuse_values=false + skip_crds=false + status="deployed" + timeout= 300
+ values=[ + <<-EOT nameOverride: "nginx" ## ref: https://hub.docker.com/r/bitnami/nginx/tags/ image: registry: docker.io repository: bitnami/nginx tag: 1.23.3-debian-11-r3 EOT,
] + verify=false + version="13.2.20" + wait=false + wait_for_jobs=false + set{ + name="image.tag" + value="1.23.3-debian-11-r3"}}Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ metadata=(known after apply)
Once we are happy with our plan, we can run a apply:
123456789101112131415161718192021222324252627
terraform apply
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ metadata=(known after apply)Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
helm_release.nginx: Creating...
helm_release.nginx: Still creating... [10s elapsed]metadata= tolist([{"app_version"="1.23.3""chart"="nginx""name"="nginx""namespace"="apps""revision"= 1
"values"="{\"image\":{\"registry\":\"docker.io\",\"repository\":\"bitnami/nginx\",\"tag\":\"1.23.3-debian-11-r3\"},\"nameOverride\":\"nginx\"}""version"="13.2.20"},
])
Then we can verify if the pod is running:
123
kubectl get pods -n apps
NAME READY STATUS RESTARTS AGE
nginx-59bdc6465-xdbfh 1/1 Running 0 2m35s
Importing Helm Releases into Terraform State
If you have an existing helm release that was deployed with helm and you want to transfer the ownership to terraform, you first need to write the terraform code, then import the resources into terraform state using:
1
terraform import helm_release.nginx apps/nginx
Where the last argument is <namespace>/<release-name>. Once that is imported you can run terraform plan and apply.
If you want to discover all helm releases managed by helm you can use:
1
kubectl get all -A -l app.kubernetes.io/managed-by=Helm
Thank You
Thanks for reading, feel free to check out my website, feel free to subscribe to my newsletter or follow me at @ruanbekker on Twitter.