net/go.book
All Parts Marketing

Network Function Virtualization (NFV) in Go

"A commercial kitchen used to need a dedicated machine for every task: a bread slicer, a meat grinder, a dough mixer, each bolted to the counter, each only able to do its one job. Replace them with a single programmable robot arm that can pick up different attachments on demand, and you have not just saved counter space -- you can now add a new capability by loading new software, not by buying and plumbing in a new machine. That is what NFV does to a network. The dedicated firewall appliance, the hardware load balancer, the physical router in the rack: NFV turns each of them into a program running on commodity servers."

Where SDN, covered in the previous chapter, separates the control plane from the data plane, NFV asks a related but distinct question: why is this network function -- firewall, load balancer, WAN optimizer, intrusion detection -- running on a dedicated physical box at all? A Virtual Network Function (VNF) is that same function reimplemented as software, deployable on a virtual machine or container on general-purpose hardware, scaled up or down the same way you would scale a web service.

The Architecture: MANO

Production NFV deployments are organized around a management-and-orchestration model usually called MANO:

  • VIM (Virtualized Infrastructure Manager): manages the compute, storage, and network resources the VNFs run on -- in practice, this role is filled by a hypervisor platform or a container orchestrator like Kubernetes.
  • VNFM (VNF Manager): manages the lifecycle of individual VNF instances -- instantiate, scale, heal, terminate.
  • NFVO (NFV Orchestrator): composes multiple VNFs into an end-to-end service (a "service chain," e.g., traffic passes through a firewall VNF, then a load balancer VNF, then reaches the application) and coordinates the VIM and VNFMs to realize it.

You will recognize this shape from ordinary cloud-native systems: it is not far from what a container orchestrator plus a service mesh already does for stateless application workloads. That overlap is not a coincidence -- much of the industry's practical NFV today runs VNFs as Cloud-native Network Functions (CNFs), i.e., containers managed by Kubernetes rather than bespoke VM-based orchestration stacks.

Go Implementation: A VNF and a Minimal VNF Manager

A VNF is, at the code level, just a network service -- the same kind of program you have been writing throughout this book. Here is a small load-balancing VNF, using net/http/httputil to reverse-proxy across backend instances -- it deliberately reuses the same atomic round-robin reverse-proxy shape as the case study in Chapter 3.24, rather than re-deriving load balancing from scratch; what is new here is presenting that shape as a VNF: a piece of software a vnfManager can instantiate, scale, and tear down like any other workload, in place of a dedicated hardware appliance.

package main

import (
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"sync/atomic"
)

// loadBalancerVNF is the "virtual network function": a piece of pure
// software doing a job that used to require a dedicated hardware box.
type loadBalancerVNF struct {
	backends []*httputil.ReverseProxy
	counter  uint64
}

func newLoadBalancerVNF(backendURLs []string) *loadBalancerVNF {
	lb := &loadBalancerVNF{}
	for _, raw := range backendURLs {
		target, err := url.Parse(raw)
		if err != nil {
			log.Fatal(err)
		}
		proxy := httputil.NewSingleHostReverseProxy(target)
		lb.backends = append(lb.backends, proxy)
	}
	return lb
}

func (lb *loadBalancerVNF) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	n := atomic.AddUint64(&lb.counter, 1)
	backend := lb.backends[n%uint64(len(lb.backends))]
	backend.ServeHTTP(w, r)
}

func main() {
	lb := newLoadBalancerVNF([]string{
		"http://127.0.0.1:9001",
		"http://127.0.0.1:9002",
	})
	log.Println("load-balancer VNF listening on :8080")
	log.Fatal(http.ListenAndServe(":8080", lb))
}

The interesting NFV part is not this proxy -- it is that a manager can now scale this function up and down programmatically, the same way it would scale any other workload, instead of provisioning another physical appliance. A minimal VNF manager that scales backend instances by driving the Docker CLI:

package main

import (
	"fmt"
	"log"
	"os/exec"
)

// vnfManager is a stand-in for a real VNFM: it instantiates and
// terminates VNF instances as containers on demand.
type vnfManager struct {
	image string
}

func (m *vnfManager) scaleUp(instanceName string, hostPort int) error {
	cmd := exec.Command("docker", "run", "-d",
		"--name", instanceName,
		"-p", fmt.Sprintf("%d:8080", hostPort),
		m.image,
	)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("scale up failed: %w (%s)", err, out)
	}
	log.Printf("started VNF instance %s on port %d\n", instanceName, hostPort)
	return nil
}

func (m *vnfManager) scaleDown(instanceName string) error {
	cmd := exec.Command("docker", "rm", "-f", instanceName)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("scale down failed: %w (%s)", err, out)
	}
	log.Printf("stopped VNF instance %s\n", instanceName)
	return nil
}

func main() {
	manager := &vnfManager{image: "my-org/loadbalancer-vnf:latest"}
	if err := manager.scaleUp("lb-vnf-01", 9101); err != nil {
		log.Fatal(err)
	}
	// ... later, in response to falling demand:
	// manager.scaleDown("lb-vnf-01")
}

This is intentionally close to what a real VNFM does, just without the formal descriptors (TOSCA or ETSI NFV templates) that a production orchestrator would use to describe a VNF's resource requirements and lifecycle hooks. Shelling out to the Docker CLI keeps the example honest about what is actually happening -- starting and stopping a containerized process -- without pretending to a full orchestration framework this chapter cannot responsibly cover in a few hundred words.

Benefits and Honest Tradeoffs

The pitch for NFV is elasticity (spin up a second firewall instance in seconds, not weeks of procurement), cost (commodity servers instead of specialized hardware), and agility (a bug fix or new feature is a software deployment, not a hardware refresh). The tradeoff is performance: a purpose-built ASIC in a hardware firewall will out-throughput a software equivalent running the same logic on a general-purpose CPU, especially at very high packet rates. Most real deployments accept that tradeoff everywhere except the small set of places (core routers, extremely high-throughput links) where it still matters, and that boundary keeps moving in software's favor as CPUs, kernel bypass techniques (like DPDK), and smart NICs improve.

This example manages containers, not the network path itself
Actually rerouting traffic through a VNF chain (so packets really flow firewall to load balancer to application) requires additional plumbing -- container networking rules, a service mesh, or an SDN controller like the one in the previous chapter. The manager above shows lifecycle control; production service chaining needs the data-plane wiring to match.

Frequently Asked Questions

Is the loadBalancerVNF in this chapter really a VNF, or just an ordinary Go reverse proxy? It is both, and that is the point -- a VNF is not some exotic new kind of software, it is exactly the kind of network service you have been writing throughout this book, just deployed and managed the way you would deploy any other workload instead of bolted to a dedicated hardware box. The httputil.ReverseProxy code here does nothing special; what makes it a VNF is that a vnfManager can instantiate and scale it programmatically, the same way a hardware load balancer used to require a procurement cycle instead of a function call.

Does calling scaleUp actually reroute my traffic through the new VNF instance? No, and the Warning box at the end of this chapter is explicit about that gap. vnfManager.scaleUp starts a container and nothing more -- getting packets to actually flow firewall to load balancer to application requires additional data-plane plumbing, whether that is container networking rules, a service mesh, or an SDN controller like the one from the previous chapter. Lifecycle control and traffic steering are separate problems, and this example only solves the first one.

Why does the VNF manager shell out to the Docker CLI instead of using a proper orchestration API? Because the chapter deliberately keeps the example honest about what is actually happening -- starting and stopping a containerized process -- rather than pretending to a full TOSCA/ETSI-style orchestration framework that would take far more than a few hundred words to cover responsibly. A production VNFM would describe VNFs with formal descriptors and drive a real container platform's API, but the underlying action is the same lifecycle operation shown here.

How is NFV different from the SDN material in the previous chapter if they both involve a central controller? The DeepDive box comparing them side by side puts it precisely: SDN is about where decisions are made, centralizing routing and forwarding logic away from individual switches, while NFV is about what the boxes are, replacing dedicated appliances with software on commodity hardware. They compose rather than compete -- an NFV-deployed virtual firewall can itself be steered by an SDN controller, and modern service meshes are essentially both ideas converged into one system.

Won't a software VNF always be slower than the dedicated hardware appliance it replaces? For raw throughput at the extreme end, often yes -- a purpose-built ASIC in a hardware firewall will out-throughput a software equivalent doing the same logic on a general-purpose CPU, particularly at very high packet rates. But most real deployments accept that tradeoff everywhere except a small set of places like core routers or extremely high-throughput links, and that boundary keeps shifting in software's favor as CPUs, kernel-bypass techniques like DPDK, and smart NICs keep improving.

Key Takeaways

  • NFV replaces dedicated hardware appliances -- firewalls, load balancers, routers -- with software running on commodity servers, deployable and scalable like any other workload.
  • Production NFV is organized around MANO: a VIM managing infrastructure, a VNFM managing VNF lifecycles, and an NFVO composing VNFs into service chains.
  • A VNF is ordinary Go networking code; what makes it a VNF is that a manager can instantiate, scale, and tear it down programmatically instead of provisioning hardware.
  • Starting or stopping a VNF's container is not the same as rerouting traffic through it -- actual service chaining needs additional data-plane plumbing.
  • SDN is about where decisions are made; NFV is about what the boxes are. They compose together, and modern service meshes are essentially both ideas converged.