-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
networking: refactor building nomad bridge config #23772
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package cni | ||
|
||
import "encoding/json" | ||
|
||
// Conflist is the .conflist format of CNI network config. | ||
type Conflist struct { | ||
CniVersion string `json:"cniVersion"` | ||
Name string `json:"name"` | ||
Plugins []any `json:"plugins"` | ||
} | ||
|
||
// Json produces indented json of the conflist. | ||
func (b Conflist) Json() ([]byte, error) { | ||
return json.MarshalIndent(b, "", "\t") | ||
} | ||
|
||
// NomadBridgeConfig determines the contents of the Conflist. | ||
type NomadBridgeConfig struct { | ||
BridgeName string | ||
AdminChainName string | ||
IPv4Subnet string | ||
HairpinMode bool | ||
ConsulCNI bool | ||
} | ||
|
||
// NewNomadBridgeConflist produces a full Conflist from the config. | ||
func NewNomadBridgeConflist(conf NomadBridgeConfig) Conflist { | ||
// Update website/content/docs/networking/cni.mdx when the bridge config | ||
// is modified. The json versions of the config can be found in | ||
// client/allocrunner/test_fixtures/*.conflist.json | ||
// If CNI plugins are added or versions need to be updated for new fields, | ||
// add a new constraint to nomad/job_endpoint_hooks.go | ||
|
||
ipRanges := [][]Range{ | ||
{{Subnet: conf.IPv4Subnet}}, | ||
} | ||
ipRoutes := []Route{ | ||
{Dst: "0.0.0.0/0"}, | ||
} | ||
|
||
plugins := []any{ | ||
Generic{ | ||
Type: "loopback", | ||
}, | ||
Bridge{ | ||
Type: "bridge", | ||
Bridgename: conf.BridgeName, | ||
IpMasq: true, | ||
IsGateway: true, | ||
ForceAddress: true, | ||
HairpinMode: conf.HairpinMode, | ||
Ipam: IPAM{ | ||
Type: "host-local", | ||
Ranges: ipRanges, | ||
Routes: ipRoutes, | ||
}, | ||
}, | ||
Firewall{ | ||
Type: "firewall", | ||
Backend: "iptables", | ||
AdminChainName: conf.AdminChainName, | ||
}, | ||
Portmap{ | ||
Type: "portmap", | ||
Capabilities: PortmapCapabilities{ | ||
Portmappings: true, | ||
}, | ||
Snat: true, | ||
}, | ||
} | ||
if conf.ConsulCNI { | ||
plugins = append(plugins, ConsulCNI{ | ||
Type: "consul-cni", | ||
LogLevel: "debug", | ||
}) | ||
} | ||
|
||
return Conflist{ | ||
CniVersion: "0.4.0", | ||
Name: "nomad", | ||
Plugins: plugins, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package cni | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/shoenig/test/must" | ||
) | ||
|
||
func TestConflist_Json(t *testing.T) { | ||
conf := &Conflist{ | ||
CniVersion: "0.0.1", | ||
Name: "test-config", | ||
Plugins: []any{ | ||
Generic{Type: "test-plugin"}, | ||
}, | ||
} | ||
bts, err := conf.Json() | ||
must.NoError(t, err) | ||
must.Eq(t, `{ | ||
"cniVersion": "0.0.1", | ||
"name": "test-config", | ||
"plugins": [ | ||
{ | ||
"type": "test-plugin" | ||
} | ||
] | ||
}`, string(bts)) | ||
} | ||
Comment on lines
+12
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this just test the stdlib? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it tests stdlib json, but the main target is the appropriate struct tags to match CNI spec. I was aiming for a minimal test, but this is probably better covered by the more holistic real-world bridge conflists. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package cni | ||
|
||
// Generic has the one key that all plugins must have: "type" | ||
type Generic struct { | ||
Type string `json:"type"` | ||
} | ||
|
||
// Bridge is the subset of options that we use to configure the "bridge" plugin. | ||
// https://www.cni.dev/plugins/current/main/bridge/ | ||
type Bridge struct { | ||
Type string `json:"type"` | ||
Bridgename string `json:"bridge"` | ||
IpMasq bool `json:"ipMasq"` | ||
IsGateway bool `json:"isGateway"` | ||
ForceAddress bool `json:"forceAddress"` | ||
HairpinMode bool `json:"hairpinMode"` | ||
Ipam IPAM `json:"ipam"` | ||
} | ||
type IPAM struct { | ||
Type string `json:"type"` | ||
Ranges [][]Range `json:"ranges"` | ||
Routes []Route `json:"routes"` | ||
} | ||
type Range struct { | ||
Subnet string `json:"subnet"` | ||
} | ||
type Route struct { | ||
Dst string `json:"dst"` | ||
} | ||
|
||
// Firewall is the "firewall" plugin. | ||
// https://www.cni.dev/plugins/current/meta/firewall/ | ||
type Firewall struct { | ||
Type string `json:"type"` | ||
Backend string `json:"backend"` | ||
AdminChainName string `json:"iptablesAdminChainName"` | ||
} | ||
|
||
// Portmap is the "portmap" plugin. | ||
// https://www.cni.dev/plugins/current/meta/portmap/ | ||
type Portmap struct { | ||
Type string `json:"type"` | ||
Capabilities PortmapCapabilities `json:"capabilities"` | ||
Snat bool `json:"snat"` | ||
} | ||
type PortmapCapabilities struct { | ||
Portmappings bool `json:"portMappings"` | ||
} | ||
|
||
// ConsulCNI is the "consul-cni" plugin used for transparent proxy. | ||
// https://github.com/hashicorp/consul-k8s/blob/main/control-plane/cni/main.go | ||
type ConsulCNI struct { | ||
Type string `json:"type"` | ||
LogLevel string `json:"log_level"` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to ensure the refactor is no-op, these files were generated by the old code (plus whitespace), and passed prior to the refactor. |
||
"cniVersion": "0.4.0", | ||
"name": "nomad", | ||
"plugins": [ | ||
{ | ||
"type": "loopback" | ||
}, | ||
{ | ||
"type": "bridge", | ||
"bridge": "bad\"", | ||
"ipMasq": true, | ||
"isGateway": true, | ||
"forceAddress": true, | ||
"hairpinMode": true, | ||
"ipam": { | ||
"type": "host-local", | ||
"ranges": [ | ||
[ | ||
{ | ||
"subnet": "172.26.64.0/20" | ||
} | ||
] | ||
], | ||
"routes": [ | ||
{ | ||
"dst": "0.0.0.0/0" | ||
} | ||
] | ||
} | ||
}, | ||
{ | ||
"type": "firewall", | ||
"backend": "iptables", | ||
"iptablesAdminChainName": "NOMAD-ADMIN" | ||
}, | ||
{ | ||
"type": "portmap", | ||
"capabilities": { | ||
"portMappings": true | ||
}, | ||
"snat": true | ||
} | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels a little weird having a separate package called
cni
here without it including much of any of the CNI configuration. There's no code isolation happening because everything is exported (has to be because of serialization.) All CNI setup including all the configuration tests are still in theclient/allocrunner
package. It seems hard to disentangle the "bridge configurator" code from CNI as well.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the initial motivation was to namespace the structs -- I found myself naming them like
FirewallCNIPlugin
just because they were in theallocrunner
package, and I thought if I made a small new package, then they could be named more simply and still make sense. I also just like small, focused packages in general.but were I to spend more time on this, I probably would move more of the CNI stuff into here, but I figure this is an incremental improvement to be perhaps built on further, later.
regarding the "networking" package comment, a decent chunk of our networking config, especially on linux, is CNI, but I might still like to distinguish the things that are specifically satisfying CNI spec from other more-general networking concerns. i.e. if I really went ham refactoring this area of allocrunner, I might make a "networking" package, then still put a (relatively smaller) "cni" package within there.
but as you might imagine, I am trying not to boil the ocean! 😅
all that said, I could move the new golden-file tests into here, since they are a direct output of this package. I left them where they were because that was the hook into this refactor, but I did create them here in the first place...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll also admit to being a little concerned about backports, since I've been intending to go back to 1.6.x for future maintainability, but e.g. consul-cni is 1.8.x only. backports won't be tooooo painful anyway, or I could forego them entirely, but at any rate, it was on my mind when deciding what all to move into this new package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally reasonable 👍