mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-11 09:26:47 +00:00
The use of containerd and CNI functions has been refactored to reuse the same codebase. Added all network functionality to own directory and package. Removed netlink and weave library in favor of using CNI plugin result files. Rename containers handler to functions to clear-up functionality. Signed-off-by: Carlos de Paula <me@carlosedp.com>
229 lines
6.5 KiB
Go
229 lines
6.5 KiB
Go
package cninetwork
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
|
|
"github.com/containerd/containerd"
|
|
gocni "github.com/containerd/go-cni"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
// CNIBinDir describes the directory where the CNI binaries are stored
|
|
CNIBinDir = "/opt/cni/bin"
|
|
// CNIConfDir describes the directory where the CNI plugin's configuration is stored
|
|
CNIConfDir = "/etc/cni/net.d"
|
|
// NetNSPathFmt gives the path to the a process network namespace, given the pid
|
|
NetNSPathFmt = "/proc/%d/ns/net"
|
|
// CNIResultsDir is the directory CNI stores allocated IP for containers
|
|
CNIResultsDir = "/var/lib/cni/results"
|
|
// defaultCNIConfFilename is the vanity filename of default CNI configuration file
|
|
defaultCNIConfFilename = "10-openfaas.conflist"
|
|
// defaultNetworkName names the "docker-bridge"-like CNI plugin-chain installed when no other CNI configuration is present.
|
|
// This value appears in iptables comments created by CNI.
|
|
defaultNetworkName = "openfaas-cni-bridge"
|
|
// defaultBridgeName is the default bridge device name used in the defaultCNIConf
|
|
defaultBridgeName = "openfaas0"
|
|
// defaultSubnet is the default subnet used in the defaultCNIConf -- this value is set to not collide with common container networking subnets:
|
|
defaultSubnet = "10.62.0.0/16"
|
|
)
|
|
|
|
// defaultCNIConf is a CNI configuration that enables network access to containers (docker-bridge style)
|
|
var defaultCNIConf = fmt.Sprintf(`
|
|
{
|
|
"cniVersion": "0.4.0",
|
|
"name": "%s",
|
|
"plugins": [
|
|
{
|
|
"type": "bridge",
|
|
"bridge": "%s",
|
|
"isGateway": true,
|
|
"ipMasq": true,
|
|
"ipam": {
|
|
"type": "host-local",
|
|
"subnet": "%s",
|
|
"routes": [
|
|
{ "dst": "0.0.0.0/0" }
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"type": "firewall"
|
|
}
|
|
]
|
|
}
|
|
`, defaultNetworkName, defaultBridgeName, defaultSubnet)
|
|
|
|
// InitNetwork writes configlist file and initializes CNI network
|
|
func InitNetwork() (gocni.CNI, error) {
|
|
|
|
log.Printf("Writing network config...\n")
|
|
if !dirExists(CNIConfDir) {
|
|
if err := os.MkdirAll(CNIConfDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("cannot create directory: %s", CNIConfDir)
|
|
}
|
|
}
|
|
|
|
netConfig := path.Join(CNIConfDir, defaultCNIConfFilename)
|
|
if err := ioutil.WriteFile(netConfig, []byte(defaultCNIConf), 644); err != nil {
|
|
return nil, fmt.Errorf("cannot write network config: %s", defaultCNIConfFilename)
|
|
|
|
}
|
|
// Initialize CNI library
|
|
cni, err := gocni.New(gocni.WithPluginConfDir(CNIConfDir),
|
|
gocni.WithPluginDir([]string{CNIBinDir}))
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error initializing cni: %s", err)
|
|
}
|
|
|
|
// Load the cni configuration
|
|
if err := cni.Load(gocni.WithLoNetwork, gocni.WithConfListFile(filepath.Join(CNIConfDir, defaultCNIConfFilename))); err != nil {
|
|
return nil, fmt.Errorf("failed to load cni configuration: %v", err)
|
|
}
|
|
|
|
return cni, nil
|
|
}
|
|
|
|
// CreateCNINetwork creates a CNI network interface and attaches it to the context
|
|
func CreateCNINetwork(ctx context.Context, cni gocni.CNI, task containerd.Task, labels map[string]string) (*gocni.CNIResult, error) {
|
|
id := netID(task)
|
|
netns := netNamespace(task)
|
|
result, err := cni.Setup(ctx, id, netns, gocni.WithLabels(labels))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "Failed to setup network for task %q: %v", id, err)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// DeleteCNINetwork deletes a CNI network based on task ID and Pid
|
|
func DeleteCNINetwork(ctx context.Context, cni gocni.CNI, client *containerd.Client, name string) error {
|
|
container, containerErr := client.LoadContainer(ctx, name)
|
|
if containerErr == nil {
|
|
task, err := container.Task(ctx, nil)
|
|
if err != nil {
|
|
log.Printf("[Delete] unable to find task for container: %s\n", name)
|
|
return nil
|
|
}
|
|
|
|
log.Printf("[Delete] removing CNI network for: %s\n", task.ID())
|
|
|
|
id := netID(task)
|
|
netns := netNamespace(task)
|
|
|
|
if err := cni.Remove(ctx, id, netns); err != nil {
|
|
return errors.Wrapf(err, "Failed to remove network for task: %q, %v", id, err)
|
|
}
|
|
log.Printf("[Delete] removed: %s from namespace: %s, ID: %s\n", name, netns, id)
|
|
|
|
return nil
|
|
}
|
|
|
|
return errors.Wrapf(containerErr, "Unable to find container: %s, error: %s", name, containerErr)
|
|
}
|
|
|
|
// GetIPAddress returns the IP address of the created container
|
|
func GetIPAddress(result *gocni.CNIResult, task containerd.Task) (net.IP, error) {
|
|
// Get the IP of the created interface
|
|
var ip net.IP
|
|
for ifName, config := range result.Interfaces {
|
|
if config.Sandbox == netNamespace(task) {
|
|
for _, ipConfig := range config.IPConfigs {
|
|
if ifName != "lo" && ipConfig.IP.To4() != nil {
|
|
ip = ipConfig.IP
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ip == nil {
|
|
return nil, fmt.Errorf("unable to get IP address for: %s", task.ID())
|
|
}
|
|
return ip, nil
|
|
}
|
|
|
|
func GetIPfromPID(pid int) (*net.IP, error) {
|
|
// https://github.com/weaveworks/weave/blob/master/net/netdev.go
|
|
|
|
peerIDs, err := ConnectedToBridgeVethPeerIds(defaultBridgeName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to find peers on: %s %s", defaultBridgeName, err)
|
|
}
|
|
|
|
addrs, addrsErr := GetNetDevsByVethPeerIds(pid, peerIDs)
|
|
if addrsErr != nil {
|
|
return nil, fmt.Errorf("unable to find address for veth pair using: %v %s", peerIDs, addrsErr)
|
|
}
|
|
|
|
if len(addrs) > 0 && len(addrs[0].CIDRs) > 0 {
|
|
return &addrs[0].CIDRs[0].IP, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("no IP found for function")
|
|
}
|
|
|
|
// CNIGateway returns the gateway for default subnet
|
|
func CNIGateway() (string, error) {
|
|
ip, _, err := net.ParseCIDR(defaultSubnet)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error formatting gateway for network %s", defaultSubnet)
|
|
}
|
|
ip = ip.To4()
|
|
ip[3] = 1
|
|
return ip.String(), nil
|
|
}
|
|
|
|
// netID generates the network IF based on task name and task PID
|
|
func netID(task containerd.Task) string {
|
|
return fmt.Sprintf("%s-%d", task.ID(), task.Pid())
|
|
}
|
|
|
|
// netNamespace generates the namespace path based on task PID.
|
|
func netNamespace(task containerd.Task) string {
|
|
return fmt.Sprintf(NetNSPathFmt, task.Pid())
|
|
}
|
|
|
|
func dirEmpty(dirname string) (isEmpty bool) {
|
|
if !dirExists(dirname) {
|
|
return
|
|
}
|
|
|
|
f, err := os.Open(dirname)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
|
|
// If the first file is EOF, the directory is empty
|
|
if _, err = f.Readdir(1); err == io.EOF {
|
|
isEmpty = true
|
|
}
|
|
return isEmpty
|
|
}
|
|
|
|
func dirExists(dirname string) bool {
|
|
exists, info := pathExists(dirname)
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
return info.IsDir()
|
|
}
|
|
|
|
func pathExists(path string) (bool, os.FileInfo) {
|
|
info, err := os.Stat(path)
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
|
|
return true, info
|
|
}
|