Let's discuss the CNM objects in detail.
This contains the configuration of a container's network stack, which includes management of routing tables, the container's interface, and DNS settings. An implementation of a sandbox can be a Linux network namespace, a FreeBSD jail, or other similar concept. A sandbox may contain many endpoints from multiple networks. It also represents a container's network configuration such as IP-address, MAC address, and DNS entries. libnetwork makes use of the OS-specific parameters to populate the network configuration represented by sandbox. libnetwork provides a framework to implement sandbox in multiple operating systems. Netlink is used to manage the routing table in namespace, and currently two implementations of sandbox exist, namespace_linux.go
and configure_linux.go
, to uniquely identify the path on the host filesystem.
A sandbox is associated with a single Docker container. The following data structure shows the runtime elements of a sandbox:
type sandbox struct { id string containerID string config containerConfig osSbox osl.Sandbox controller *controller refCnt int endpoints epHeap epPriority map[string]int joinLeaveDone chan struct{} dbIndex uint64 dbExists bool isStub bool inDelete bool sync.Mutex }
A new sandbox is instantiated from a network controller (which is explained in more detail later):
func (c *controller) NewSandbox(containerID string, options ...SandboxOption) (Sandbox, error) { ….. }
An endpoint joins a sandbox to the network and provides connectivity for services exposed by a container to the other containers deployed in the same network. It can be an internal port of Open vSwitch or a similar veth pair. An endpoint can belong to only one network but may only belong to one sandbox. An endpoint represents a service and provides various APIs to create and manage the endpoint. It has a global scope but gets attached to only one network, as shown in the following figure:
An endpoint is specified by the following data structure:
type endpoint struct { name string id string network *network iface *endpointInterface joinInfo *endpointJoinInfo sandboxID string exposedPorts []types.TransportPort anonymous bool generic map[string]interface{} joinLeaveDone chan struct{} prefAddress net.IP prefAddressV6 net.IP ipamOptions map[string]string dbIndex uint64 dbExists bool sync.Mutex }
An endpoint is associated with a unique ID and name. It is attached to a network and a sandbox ID. It is also associated with an IPv4 and IPv6 address space. Each endpoint is associated with an endpointInterface
struct.
A network is a group of endpoints that are able to communicate with each other directly. It provides the required connectivity within the same host or multiple hosts, and whenever a network is created or updated, the corresponding driver is notified. An example is a VLAN or Linux bridge, which has a global scope within a cluster.
Networks are controlled from a network controller, which we will discuss in the next section. Every network has a name, address space, ID, and network type:
type network struct { ctrlr *controller name string networkType string id string ipamType string addrSpace string ipamV4Config []*IpamConf ipamV6Config []*IpamConf ipamV4Info []*IpamInfo ipamV6Info []*IpamInfo enableIPv6 bool postIPv6 bool epCnt *endpointCnt generic options.Generic dbIndex uint64 svcRecords svcMap dbExists bool persist bool stopWatchCh chan struct{} drvOnce *sync.Once internal bool sync.Mutex }
A network controller object provides APIs to create and manage a network object. It is an entry point in the libnetwork by binding a particular driver to a given network, and it supports multiple active drivers, both in-built and remote. Network controller allows users to bind a particular driver to a given network:
type controller struct { id string drivers driverTable ipamDrivers ipamTable sandboxes sandboxTable cfg *config.Config stores []datastore.DataStore discovery hostdiscovery.HostDiscovery extKeyListener net.Listener watchCh chan *endpoint unWatchCh chan *endpoint svcDb map[string]svcMap nmap map[string]*netWatch defOsSbox osl.Sandbox sboxOnce sync.Once sync.Mutex }
Each network controller has reference to the following:
The following figure shows how Network Controller sits between the Docker Engine and the containers and networks they are attached to:
There are two types of attributes, as follows:
–labels
option. Their main function is to perform driver-specific operations and they are passed from the UI.Consumers of the container network model interact through the CNM objects and its APIs to network the containers that they manage.
Drivers register with network controller. Built-in drivers register inside of libnetwork, while remote drivers register with libnetwork via a plugin mechanism (WIP). Each driver handles a particular network type.
A network controller object is created using the libnetwork.New()
API to manage the allocation of networks and optionally configure a driver with driver-specific options.
The network is created using the controller's NewNetwork()
API by providing a name and networkType
. The networkType
parameter helps to choose a corresponding driver and binds the created network to that driver. From this point, any operation on the network will be handled by that driver.
The controller.NewNetwork()
API also takes in optional options parameters that carry driver-specific options and labels, which the drivers can make use for its purpose.
network.CreateEndpoint()
can be called to create a new endpoint in a given network. This API also accepts optional options parameters that vary with the driver.
Drivers will be called with driver.CreateEndpoint
and it can choose to reserve IPv4/IPv6 addresses when an endpoint is created in a network. The driver will assign these addresses using the InterfaceInfo
interface defined in the driver
API. The IPv4/IPv6 addresses are needed to complete the endpoint as a service definition along with the ports the endpoint exposes. A service endpoint is a network address and the port number that the application container is listening on.
endpoint.Join()
can be used to attach a container to an endpoint. The Join
operation will create a sandbox if it doesn't exist for that container. The drivers make use of the sandbox key to identify multiple endpoints attached to the same container.
There is a separate API to create an endpoint and another to join the endpoint.
An endpoint represents a service that is independent of the container. When an endpoint is created, it has resources reserved for the container to get attached to the endpoint later. It gives a consistent networking behavior.
endpoint.Leave()
is invoked when a container is stopped. The driver can clean up the states that it allocated during the Join()
call. libnetwork will delete the sandbox when the last referencing endpoint leaves the network.
libnetwork keeps holding on to IP addresses as long as the endpoint is still present. These will be reused when the container (or any container) joins again. It ensures that the container's resources are re-used when they are stopped and started again.
endpoint.Delete()
is used to delete an endpoint from a network. This results in deleting the endpoint and cleaning up the cached sandbox.Info
.
network.Delete()
is used to delete a network. Delete is allowed if there are no endpoints attached to the network.