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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

K8s Network policy Support #1090

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Conversation

g1rana
Copy link
Contributor

@g1rana g1rana commented Nov 30, 2017

Description of the changes

This is feature commit to support K8s Network Policy at contiv. Using feature, Contiv will support K8s Ingress Network Policy however egress policy support comes in future code commit

Type of fix: New feature

Fixes #1089

Please describe:

  • changes made in the Pull request
  • type of testing done (both manual and automated)
  • Manual Testing :
    = Create k8s Network Policy without configuring Pods
    cat network-policy.yaml
    kind: NetworkPolicy
    apiVersion: networking.k8s.io/v1
    metadata:
    name: access-nginx
    spec:
    podSelector:
    matchLabels:
    app: nginx
    ingress:
    • from:
      • podSelector:
        matchLabels:
        app: myapp

kubectl get netpol

NAME POD-SELECTOR AGE
access-nginx app=nginx 6m

  1. Contiv system after k8s policy :
    netctl group ls
    Tenant Group Network IP Pool CfgdTag Policies Network profile

default default default-net
default default-group default-net ingress-policy,access-nginx
3. Bringup Ingress policy Pod and Src Pods
: kubectl create -f nginx-deployment.yaml
: kubectl create -f apod.yaml

kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
apod 1/1 Running 0 2m 10.233.64.8 k8s1
nginx-deployment-431080787-6b6zh 1/1 Running 0 2m 10.233.64.7 k8s1
nginx-deployment-431080787-9d949 1/1 Running 0 2m 10.233.64.6 k8s1

netctl policy rule-ls access-nginx
Incoming Rules:
Rule Priority From EndpointGroup From Network From IpAddress TO IpAddress Protocol Port Action


access-nginx-10.233.64.6-10.233.64.8 2 10.233.64.8 10.233.64.6 0 allow
access-nginx-10.233.64.7-10.233.64.8 2 10.233.64.8 10.233.64.7 0 allow
Outgoing Rules:
Rule Priority To EndpointGroup To Network To IpAddress Protocol Port Action


====

  • Tests = Only for Ingress K8s Network policy
  1. Create Network Policy resource before configuring any policy pods configuration ;
  • Result : make sure K8s policy object created in K8s and contiv system
  1. Create Network policy after configuring Policy Pods without policy Label .
  • Result : make sure only K8s policy object create but no policy configured in contiv system
  1. Create Network Policy after configuring Policy Pods with policy Label .
  • Result : Make sure K8s Policy object and its policy get programmed in Contiv system verify it by "nlicyetctl policy rule-ls
  1. Add more Pods In existing policy on Ingress policy Src side .
  • Result : More Policy rules should be added in contiv system
  1. Add more Pods in existing policy on Policy . destination side.
  • Result : more policy rules should show up in contiv system
  1. Repeat test-4 and 5 by with Pod deletio operation
  2. delete network-policy
    • Result: Policy object should be deleted from both K8s and contiv system
  3. Update Policy Pod information in existing Policy .
    -Result : Make sure update policy rules programmed in contiv system
  • Documentation

@g1rana g1rana changed the title Network policy K8s Network policy Support Nov 30, 2017
@g1rana
Copy link
Contributor Author

g1rana commented Nov 30, 2017

@eng-contiv

Copy link
Contributor

@tiewei tiewei left a comment

Choose a reason for hiding this comment

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

reviewed half of the changes. some suggestions

  1. logging on success request as well
  2. logging and avoid infinity loop
  3. transactional operation should be handled on server side, this way will avoid wired race condition and other concurrency issue

)

const defaultTenantName = "default"
const defaultNetworkName = "net"
const defaultNetworkName = "default-net"
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need to provide upgrading guide for this change ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems from earlier conversations, admin would need to create a "default-net" but would have to be a different subnet? Could that break some deployments? Requires network changes to pods and redeploy?

Copy link

@jerana jerana Jan 4, 2018

Choose a reason for hiding this comment

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

@tiewei: No , don't need to provide upgrade . infect, we can revert this change
@chrisplo: based one tiewei query, user won't be have option to configure "net" network
"

return defaultEpgName
}
func getTenantInfo() string {
return defaultTenantName
Copy link
Contributor

Choose a reason for hiding this comment

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

why these 4 functions are required ?

Copy link
Contributor

Choose a reason for hiding this comment

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

agreed

Copy link

Choose a reason for hiding this comment

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

Initially thinking to use wrapper function to access default tenant and EPG name . look like currently not using it. will remove it

return err
}() != nil {
//XXX:Should we really poll here ;
Copy link
Contributor

Choose a reason for hiding this comment

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

if an infinity loop is here ( without enough logs and hints), no one would able to know how to find out.
please put logs and a maxretry here.

Copy link
Contributor

Choose a reason for hiding this comment

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

idea: log first loop iteration
optionally, additionally we log every minute so we know there is an issue

Copy link

Choose a reason for hiding this comment

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

agree, will put logs with timeout

return err
}() == nil {
}() == nil { //XXX: Same here as above
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

Copy link

Choose a reason for hiding this comment

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

ok

if _, err := k8sNet.contivClient.NetworkGet(defaultTenantName, nwName); err != nil {
if _, err := k8sNet.contivClient.NetworkGet(
defaultTenantName,
nwName); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

log the network deleted correctly

Copy link

Choose a reason for hiding this comment

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

ok. will put log.

if _, err := k8sNet.contivClient.NetworkGet(defaultTenantName, nwName); err == nil {
if _, err := k8sNet.contivClient.NetworkGet(
defaultTenantName,
nwName); err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

get network info and log it

Copy link
Contributor

Choose a reason for hiding this comment

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

to follow up, creating a network that is already there could be construed as a problem, would be good to log so that we can track it down when we have time, I know this was just a reformat

Copy link

Choose a reason for hiding this comment

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

ok , Will put the meaningful logs

return err
}() != nil {
}() != nil { //XXX: Same as above
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

Copy link

Choose a reason for hiding this comment

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

ok

npLog.Errorf("failed to update EPG %v: err:%v ", epgName, err)
return err
}
k8sNet.epgName[epgName] = epgName
Copy link
Contributor

Choose a reason for hiding this comment

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

log default policy and default epg content

//policy := k8sutils.EpgNameToPolicy(epgName, policyName)
if _, err := k8sNet.contivClient.
PolicyGet(tenantName, policyName); err == nil {
npLog.Infof("Policy:%v found contiv", policyName)
Copy link
Contributor

Choose a reason for hiding this comment

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

i think instead get-check-create, the creating checking operations should be handled on server side, this way will avoid concurrency issue

Copy link
Contributor

@chrisplo chrisplo left a comment

Choose a reason for hiding this comment

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

haven't finished reviewing, however, the pod selector logic for pod selectors matchlabels should be AND and not OR:

matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is “key”, the operator is “In”, and the values array contains only “value”. The requirements are ANDed.

https://kubernetes.io/docs/api-reference/v1.8/#labelselector-v1-meta

}
type labelPolicySpec struct {
policy []*k8sNetworkPolicy
}
Copy link
Contributor

Choose a reason for hiding this comment

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

this struct is never referenced

Port int
Protocol string
}
type k8sNameSelector struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

if this is namespace selector, can we name the struct k8sNamespaceSelector?

Copy link

Choose a reason for hiding this comment

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

OK.

type k8sIngress struct {
IngressRules []k8sPolicyPorts
IngressPodSelector []*k8sPodSelector
IngressNameSelector *k8sNameSelector
Copy link
Contributor

Choose a reason for hiding this comment

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

IngressNamespaceSelector?

type npPodInfo struct {
nameSpace string
labelSelectors []string
IP string //??? should care for ipv6 address
Copy link
Contributor

Choose a reason for hiding this comment

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

use net.IP ?

podEpg string
}
type k8sIPBlockSelector struct {
subNetIps []string
Copy link
Contributor

Choose a reason for hiding this comment

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

use net.IPNet?

Copy link
Contributor

Choose a reason for hiding this comment

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

how is the "except" clause going to be handled? see in the docs an example:

- ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24

//List of Rules Per Policy
// policyRules map[string][]string
//List of Network configured
network map[string]bool
Copy link
Contributor

Choose a reason for hiding this comment

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

pluralize

//List of Network configured
network map[string]bool
//List of EPG configured as set
epgName map[string]bool
Copy link
Contributor

Choose a reason for hiding this comment

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

pluralize

policyPerEpg map[string]map[string][]string
//Cache table for given Pods
//Policy Obj per Policy Name
nwPolicyPerNameSpace map[string][]*k8sNetworkPolicy
Copy link
Contributor

Choose a reason for hiding this comment

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

nwPoliciesPerNamespace (plural)

return
//Get Network Policy object sets which ToSpec labelMap information match
//with given pods labelMap
func (k8sNet *k8sContext) getMatchToSpecPartNetPolicy(
Copy link
Contributor

Choose a reason for hiding this comment

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

This might be easier to unit test if you passed in the podInfo's namespace and labelSelectors values instead of the podInfo itself.

I had a hard time with this function name, could we rename with some hints for input, output, and more specific behavior?
e.g.: getPoliciesForPodByLabels


if len(toPartPolicy) > 0 {
npLog.Infof("Recv Pod belongs to ToSpec part of Policy")
for _, nw := range toPartPolicy {
Copy link
Contributor

Choose a reason for hiding this comment

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

nw looks like a network, but this is a policy, please update the name?

@dseevr dseevr removed the request for review from rchirakk December 8, 2017 19:39
Copy link
Contributor

@chrisplo chrisplo left a comment

Choose a reason for hiding this comment

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

getting closer to first pass complete! but having to rethink how I approach the review since it's so big, working through a top down code execution path review now so I can get some clarity on some implementation choices that have been made.

most of the feedback is minor naming / logging type stuff to help fresh eyes figure out what's going on, but there are a few real concerns sprinkled in

return defaultTenantName
}

//Start Network Policy feature enabler
Copy link
Contributor

Choose a reason for hiding this comment

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

I know you didn't touch, but could you please log infon on first pass through he infinite sleep below?

if _, err := k8sNet.contivClient.NetworkGet(defaultTenantName, nwName); err == nil {
if _, err := k8sNet.contivClient.NetworkGet(
defaultTenantName,
nwName); err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

to follow up, creating a network that is already there could be construed as a problem, would be good to log so that we can track it down when we have time, I know this was just a reformat

return err
}() != nil {
//XXX:Should we really poll here ;
Copy link
Contributor

Choose a reason for hiding this comment

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

idea: log first loop iteration
optionally, additionally we log every minute so we know there is an issue

if err = k8sNet.createDefaultPolicy(
defaultTenantName,
epgName); err != nil {
npLog.Errorf("failed Default ingress policy EPG %v: err:%v ",
Copy link
Contributor

Choose a reason for hiding this comment

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

failed creating? maybe could be more clear, default ingress policy for which network/tenant?

return err
}
if err = k8sNet.createEpg(nwName, epgName, policy); err != nil {
npLog.Errorf("failed to update EPG %v: err:%v ", epgName, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

failed to create?

k8sNet.parseIngressPolicy(np.Spec.Ingress,
np.Namespace)
if err != nil {
npLog.Warnf("ignore network policy: %s, %v", np.Name, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

like above, maybe not "ignore"

@@ -343,7 +750,7 @@ func (k8sNet *k8sContext) watchK8sEvents(errChan chan error) {
for k8sNet.isLeader() != true {
time.Sleep(time.Millisecond * 100)
Copy link
Contributor

Choose a reason for hiding this comment

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

marking a spot where we can use a logging retry function

func (k8sNet *k8sContext) processK8sNetworkPolicy(
opCode watch.EventType, np *network_v1.NetworkPolicy) {
if np.Namespace == "kube-system" { //not applicable for system namespace
return
}
Copy link
Contributor

Choose a reason for hiding this comment

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

minor, but since both this and processK8sPod both do this check, you could do it in processK8sEvent instead?


func (k8sNet *k8sContext) addNetworkPolicy(np *network_v1.NetworkPolicy) {
//check if given Policy already exist
if _, ok := k8sNet.networkPolicy[np.Name]; ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure about the top level network policy map as I thought network policy names can be reused across namespaces

}
type k8sNetworkPolicy struct {
PodSelector *k8sPodSelector
Ingress []k8sIngress
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the policy name should be here instead of in the pod selector for instance

PodSelector := k8sPodSelector{
TenantName: getTenantInfo(),
NetworkName: getNetworkInfo(),
GroupName: getEpgInfo()}
Copy link
Contributor

Choose a reason for hiding this comment

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

these three look like placeholders for needed functionality, I think Ranjith's other PR I'm looking at (well I made my own PR based on his) might fit in here for the group name.

Let's discuss/see if we can get to wider consensus that mapping tenant to namespace make sense (thought I saw some conversations that this could be a really good mapping)

for _, l := range label {
if ipMap, ok :=
podSelector.labelPodMap[l]; ok {
ipMap[ip] = true
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this might be a spot to adjust for the AND logic of the matchLabels

nw *k8sNetworkPolicy, label []string, ip string) {
for _, ingress := range nw.Ingress {
for _, podSelector := range ingress.IngressPodSelector {
npLog.Infof("podSelector:%+v", podSelector)
Copy link
Contributor

Choose a reason for hiding this comment

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

leftover debug statement?

//At each Label Walk all its Ips
for ip := range lMap {
nw.PodSelector.podIps[ip] = ip
}
Copy link
Contributor

Choose a reason for hiding this comment

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

another spot that might be implementing OR logic instead of AND logic for matchLabels

if err = k8sNet.deleteRule(policyName, defaultRuleID); err != nil {
npLog.Errorf("failed to delete default rule, %s", err)
//Consolidate all Ips belongs to Label for Pod Selector object
func (k8sNet *k8sContext) updatePodSelectorPodIps(
Copy link
Contributor

Choose a reason for hiding this comment

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

this function doesn't appear to use k8sContext, be more appropriate to be part of the k8sPodSelector struct?

func (k8sNet *k8sContext) initPodSelectorCacheTbl(m map[string]string,
podSelector *k8sPodSelector) error {
if podSelector == nil {
return fmt.Errorf("Passe Nil Pod Selector reference")
Copy link
Contributor

Choose a reason for hiding this comment

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

"Passed", though this is an internal method and we create the podselector in the caller, why would we expect this to ever be nil?

for key, val := range pod.ObjectMeta.Labels {
lkey := getLabelSelector(key, val)
npLog.Infof("Update label Selector key:%v", lkey)
if ipMap, ok := PodSelector.labelPodMap[lkey]; ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

marking a spot that I think is aligning with OR based logic for label matching instead of AND

// reset policy to deny on any error
policyResetOnErr := func(tenantName, groupName string) {
if err != nil {
//k8sNet.resetPolicy(tenantName, groupName)
Copy link
Contributor

Choose a reason for hiding this comment

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

missing implementation, though this could be touchy, not sure we should back out of the existing working policy . . . we might want to focus more on reconciliation of policy against contiv resources for the next attempt

//Get PolicyMap using EPG
policyMap := k8sNet.policyPerEpg[np.PodSelector.GroupName]
//Check if Policy is already programmed in EPG or not
if _, ok := k8sNet.networkPolicy[np.PodSelector.PolicyName]; !ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

policy names I think can be repeated across namespaces, this datastructure would have false positives for existence

if _, ok := k8sNet.networkPolicy[np.PodSelector.PolicyName]; !ok {
//Create Policy and published to ofnet controller
if err = k8sNet.createPolicy(
defaultTenantName,
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't this be the podselector's tenant?

}
//k8sNet.policyPerEpg[np.PodSelector.GroupName] = attachPolicy
} else {
//XXX: Need check if policy rules are still same or not
Copy link
Contributor

Choose a reason for hiding this comment

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

noting missing implementation

policyName string) (lRules *[]client.Rule) {
var listRules []client.Rule
for _, ingress := range np.Ingress {
isPortsCfg := false
Copy link
Contributor

Choose a reason for hiding this comment

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

just initiaze the variable to the expression eval instead of separate if block?

attachPolicy := []string{}
for policyN := range policyMap {
attachPolicy = append(attachPolicy, policyN)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

append(attachPolicy, policyMap...) ?

attachPolicy := []string{}
for policyN := range policyMap {
attachPolicy = append(attachPolicy, policyN)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

append(attachPolicy, policyMap...) ? or even

attachPolicy := make([]string, len(policyMap))
copy(attachPolicy, policyMap)

Include default deny policy

Policy cleanup code

Pod Add/Delete event processing

unit test cases fix

fix core issue in pod delete

fix core issue in pod delete

some cleanup in existing code

 K8s Network Policy support code changes
@lihezhong93
Copy link

After creating EPG, are the spec pods updated to belong to this EPG? If not,will the flow table of ovs install this policy?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

K8s Network Policy(Ingress) Support
5 participants