A while ago, I blogged about an Azure YAML pipeline to deploy AKS together with Traefik. As a variation on that theme, this post talks about deploying AKS together with Nginx, External DNS, a Helm Operator and Flux CD. I blogged about Flux before if you want to know what it does.
Let’s break the pipeline down a little. In what follows, replace AzureMPN with a reference to your own subscription. The first two tasks, AKS deployment and IP address deployment are ARM templates that deploy these resources in Azure. Nothing too special there.
Note that the AKS cluster is one with default networking, no Azure AD integration and without VMSS (so no multiple node pools either).
Note: I modified the pipeline to deploy a VMSS-based cluster with a standard load balancer, which is recommended instead of a cluster based on an availability set with a basic load balancer.
The third task takes the output of the IP address deployment and parses out the IP address using jq (last echo statement on one line):
task: Bash@3 name: GetIP inputs: targetType: 'inline' script: | echo "##vso[task.setvariable variable=test-ip;]$(echo '$(armoutputs)' | jq .ipaddress.value -r)"
The IP address is saved in a variable test-ip for easy reuse later.
Next, we install kubectl and Helm v3. Indeed, Azure DevOps now supports installation of Helm v3 with:
- task: HelmInstaller@1 inputs: helmVersionToInstall: 'latest'
Next, we need to run a script to achieve a couple of things:
- Get AKS credentials with Azure CLI
- Add Helm repositories
- Install a custom resource definition (CRD) for the Helm operator
This is achieved with the following inline Bash script:
- task: AzureCLI@1 inputs: azureSubscription: 'AzureMPN' scriptLocation: 'inlineScript' inlineScript: | az aks get-credentials -g $(aksTestRG) -n $(aksTest) --admin helm repo add stable https://kubernetes-charts.storage.googleapis.com/ helm repo add fluxcd https://charts.fluxcd.io helm repo update kubectl apply -f https://raw.githubusercontent.com/fluxcd/helm-operator/master/deploy/flux-helm-release-crd.yaml
Next, we create a Kubernetes namespace called fluxcd. I create the namespace with some inline YAML in the Kubernetes@1 task:
- task: Kubernetes@1 inputs: connectionType: 'None' command: 'apply' useConfigurationFile: true configurationType: 'inline' inline: | apiVersion: v1 kind: Namespace metadata: name: fluxcd
It’s best to use the approach above instead of kubectl create ns. If the namespace already exists, you will not get an error.
Now we are ready to deploy Nginx, External DNS, Helm operator and Flux CD
This is a pretty basic installation with the Azure DevOps Helm task:
- task: HelmDeploy@0 inputs: connectionType: 'None' namespace: 'kube-system' command: 'upgrade' chartType: 'Name' chartName: 'stable/nginx-ingress' releaseName: 'nginx' overrideValues: 'controller.service.loadBalancerIP=$(test-ip),controller.publishService.enabled=true,controller.metrics.enabled=true'
For External DNS to work, I found I had to set controller.publishService.enabled=true. As you can see, the Nginx service is configured to use the IP we created earlier. Azure will create a load balancer with a front end IP configuration that uses this address. This all happens automatically.
Note: controller.metrics.enabled enables a Prometheus scraping endpoint; that is not discussed further in this blog
External DNS can automatically add DNS records for ingresses and services you add to Kubernetes. For instance, if I create an ingress for test.baeke.info, External DNS can create this record in the baeke.info zone and use the IP address of the Ingress Controller (nginx here). Installation is pretty straightforward but you need to provide credentials to your DNS provider. In my case, I use CloudFlare. Many others are available. Here is the task:
- task: HelmDeploy@0 inputs: connectionType: 'None' namespace: 'kube-system' command: 'upgrade' chartType: 'Name' chartName: 'stable/external-dns' releaseName: 'externaldns' overrideValues: 'cloudflare.apiToken=$(CFAPIToken)' valueFile: 'externaldns/values.yaml'
On CloudFlare, I created a token that has the required access rights to my zone (read, edit). I provide that token to the chart via the CFAPIToken variable defined as a secret on the pipeline. The valueFile looks like this:
rbac: create: true provider: cloudflare logLevel: debug cloudflare: apiToken: CFAPIToken email: email address proxied: false interval: "1m" policy: sync # or upsert-only domainFilters: [ 'baeke.info' ]
In the beginning, it’s best to set the logLevel to debug in case things go wrong. With interval 1m, External DNS checks for ingresses and services every minute and syncs with your DNS zone. Note that External DNS only touches the records it created. It does so by creating TXT records that provide a record that External DNS is indeed the owner.
With External DNS in place, you just need to create an ingress like below to have the A record real.baeke.info created:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: realtime-ingress annotations: kubernetes.io/ingress.class: nginx spec: rules: - host: real.baeke.info http: paths: - path: / backend: serviceName: realtime servicePort: 80
The Helm Operator allows us to install Helm chart by simply using a yaml file. First, we install the operator:
- task: HelmDeploy@0 name: HelmOp displayName: Install Flux CD Helm Operator inputs: connectionType: 'None' namespace: 'kube-system' command: 'upgrade' chartType: 'Name' chartName: 'fluxcd/helm-operator' releaseName: 'helm-operator' overrideValues: 'extraEnvs.name=HELM_VERSION,extraEnvs.value=v3,image.repository=docker.io/fluxcd/helm-operator-prerelease,image.tag=helm-v3-dev-53b6a21d' arguments: '--namespace fluxcd'
This installs the latest version of the operator at the time of this writing (image.repository and image.tag) and also sets Helm to v3. With this installed, you can install a Helm chart by submitting files like below:
apiVersion: helm.fluxcd.io/v1 kind: HelmRelease metadata: name: influxdb namespace: default spec: releaseName: influxdb chart: repository: https://charts.bitnami.com/bitnami name: influxdb version: 0.2.4
You can create files that use kind HelmRelease (HR) because we installed the Helm Operator CRD before. To check installed Helm releases in a namespace, you can run kubectl get hr.
The Helm operator is useful if you want to install Helm charts from a git repository with the help of Flux CD.
Deploy Flux CD with the following task:
- task: HelmDeploy@0 name: FluxCD displayName: Install Flux CD inputs: connectionType: 'None' namespace: 'fluxcd' command: 'upgrade' chartType: 'Name' chartName: 'fluxcd/flux' releaseName: 'flux' overrideValues: 'email@example.com:$(gitURL),git.pollInterval=1m'
The gitURL variable should be set to a git repo that contains your cluster configuration. For instance: gbaeke/demo-clu-flux. Flux will check the repo for changes every minute. Note that we are using a public repo here. Private repos and systems other than GitHub are supported.
Take a look at GitOps with Weaveworks Flux for further instructions. Some things you need to do:
- Install fluxctl
- Use fluxctl identity to obtain the public key from the key pair created by Flux (when you do not use your own)
- Set the public key as a deploy key on the git repo
By connecting the https://github.com/gbaeke/demo-clu-flux repo to Flux CD (as done here), the following is done based on the content of the repo (the complete repo is scanned:
- Install InfluxDB Helm chart
- Add a simple app that uses a Go socket.io implementation to provide realtime updates based on Redis channel content; this app is published via nginx and real.baeke.info is created in DNS (by External DNS)
- Adds a ConfigMap that is used to configure Azure Monitor to enable Prometheus endpoint scraping (to show this can be used for any object you need to add to Kubernetes)
Note that the ingress of the Go app has an annotation (in realtime.yaml, in the git repo) to issue a certificate via cert-manager. If you want to make that work, add an extra task to the pipeline that installs cert-manager:
- task: HelmDeploy@0 inputs: connectionType: 'None' namespace: 'cert-manager' command: 'upgrade' chartType: 'Name' chartName: 'jetstack/cert-manager' releaseName: 'cert-manager' arguments: '--version v0.12.0'
You will also need to create another namespace, cert-manager, just like we created the fluxcd namespace.
In order to make the above work, you will need Issuers or ClusterIssuers. The repo used by Flux CD contains two ClusterIssuers, one for Let’s Encrypt staging and one for production. The ingress resource uses the production issuer due to the following annotation:
The Go app that is deployed by Flux now has TLS enabled by default:
I often use this deployment in demo’s of all sorts. I hope it is helpful for you too in that way!