Flux is a declarative system for managing Kubernetes deployments. This tutorial will guide you through deploying an ExpressJS application to Azure Kubernetes Service (AKS) using Flux, along with managing resources with Terraform. I have created a repository for reference to help follow along with the tutorial.
Review my previous tutorial on deploying Express js api to AKS.
Install K3D for a local cluster for testing purposes.
Startup a fresh AKS and Azure Container Registry (ACR) deployment. We will utilize Terraform files to declaratively deploy these resources to Azure using Terraform CLI .
Create a .tf
file to describe you resources.
terraform {
required_version = ">=1.0"
required_providers {
azapi = {
source = "azure/azapi"
version = "~>1.5"
}
azurerm = {
source = "hashicorp/azurerm"
version = "~>3.0"
}
time = {
source = "hashicorp/time"
version = "0.9.1"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg" {
name = "KubernetesTest"
location = var.location
}
# Container Registry
resource "azurerm_container_registry" "acr" {
name = var.container_registry_name
resource_group_name = azurerm_resource_group.rg.name
location = var.location
sku = "Basic"
admin_enabled = false
identity {
type = "SystemAssigned"
}
tags = {
Environment = "dev"
}
}
resource "azurerm_kubernetes_cluster" "k8s" {
name = var.azurerm_kubernetes_cluster_dns_name
location = "canadacentral"
resource_group_name = azurerm_resource_group.rg.name
dns_prefix = var.azurerm_kubernetes_cluster_dns_prefix
identity {
type = "SystemAssigned"
}
default_node_pool {
name = "agentpool"
vm_size = "Standard_A2_v2"
node_count = var.node_count
}
tags = {
Environment = "dev"
}
}
Deploy resources to Azure
terraform apply
Create new role assignment to give AKS AcrPull access.
az aks show --resource-group KubernetesTest --name devdeveloper-aks-cluster --query "identityProfile.kubeletidentity.objectId" -o tsv
az role assignment create --assignee ed6b1c8a-ca84-4041-9d87-da92420c8565 --role "AcrPull" --scope /subscriptions/59966e90-8185-44af-a00c-13bc237e59cb/resourceGroups/KubernetesTest/providers/Microsoft.ContainerRegistry/registries/devdeveloperregistry
Register aks deployment with Kubectl
az aks get-credentials --resource-group KubernetesTest --name devdeveloper-aks-cluster
Now we need to setup the Azure Devops Pipeline. Like mentioned before this is a similar CI/CD pipeline to the previous tutorial but with the deployment step to AKS removed. Flux will be handeling that going forward. This yaml config will be automatically generated by the Azure Pipelines web application and you'll only have to make slight motifications based on your setup. Take a look inside the code snippet bellow for comments.
trigger:
- version_2
resources:
- repo: self
variables:
# Container registry service connection established during pipeline creation
dockerRegistryServiceConnection: "766a7075-56b5-40d1-8d79-2d89a8710f11" <=== automatically generated
imageRepository: "node-ts-api" <=== this is going to be the name of the image repository and will be refered to later
containerRegistry: "devdeveloperregistry.azurecr.io" <=== make sure that the container registry matches the resource in Azure
dockerfilePath: "**/Dockerfile" <=== make sure that your docker file matches the one inside the repository
tag: "$(Build.BuildId)" <=== this is a tag which Flux will refer to when changes are made to the image.
imagePullSecret: "devdeveloperregistry41003ccd-auth"
# Agent VM image name
poolName: "Personal Laptop" <=== this is the agent we will use for building the image artifacts.
stages:
- stage: Build
displayName: Build stage
jobs:
- job: Build
displayName: Build
pool:
name: $(poolName)
steps:
- task: Docker@2
displayName: Build and push an image to container registry
inputs:
command: buildAndPush
repository: $(imageRepository)
dockerfile: $(dockerfilePath)
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
- upload: manifests
artifact: manifests
Now let's deploy our ExpressJS application in our local cluster.
Kubernetes offers the benefit of running applications inside their own containers, isolated from one another. This containerization allows teams to develop applications in separate, independent environments, which simplifies managing dependencies and configurations. To take advantage of this for local development, we can deploy our ExpressJS application within a local Kubernetes cluster using K3D, a lightweight Kubernetes implementation designed specifically for development purposes.
k3d cluster create test-cluster --registry-create test-cluster-registry.localhost --port 44397
docker build -t node-ts-api . -f Dockerfile.dev
docker tag node-ts-api:latest test-cluster-registry.localhost:36741/node-ts-api:local
docker push test-cluster-registry.localhost:36741/node-ts-api:local
Run your application inside a local k3d cluster. The -k
flag tells kubectl to apply the resource manifest files inside the kustomization.yaml
file using the dev
overlay kustomization.yaml
.
kubectl apply -k ./manifests/overlays/dev
Check that the services and deployments are up and running.
kubectl get services,deployments,pods
// Example Output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 8m12s
node-ts-api LoadBalancer 10.43.180.22 172.19.0.3 8080:32289/TCP 5m25s
Now visit your browser and hit the load balancer IP address (172.19.0.3) on port 8080 (mapped to port 3000 on ExpressJS application container).
172.19.0.3:8080
Setup a new repository for the main Flux Configuration. This is the repository that our Kubernetes cluster will interact with and watch for changes.
The flux bootstrap
command utilizes kubectl
to deploy flux controllers on your AKS cluster. It also authenticates with your GitHub account to create a repository to store your Flux configurations. This repository represents the desired state of your cluster. Whenever new changes are pushed to this repository, the Flux controllers running on your cluster will recognise it. It will then attempt to reconsile any differences so that the desired state matches the actual state of the cluster.
flux bootstrap github \
--owner=$GITHUB_USER \
--repository=flux-kubernetes-test \
--branch=main \
--path=./clusters/test-cluster \
--personal \
--components-extra image-reflector-controller,image-automation-controller #<=== add extra components to automate image updates \
--read-write-key=true
Now clone the respository:
git clone https://github.com/$GITHUB_USER/flux-kubernetes-test
cd flux-kubernetes-test
The next step is to add a GitRepository
object referencing the ExpressJS application repository to the Flux repository so that it becomes aware of its manifest files. Notice that we are creating a file flux-kubernetes-test-source.yaml
under the /clusters/test-cluster/
directory of the Flux configuration repository we just cloned.
flux create source git flux-kubernetes-test \
--url=https://github.com/barnacleDevelopments/kubernetes-test
--branch=version_2 \
--interval=1m \
--export > ./clusters/test-cluster/flux-kubernetes-test-source.yaml
View the flux-kubernetes-test-source.yaml
file that was created.
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: node-ts-api
namespace: flux-system
spec:
interval: 1m0s
ref:
branch: version_2
url: https://github.com/barnacleDevelopments/kubernetes-test
secretRef:
name: kubernetes-test-auth
Create secret to authenticate with repository. This is different from the personal access token (PAT) provided when bootstraping flux.
kubectl create secret generic flux-git-auth --namespace flux-system --from-literal=username=barnacleDevelopments --from-literal=password=$GITHUB_PAT
the next few objects will go in the same file.
The Azure container registry is where the Docker images for our ExpressJS application will be stored after they have been built in our CI/CD Azure Devops Pipeline. This is not to be confused with the GitRepository
object. This object will configure Flux to watch for new images and trigger Kubernetes to re-deploy the application.
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: node-ts-api
namespace: flux-system
spec:
image: devdeveloperregistry.azurecr.io/node-ts-api #<=== this is the address of our image
interval: 1h #<=== we are checking every hour
provider: azure
The image policy object will tell Kubernetes how to select the latest image from our container registry.
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: node-ts-api
namespace: flux-system
spec:
imageRepositoryRef:
name: node-ts-api # <=== reference to the ImageRepository object.
policy:
numerical:
order: asc # <=== the image we would like to select (the latest image in this case)
This object tells Kubernetes how often to check our ExpressJS repository for changes and where to update the latest image tag.
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
name: node-ts-api-automation
namespace: flux-system
spec:
interval: 5m #<=== check the repository every 5 minutes for changes
sourceRef:
kind: GitRepository
name: node-ts-api #<=== use the node-ts-api GitRepository object
git:
checkout:
ref:
branch: version_2
push:
branch: version_2
commit:
author:
email: devin@mailfence.com
name: devin
update:
path: ./manifests/overlays/prod
strategy: Setters
In order for Flux to automate image deployment later we will need to update our ExpressJS application manifest files. We are going to update the overlay with a couple comments. These comments will allow flux to identify these fields and push updates to the repository whenever the ExpressJS API image changes. It will update the tag and name seperately as needed.
resources:
- ../../base
namePrefix: prod-
images:
- name: node-ts-api
newName: devdeveloperregistry.azurecr.io/node-ts-api # {"$imagepolicy": "flux-system:node-ts-api:name"} #<=== comment 1
newTag: "88" # {"$imagepolicy": "flux-system:node-ts-api:tag"} #<=== comment 2
Here is the complete configuration for the ExpressJS application. We are going to place this all in one file for simplicity because all these objects are highly related to each other.
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: node-ts-api
namespace: flux-system
spec:
interval: 1m0s
ref:
branch: master
url: https://github.com/barnacleDevelopments/kubernetes-test
secretRef:
name: flux-git-auth
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: node-ts-api
namespace: flux-system
spec:
image: devdeveloperregistry.azurecr.io/node-ts-api #<=== this is the address of our image
interval: 1m #<=== we are checking every hour
provider: azure
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
name: node-ts-api-automation
namespace: flux-system
spec:
interval: 5m #<=== check the repository every 5 minutes for changes
sourceRef:
kind: GitRepository
name: node-ts-api #<=== use the node-ts-api GitRepository object
git:
checkout:
ref:
branch: master
push:
branch: master
commit:
author:
email: devin@mailfence.com
name: devin
update:
path: ./manifests/overlays/prod
strategy: Setters
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: node-ts-api
namespace: flux-system
spec:
imageRepositoryRef:
name: node-ts-api # <=== reference to the ImageRepository object.
policy:
numerical:
order: asc # <=== the image we would like to select (the latest image in this case)
---
Commit and push changes to github.
git add -A && git commit -m "Add flux-kubernetes-test GitRepository"
git push
Next, we are creating a kustomization object. This object tells flux where the manifest configurations are in our ExpressJS Application repository and how often to check them for changes. Notice that we are creating a file flux-kubernetes-test-kustomization.yaml
under the /clusters/test-cluster/
directory of the Flux Configuration Repository we just cloned.
flux create kustomization flux-kubernetes-test \
--target-namespace=default \
--source=node-ts-api \
--path="./manifests/overlays/prod" \
--prune=true \
--wait=true \
--interval=30m \
--retry-interval=2m \
--health-check-timeout=3m \
--export > ./clusters/test-cluster/flux-kubernetes-test-kustomization.yaml
Commit and push changes to Github.
git add -A && git commit -m "Add flux-kubernetes-test Kustomization"
git push
Now let's watch Flux and Kuberenets do their work reconsiling this deployment:
flux get kustomizations --watch
In another terminal look at the deployments, pods, and services:
kubectl -n default get deployments,services,pods
In the previous tutorial I demonstrated uploading new Docker images of our ExpressJS application to our Azure Container Registry. Once this image was uploaded, Azure Pipelines (CI/CD) trigged AKS to pull the latest image from the registry to re-deploy the updated image to Kubernetes pods. Now because we are utilizing Flux to manage deployments to Kubernetes, our pipeline simply needs to handle the build and upload to the container registry and Flux will handle the rest.
app.get("/flux", (req: Request, res: Response) => {
res.send("Welcome to Flux!");
});
git add .
git commit -m "New route afte Flux setup"
git push
View images being built on Azure and uploaded to container registry.
Monitor our pods re-deploying.
kubectl get pods