Skip to content

Commit

Permalink
Added documentation for Netwatcher NAD support
Browse files Browse the repository at this point in the history
Also refactored CNI spec patching code to be re-usable from different packages
  • Loading branch information
Levovar committed Mar 10, 2021
1 parent 9244591 commit 41268df
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 27 deletions.
3 changes: 3 additions & 0 deletions crd/apis/k8s.cni.cncf.io/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +resourceName=network-attachment-definitions

//NetworkAttachmentDefinition is an API defined by the K8s plumbing workgroup in their effort to standardize K8s network management experience
//This spec is directly taken from the upstream specification defined in https://github.com/k8snetworkplumbingwg/multi-net-spec
//Current spec is based on the latest, 1.2 version
type NetworkAttachmentDefinition struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Expand Down
8 changes: 1 addition & 7 deletions pkg/cnidel/cniconfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,10 @@ func readCniConfigFile(cniconfDir string, netInfo *danmtypes.DanmNet, ipamOption
}
//Only overwrite "ipam" of the static CNI config if user wants
if len(ipamOptions.Ips) > 0 {
genericCniConf := map[string]interface{}{}
err = json.Unmarshal(rawConfig, &genericCniConf)
if err != nil {
return nil, errors.New("could not Unmarshal CNI config file:" + cniConfig + ".conf for plugin: " + netInfo.Spec.NetworkType + ", because:" + err.Error())
}
ipamRaw,_ := json.Marshal(ipamOptions)
ipamInGenericFormat := map[string]interface{}{}
json.Unmarshal(ipamRaw, &ipamInGenericFormat)
genericCniConf["ipam"] = ipamInGenericFormat
rawConfig,_ = json.Marshal(genericCniConf)
rawConfig = netcontrol.PatchCniConf(rawConfig, "ipam", ipamInGenericFormat)
}
return rawConfig, nil
}
Expand Down
24 changes: 11 additions & 13 deletions pkg/netcontrol/netcontrol.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,13 +395,8 @@ func (netWatcher *NetWatcher) AddNad(obj interface{}) {
//Upstream IPVLAN/MACVLAN plugins are dumb animals, so we need to modify parent device in their NAD to the exact VLAN/VxLAN host interface
//TODO: on one hand this would make much more sense to be done in an admission controller, on the other one it makes sense for netwatcher to be self-containing
// Let's see if this causes issues in production. A random initial Pod restart here and there when the network and a Pod using it are created the same time we can live with IMO
if dnet.Spec.Options.Vlan != 0 ||dnet.Spec.Options.Vxlan != 0 {
//TODO: copy-paste of cniconfs, need refactoring if works
transparentCniConf := map[string]interface{}{}
json.Unmarshal([]byte(nad.Spec.Config), &transparentCniConf)
transparentCniConf["master"] = DetermineHostDeviceName(dnet)
moddedCniConf,_ := json.Marshal(transparentCniConf)
nad.Spec.Config = string(moddedCniConf)
if dnet.Spec.Options.Vlan != 0 || dnet.Spec.Options.Vxlan != 0 {
nad.Spec.Config = string(PatchCniConf([]byte(nad.Spec.Config), "master", DetermineHostDeviceName(dnet)))
_, err = netWatcher.NadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(nad.ObjectMeta.Namespace).Update(context.TODO(), nad, meta_v1.UpdateOptions{})
if err != nil {
log.Println("INFO: Could not update NetworkAttachmentDefinition:" + nad.ObjectMeta.Name + " with the new parent interface name because:" + err.Error())
Expand Down Expand Up @@ -441,12 +436,7 @@ func (netWatcher *NetWatcher) UpdateNad(oldObj, newObj interface{}) {
log.Println("INFO: Creating host interfaces for modified NetworkAttachmentDefinition:" + newNad.ObjectMeta.Name + " after update failed with error:" + err.Error())
}
if parentUpdateNeeded {
//TODO: copy-paste of cniconfs, need refactoring if works
transparentCniConf := map[string]interface{}{}
json.Unmarshal([]byte(newNad.Spec.Config), &transparentCniConf)
transparentCniConf["master"] = DetermineHostDeviceName(newdDn)
moddedCniConf,_ := json.Marshal(transparentCniConf)
newNad.Spec.Config = string(moddedCniConf)
newNad.Spec.Config = string(PatchCniConf([]byte(newNad.Spec.Config), "master", DetermineHostDeviceName(newdDn)))
_, err = netWatcher.NadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(newNad.ObjectMeta.Namespace).Update(context.TODO(), newNad, meta_v1.UpdateOptions{})
if err != nil {
log.Println("INFO: Could not update NetworkAttachmentDefinition:" + newNad.ObjectMeta.Name + " with the new parent interface name because:" + err.Error())
Expand Down Expand Up @@ -653,6 +643,14 @@ func DetermineHostDeviceName(dnet *danmtypes.DanmNet) string {
return device
}

func PatchCniConf(rawConf []byte, patchKey string, patchValue interface{}) []byte {
transparentCniConf := map[string]interface{}{}
json.Unmarshal(rawConf, &transparentCniConf)
transparentCniConf[patchKey] = patchValue
moddedCniConf,_ := json.Marshal(transparentCniConf)
return moddedCniConf
}

//Little trickery: if there was no change in the VNI+host_device combo during the update we set it to 0 in the manifests.
//Thus we avoid unnecessarily recreating host interfaces.
func zeroVnis(oldDn, newDn *danmtypes.DanmNet) {
Expand Down
76 changes: 69 additions & 7 deletions user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
* [ClusterNetwork](#clusternetwork)
* [TenantConfig](#tenantconfig)
* [Usage of DANM's Netwatcher component](#usage-of-danms-netwatcher-component)
* [Feature description](#feature-description)
* [Usage with DANM APIs](#usage-with-danm-apis)
* [Usage with NetworkAttachmentDefinition API](#usage-with-networkattachmentdefinition-api)
* [Usage of DANM's Svcwatcher component](#usage-of-danms-svcwatcher-component)
* [Feature description](#feature-description)
* [Svcwatcher compatible Service descriptors](#svcwatcher-compatible-service-descriptors)
Expand Down Expand Up @@ -75,7 +78,7 @@ To satisfy the needs of this complex ecosystem, DANM provides different APIs for
**TenantNetworks** is a namespaced API, and can be freely created by tenant users. It basically is the same API as DanmNet, with one big difference: parameters any way related to host settings cannot be freely configured through this API. These parameters are automatically filled by DANM instead!
Wonder how? Refer to chapter [Connecting TenantNetworks to TenantConfigs](#connecting-tenantnetworks-to-tenantconfigs) for more information.

**ClusterNetworks** on the other hand is a cluster-wide API, and as such, can be -or should be- only provisioned by administrator level users. Administrators can freely set all available configuration options, even the physical parameters.
**ClusterNetworks** on the other hand is a cluster-wide API, and as such, can be -or should be- only provisioned by administrator level users. Administrators can freely set all available configuration options, even the physical parameters.
The other nice thing in ClusterNetworks is that all Pods, in any namespace can connect to them - unless the network administrator forbade it via the newly introduced **AllowedTenants** configuration list.

Interested user can find reference manifests showcasing the features of the new APIs under [DANM V4 example manifests](https://github.com/nokia/danm/tree/master/example/4_0_examples).
Expand Down Expand Up @@ -455,17 +458,76 @@ Every CREATE, and PUT TenantConfig operation is subject to the following validat
4. A NetworkID cannot be longer than 10 characters in a NetworkType: NetworkID mapping belonging to a dynamic NetworkType

### Usage of DANM's Netwatcher component
Netwatcher is a mandatory component of the DANM networking suite.
It is implemented using Kubernetes' Informer paradigm, and is deployed as a DaemonSet.
It shall be running on all hosts where DANM CNI is the configured CNI plugin.

The netwatcher component is responsible for dynamically managing (i.e. creation and deletion) VxLAN and VLAN interfaces on all the hosts based on the dynamic network management APIs.
#### Feature description
Netwatcher is a standalone Network Operator responsible for dynamically managing (i.e. creation and deletion) VxLAN and VLAN interfaces on all the hosts based on dynamic network management K8s APIs.
Netwatcher is a mandatory component of the DANM networking suite, but can be a great standalone add to Multus, or any other NetworkAttachmentDefinition driven K8s clusters!
When netwatcher is deployed it runs as a DaemonSet, brought-up on all hosts where a meta CNI plugin is configured.

Whenever a network is created, modified, or deleted -any network, belonging to any of the supported API types- within the Kubernetes cluster, netwatcher will be triggered.
#### Usage with DANM APIs
Whenever a DANM network is created, modified, or deleted -any network, belonging to any of the supported API types- within the Kubernetes cluster, netwatcher will be triggered.
If the network in question contained either the "vxlan", or the "vlan" attributes; then netwatcher immediately creates, or deletes the VLAN or VxLAN host interface with the matching VID.
If the Spec.Options.host_device, .vlan, or .vxlan attributes are modified netwatcher first deletes the old, and then creates the new host interface.

This feature is the most beneficial when used together with a dynamic network provisioning backend supporting connecting Pod interfaces to virtual host devices (IPVLAN, MACVLAN, SR-IOV for VLANs). Whenever a Pod is connected to such a network containing a virtual network identifier, the CNI component automatically connects the created interface to the VxLAN or VLAN host interface created by the netwatcher; instead of directly connecting it to the configured host device.

#### Usage with NetworkAttachmentDefinition API
But wait that's not all - Netwatcher is an API agnostic standalone Operator! This means all of its supported features can be used even in clusters where DANM is not the configured meta CNI solution!
If your cluster uses a CNI solution driven by the NetworkAttachmentDefinition API -such as Multus, or Genie-, you can deploy netwatcher as-is to automate various network management operatios of TelCo workloads.

Whenever you deploy a NAD Netwatcher will inspect the CNI config portion stored under Spec.Config. If there is a VLAN, or VxLAN identifier added to a CNI configuration it will trigger Netwatcher to create the necessary host interfaces, the exact same way as if these attributes were added to a DANM API object.
For example if you want your IPVLAN type NAD to be connected to a specific VLAN just add the tag to your object the following way:
```
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-conf
spec:
config: '{
"name": "vlantest",
"cniVersion": "0.3.1",
"type": "ipvlan",
"master": "tenant-bond",
"vlan": 500,
"ipam": {
"type": "static",
"routes": [
{
"dst": "0.0.0.0/0",
"gw": "10.1.1.1"
}
]
}
}'
```
When it comes to dealing with NADs Netwatcher understands that these extra tags are not recognized by the existing CNI eco-system. So to achieve E2E automation Netwatcher will also modify the CNI configuration of the NAD to point to the right host interface!
Let's use the above example to show how this works!
First, upon seeing this network Netwatcher creates the appropriate host interface with the tag:
```
# ip l | grep vlantest
568: vlantest.500@tenant-bond: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc noqueue state UP mode DEFAULT group default
```
Then it also initiates an Update operation on the NAD, exchanging the old host interface reference to the correct one:
```
# kubectl get network-attachment-definitions.k8s.cni.cncf.io ipvlan-conf -o yaml
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
...
- apiVersion: k8s.cni.cncf.io/v1
fieldsType: FieldsV1
fieldsV1:
f:spec:
f:config: {}
manager: netwatcher
operation: Update
time: "2021-03-01T17:21:11Z"
name: ipvlan-conf
namespace: default
spec:
config: '{"cniVersion":"0.3.1","ipam":{"routes":[{"dst":"0.0.0.0/0","gw":"10.1.1.1"}],"type":"static"},"master":"vlantest.500","name":"vlantest","type":"ipvlan","vlan":500}'
```
This approach ensures users can seamlessly integrate Netwatcher into their existing clusters and enjoy its extra capabilities without any extra hassle - just the way we like it!

### Usage of DANM's Svcwatcher component
#### Feature description
Svcwatcher component showcases the whole reason why DANM exists, and is designed the way it is. It is the first higher-level feature accomplishing our true goal described in the introduction section, that is, extending basic Kubernetes constructs to seamlessly work with multiple network interfaces.
Expand Down

0 comments on commit 41268df

Please sign in to comment.