vSphere 8 with Tanzu: How to configure AVI AKO with NodePortLocal.


NSX Advanced load balancer (AVI) is a 100% software-defined multi-cloud application services platform with Software Load Balancers, Intelligent WAF (iWAF), and Container Ingress.

All modern applications use load balancing nowadays. Load balancing is the process of distributing network traffic across multiple servers.

vSphere with Tanzu (TKGs) is the new generation of vSphere for containerized applications. This single, streamlined solution bridges the gap between IT operations and developers with a new kind of infrastructure for modern, cloud-native applications both on-premises and in public clouds.

Antrea is a Kubernetes-native project that implements the Container Network Interface (CNI) and Kubernetes NetworkPolicy, providing network connectivity and security for pod workloads.
Antrea extends the benefit of programmable networks from Open vSwitch (OVS) to Kubernetes.

The goal of this blog

The goal of this blog is to install AKO in the workload cluster using NodePortLocal.
NodePortLocal is a featuregate for Antrea.
AVI will be configured with NSX-T cloud.

I will not explain how to install vSphere 8, NSX-T, AVI, and vSphere with Tanzu.
Check vSphere with Tanzu series part 1 – Getting ready for NSX Application Platform – FQDN blog
and vSphere with Tanzu series part 2 – Create Tanzu cluster for NSX Application platform – FQDN blog
on how to install vSphere with Tanzu with NSX-T.

In vSphere 7, NodePortLocal was enabled by default (in later versions)
In vSphere 8, NodePortLocal is not enabled by default but can be enabled using AntreaConfig. (This might be changed in later releases. Anyway, you can use AntreaConfig to enable it)

What I am using

For my setup, I am using the following.
AVI 22.1.2-2p2
AKO 1.8.2
vSphere 8 GA

Officially AVI 22.1.2 is NOT (yet) supported with vSphere 8.

Getting AVI ready


For the setup to work, the ServiceEngine Data Network Segment needs to be on its own T1.
My T1 is called ‘mgmt’
My Data Network Segment is called ‘se-datasegment’ (
The VIP network is called ‘vip-network’ ( Avi will create a route on the T1 (mgmt) with next-hop the SEs IP.
My workload clusters are in 10.244.x.x (This is the default when installing vSphere with Tanzu)
My setup will look like this.

This is how the supervisor cluster is connected.

This is how the workload cluster is connected

This is how AVI SE is connected.
se-datasegment (SE, datapath)
mgmt (SE, management)

1. Configure NSX-T cloud

Setup your NSX-T cloud (if you not already did)
Do not forget to set up IPAM and DNS profiles.
For DNS I am using ‘nordic.fqdn.nl’

Here you can see my T1 and Data Network Segment

I am using two static pools, one on ‘se-datasegment’ for SEs
and one on ‘vip-network’ for the VIPs

2. Opening NSX-T DFW

vSphere for Tanzu manages the NSX-T DFW.
For that reason we need to open up the DFW, to allow Service Engine traffic to the workload clusters.
AVI SE’s are added to the NSX-T exclude list by default, this is needed for native scaling.
I added the rule to the Emergency group, just to make it simple.

Important note: AVI AKO needs to talk to AVI controller. Ako will be running on the workload cluster. Check your firewall setting where needed.

Creating a workload cluster.

1. Create a new namespace.

Take a look at vSphere with Tanzu series part 2 – Create Tanzu cluster for NSX Application platform – FQDN blog on how to create a namespace in vSphere with Tanzu

In this blog, my namespace is called ‘chris’
My IP of the supervisor cluster is ‘’
You can add your password to .bashrc, if you want.
I am using self-signed certificates and I use an IP address instead of a hostname. Because of this, I need ‘–insecure-skip-tls-verify’. In a production environment, you should use hostnames and proper certificates.

#Login to tkgs
kubectl vsphere login --server= -u administrator@vsphere.local --insecure-skip-tls-verify
kubectl config use-context chris

2. AntreaConfig

Please note the following.
My cluster name will be ‘tkgs-cluster01’
In AntreaConfig you need to add ‘-antrea-package’ to the name.
The name of the config will be ‘tkgs-cluster01-antrea-package’
I have enabled NodePortLocal. ‘NodePortLocal: true

apiVersion: cni.tanzu.vmware.com/v1alpha1
kind: AntreaConfig
  name: tkgs-cluster01-antrea-package
  namespace: chris 
        AntreaProxy: true
        EndpointSlice: true
        NodePortLocal: true
        AntreaPolicy: true
        FlowExporter: false
        NetworkPolicyStats: false
        Egress: false
kubectl apply -f antreaconfig.yaml
kubectl get antreaconfig
kubectl describe antreaconfig

#check if Node Port Local is true

3. Creating a cluster

The name of my cluster is ‘tkgs-cluster01’
I will be using ‘kind: Cluster‘ to create the cluster (More info on ClusterClass here).
The workload cluster will have one master and one worker node, running tkg version ‘v1.23.8+vmware.2-tkg.2-zshippable’ (which can be downloaded from https://wp-content.vmware.com/v2/latest) (Release Notes)
vmClass and storageClass are defined when you create the namespace. You can use kubectl to get them from the cluster.

apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
  name: tkgs-cluster01
  namespace: chris
      cidrBlocks: [""] 
      cidrBlocks: [""]
    serviceDomain: "cluster.local"
    class: tanzukubernetescluster
    version: v1.23.8+vmware.2-tkg.2-zshippable
      replicas: 1
          run.tanzu.vmware.com/resolve-os-image: os-name=photon
        - class: node-pool
          name: node-pool-01
          replicas: 1
              run.tanzu.vmware.com/resolve-os-image: os-name=photon
      - name: vmClass
        value: best-effort-small
      - name: storageClass
        value: vsan-default-storage-policy
kubectl apply -f cluster.yaml

vSphere with Tanzu will create the cluster.
After a while you can login to the cluster.

kubectl vsphere login --server= -u administrator@vsphere.local --insecure-skip-tls-verify --tanzu-kubernetes-cluster-name tkgs-cluster01 --tanzu-kubernetes-cluster-namespace chris
kubectl config use-context tkgs-cluster01

4. Install AKO

For installing AKO, we need HELM. See here for installation.

# We need permissions in the cluster.
kubectl create clusterrolebinding default-tkg-admin-privileged-binding --clusterrole=psp:vmware-system-privileged --group=system:authenticated

# Create avi-system namespace
kubectl create ns avi-system

# Install AKO
helm install  ako/ako  --generate-name --version 1.8.2 -f values.yaml  --set ControllerSettings.controllerHost= --set avicredentials.username=admin --set avicredentials.password=VMware1\! --namespace=avi-system

This is the values.yaml file I use.
Please note the following. AKO will only do L7. L4 will be done by NSX.
Change the following settings to match yours.

cniPlugin: 'antrea'
clusterName: tkgs
layer7Only: true
nsxtT1LR: '/infra/tier-1s/mgmt'
   - networkName: vip-network
serviceType: NodePortLocal
  serviceEngineGroupName: Default-Group
  controllerVersion: '22.1.2'
  cloudName: nsx01aa
  controllerHost: ''
  tenantName: admin

# values.yaml
# Default values for ako.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

  repository: projects.registry.vmware.com/ako/ako
  pullPolicy: IfNotPresent

### This section outlines the generic AKO settings
  primaryInstance: true # Defines AKO instance is primary or not. Value `true` indicates that AKO instance is primary. In a multiple AKO deployment in a cluster, only one AKO instance should be primary. Default value: true.
  enableEvents: 'true' # Enables/disables Event broadcasting via AKO 
  logLevel: WARN   # enum: INFO|DEBUG|WARN|ERROR
  fullSyncFrequency: '1800' # This frequency controls how often AKO polls the Avi controller to update itself with cloud configurations.
  apiServerPort: 8080 # Internal port for AKO's API server for the liveness probe of the AKO pod default=8080
  deleteConfig: 'false' # Has to be set to true in configmap if user wants to delete AKO created objects from AVI 
  disableStaticRouteSync: 'false' # If the POD networks are reachable from the Avi SE, set this knob to true.
  clusterName: tkgs   # A unique identifier for the kubernetes cluster, that helps distinguish the objects for this cluster in the avi controller. // MUST-EDIT
  cniPlugin: 'antrea' # Set the string if your CNI is calico or openshift. enum: calico|canal|flannel|openshift|antrea|ncp
  enableEVH: false # This enables the Enhanced Virtual Hosting Model in Avi Controller for the Virtual Services
  layer7Only: true # If this flag is switched on, then AKO will only do layer 7 loadbalancing.
  # NamespaceSelector contains label key and value used for namespacemigration
  # Same label has to be present on namespace/s which needs migration/sync to AKO
    labelKey: ''
    labelValue: ''
  servicesAPI: false # Flag that enables AKO in services API mode: https://kubernetes-sigs.github.io/service-apis/. Currently implemented only for L4. This flag uses the upstream GA APIs which are not backward compatible 
                     # with the advancedL4 APIs which uses a fork and a version of v1alpha1pre1 
  vipPerNamespace: 'false' # Enabling this flag would tell AKO to create Parent VS per Namespace in EVH mode
  istioEnabled: false # This flag needs to be enabled when AKO is be to brought up in an Istio environment
  # This is the list of system namespaces from which AKO will not listen any Kubernetes or Openshift object event.
  blockedNamespaceList: []
  #   - kube-system 
  #   - kube-public
  #   - tanzu-system-registry
  ipFamily: 'V4' # This flag can take values V4 or V6 (default V4)

### This section outlines the network settings for virtualservices. 
  ## This list of network and cidrs are used in pool placement network for vcenter cloud.
  ## Node Network details are not needed when in nodeport mode / static routes are disabled / non vcenter clouds.
  nodeNetworkList: []
  enableRHI: false # This is a cluster wide setting for BGP peering.
  nsxtT1LR: '/infra/tier-1s/mgmt' # T1 Logical Segment mapping for backend network. Only applies to NSX-T cloud.
  bgpPeerLabels: [] # Select BGP peers using bgpPeerLabels, for selective VsVip advertisement.
  # bgpPeerLabels:
  #   - peer1
  #   - peer2
  #vipNetworkList: [] # Network information of the VIP network. Multiple networks allowed only for AWS Cloud.
   - networkName: vip-network
  #    v6cidr: 2002::1234:abcd:ffff:c0a8:101/64

### This section outlines all the knobs  used to control Layer 7 loadbalancing settings in AKO.
  defaultIngController: 'false'
  noPGForSNI: false # Switching this knob to true, will get rid of poolgroups from SNI VSes. Do not use this flag, if you don't want http caching. This will be deprecated once the controller support caching on PGs.
  serviceType: NodePortLocal # enum NodePort|ClusterIP|NodePortLocal
  shardVSSize: SMALL   # Use this to control the layer 7 VS numbers. This applies to both secure/insecure VSes but does not apply for passthrough. ENUMs: LARGE, MEDIUM, SMALL, DEDICATED
  passthroughShardSize: SMALL   # Control the passthrough virtualservice numbers using this ENUM. ENUMs: LARGE, MEDIUM, SMALL
  enableMCI: 'false' # Enabling this flag would tell AKO to start processing multi-cluster ingress objects.

### This section outlines all the knobs  used to control Layer 4 loadbalancing settings in AKO.
  defaultDomain: '' # If multiple sub-domains are configured in the cloud, use this knob to set the default sub-domain to use for L4 VSes.
  autoFQDN: default   # ENUM: default(<svc>.<ns>.<subdomain>), flat (<svc>-<ns>.<subdomain>), "disabled" If the value is disabled then the FQDN generation is disabled.

### This section outlines settings on the Avi controller that affects AKO's functionality.
  serviceEngineGroupName: Default-Group   # Name of the ServiceEngine Group.
  controllerVersion: '22.1.2' # The controller API version
  cloudName: nsx01aa   # The configured cloud name on the Avi controller.
  controllerHost: '' # IP address or Hostname of Avi Controller
  tenantName: admin   # Name of the tenant where all the AKO objects will be created in AVI.

nodePortSelector: # Only applicable if serviceType is NodePort
  key: ''
  value: ''

    cpu: 350m
    memory: 400Mi
    cpu: 200m
    memory: 300Mi

podSecurityContext: {}

  # Creates the pod security policy if set to true
  pspEnable: false

  username: 'admin'
  password: 'VMware1!'

persistentVolumeClaim: ''
mountPath: /log
logFile: avi.log

5. Check AKO

You can look at the log file of AKO and check if everything is ok.
The most common error you might see is ‘IPAM or DNSprofile’

kubectl get logs ako-0 -n avi-system


Let the fun begin 🙂 Testing if everything works as intended.
Use these simple YAML files.
Important note:
nodeportlocal.antrea.io/enabled: “true”

kubectl create ns demo
kubectl apply -f k8sDeploymentBusyBoxFrontEndV1.yml -n demo
kubectl apply -f k8sSvcClusterIpBusyBoxFrontEndV1.yml -n demo
kubectl apply -f ingress.yaml -n demo
# k8sDeploymentBusyBoxFrontEndV1.yml
apiVersion: apps/v1
kind: Deployment
  name: web-front1
    nodeportlocal.antrea.io/enabled: "true"
  replicas: 4
      app: web-front1
      name: web-front1
        app: web-front1
        - name: web-front1
          image: tacobayle/busybox-v1
          command: [ "sh", "-c"]
          - while true; do
              echo -e "HTTP/1.1 200 OK\n\n$(date)\nApp version is $(printenv AppVersion)\nNode is on nordiclab $(printenv MY_NODE_NAME)\nPod is $(printenv MY_POD_NAME)\nNamespace is $(printenv MY_POD_NAMESPACE)\nPod IP is $(printenv MY_POD_IP)\nPod Service account is $(printenv MY_POD_SERVICE_ACCOUNT)" | nc -l -p 8080;
            - name: MY_NODE_NAME
                  fieldPath: spec.nodeName
            - name: MY_POD_NAME
                  fieldPath: metadata.name
            - name: MY_POD_NAMESPACE
                  fieldPath: metadata.namespace
            - name: MY_POD_IP
                  fieldPath: status.podIP
            - name: MY_POD_SERVICE_ACCOUNT
                  fieldPath: spec.serviceAccountName
      restartPolicy: Always
# k8sSvcClusterIpBusyBoxFrontEndV1.yml
apiVersion: v1
kind: Service
  name: web-front-1
    nodeportlocal.antrea.io/enabled: "true"
    app: web-front1
    - protocol: TCP
      port: 80
      targetPort: 8080
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
  name: ingress
  ingressClassName: avi-lb
    - host: ingress2.k8s.nordic.fqdn.nl #Provide Your Hostname here
          - pathType: Prefix
            path: "/v1"
                name: web-front-1
                  number: 8080

In AVI, you should see something similar.
10.244.x.x are the IPs of the worker nodes. In my example, there is only one worker node

Testing the URL in the browser

Final words

vSphere with Tanzu has added more and more possibilities.
Very happy to see NodePortLocal available in that.

Leave Comment

Your email address will not be published. Required fields are marked *