Kubernetes provider for Bicep together with user-defined types
Mattias Fjellstr?m
Cloud Architect | Author | HashiCorp Ambassador | HashiCorp User Group Leader
This article was originally posted on my blog mattias.engineer
In the?latest release?of Azure Bicep user-defined functions was introduced. That is an excellent news, but I will not be covering that in this post. I want to introduce the idea of modules being very much like user-defined functions. In the past I have done crazy things like?solving Advent of Code problems using Azure Bicep, and the most important thing I learned from that adventure was that a module is the closest thing to a function that Bicep has. This will of course change with the introduction of user-defined functions.
In this short post I want to use a Bicep module to deploy Deployment-objects in a Kubernetes cluster. So I will be using two experimental features of Bicep: the Kubernetes provider and user-defined types. I will create a Bicep module for Kubernetes deployments, and illustrate how that module can work like a function that generates deployments for you.
Bicep configuration
To be able to work with Kubernetes resources in Azure, you need to set the?experimentalFeaturesEnabled.extensibility?to?true?in?bicepconfig.json. You will also need to set?experimentalFeaturesEnabled.userDefinedTypes?to?true?to be able to use user-defined types. Your?bicepconfig.json?should look like this:
{
"experimentalFeaturesEnabled": {
"extensibility": true,
"userDefinedTypes": true
}
}
Now we are ready to write some Bicep!
Writing the deployment module
When I write a module in Bicep I usually create a?modules?directory and put my modules there. This is true this time as well. In my modules directory I create a file called?deployment.bicep?where I will define my Kubernetes deployment module.
To start off I will create a user-defined type for my Kubernetes deployments. A deployment in Kubernetes can be complex, so to keep the complexity down a bit I will only require the deployment to have a name and an image. My custom type looks like this:
@description('Custom type for a Kubernetes deployment')
@sealed()
type deploymentConfigType = {
name: string
image: string
}
I use the?@sealed()?decorator to specify that I can’t provide additional fields to parameters using this type, I will specifically require the?name?and the?image?fields. If you use this example as a starting-point for your own work then this user-defined type needs to be extended to include additional fields.
Next up I define three parameters for my module:
领英推荐
@description('kubeconfig to authenticate to the cluster')
param kubeConfig string
@description('Kubernetes namespace for the deployments')
param namespace string
@description('Configurations for the deployments')
param deployments deploymentConfigType[]
The?kubeConfig?parameter is used to authenticate to the Kubernetes cluster I want to create the deployments in. The?namespace?parameter is the Kubernetes namespace where the deployments should live, this is required for the configuration of the Kubernetes provider (see below). The last parameter is?deployments?which is a list of my user-defined type?deploymentConfigType. This list will contain the definitions of all the Kubernetes deployments I want to create.
Next I need to import the Kubernetes provider:
import '[email protected]' with {
kubeConfig: kubeConfig
namespace: namespace
}
Here I used the?kubeConfig?and?namespace?parameters. When I have imported the Kubernetes provider I can start creating Kubernetes resources just like I would create Azure resources! So, the last step in this module is to do just that. I create my deployment objects in Kubernetes like so:
resource deploy 'apps/Deployment@v1' = [for deployment in deployments: {
metadata: {
name: deployment.name
}
spec: {
selector: {
matchLabels: {
app: deployment.name
}
}
template: {
metadata: {
name: deployment.name
labels: {
app: deployment.name
}
}
spec: {
containers: [
{
name: 'main'
image: deployment.image
}
]
}
}
}
}]
If you are familiar with Kubernetes deployments then this will look very familiar. It is simply a Kubernetes deployment in Bicep code. As I mentioned above, it is a simplified deployment object where I only specify a few things. To summarize, the?deployment.bicep?module should look like the following:
@description('Custom type for a Kubernetes deployment')
@sealed()
type deploymentConfigType = {
name: string
image: string
}
@description('kubeconfig to authenticate to the cluster')
param kubeConfig string
@description('Kubernetes namespace for the deployments')
param namespace string
@description('Configurations for the deployments')
param deployments deploymentConfigType[]
import '[email protected]' with {
kubeConfig: kubeConfig
namespace: namespace
}
resource deploy 'apps/Deployment@v1' = [for deployment in deployments: {
metadata: {
name: deployment.name
}
spec: {
selector: {
matchLabels: {
app: deployment.name
}
}
template: {
metadata: {
name: deployment.name
labels: {
app: deployment.name
}
}
spec: {
containers: [
{
name: 'main'
image: deployment.image
}
]
}
}
}
}]
Using the module
Now it is time to use the module! I create a?main.bicep?file with the following content:
@description('kubeconfig to authenticate to the cluster')
param kubeConfig string
@description('Kubernetes namespace for the deployments')
param namespace string
var deployments = [
{
name: 'nginx1'
image: 'nginx:1.23.4'
}
{
name: 'nginx2'
image: 'nginx:1.24.0'
}
]
module deploymentModule 'modules/deployment.bicep' = {
name: 'deployment-module'
params: {
deployments: deployments
kubeConfig: kubeConfig
namespace: namespace
}
}
I have the?kubeConfig?and?namespace?parameters repeated here, so I expect them to be sent into the deployment command. I define a variable?deployments?which is an array of deployment objects. Each object follows the user-defined type I have in my?deployment.bicep?file. Note that as of now (May 2023) you can’t put your user-defined types in a separate file and import them to where you need them. This is why I keep the user-defined type in?deployment.bicep?only, for now. If you use the Bicep extension in VS Code you will get a warning if you forget a certain property, or if you add a property the type does not contain.
In this case I created two Kubernetes deployments, with two different versions of nginx. This was so simplify my own life a bit, because the nginx container will start up without any need for a command. This allowed me to keep the user-defined type to a minimum, in order to not get bogged down in details about containers!
Now you are ready to run a Bicep deployment to create your Kubernetes deployments, good luck!