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
Support Pod /32 IP request via k8s annotation (ipAddrsNoIpam) #264
Conversation
calico_cni_k8s_test.go
Outdated
_, _, _, _, contAddresses, _, err := CreateContainer(netconfCalicoIPAM, name) | ||
Expect(err).NotTo(HaveOccurred()) | ||
|
||
podIP := contAddresses[0].IP |
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.
What about checking what IP was stored in the workload endpoint in etcd?
k8s/k8s.go
Outdated
if visited6 >= 1 { | ||
logger.Fatal("Can not have more than one IPv6 addresses in ipAddrsNoIpam annotation") | ||
} else { | ||
result.IP6.IP.IP = ipAddr |
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.
Do you really need the visited
stuff? Why not just checkout result.IPx.IP or whatever?
k8s/k8s.go
Outdated
logger.WithField("annotation", ipAddrsNoIpam).Fatal("Invalid JSON") | ||
} | ||
|
||
result := types.Result{ |
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.
Do you really need all of this for creating the Result
?
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.
Not sure I understand what you mean here. Can you elaborate?
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 think I get what you're saying now. Done!
k8s/k8s.go
Outdated
// We also make sure there is only one IPv4 and/or one IPv6 passed in, | ||
// since CNI spec only supports one of each right now. | ||
for _, ip := range ips { | ||
|
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.
Extra blank line
k8s/k8s.go
Outdated
} | ||
logger.Debugf("IPAM plugin returned: %+v", result) | ||
} else { | ||
// ipAddrsNoIpam annotation is set so bypass IPMA, and set the IPs manually. |
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.
typo: IPMA
k8s/k8s.go
Outdated
} else { | ||
// ipAddrsNoIpam annotation is set so bypass IPMA, and set the IPs manually. | ||
result = overrideIPAMResult(ipAddrsNoIpam, logger) | ||
logger.Debugf("Bypassing IPAM to set the IP config to: %+v", result) |
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.
IP config -> result to be consistent.
k8s/k8s.go
Outdated
|
||
err := json.Unmarshal([]byte(ipAddrsNoIpam), &ips) | ||
if err != nil { | ||
logger.WithField("annotation", ipAddrsNoIpam).Fatal("Invalid JSON") |
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.
Could you make the error here more descriptive? It's user facing, so something like "failed to parse ipAddrsNoIpam as json"
utils/types.go
Outdated
AssignIpv6 *string `json:"assign_ipv6"` | ||
IPv4Pools []string `json:"ipv4_pools,omitempty"` | ||
IPv6Pools []string `json:"ipv6_pools,omitempty"` | ||
IPAddrsNoIpam []string `json:"ip_addrs_noipam,omitempty"` |
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 don't think this needs to be part of the CNI config file.
k8s/k8s.go
Outdated
// overrideIPAMResult generates types.Result like the one produced by IPAM plugin, | ||
// but sets IP field manually since IPAM is bypassed with this annotation. | ||
// Example annotation: | ||
// cni.projectcalico.org/ipAddrsNoIpam: "[\"10.0.0.1\", \"2001:db8::1\"]" |
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 whole annotation isn't passed to this function, right? We should document the format of the string that gets passed to this function.
return nil, err | ||
} | ||
logger.Debugf("IPAM plugin returned: %+v", result) | ||
} else { |
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.
Are we worried about the fact that we do IPAM related stuff up above even when this annotation is specified?
e.g https://github.com/projectcalico/cni-plugin/pull/264/files#diff-b396ca928d5bd0e161acd283b56ead9fR84
and https://github.com/projectcalico/cni-plugin/pull/264/files#diff-b396ca928d5bd0e161acd283b56ead9fR124?
Probably not because it's a kinda weird case, but it does mean we'll be doing more work than we need to.
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.
(discussed this offline. Going to leave this as it is for now. Will need to refactor how we call k8s APIs at some point. )
@tomdee @caseydavenport addressed your comments. PTAL |
k8s/k8s.go
Outdated
|
||
err := json.Unmarshal([]byte(ipAddrsNoIpam), &ips) | ||
if err != nil { | ||
logger.WithField("annotation", ipAddrsNoIpam).Fatal("Failed to parse ipAddrsNoIpam as json") |
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.
Should we be doing a log.Fatal here? Shouldn't this function return an error so we can pass back an error over CNI?
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.
Same below.
k8s/k8s.go
Outdated
|
||
// annotation value can't be empty. | ||
if len(ips) == 0 { | ||
logger.WithField("annotation", "cni.projectcalico.org/ipAddrsNoIpam").Fatal("No IPs specified") |
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 always forget... should this actually be ipAddrsNoIPAM
?
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 think that's what you and @lxpollitt decided, but then later you had a suggestion of using ipAddrsSkipIPAM
which I personally like better
k8s/k8s.go
Outdated
err := json.Unmarshal([]byte(ipAddrsNoIpam), &ips) | ||
if err != nil { | ||
logger.WithField("annotation", ipAddrsNoIpam).Error("Failed to parse ipAddrsNoIpam as json") | ||
return nil, err |
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.
Would be nice to wrap these errors with context before returning - remember they all end up in the user's eyes via kubectl describe pod
e.g return nil, fmt.Errorf("Failed to parse ipAddrsNoIpam: %s", err)
Same goes for other returned errors.
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.
done! BTW, this is why I think we should be using https://github.com/pkg/errors 🙂
k8s/k8s.go
Outdated
ipAddr := net.ParseIP(ip) | ||
if ipAddr == nil { | ||
logger.WithField("IP", ip).Error("Invalid IP format") | ||
return nil, fmt.Errorf("Invalid IP format: %s", err) |
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's this line that should include the IP in the returned error so that it's clear what the misconfig is.
k8s/k8s.go
Outdated
// If/when CNI spec supports more than one IP, we can loosen this requirement. | ||
if result.IP6 != nil { | ||
logger.Error("Can not have more than one IPv6 addresses in ipAddrsNoIpam annotation") | ||
return nil, fmt.Errorf("Can not have more than one IPv6 addresses in ipAddrsNoIpam annotation") |
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.
typo: more than one IPv6 addresses
k8s/k8s.go
Outdated
// It's an IPv4 address. | ||
if result.IP4 != nil { | ||
logger.Error("Can not have more than one IPv4 addresses in ipAddrsNoIpam annotation") | ||
return nil, fmt.Errorf("Can not have more than one IPv4 addresses in ipAddrsNoIpam annotation") |
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.
more than one IPv4 addresses (shouldn't be plural)
634f54d
to
d5cd7e4
Compare
Part of issue #258
If specified, this annotation ignores all other IPAM settings and will be stored in the Calico WorkloadEndpoint without calling any IPAM plugin, no questions asked. This allows non-CNI things to be the source of truth for IPAM.