Skip to content

Commit d7d0223

Browse files
authored
[WIP] support for Hostport (#335)
* Support for hostport - remove the hardcode cni conf file path - move it to env variable - keep it backward compatible even if env variable is not present - support both .conf and .conflist - daemonset with hostport support Fixes: #168 * add unit test for InsertPodCidrInCniSpec for .conflist * add documentation for hostport
1 parent c7ce398 commit d7d0223

File tree

5 files changed

+317
-25
lines changed

5 files changed

+317
-25
lines changed

Documentation/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,41 @@ For destination hashing scheduling use:
264264
kubectl annotate service my-service "kube-router.io/service.scheduler=dh"
265265
```
266266

267+
### HostPort support
268+
269+
If you would like to use `HostPort` functionality below changes are required in the manifest.
270+
271+
- By default kube-router assumes CNI conf file to be `/etc/cni/net.d/10-kuberouter.conf`. Add an environment variable `KUBE_ROUTER_CNI_CONF_FILE` to kube-router manifest and set it to `/etc/cni/net.d/10-kuberouter.conflist`
272+
- Modify `kube-router-cfg` ConfigMap with CNI config that supports `portmap` as additional plug-in
273+
```
274+
{
275+
"cniVersion":"0.3.0",
276+
"name":"mynet",
277+
"plugins":[
278+
{
279+
"name":"kubernetes",
280+
"type":"bridge",
281+
"bridge":"kube-bridge",
282+
"isDefaultGateway":true,
283+
"ipam":{
284+
"type":"host-local"
285+
}
286+
},
287+
{
288+
"type":"portmap",
289+
"capabilities":{
290+
"snat":true,
291+
"portMappings":true
292+
}
293+
}
294+
]
295+
}
296+
```
297+
- Update init container command to create `/etc/cni/net.d/10-kuberouter.conflist` file
298+
- Restart the container runtime
299+
300+
For an e.g manifest please look at [manifest](../daemonset/kubeadm-kuberouter-all-features-hostport.yaml) with necessary changes required for `HostPort` functionality.
301+
267302
## BGP configuration
268303

269304
[Configuring BGP Peers](bgp.md)

app/controllers/network_routes_controller.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ type NetworkRoutingController struct {
6868
bgpRRClient bool
6969
bgpRRServer bool
7070
bgpClusterId uint32
71+
cniConfFile string
7172
}
7273

7374
var (
@@ -89,7 +90,7 @@ const (
8990

9091
// Run runs forever until we are notified on stop channel
9192
func (nrc *NetworkRoutingController) Run(healthChan chan<- *ControllerHeartbeat, stopCh <-chan struct{}, wg *sync.WaitGroup) {
92-
cidr, err := utils.GetPodCidrFromCniSpec("/etc/cni/net.d/10-kuberouter.conf")
93+
cidr, err := utils.GetPodCidrFromCniSpec(nrc.cniConfFile)
9394
if err != nil {
9495
glog.Errorf("Failed to get pod CIDR from CNI conf file: %s", err.Error())
9596
}
@@ -102,7 +103,7 @@ func (nrc *NetworkRoutingController) Run(healthChan chan<- *ControllerHeartbeat,
102103
}
103104

104105
if len(cidr.IP) == 0 || strings.Compare(oldCidr, currentCidr) != 0 {
105-
err = utils.InsertPodCidrInCniSpec("/etc/cni/net.d/10-kuberouter.conf", currentCidr)
106+
err = utils.InsertPodCidrInCniSpec(nrc.cniConfFile, currentCidr)
106107
if err != nil {
107108
glog.Errorf("Failed to insert pod CIDR into CNI conf file: %s", err.Error())
108109
}
@@ -1563,6 +1564,14 @@ func NewNetworkRoutingController(clientset *kubernetes.Clientset,
15631564
nrc.bgpRRServer = false
15641565
nrc.bgpServerStarted = false
15651566

1567+
nrc.cniConfFile = os.Getenv("KUBE_ROUTER_CNI_CONF_FILE")
1568+
if nrc.cniConfFile == "" {
1569+
nrc.cniConfFile = "/etc/cni/net.d/10-kuberouter.conf"
1570+
}
1571+
if _, err := os.Stat(nrc.cniConfFile); os.IsNotExist(err) {
1572+
return nil, errors.New("CNI conf file " + nrc.cniConfFile + " does not exist.")
1573+
}
1574+
15661575
nrc.ipSetHandler, err = utils.NewIPSet()
15671576
if err != nil {
15681577
return nil, err
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: kube-router-cfg
5+
namespace: kube-system
6+
labels:
7+
tier: node
8+
k8s-app: kube-router
9+
data:
10+
cni-conf.json: |
11+
{
12+
"cniVersion":"0.3.0",
13+
"name":"mynet",
14+
"plugins":[
15+
{
16+
"name":"kubernetes",
17+
"type":"bridge",
18+
"bridge":"kube-bridge",
19+
"isDefaultGateway":true,
20+
"ipam":{
21+
"type":"host-local"
22+
}
23+
},
24+
{
25+
"type":"portmap",
26+
"capabilities":{
27+
"snat":true,
28+
"portMappings":true
29+
}
30+
}
31+
]
32+
}
33+
---
34+
apiVersion: extensions/v1beta1
35+
kind: DaemonSet
36+
metadata:
37+
labels:
38+
k8s-app: kube-router
39+
tier: node
40+
name: kube-router
41+
namespace: kube-system
42+
spec:
43+
template:
44+
metadata:
45+
labels:
46+
k8s-app: kube-router
47+
tier: node
48+
annotations:
49+
scheduler.alpha.kubernetes.io/critical-pod: ''
50+
spec:
51+
serviceAccountName: kube-router
52+
serviceAccount: kube-router
53+
containers:
54+
- name: kube-router
55+
image: cloudnativelabs/kube-router
56+
imagePullPolicy: Always
57+
args:
58+
- --run-router=true
59+
- --run-firewall=true
60+
- --run-service-proxy=true
61+
- --kubeconfig=/var/lib/kube-router/kubeconfig
62+
env:
63+
- name: NODE_NAME
64+
valueFrom:
65+
fieldRef:
66+
fieldPath: spec.nodeName
67+
- name: KUBE_ROUTER_CNI_CONF_FILE
68+
value: /etc/cni/net.d/10-kuberouter.conflist
69+
livenessProbe:
70+
httpGet:
71+
path: /healthz
72+
port: 20244
73+
initialDelaySeconds: 10
74+
periodSeconds: 3
75+
resources:
76+
requests:
77+
cpu: 250m
78+
memory: 250Mi
79+
securityContext:
80+
privileged: true
81+
volumeMounts:
82+
- name: lib-modules
83+
mountPath: /lib/modules
84+
readOnly: true
85+
- name: cni-conf-dir
86+
mountPath: /etc/cni/net.d
87+
- name: kubeconfig
88+
mountPath: /var/lib/kube-router
89+
readOnly: true
90+
initContainers:
91+
- name: install-cni
92+
image: busybox
93+
imagePullPolicy: Always
94+
command:
95+
- /bin/sh
96+
- -c
97+
- set -e -x;
98+
if [ ! -f /etc/cni/net.d/10-kuberouter.conflist ]; then
99+
TMP=/etc/cni/net.d/.tmp-kuberouter-cfg;
100+
cp /etc/kube-router/cni-conf.json ${TMP};
101+
mv ${TMP} /etc/cni/net.d/10-kuberouter.conflist;
102+
fi
103+
volumeMounts:
104+
- name: cni-conf-dir
105+
mountPath: /etc/cni/net.d
106+
- name: kube-router-cfg
107+
mountPath: /etc/kube-router
108+
hostNetwork: true
109+
tolerations:
110+
- key: CriticalAddonsOnly
111+
operator: Exists
112+
- effect: NoSchedule
113+
key: node-role.kubernetes.io/master
114+
operator: Exists
115+
volumes:
116+
- name: lib-modules
117+
hostPath:
118+
path: /lib/modules
119+
- name: cni-conf-dir
120+
hostPath:
121+
path: /etc/cni/net.d
122+
- name: kube-router-cfg
123+
configMap:
124+
name: kube-router-cfg
125+
- name: kubeconfig
126+
configMap:
127+
name: kube-proxy
128+
items:
129+
- key: kubeconfig.conf
130+
path: kubeconfig
131+
---
132+
apiVersion: v1
133+
kind: ServiceAccount
134+
metadata:
135+
name: kube-router
136+
namespace: kube-system
137+
---
138+
kind: ClusterRole
139+
apiVersion: rbac.authorization.k8s.io/v1beta1
140+
metadata:
141+
name: kube-router
142+
namespace: kube-system
143+
rules:
144+
- apiGroups:
145+
- ""
146+
resources:
147+
- namespaces
148+
- pods
149+
- services
150+
- nodes
151+
- endpoints
152+
verbs:
153+
- list
154+
- get
155+
- watch
156+
- apiGroups:
157+
- "networking.k8s.io"
158+
resources:
159+
- networkpolicies
160+
verbs:
161+
- list
162+
- get
163+
- watch
164+
- apiGroups:
165+
- extensions
166+
resources:
167+
- networkpolicies
168+
verbs:
169+
- get
170+
- list
171+
- watch
172+
---
173+
kind: ClusterRoleBinding
174+
apiVersion: rbac.authorization.k8s.io/v1beta1
175+
metadata:
176+
name: kube-router
177+
roleRef:
178+
apiGroup: rbac.authorization.k8s.io
179+
kind: ClusterRole
180+
name: kube-router
181+
subjects:
182+
- kind: ServiceAccount
183+
name: kube-router
184+
namespace: kube-system

utils/pod_cidr.go

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io/ioutil"
88
"net"
99
"reflect"
10+
"strings"
1011

1112
"github.com/containernetworking/cni/libcni"
1213
"github.com/containernetworking/cni/plugins/ipam/host-local/backend/allocator"
@@ -15,22 +16,39 @@ import (
1516

1617
// GetPodCidrFromCniSpec gets pod CIDR allocated to the node from CNI spec file and returns it
1718
func GetPodCidrFromCniSpec(cniConfFilePath string) (net.IPNet, error) {
18-
netconfig, err := libcni.ConfFromFile(cniConfFilePath)
19-
if err != nil {
20-
return net.IPNet{}, fmt.Errorf("Failed to load CNI conf file: %s", err.Error())
21-
}
22-
19+
var podCidr net.IPNet
20+
var err error
2321
var ipamConfig *allocator.IPAMConfig
24-
ipamConfig, _, err = allocator.LoadIPAMConfig(netconfig.Bytes, "")
25-
if err != nil {
26-
return net.IPNet{}, fmt.Errorf("Failed to get IPAM details from the CNI conf file: %s", err.Error())
27-
}
2822

29-
podCidr := net.IPNet(ipamConfig.Subnet)
23+
if strings.HasSuffix(cniConfFilePath, ".conflist") {
24+
var confList *libcni.NetworkConfigList
25+
confList, err = libcni.ConfListFromFile(cniConfFilePath)
26+
if err != nil {
27+
return net.IPNet{}, fmt.Errorf("Failed to load CNI config list file: %s", err.Error())
28+
}
29+
for _, conf := range confList.Plugins {
30+
if conf.Network.IPAM.Type != "" {
31+
ipamConfig, _, err = allocator.LoadIPAMConfig(conf.Bytes, "")
32+
if err != nil {
33+
return net.IPNet{}, fmt.Errorf("Failed to get IPAM details from the CNI conf file: %s", err.Error())
34+
}
35+
break
36+
}
37+
}
38+
} else {
39+
netconfig, err := libcni.ConfFromFile(cniConfFilePath)
40+
if err != nil {
41+
return net.IPNet{}, fmt.Errorf("Failed to load CNI conf file: %s", err.Error())
42+
}
43+
ipamConfig, _, err = allocator.LoadIPAMConfig(netconfig.Bytes, "")
44+
if err != nil {
45+
return net.IPNet{}, fmt.Errorf("Failed to get IPAM details from the CNI conf file: %s", err.Error())
46+
}
47+
}
48+
podCidr = net.IPNet(ipamConfig.Subnet)
3049
if reflect.DeepEqual(podCidr, net.IPNet{}) {
3150
return net.IPNet{}, errors.New("subnet missing from CNI IPAM")
3251
}
33-
3452
return podCidr, nil
3553
}
3654

@@ -41,15 +59,46 @@ func InsertPodCidrInCniSpec(cniConfFilePath string, cidr string) error {
4159
if err != nil {
4260
return fmt.Errorf("Failed to load CNI conf file: %s", err.Error())
4361
}
44-
config := make(map[string]interface{})
45-
err = json.Unmarshal(file, &config)
46-
if err != nil {
47-
return fmt.Errorf("Failed to parse JSON from CNI conf file: %s", err.Error())
48-
}
62+
var config interface{}
63+
if strings.HasSuffix(cniConfFilePath, ".conflist") {
64+
err = json.Unmarshal(file, &config)
65+
if err != nil {
66+
return fmt.Errorf("Failed to parse JSON from CNI conf file: %s", err.Error())
67+
}
68+
updatedCidr := false
69+
configMap := config.(map[string]interface{})
70+
for key := range configMap {
71+
if key != "plugins" {
72+
continue
73+
}
74+
// .conflist file has array of plug-in config. Find the one with ipam key
75+
// and insert the CIDR for the node
76+
pluginConfigs := configMap["plugins"].([]interface{})
77+
for _, pluginConfig := range pluginConfigs {
78+
pluginConfigMap := pluginConfig.(map[string]interface{})
79+
if val, ok := pluginConfigMap["ipam"]; ok {
80+
valObj := val.(map[string]interface{})
81+
valObj["subnet"] = cidr
82+
updatedCidr = true
83+
break
84+
}
85+
}
86+
}
4987

50-
config["ipam"].(map[string]interface{})["subnet"] = cidr
51-
configJson, _ := json.Marshal(config)
52-
err = ioutil.WriteFile(cniConfFilePath, configJson, 0644)
88+
if !updatedCidr {
89+
return fmt.Errorf("Failed to insert subnet cidr into CNI conf file: %s as CNI file is invalid.", cniConfFilePath)
90+
}
91+
92+
} else {
93+
err = json.Unmarshal(file, &config)
94+
if err != nil {
95+
return fmt.Errorf("Failed to parse JSON from CNI conf file: %s", err.Error())
96+
}
97+
pluginConfig := config.(map[string]interface{})
98+
pluginConfig["ipam"].(map[string]interface{})["subnet"] = cidr
99+
}
100+
configJSON, _ := json.Marshal(config)
101+
err = ioutil.WriteFile(cniConfFilePath, configJSON, 0644)
53102
if err != nil {
54103
return fmt.Errorf("Failed to insert subnet cidr into CNI conf file: %s", err.Error())
55104
}

0 commit comments

Comments
 (0)