Skip to content

Commit

Permalink
Implement support for cilium-health in ENI mode
Browse files Browse the repository at this point in the history
In ENI mode, rules and routes are created for each endpoint (and
therefore pod) in order to direct traffic to the appropriate ENI device.
This logic was only running in the CNI plugin. Since cilium-health is
not a pod, its rules and routes needed for traffic to flow were missing.

This commit implements creating the necessary rules and routes for
cilium-health in ENI mode. This commit also unifies the installation
logic of the rules and routes in `pkg/aws/eni/routing`, allowing the CNI
plugin and the cilium-health creation code to share a common
implementation.

Fixes #10810

Signed-off-by: Chris Tarazi <chris@isovalent.com>
  • Loading branch information
christarazi authored and qmonnet committed Apr 28, 2020
1 parent dbedb10 commit 26308b6
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 104 deletions.
28 changes: 23 additions & 5 deletions cilium-health/launch/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"time"

"github.com/cilium/cilium/api/v1/models"
enirouting "github.com/cilium/cilium/pkg/aws/eni/routing"
"github.com/cilium/cilium/pkg/datapath/connector"
"github.com/cilium/cilium/pkg/datapath/linux/route"
"github.com/cilium/cilium/pkg/defaults"
Expand Down Expand Up @@ -229,12 +230,20 @@ type EndpointAdder interface {
}

// LaunchAsEndpoint launches the cilium-health agent in a nested network
// namespace and attaches it to Cilium the same way as any other endpoint,
// but with special reserved labels.
// namespace and attaches it to Cilium the same way as any other endpoint, but
// with special reserved labels.
//
// CleanupEndpoint() must be called before calling LaunchAsEndpoint() to ensure
// cleanup of prior cilium-health endpoint instances.
func LaunchAsEndpoint(baseCtx context.Context, owner regeneration.Owner, n *nodeTypes.Node, mtuConfig mtu.Configuration, epMgr EndpointAdder, proxy endpoint.EndpointProxy, allocator cache.IdentityAllocator) (*Client, error) {
func LaunchAsEndpoint(baseCtx context.Context,
owner regeneration.Owner,
n *nodeTypes.Node,
mtuConfig mtu.Configuration,
epMgr EndpointAdder,
proxy endpoint.EndpointProxy,
allocator cache.IdentityAllocator,
eniHealthRouting *enirouting.RoutingInfo) (*Client, error) {

var (
cmd = launcher.Launcher{}
info = &models.EndpointChangeRequest{
Expand Down Expand Up @@ -331,11 +340,20 @@ func LaunchAsEndpoint(baseCtx context.Context, owner regeneration.Owner, n *node
}

// Set up the endpoint routes
hostAddressing := node.GetNodeAddressing()
if err = configureHealthRouting(info.ContainerName, epIfaceName, hostAddressing, mtuConfig); err != nil {
if err = configureHealthRouting(info.ContainerName, epIfaceName, node.GetNodeAddressing(), mtuConfig); err != nil {
return nil, fmt.Errorf("Error while configuring routes: %s", err)
}

if option.Config.IPAM == option.IPAMENI {
if err := enirouting.Install(healthIP,
eniHealthRouting,
mtuConfig.GetDeviceMTU(),
option.Config.Masquerade); err != nil {

return nil, fmt.Errorf("Error while configuring health endpoint rules and routes: %s", err)
}
}

if err := epMgr.AddEndpoint(owner, ep, "Create cilium-health endpoint"); err != nil {
return nil, fmt.Errorf("Error while adding endpoint: %s", err)
}
Expand Down
5 changes: 5 additions & 0 deletions daemon/cmd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
hubbleProto "github.com/cilium/cilium/api/v1/flow"
"github.com/cilium/cilium/api/v1/models"
health "github.com/cilium/cilium/cilium-health/launch"
enirouting "github.com/cilium/cilium/pkg/aws/eni/routing"
"github.com/cilium/cilium/pkg/bpf"
"github.com/cilium/cilium/pkg/clustermesh"
"github.com/cilium/cilium/pkg/controller"
Expand Down Expand Up @@ -143,6 +144,10 @@ type Daemon struct {

k8sWatcher *watchers.K8sWatcher

// healthEndpointRouting is the information required to set up the health
// endpoint's routing in ENI mode
healthEndpointRouting *enirouting.RoutingInfo

hubbleObserver *observer.LocalObserverServer
}

Expand Down
10 changes: 9 additions & 1 deletion daemon/cmd/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,15 @@ func (d *Daemon) initHealth() {
if client == nil || err != nil {
var launchErr error
d.cleanupHealthEndpoint()
client, launchErr = health.LaunchAsEndpoint(ctx, d, &d.nodeDiscovery.LocalNode, d.mtuConfig, d.endpointManager, d.l7Proxy, d.identityAllocator)

client, launchErr = health.LaunchAsEndpoint(ctx,
d,
&d.nodeDiscovery.LocalNode,
d.mtuConfig,
d.endpointManager,
d.l7Proxy,
d.identityAllocator,
d.healthEndpointRouting)
if launchErr != nil {
if err != nil {
return fmt.Errorf("failed to restart endpoint (check failed: %q): %s", err, launchErr)
Expand Down
52 changes: 51 additions & 1 deletion daemon/cmd/ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package cmd

import (
"errors"
"fmt"
"net"
"strings"
Expand All @@ -23,11 +24,13 @@ import (
"github.com/cilium/cilium/api/v1/models"
ipamapi "github.com/cilium/cilium/api/v1/server/restapi/ipam"
"github.com/cilium/cilium/pkg/api"
enirouting "github.com/cilium/cilium/pkg/aws/eni/routing"
"github.com/cilium/cilium/pkg/cidr"
"github.com/cilium/cilium/pkg/datapath"
"github.com/cilium/cilium/pkg/defaults"
"github.com/cilium/cilium/pkg/ipam"
"github.com/cilium/cilium/pkg/logging/logfields"
"github.com/cilium/cilium/pkg/mac"
"github.com/cilium/cilium/pkg/node"
nodeTypes "github.com/cilium/cilium/pkg/node/types"
"github.com/cilium/cilium/pkg/option"
Expand Down Expand Up @@ -218,8 +221,17 @@ func (d *Daemon) allocateHealthIPs() error {
return fmt.Errorf("unable to allocate health IPs: %s,see https://cilium.link/ipam-range-full", err)
}

d.nodeDiscovery.LocalNode.IPv4HealthIP = result.IP
log.Debugf("IPv4 health endpoint address: %s", result.IP)
d.nodeDiscovery.LocalNode.IPv4HealthIP = result.IP

// In ENI mode, we require the gateway, CIDRs, and the ENI MAC addr
// in order to set up rules and routes on the local node to direct
// endpoint traffic out of the ENIs.
if option.Config.IPAM == option.IPAMENI {
if err := d.parseHealthEndpointInfo(result); err != nil {
log.Warningf("Unable to allocate health information for ENI: %s", err)
}
}
}

if option.Config.EnableIPv6 {
Expand Down Expand Up @@ -354,3 +366,41 @@ func (d *Daemon) bootstrapIPAM() {
d.ipam = ipam.NewIPAM(d.datapath.LocalNodeAddressing(), option.Config, d.nodeDiscovery, d.k8sWatcher)
bootstrapStats.ipam.End(true)
}

func (d *Daemon) parseHealthEndpointInfo(result *ipam.AllocationResult) error {
if d.healthEndpointRouting == nil {
d.healthEndpointRouting = &enirouting.RoutingInfo{}
}

if ip := net.ParseIP(result.GatewayIP); ip != nil {
log.Debugf("IPv4 health gateway address: %s", ip)

d.healthEndpointRouting.IPv4Gateway = ip
} else {
return errors.New("IPv4 health gateway address could not be parsed")
}

if len(result.CIDRs) == 0 {
return errors.New("IPv4 health CIDR missing")
}

for _, c := range result.CIDRs {
if _, cidr, err := net.ParseCIDR(c); cidr != nil && err == nil {
d.healthEndpointRouting.IPv4CIDRs = append(d.healthEndpointRouting.IPv4CIDRs, *cidr)
} else {
return fmt.Errorf("IPv4 health CIDR could not be parsed: %v", err)
}
}

log.Debugf("IPv4 health CIDRs: %s", d.healthEndpointRouting.IPv4CIDRs)

if mac, err := mac.ParseMAC(result.Master); mac != nil && err == nil {
log.Debugf("Health master interface MAC: %s", mac)

d.healthEndpointRouting.MasterIfMAC = mac
} else {
return fmt.Errorf("health master interface MAC could not be parsed: %v", err)
}

return nil
}
158 changes: 158 additions & 0 deletions pkg/aws/eni/routing/routing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright 2020 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package enirouting

import (
"fmt"
"net"

"github.com/cilium/cilium/pkg/datapath/linux/route"
"github.com/cilium/cilium/pkg/mac"

"github.com/vishvananda/netlink"
)

// RoutingInfo represents information that's required to enable
// connectivity via the local rule and route tables while in ENI mode. The
// information in this struct is used to create rules and routes which direct
// traffic out of the ENI devices (egress).
//
// This struct is mostly derived from the `ipam.AllocationResult` as the
// information comes from IPAM.
type RoutingInfo struct {
// IPv4Gateway is the gateway where outbound/egress traffic is directed.
IPv4Gateway net.IP

// IPv4CIDRs is a list of CIDRs which the ENI device has access to. In most
// cases, it'll at least contain the CIDR of the IPv4Gateway IP address.
IPv4CIDRs []net.IPNet

// MasterIfMAC is the MAC address of the master interface that egress
// traffic is directed to. This is the MAC of the ENI itself which
// corresponds to the IPv4Gateway IP addr.
MasterIfMAC mac.MAC
}

// Install sets up the rules and routes needed when running in ENI mode. These
// rules and routes direct egress traffic out of the ENI device and ingress
// traffic back to the endpoint (`ip`).
//
// ip: The endpoint IP address to direct traffic out / from ENI device.
// info: The ENI device routing info used to create rules and routes.
// mtu: The ENI device MTU.
// masq: Whether masquerading is enabled.
func Install(ip net.IP, info *RoutingInfo, mtu int, masq bool) error {
ifindex, err := retrieveIfIndexFromMAC(info.MasterIfMAC, mtu)
if err != nil {
return fmt.Errorf("unable to find ifindex for interface MAC: %s", err)
}

ipWithMask := net.IPNet{
IP: ip,
Mask: net.CIDRMask(32, 32),
}

// Route all traffic to the ENI address via the main routing table
if err := route.ReplaceRule(route.Rule{
Priority: 20, // After encryption and proxy rules, before local table
To: &ipWithMask,
Table: route.MainTable,
}); err != nil {
return fmt.Errorf("unable to install ip rule: %s", err)
}

if masq {
// Lookup a VPC specific table for all traffic from an endpoint to the
// CIDR configured for the VPC on which the endpoint has the IP on.
for _, cidr := range info.IPv4CIDRs {
if err := route.ReplaceRule(route.Rule{
Priority: 110, // After local table
From: &ipWithMask,
To: &cidr,
Table: ifindex,
}); err != nil {
return fmt.Errorf("unable to install ip rule: %s", err)
}
}
} else {
// Lookup a VPC specific table for all traffic from an endpoint.
if err := route.ReplaceRule(route.Rule{
Priority: 110, // After local table
From: &ipWithMask,
Table: ifindex,
}); err != nil {
return fmt.Errorf("unable to install ip rule: %s", err)
}
}

// Nexthop route to the VPC or subnet gateway
//
// Note: This is a /32 route to avoid any L2. The endpoint does no L2
// either.
if err := netlink.RouteReplace(&netlink.Route{
LinkIndex: ifindex,
Dst: &net.IPNet{IP: info.IPv4Gateway, Mask: net.CIDRMask(32, 32)},
Scope: netlink.SCOPE_LINK,
Table: ifindex,
}); err != nil {
return fmt.Errorf("unable to add L2 nexthop route: %s", err)
}

// Default route to the VPC or subnet gateway
if err := netlink.RouteReplace(&netlink.Route{
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)},
Table: ifindex,
Gw: info.IPv4Gateway,
}); err != nil {
return fmt.Errorf("unable to add L2 nexthop route: %s", err)
}

return nil
}

// retrieveIfIndexFromMAC finds the corresponding device index (ifindex) for a
// given MAC address. This is useful for creating rules and routes in order to
// specify the table. When the ifindex is found, the device is brought up and
// its MTU is set.
func retrieveIfIndexFromMAC(mac mac.MAC, mtu int) (index int, err error) {
var links []netlink.Link

links, err = netlink.LinkList()
if err != nil {
err = fmt.Errorf("unable to list interfaces: %s", err)
return
}

for _, link := range links {
if link.Attrs().HardwareAddr.String() == mac.String() {
index = link.Attrs().Index

if err = netlink.LinkSetMTU(link, mtu); err != nil {
err = fmt.Errorf("unable to change MTU of link %s to %d: %s", link.Attrs().Name, mtu, err)
return
}

if err = netlink.LinkSetUp(link); err != nil {
err = fmt.Errorf("unable to up link %s: %s", link.Attrs().Name, err)
return
}

return
}
}

err = fmt.Errorf("interface with MAC %s not found", mac)
return
}
2 changes: 1 addition & 1 deletion pkg/endpoint/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import (
"github.com/cilium/cilium/pkg/identity"
"github.com/cilium/cilium/pkg/identity/cache"
"github.com/cilium/cilium/pkg/identity/identitymanager"
"github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
ciliumio "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
"github.com/cilium/cilium/pkg/labels"
pkgLabels "github.com/cilium/cilium/pkg/labels"
"github.com/cilium/cilium/pkg/lock"
Expand Down
Loading

0 comments on commit 26308b6

Please sign in to comment.