Azure,Kubernetes,Security An AKS Cluster with no Public IP

An AKS Cluster with no Public IP

Today we are going to look at how to create a truly private AKS Cluster with no public IPs or end-points.
If you have previously built AKS clusters you would have noticed that during provisioning the AKS Cluster provisions a Public IP and a Public Load balancer. This Public IP and used for two reason.
1) Allow connection to the AKS Control Plane
2) Default route NAT for the AKS Node Pools to talk to the Control plane.
If you work for an organisation with higher security requirements and trying to build AKS Clusters to service internal applications there can be some pushback from Security Teams as having a Public IP connecting to a VNET that is private is a big no-no. As such some organisations have Azure Policies in place preventing the creation of Public IP addresses without an exception.
The biggest risk with this is that 1) as a developer if you did not write your yaml files properly you could accidently make your internal application public facing or, 2) The AKS Control plane is accessible over the internet.
To resolve these security issues, Microsoft introduced 3 features to their AKS Clusters:
1) Internal only Load Balancer
2) Using User Defined routes for the Node Pools
3) Private endpoint for the AKS Control plane.

We are going to day see how we are going to use all these features to build a truly private AKS Cluster and deploy a private application.
There are a few things you need to have setup as pre-requisites for this to work.
1) Virtual Appliance or VPG or Azure Firewall, to be your default route out to the internet, in my case my LAB has an express route connecting to our OnPrem environment
2) DNS Server setup with PrivateLink Forwarding, for more information check out the link here.
3) A basic understanding on how your networking is setup.

In my environment I already have this stuff setup, and my canvas looks something like this.

To make sure this works we will first create a User Define Route (UDR) for our Spoke VNET and Subnet and attach it, the UDR requires to have a default route of to either a virtual Gateway, or Virtual Appliance. And associate it to your subnet.

Now we are going to create our AKS cluster, for this we are going to use az cli

> az aks create --resource-group montytest-aue-rg --name monty-private --outbound-type userDefinedRouting --network-plugin azure --vnet-subnet-id $Subnet --service-principal *** --client-secret ***  --node-count 1 --enable-private-cluster

NOTE: First time around when I tried to run this it failed for me with the below:

I learnt that even thought I had PrivteLink setup I had to add a conditional forwarder for * to my Private Link DNS Server

Trying this again. And built 🙂 .

You can see the API Server is now a privatelink URL and trying to ping it it resolves to an internal address.

Server:  localhost.domain

Non-authoritative answer:

And looking under the covers at the resources that get created in the MC

We can see that there is no default Public IP address and a Private endpoint for the api server.
The two main syntax’s that we added to the command above was –-outbound-type userDefinedRouting. This command basicly removes the requirement for having a public IP to be provisioned as it tells the worker nodes to use the UserDefined Route to get to the Management API. The second argument --enable-private-cluster creates the private endpoint for the API Server and setups up the Privatelink DNS.
Connecting to this cluster is all the same just needs to be on the inside network.

$ az login
To sign in, use a web browser to open the page and enter the code QZUMV5ZFN to authenticate.
    "cloudName": "AzureCloud",
    "homeTenantId": "***",
    "id": "**",
    "isDefault": true,
$ az aks get-credentials -n monty-private -g P-montytest-DEV-aue-resourceGroups
$ kubectl get nodes
NAME                                STATUS   ROLES   AGE   VERSION
aks-nodepool1-31554975-vmss000000   Ready    agent   39m   v1.17.13

Creating a new Pod and is all the same just the loadbalancer you need to add an annotation to state it is internal.

apiVersion: apps/v1
kind: Deployment
  name: nginx
  replicas: 3
      app: nginx
        app: nginx
        name: nginx
        type: webserver
      - name: nginx
        image: nginx
        - containerPort: 80
            cpu: 250m
            memory: 64Mi
            cpu: 500m
            memory: 256Mi
apiVersion: v1
kind: Service
  name: internal-app
  annotations: "true"
  type: LoadBalancer
  - port: 80
    app: nginx
$ kubectl apply -f test-internal-aks.yaml
deployment.apps/nginx created
service/internal-app created
$ kubectl get svc --watch
NAME           TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
internal-app   LoadBalancer   <pending>     80:30085/TCP   19s
kubernetes     ClusterIP      <none>        443/TCP        80m
internal-app   LoadBalancer   80:30085/TCP   87s

Adding this line to the load balancer will create an internal LB.

kind: Service
  name: internal-app
  annotations: "true"

And testing it out and there you go

And we are done a fully private AKS Cluster 🙂
If you have any questions, please feel free to comment below.

2 thoughts on “An AKS Cluster with no Public IP”

  1. Hi this is a great post thanks

    Just wanted to check if we can change the load balancer from public ip to private for a running AKS Cluster instead of creating a new one with private load balance ip,which is a straight forward process?

    What will be user defined route in that perspective where we have private cidr space using azure express route and aks public load balancer isn’t accessible?


    1. Thanks for your message, I have not tested this but you can try running “az aks update –name –resource-group outbound-type userDefinedRouting”. As for your User defined route you will still need to setup a default route.

Comments are closed.