Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

CNI: Add support for OpenShift and Multus #1527

Merged
merged 3 commits into from
Sep 27, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ commands:
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl

wget https://get.helm.sh/helm-v3.9.4-linux-amd64.tar.gz
tar -zxvf helm-v3.9.4-linux-amd64.tar.gz
wget https://get.helm.sh/helm-v3.7.0-linux-amd64.tar.gz
tar -zxvf helm-v3.7.0-linux-amd64.tar.gz
sudo mv linux-amd64/helm /usr/local/bin/helm
create-kind-clusters:
parameters:
Expand Down
8 changes: 8 additions & 0 deletions charts/consul/templates/cni-clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,12 @@ rules:
- {{ template "consul.fullname" . }}-cni
verbs:
- use
{{- if .Values.global.openshift.enabled}}
- apiGroups: ["security.openshift.io"]
resources: ["securitycontextconstraints"]
resourceNames:
- {{ template "consul.fullname" . }}-cni
verbs:
- use
{{- end }}
{{- end }}
1 change: 1 addition & 0 deletions charts/consul/templates/cni-daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ spec:
- -log-level={{ default .Values.global.logLevel .Values.connectInject.cni.logLevel }}
- -cni-bin-dir={{ .Values.connectInject.cni.cniBinDir }}
- -cni-net-dir={{ .Values.connectInject.cni.cniNetDir }}
- -multus={{ .Values.connectInject.cni.multus }}
{{- with .Values.connectInject.cni.resources }}
resources:
{{- toYaml . | nindent 12 }}
Expand Down
25 changes: 25 additions & 0 deletions charts/consul/templates/cni-networkattachmentdefinition.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{{- if (and (.Values.connectInject.cni.enabled) (.Values.connectInject.cni.multus)) }}
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: {{ template "consul.fullname" . }}-cni
namespace: {{ .Release.Namespace }}
labels:
app: {{ template "consul.name" . }}
chart: {{ template "consul.chart" . }}
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
component: cni
spec:
config: '{
"cniVersion": "0.3.1",
"type": "consul-cni",
"cni_bin_dir": "{{ .Values.connectInject.cni.cniBinDir }}",
"cni_net_dir": "{{ .Values.connectInject.cni.cniNetDir }}",
"kubeconfig": "ZZZ-consul-cni-kubeconfig",
"log_level": "{{ default .Values.global.logLevel .Values.connectInject.cni.logLevel }}",
"multus": true,
"name": "consul-cni",
"type": "consul-cni"
}'
{{- end }}
50 changes: 50 additions & 0 deletions charts/consul/templates/cni-securitycontextconstraints.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{{- if (and (.Values.connectInject.cni.enabled) (.Values.global.openshift.enabled)) }}
apiVersion: security.openshift.io/v1
kind: SecurityContextConstraints
metadata:
name: {{ template "consul.fullname" . }}-cni
namespace: {{ .Release.Namespace }}
labels:
app: {{ template "consul.name" . }}
chart: {{ template "consul.chart" . }}
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
component: cni
annotations:
kubernetes.io/description: {{ template "consul.fullname" . }}-cni are the security context constraints required
to run consul-cni.
allowHostDirVolumePlugin: true
allowHostIPC: false
allowHostNetwork: false
allowHostPID: false
allowHostPorts: false
allowPrivilegeEscalation: true
allowPrivilegedContainer: true
allowedCapabilities: null
defaultAddCapabilities: null
fsGroup:
type: MustRunAs
groups: []
priority: null
readOnlyRootFilesystem: false
requiredDropCapabilities:
- KILL
- MKNOD
- SETUID
- SETGID
runAsUser:
type: MustRunAsRange
seLinuxContext:
type: MustRunAs
supplementalGroups:
type: MustRunAs
users: []
volumes:
- configMap
- downwardAPI
- emptyDir
- persistentVolumeClaim
- projected
- secret
- hostPath
{{- end }}
5 changes: 5 additions & 0 deletions charts/consul/test/unit/cni-daemonset.bats
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ load _helpers
--set 'connectInject.cni.logLevel=bar' \
--set 'connectInject.cni.cniBinDir=baz' \
--set 'connectInject.cni.cniNetDir=foo' \
--set 'connectInject.cni.multus=false' \
bar \
. | tee /dev/stderr |
yq '.spec.template.spec.containers[0].command' | tee /dev/stderr)
Expand All @@ -86,6 +87,10 @@ load _helpers
local actual=$(echo "$cmd" |
yq 'any(contains("cni-net-dir=foo"))' | tee /dev/stderr)
[ "${actual}" = "true" ]

local actual=$(echo "$cmd" |
yq 'any(contains("multus=false"))' | tee /dev/stderr)
[ "${actual}" = "true" ]
}

#--------------------------------------------------------------------
Expand Down
61 changes: 61 additions & 0 deletions charts/consul/test/unit/cni-networkattachmentdefinition.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bats

load _helpers

@test "cni/NetworkAttachmentDefinition: disabled by default" {
cd `chart_dir`
assert_empty helm template \
-s templates/cni-networkattachmentdefinition.yaml \
.
}

@test "cni/NetworkAttachmentDefinition: disabled when cni enabled and multus disabled" {
cd `chart_dir`
assert_empty helm template \
-s templates/cni-securitycontextconstraints.yaml \
--set 'connectInject.enabled=true' \
--set 'connectInject.cni.enabled=true' \
--set 'connectInject.cni.multus=false' \
.
}

@test "cni/NetworkAttachmentDefinition: enabled when cni enabled and multus enabled" {
cd `chart_dir`
local actual=$(helm template \
-s templates/cni-networkattachmentdefinition.yaml \
--set 'connectInject.enabled=true' \
--set 'connectInject.cni.enabled=true' \
--set 'connectInject.cni.multus=true' \
. | tee /dev/stderr |
yq -s 'length > 0' | tee /dev/stderr)
[ "${actual}" = "true" ]
}

@test "cni/NetworkAttachmentDefinition: config is set" {
cd `chart_dir`
local cmd=$(helm template \
-s templates/cni-networkattachmentdefinition.yaml \
--set 'connectInject.cni.enabled=true' \
--set 'connectInject.enabled=true' \
--set 'connectInject.cni.multus=true' \
--set 'connectInject.cni.logLevel=bar' \
--set 'connectInject.cni.cniBinDir=baz' \
--set 'connectInject.cni.cniNetDir=foo' \
bar \
. | tee /dev/stderr |
yq -rc '.spec.config' | tee /dev/stderr)

local actual=$(echo "$cmd" |
yq '.log_level' | tee /dev/stderr)
[ "${actual}" = '"bar"' ]

local actual=$(echo "$cmd" |
yq '.cni_bin_dir' | tee /dev/stderr)
[ "${actual}" = '"baz"' ]

local actual=$(echo "$cmd" |
yq '.cni_net_dir' | tee /dev/stderr)
[ "${actual}" = '"foo"' ]

}

33 changes: 33 additions & 0 deletions charts/consul/test/unit/cni-securitycontextcontstraints.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bats

load _helpers

@test "cni/SecurityContextConstraints: disabled by default" {
cd `chart_dir`
assert_empty helm template \
-s templates/cni-securitycontextconstraints.yaml \
.
}

@test "cni/SecurityContextConstraints: disabled when cni disabled and global.openshift.enabled=true" {
cd `chart_dir`
assert_empty helm template \
-s templates/cni-securitycontextconstraints.yaml \
--set 'connectInject.enabled=true' \
--set 'connectInject.cni.enabled=false' \
--set 'global.openshift.enabled=true' \
.
}

@test "cni/SecurityContextConstraints: enabled when cni enabled and global.openshift.enabled=true" {
cd `chart_dir`
local actual=$(helm template \
-s templates/cni-securitycontextconstraints.yaml \
--set 'connectInject.enabled=true' \
--set 'connectInject.cni.enabled=true' \
--set 'global.openshift.enabled=true' \
. | tee /dev/stderr |
yq -s 'length > 0' | tee /dev/stderr)
[ "${actual}" = "true" ]
}

15 changes: 14 additions & 1 deletion charts/consul/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1960,7 +1960,20 @@ connectInject:
# Location on the kubernetes node of all CNI configuration. Should be the absolute path and start with a '/'
# @type: string
cniNetDir: "/etc/cni/net.d"


# If multus CNI plugin is enabled with consul-cni. When enabled, consul-cni will not be installed as a chained
# CNI plugin. Instead, a NetworkAttachementDefinition CustomResourceDefinition (CRD) will be created in the helm
# release namespace. Following multus plugin standards, an annotation is required in order for the consul-cni plugin
# to be executed and for your service to be added to the Consul Service Mesh.
#
# Add the annotation `'k8s.v1.cni.cncf.io/networks': '[{ "name":"consul-cni","namespace": "consul" }]'` to your pod
# to use the default installed NetworkAttachementDefinition CRD.
#
# Please refer to the [Multus Quickstart Guide](https://github.com/k8snetworkplumbingwg/multus-cni/blob/master/docs/quickstart.md)
# for more information about using multus.
# @type: string
Comment on lines +1964 to +1974
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful!

multus: false

# The resource settings for CNI installer daemonset.
# @recurse: false
# @type: map
Expand Down
49 changes: 28 additions & 21 deletions control-plane/cni/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,26 +137,28 @@ func (c *Command) cmdAdd(args *skel.CmdArgs) error {
Level: hclog.LevelFromString(cfg.LogLevel),
})

// Check to see if the plugin is a chained plugin.
if cfg.PrevResult == nil {
return fmt.Errorf("must be called as final chained plugin")
}

logger.Debug("consul-cni plugin config", "config", cfg)
// Convert the PrevResult to a concrete Result type that can be modified. The CNI standard says
// that the previous result needs to be passed onto the next plugin.
prevResult, err := current.GetResult(cfg.PrevResult)
if err != nil {
return fmt.Errorf("failed to convert prevResult: %w", err)
}

if len(prevResult.IPs) == 0 {
return fmt.Errorf("got no container IPs")
}
// Only chained plugins have a previous result.
var result *current.Result

// Pass the prevResult through this plugin to the next one.
result := prevResult
logger.Debug("consul-cni previous result", "result", result)
// Check to see if the plugin is a chained plugin.
if cfg.PrevResult == nil {
// The plugin is not chained (ie multus) so create a fake result
result = &current.Result{
CNIVersion: "0.3.1",
}
} else {
prevResult, err := current.GetResult(cfg.PrevResult)
if err != nil {
return fmt.Errorf("failed to convert prevResult: %w", err)
}
if len(prevResult.IPs) == 0 {
return fmt.Errorf("got no container IPs")
}
// Pass the prevResult through this plugin to the next one.
result = prevResult
}

ctx := context.Background()
if c.client == nil {
Expand Down Expand Up @@ -186,7 +188,7 @@ func (c *Command) cmdAdd(args *skel.CmdArgs) error {

// We do not throw an error here because kubernetes will often throw a benign error where the pod has been
// updated in between the get and update of the annotation. Eventually kubernetes will update the annotation
ok := c.updateTransparentProxyStatusAnnotation(pod, podNamespace, waiting)
ok := c.updateTransparentProxyStatusAnnotation(podName, podNamespace, waiting)
if !ok {
logger.Info("unable to update %s pod annotation to waiting", keyTransparentProxyStatus)
}
Expand All @@ -213,7 +215,7 @@ func (c *Command) cmdAdd(args *skel.CmdArgs) error {

// We do not throw an error here because kubernetes will often throw a benign error where the pod has been
// updated in between the get and update of the annotation. Eventually kubernetes will update the annotation
ok = c.updateTransparentProxyStatusAnnotation(pod, podNamespace, complete)
ok = c.updateTransparentProxyStatusAnnotation(podName, podNamespace, complete)
if !ok {
logger.Info("unable to update %s pod annotation to complete", keyTransparentProxyStatus)
}
Expand Down Expand Up @@ -270,8 +272,13 @@ func parseAnnotation(pod corev1.Pod, annotation string) (iptables.Config, error)

// updateTransparentProxyStatusAnnotation updates the transparent-proxy-status annotation. We use it as a simple inicator of
// CNI status on the pod. Failing is not fatal.
func (c *Command) updateTransparentProxyStatusAnnotation(pod *corev1.Pod, namespace, status string) bool {
func (c *Command) updateTransparentProxyStatusAnnotation(podName, namespace, status string) bool {
// Refresh the pod so that we can update it without problems
pod, err := c.client.CoreV1().Pods(namespace).Get(context.Background(), podName, metav1.GetOptions{})
if err != nil {
return false
}
pod.Annotations[keyTransparentProxyStatus] = status
_, err := c.client.CoreV1().Pods(namespace).Update(context.Background(), pod, metav1.UpdateOptions{})
_, err = c.client.CoreV1().Pods(namespace).Update(context.Background(), pod, metav1.UpdateOptions{})
return err == nil
}
48 changes: 46 additions & 2 deletions control-plane/subcommand/install-cni/cniconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
// defaultCNIConfigFile gets the the correct config file from the cni net dir.
// Adapted from kubelet: https://github.com/kubernetes/kubernetes/blob/954996e231074dc7429f7be1256a579bedd8344c/pkg/kubelet/dockershim/network/cni/cni.go#L134.
func defaultCNIConfigFile(dir string) (string, error) {
files, err := libcni.ConfFiles(dir, []string{".conf", ".conflist", ".json"})
files, err := libcni.ConfFiles(dir, []string{".conf", ".conflist"})
if err != nil {
return "", fmt.Errorf("error while trying to find files in %s: %w", dir, err)
}
Expand Down Expand Up @@ -58,13 +58,57 @@ func defaultCNIConfigFile(dir string) (string, error) {
// CNI config list has no networks, skipping".
continue
}

return confFile, nil
}
// There were files but none of them were valid
return "", fmt.Errorf("no valid config files found in %s", dir)
}

// confListFileFromConfFile converts a .conf file into a .conflist file. Chained plugins use .conflist files.
func confListFileFromConfFile(cfgFile string) (string, error) {
if !strings.HasSuffix(cfgFile, ".conf") {
return "", fmt.Errorf("invalid conf file: %s", cfgFile)
}

// Convert the .conf file into a map so that we can remove pieces of it.
cfgMap, err := configFileToMap(cfgFile)
if err != nil {
return "", fmt.Errorf("could not convert .conf file to map: %w", err)
}

// Remove the cniVersion header from the conf map.
delete(cfgMap, "cniVersion")

// Create the new plugins: [] section and add the contents from cfgMap to it.
plugins := make([]map[string]interface{}, 1)
plugins[0] = cfgMap

listMap := map[string]interface{}{
"name": "k8s-pod-network",
"cniVersion": "0.3.1",
"plugins": plugins,
}

listFile := fmt.Sprintf("%s%s", cfgFile, "list")

// Marshal into a new json object.
listJSON, err := json.MarshalIndent(listMap, "", " ")
if err != nil {
return "", fmt.Errorf("error marshalling conflist: %w", err)
}

// Libcni nuance/bug. If the newline is missing, the cni plugin will throw errors saying that it cannot get parse the config.
listJSON = append(listJSON, "\n"...)

// Write the .conflist file out.
err = os.WriteFile(listFile, listJSON, os.FileMode(0o644))
if err != nil {
return "", fmt.Errorf("error writing conflist file %s: %w", listFile, err)
}

return listFile, nil
}

// The format of the main cni config file is unstructured json consisting of a header and list of plugins
//
// {
Expand Down
Loading