diff --git a/.gitignore b/.gitignore index e561e397935..ee46f719bfb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ aws-k8s-agent aws-cni +aws-vpc-cni +aws-vpc-cni-init +bandwidth +host-local +loopback verify-aws verify-network *~ diff --git a/Makefile b/Makefile index 740928139af..14887ff2b40 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ LDFLAGS = -X pkg/version/info.Version=$(VERSION) -X pkg/awsutils/awssession.vers # ALLPKGS is the set of packages provided in source. ALLPKGS = $(shell go list $(VENDOR_OVERRIDE_FLAG) ./... | grep -v cmd/packet-verifier) # BINS is the set of built command executables. -BINS = aws-k8s-agent aws-cni grpc-health-probe cni-metrics-helper +BINS = aws-k8s-agent aws-cni grpc-health-probe cni-metrics-helper aws-vpc-cni aws-vpc-cni-init # Plugin binaries # Not copied: bridge dhcp firewall flannel host-device host-local ipvlan macvlan ptp sbr static tuning vlan # For gnu tar, the full path in the tar file is required @@ -119,6 +119,16 @@ build-linux: ## Build the VPC CNI plugin agent using the host's Go toolchain. go build $(VENDOR_OVERRIDE_FLAG) $(BUILD_FLAGS) -o grpc-health-probe ./cmd/grpc-health-probe go build $(VENDOR_OVERRIDE_FLAG) $(BUILD_FLAGS) -o egress-v4-cni ./cmd/egress-v4-cni-plugin +# Build VPC CNI init container entrypoint +build-aws-vpc-cni-init: BUILD_FLAGS = $(BUILD_MODE) -ldflags '-s -w $(LDFLAGS)' +build-aws-vpc-cni-init: ## Build the VPC CNI init container using the host's Go toolchain. + go build $(VENDOR_OVERRIDE_FLAG) $(BUILD_FLAGS) -o aws-vpc-cni-init ./cmd/aws-vpc-cni-init + +# Build VPC CNI container entrypoint +build-aws-vpc-cni: BUILD_FLAGS = $(BUILD_MODE) -ldflags '-s -w $(LDFLAGS)' +build-aws-vpc-cni: ## Build the VPC CNI container using the host's Go toolchain. + go build $(VENDOR_OVERRIDE_FLAG) $(BUILD_FLAGS) -o aws-vpc-cni ./cmd/aws-vpc-cni + # Build VPC CNI plugin & agent container image. docker: setup-ec2-sdk-override ## Build VPC CNI plugin & agent container image. docker build $(DOCKER_BUILD_FLAGS) \ diff --git a/cmd/aws-vpc-cni-init/main.go b/cmd/aws-vpc-cni-init/main.go new file mode 100644 index 00000000000..554f16ac838 --- /dev/null +++ b/cmd/aws-vpc-cni-init/main.go @@ -0,0 +1,177 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file 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. + +// The aws-node initialization +package main + +import ( + "os" + + "github.com/aws/amazon-vpc-cni-k8s/utils/cp" + "github.com/aws/amazon-vpc-cni-k8s/utils/imds" + "github.com/aws/amazon-vpc-cni-k8s/utils/sysctl" + log "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" +) + +const ( + defaultHostCNIBinPath = "/host/opt/cni/bin" + vpcCniInitDonePath = "/vpc-cni-init/done" + metadataLocalIP = "local-ipv4" + metadataMAC = "mac" + + envDisableIPv4TcpEarlyDemux = "DISABLE_TCP_EARLY_DEMUX" + envEnableIPv6 = "ENABLE_IPv6" + envHostCniBinPath = "HOST_CNI_BIN_PATH" +) + +func getEnv(env, def string) string { + if val, ok := os.LookupEnv(env); ok { + return val + } + return def +} + +func main() { + os.Exit(_main()) +} + +func _main() int { + log.Debug("Started Initialization") + pluginBins := []string{"loopback", "portmap", "bandwidth", "aws-cni-support.sh"} + var err error + for _, plugin := range pluginBins { + if _, err = os.Stat(plugin); err != nil { + log.WithError(err).Fatalf("Required executable: %s not found\n", plugin) + return 1 + } + } + + hostCNIBinPath := getEnv(envHostCniBinPath, defaultHostCNIBinPath) + + log.Infof("Copying CNI plugin binaries ...") + err = cp.InstallBinaries(pluginBins, hostCNIBinPath) + if err != nil { + log.WithError(err).Errorf("Failed to install binaries") + return 1 + } + + log.Infof("Copied all CNI plugin binaries to %s\n", hostCNIBinPath) + + var primaryMAC string + primaryMAC, err = imds.GetMetaData("mac") + if err != nil { + log.WithError(err).Fatalf("aws-vpc-cni init failed\n") + return 1 + } + + log.Infof("Found primaryMAC %s", primaryMAC) + + links, err := netlink.LinkList() + if err != nil { + log.WithError(err).Fatalf("Failed to list links\n") + return 1 + } + + var primaryIF string + for _, link := range links { + if link.Attrs().HardwareAddr.String() == primaryMAC { + primaryIF = link.Attrs().Name + break + } + } + + if primaryIF == "" { + log.Errorf("Failed to retrieve primary IF") + return 1 + } + log.Infof("Found primaryIF %s", primaryIF) + + sys := sysctl.New() + // Configure rp_filter in loose mode + entry := "net/ipv4/conf/" + primaryIF + "/rp_filter" + err = sys.SetSysctl(entry, 2) + if err != nil { + log.WithError(err).Fatalf("Failed to set rp_filter for %s\n", primaryIF) + return 1 + } + + val, _ := sys.GetSysctl(entry) + log.Infof("Updated entry for %d", val) + + // Enable or disable TCP early demux based on environment variable + disableIPv4EarlyDemux := getEnv(envDisableIPv4TcpEarlyDemux, "false") + entry = "net/ipv4/tcp_early_demux" + if disableIPv4EarlyDemux == "true" { + err = sys.SetSysctl(entry, 0) + if err != nil { + log.WithError(err).Fatalf("Failed to disable tcp_early_demux\n") + return 1 + } + } else { + err = sys.SetSysctl(entry, 1) + if err != nil { + log.WithError(err).Fatalf("Failed to enable tcp_early_demux\n") + return 1 + } + } + + val, _ = sys.GetSysctl(entry) + log.Infof("Updated entry for %d", val) + + // Enable IPv6 when environment variable is set + // Note that IPv6 is not disabled when environment variable is unset. This is omitted to preserve default host semantics. + enableIPv6 := getEnv(envEnableIPv6, "false") + if enableIPv6 == "true" { + entry = "net/ipv6/conf/all/disable_ipv6" + err = sys.SetSysctl(entry, 0) + if err != nil { + log.WithError(err).Fatalf("Failed to set disable_ipv6 to 0\n") + return 1 + } + val, _ = sys.GetSysctl(entry) + log.Infof("Updated entry for %d", val) + + entry = "net/ipv6/conf/all/forwarding" + err = sys.SetSysctl(entry, 1) + if err != nil { + log.WithError(err).Fatalf("Failed to enable ipv6 forwarding\n") + return 1 + } + val, _ = sys.GetSysctl(entry) + log.Infof("Updated entry for %d", val) + + entry = "net/ipv6/conf/" + primaryIF + "/accept_ra" + err = sys.SetSysctl(entry, 2) + if err != nil { + log.WithError(err).Fatalf("Failed to enable ipv6 accept_ra\n") + return 1 + } + val, _ = sys.GetSysctl(entry) + log.Infof("Updated entry for %d", val) + } + + // TODO: In order to speed up pod launch time, VPC CNI init container is not a Kubernetes init container. + // The VPC CNI container blocks on the existence of vpcCniInitDonePath + //err = cp.TouchFile(vpcCniInitDonePath) + //if err != nil { + // log.WithError(err).Errorf("Failed to set VPC CNI init done") + // return 1 + //} + + log.Infof("CNI init container done") + + // TODO: Since VPC CNI init container is a real container, it never exits + // time.Sleep(time.Duration(1<<63 - 1)) + return 0 +} diff --git a/cmd/aws-vpc-cni/main.go b/cmd/aws-vpc-cni/main.go new file mode 100644 index 00000000000..2c718f57def --- /dev/null +++ b/cmd/aws-vpc-cni/main.go @@ -0,0 +1,410 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file 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. + +// NOTE(jaypipes): Normally, we would prefer *not* to have an entrypoint script +// and instead just start the agent daemon as the container's CMD. However, the +// design of CNI is such that Kubelet looks for the presence of binaries and CNI +// configuration files in specific directories, and the presence of those files +// is the trigger to Kubelet that that particular CNI plugin is "ready". +// +// In the case of the AWS VPC CNI plugin, we have two components to the plugin. +// The first component is the actual CNI binary that is execve'd from Kubelet +// when a container is started or destroyed. The second component is the +// aws-k8s-agent daemon which houses the IPAM controller. +// +// As mentioned above, Kubelet considers a CNI plugin "ready" when it sees the +// binary and configuration file for the plugin in a well-known directory. For +// the AWS VPC CNI plugin binary, we only want to copy the CNI plugin binary +// into that well-known directory AFTER we have successfully started the IPAM +// daemon and know that it can connect to Kubernetes and the local EC2 metadata +// service. This is why this entrypoint script exists; we start the IPAM daemon +// and wait until we know it is up and running successfully before copying the +// CNI plugin binary and its configuration file to the well-known directory that +// Kubelet looks in. + +package main + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net" + "os" + "os/exec" + "strings" + "time" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + + "github.com/aws/amazon-vpc-cni-k8s/utils/cp" + "github.com/aws/amazon-vpc-cni-k8s/utils/imds" + "github.com/containernetworking/cni/pkg/types" +) + +const ( + defaultHostCNIBinPath = "/host/opt/cni/bin" + defaultHostCNIConfDirPath = "/host/etc/cni/net.d" + defaultAWSconflistFile = "/app/10-aws.conflist" + tmpAWSconflistFile = "/tmp/10-aws.conflist" + defaultAgentLogPath = "aws-k8s-agent.log" + defaultVethPrefix = "eni" + defaultMTU = "9001" + defaultPodSGEnforcingMode = "strict" + defaultPluginLogFile = "/var/log/aws-routed-eni/plugin.log" + defaultEgressV4PluginLogFile = "/var/log/aws-routed-eni/egress-v4-plugin.log" + defaultPluginLogLevel = "Debug" + defaultEnableIPv6 = "false" + defaultRandomizeSNAT = "prng" + awsConflistFile = "/10-aws.conflist" + vpcCniInitDonePath = "/vpc-cni-init/done" + + envAgentLogPath = "AGENT_LOG_PATH" + envHostCniBinPath = "HOST_CNI_BIN_PATH" + envHostCniConfDirPath = "HOST_CNI_CONFDIR_PATH" + envVethPrefix = "AWS_VPC_K8S_CNI_VETHPREFIX" + envEniMTU = "AWS_VPC_ENI_MTU" + envPodSGEnforcingMode = "POD_SECURITY_GROUP_ENFORCING_MODE" + envPluginLogFile = "AWS_VPC_K8S_PLUGIN_LOG_FILE" + envPluginLogLevel = "AWS_VPC_K8S_PLUGIN_LOG_LEVEL" + envEgressV4PluginLogFile = "AWS_VPC_K8S_EGRESS_V4_PLUGIN_LOG_FILE" + envConfRPFfilter = "AWS_VPC_K8S_CNI_CONFIGURE_RPFILTER" + envEnPrefixDelegation = "ENABLE_PREFIX_DELEGATION" + envWarmIPTarget = "WARM_IP_TARGET" + envMinIPTarget = "MINIMUM_IP_TARGET" + envWarmPrefixTarget = "WARM_PREFIX_TARGET" + envEnBandwidthPlugin = "ENABLE_BANDWIDTH_PLUGIN" + envEnIPv6 = "ENABLE_IPv6" + envRandomizeSNAT = "AWS_VPC_K8S_CNI_RANDOMIZESNAT" +) + +func getEnv(env, def string) string { + if val, ok := os.LookupEnv(env); ok { + return val + } + return def +} + +// zero values of structs with omitempty is not supported, Golang issue for - https://github.com/golang/go/issues/11939 +// cni/types DNS struct is missing omitstring +// NetConfList describes an ordered list of networks. +type NetConfList struct { + CNIVersion string `json:"cniVersion,omitempty"` + + Name string `json:"name,omitempty"` + DisableCheck bool `json:"disableCheck,omitempty"` + Plugins []*NetConf `json:"plugins,omitempty"` +} + +// NetConf stores the common network config for the CNI plugin +type NetConf struct { + CNIVersion string `json:"cniVersion,omitempty"` + + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Capabilities map[string]bool `json:"capabilities,omitempty"` + IPAM *IPAMConfig `json:"ipam,omitempty"` + DNS *types.DNS `json:"dns,omitempty"` + + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult types.Result `json:"-"` + + // Interface inside container to create + IfName string `json:"ifName,omitempty"` + + Enabled string `json:"enabled,,omitempty"` + + // IP to use as SNAT target + NodeIP net.IP `json:"nodeIP,omitempty"` + + VethPrefix string `json:"vethPrefix,omitempty"` + + PodSGEnforcingMode string `json:"podSGEnforcingMode,omitempty"` + + RandomizeSNAT string `json:"randomizeSNAT,omitempty"` + + // MTU for eth0 + MTU string `json:"mtu,omitempty"` + + PluginLogFile string `json:"pluginLogFile,omitempty"` + + PluginLogLevel string `json:"pluginLogLevel,omitempty"` +} + +//Ref : https://github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config.go +type IPAMConfig struct { + *Range + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Routes []*types.Route `json:"routes,omitempty"` + DataDir string `json:"dataDir,omitempty"` + ResolvConf string `json:"resolvConf,omitempty"` + Ranges []RangeSet `json:"ranges"` + IPArgs []net.IP `json:"-"` // Requested IPs from CNI_ARGS and args +} + +type RangeSet []Range + +type Range struct { + RangeStart net.IP `json:"rangeStart,omitempty"` // The first ip, inclusive + RangeEnd net.IP `json:"rangeEnd,omitempty"` // The last ip, inclusive + Subnet types.IPNet `json:"subnet"` + Gateway net.IP `json:"gateway,omitempty"` +} + +func waitForIPAM() bool { + for { + cmd := exec.Command("./grpc-health-probe", "-addr", "127.0.0.1:50051", ">", "/dev/null", "2>&1") + var outb bytes.Buffer + cmd.Stdout = &outb + cmd.Run() + if outb.String() == "" { + return true + } + } +} + +// Wait for vpcCniInitDonePath to exist (maximum wait time is 60 seconds) +func waitForInit() error { + start := time.Now() + maxEnd := start.Add(time.Minute) + for { + // Check for existence of vpcCniInitDonePath + if _, err := os.Stat(vpcCniInitDonePath); err == nil { + // Delete the done file in case of a reboot of the node or restart of the container (force init container to run again) + if err := os.Remove(vpcCniInitDonePath); err == nil { + return nil + } else { + // If file deletion fails, log and allow retry + log.Errorf("Failed to delete file: %s", vpcCniInitDonePath) + } + } + if time.Now().After(maxEnd) { + return errors.Errorf("time exceeded") + } + time.Sleep(1 * time.Second) + } +} + +func getNodePrimaryV4Address() (string, error) { + var hostIP string + var err error + for { + hostIP, err = imds.GetMetaData("local-ipv4") + if err != nil { + log.WithError(err).Fatalf("aws-vpc-cni failed\n") + return "", err + } + if hostIP != "" { + return hostIP, nil + } + + time.Sleep(1 * time.Second) + } +} + +func isValidJSON(inFile string) error { + var result map[string]interface{} + return json.Unmarshal([]byte(inFile), &result) +} + +func generateJson(jsonFile string, outFile string) error { + byteValue, err := ioutil.ReadFile(jsonFile) + if err != nil { + return err + } + + var nodeIP string + nodeIP, err = getNodePrimaryV4Address() + if err != nil { + log.Errorf("Failed to get Node IP") + return err + } + + vethPrefix := getEnv(envVethPrefix, defaultVethPrefix) + mtu := getEnv(envEniMTU, defaultMTU) + podSGEnforcingMode := getEnv(envPodSGEnforcingMode, defaultPodSGEnforcingMode) + pluginLogFile := getEnv(envPluginLogFile, defaultPluginLogFile) + pluginLogLevel := getEnv(envPluginLogLevel, defaultPluginLogLevel) + egressV4pluginLogFile := getEnv(envEgressV4PluginLogFile, defaultEgressV4PluginLogFile) + enabledIPv6 := getEnv(envEnIPv6, defaultEnableIPv6) + randomizeSNAT := getEnv(envRandomizeSNAT, defaultRandomizeSNAT) + + netconf := string(byteValue) + netconf = strings.Replace(netconf, "__VETHPREFIX__", vethPrefix, -1) + netconf = strings.Replace(netconf, "__MTU__", mtu, -1) + netconf = strings.Replace(netconf, "__PODSGENFORCINGMODE__", podSGEnforcingMode, -1) + netconf = strings.Replace(netconf, "__PLUGINLOGFILE__", pluginLogFile, -1) + netconf = strings.Replace(netconf, "__PLUGINLOGLEVEL__", pluginLogLevel, -1) + netconf = strings.Replace(netconf, "__EGRESSV4PLUGINLOGFILE__", egressV4pluginLogFile, -1) + netconf = strings.Replace(netconf, "__EGRESSV4PLUGINENABLED__", enabledIPv6, -1) + netconf = strings.Replace(netconf, "__RANDOMIZESNAT__", randomizeSNAT, -1) + netconf = strings.Replace(netconf, "__NODEIP__", nodeIP, -1) + + byteValue = []byte(netconf) + + enBandwidthPlugin := getEnv(envEnBandwidthPlugin, "false") + if enBandwidthPlugin == "true" { + data := NetConfList{} + err = json.Unmarshal(byteValue, &data) + if err != nil { + return err + } + + bwPlugin := NetConf{ + Type: "bandwidth", + Capabilities: map[string]bool{"bandwidth": true}, + } + data.Plugins = append(data.Plugins, &bwPlugin) + byteValue, err = json.MarshalIndent(data, "", " ") + if err != nil { + return err + } + } + + err = isValidJSON(string(byteValue)) + if err != nil { + log.Fatalf("%s is not a valid json object\nerror: %s", netconf, err) + } + + err = ioutil.WriteFile(outFile, byteValue, 0644) + return err +} + +func validate_env_vars() bool { + pluginLogFile := getEnv(envPluginLogFile, defaultPluginLogFile) + if pluginLogFile == "stdout" { + log.Errorf("AWS_VPC_K8S_PLUGIN_LOG_FILE cannot be set to stdout") + return false + } + + vethPrefix := getEnv(envVethPrefix, defaultVethPrefix) + if len(vethPrefix) > 4 { + log.Errorf("AWS_VPC_K8S_CNI_VETHPREFIX cannot be longer than 4 characters") + return false + } + + if vethPrefix == "eth" || vethPrefix == "lo" || vethPrefix == "vlan" { + log.Errorf("AWS_VPC_K8S_CNI_VETHPREFIX cannot be set to reserved values 'eth', 'vlan', or 'lo'") + return false + } + + podSGEnforcingMode := getEnv(envPodSGEnforcingMode, defaultPodSGEnforcingMode) + if podSGEnforcingMode != "strict" && podSGEnforcingMode != "standard" { + log.Errorf("%s must be set to either 'strict' or 'standard'", envPodSGEnforcingMode) + return false + } + + prefixDelegationEn := getEnv(envEnPrefixDelegation, "false") + warmIPTarget := getEnv(envWarmIPTarget, "0") + warmPrefixTarget := getEnv(envWarmPrefixTarget, "0") + minimumIPTarget := getEnv(envMinIPTarget, "0") + + if (prefixDelegationEn == "true") && (warmIPTarget <= "0" && warmPrefixTarget <= "0" && minimumIPTarget <= "0") { + log.Errorf("Setting WARM_PREFIX_TARGET = 0 is not supported while WARM_IP_TARGET/MINIMUM_IP_TARGET is not set. Please configure either one of the WARM_{PREFIX/IP}_TARGET or MINIMUM_IP_TARGET env variables") + return false + } + return true +} + +func main() { + os.Exit(_main()) +} + +func _main() int { + log.Debug("Started aws-node container") + if !validate_env_vars() { + return 1 + } + + hostCNIBinPath := getEnv(envHostCniBinPath, defaultHostCNIBinPath) + configureRPFfilter := getEnv(envConfRPFfilter, "true") + if configureRPFfilter != "false" { + log.Infof("Copying CNI plugin binaries ... ") + pluginBins := []string{"loopback", "portmap", "bandwidth", "aws-cni-support.sh"} + err := cp.InstallBinaries(pluginBins, hostCNIBinPath) + if err != nil { + log.WithError(err).Errorf("Failed to install binaries") + return 1 + } + } + + log.Infof("Install CNI binaries..") + pluginBins := []string{"aws-cni", "egress-v4-cni"} + err := cp.InstallBinaries(pluginBins, hostCNIBinPath) + if err != nil { + log.WithError(err).Errorf("Failed to install CNI binaries") + return 1 + } + + log.Infof("Starting IPAM daemon... ") + agentLogPath := getEnv(envAgentLogPath, defaultAgentLogPath) + + cmd := "./aws-k8s-agent" + ipamdDaemon := exec.Command(cmd, "|", "tee", "-i", agentLogPath, "2>&1") + err = ipamdDaemon.Start() + if err != nil { + log.WithError(err).Errorf("Failed to execute command: %s", cmd) + return 1 + } + + log.Infof("Checking for IPAM connectivity... ") + if !waitForIPAM() { + log.Errorf("Timed out waiting for IPAM daemon to start") + + byteValue, err := ioutil.ReadFile(agentLogPath) + if err != nil { + log.WithError(err).Errorf("Failed to read %s", agentLogPath) + } + log.Infof("%s", string(byteValue)) + return 1 + } + + // Wait for init container to complete + //if err = waitForInit(); err != nil { + // log.WithError(err).Errorf("Init container failed to complete") + // return 1 + //} + + log.Infof("Copying config file... ") + err = generateJson(defaultAWSconflistFile, tmpAWSconflistFile) + if err != nil { + log.WithError(err).Errorf("Failed to update 10-awsconflist") + return 1 + } + + err = cp.CopyFile(tmpAWSconflistFile, defaultHostCNIConfDirPath+awsConflistFile) + if err != nil { + log.WithError(err).Errorf("Failed to update 10-awsconflist") + return 1 + } + + log.Infof("Successfully copied CNI plugin binary and config file.") + + awsConfFile := defaultHostCNIConfDirPath + "/aws.conf" + if _, err := os.Stat(awsConfFile); err == nil { + err = os.Remove(awsConfFile) + if err != nil { + log.WithError(err).Errorf("Failed to delete file %s", awsConfFile) + return 1 + } + } + + err = ipamdDaemon.Wait() + if err != nil { + log.WithError(err).Errorf("Failed to wait for IPAM daemon to complete") + return 1 + } + log.Infof("IPAMD stopped hence exiting ...") + return 0 +} diff --git a/go.mod b/go.mod index 0adc32c18ed..de3ce0d1533 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/prometheus/client_golang v1.7.1 github.com/prometheus/client_model v0.2.0 github.com/prometheus/common v0.10.0 + github.com/sirupsen/logrus v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.0 github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 @@ -82,13 +83,11 @@ require ( github.com/go-openapi/jsonreference v0.19.3 // indirect github.com/go-openapi/spec v0.19.3 // indirect github.com/go-openapi/swag v0.19.5 // indirect - github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/google/btree v1.0.0 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/google/uuid v1.1.2 // indirect github.com/googleapis/gnostic v0.5.1 // indirect github.com/gorilla/mux v1.7.3 // indirect @@ -127,7 +126,6 @@ require ( github.com/russross/blackfriday v1.5.2 // indirect github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 // indirect github.com/shopspring/decimal v1.2.0 // indirect - github.com/sirupsen/logrus v1.7.0 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v1.1.1 // indirect github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect diff --git a/go.sum b/go.sum index 5a1fa27628c..2784f25fe11 100644 --- a/go.sum +++ b/go.sum @@ -336,8 +336,6 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= @@ -422,8 +420,6 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -491,7 +487,6 @@ github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= diff --git a/scripts/dockerfiles/Dockerfile.init b/scripts/dockerfiles/Dockerfile.init index 5ba768e6f18..b74b95fdf07 100644 --- a/scripts/dockerfiles/Dockerfile.init +++ b/scripts/dockerfiles/Dockerfile.init @@ -7,16 +7,18 @@ ARG TARGETARCH ENV GO111MODULE=on ENV GOPROXY=direct +# Copy modules in before the rest of the source to only expire cache on module changes: +COPY go.mod go.sum ./ +RUN go mod download + COPY Makefile ./ RUN make plugins && make debug-script COPY . ./ +RUN make build-aws-vpc-cni-init -# Build the architecture specific container image: -FROM public.ecr.aws/amazonlinux/amazonlinux:2 -RUN yum update -y && \ - yum install -y iproute procps-ng && \ - yum clean all +# Build from EKS minimal base +FROM public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-glibc:latest.2 WORKDIR /init @@ -25,6 +27,6 @@ COPY --from=builder \ /go/src/github.com/aws/amazon-vpc-cni-k8s/portmap \ /go/src/github.com/aws/amazon-vpc-cni-k8s/bandwidth \ /go/src/github.com/aws/amazon-vpc-cni-k8s/aws-cni-support.sh \ - /go/src/github.com/aws/amazon-vpc-cni-k8s/scripts/init.sh /init/ + /go/src/github.com/aws/amazon-vpc-cni-k8s/aws-vpc-cni-init /init/ -ENTRYPOINT ["/init/init.sh"] +CMD ["/init/aws-vpc-cni-init"] diff --git a/scripts/dockerfiles/Dockerfile.release b/scripts/dockerfiles/Dockerfile.release index 0e79a389c24..41aa3605dbb 100644 --- a/scripts/dockerfiles/Dockerfile.release +++ b/scripts/dockerfiles/Dockerfile.release @@ -15,13 +15,10 @@ COPY Makefile ./ RUN make plugins && make debug-script COPY . ./ -RUN make build-linux +RUN make build-aws-vpc-cni && make build-linux -# Build the architecture specific container image: -FROM public.ecr.aws/amazonlinux/amazonlinux:2 -RUN yum update -y && \ - yum install -y iptables iproute jq && \ - yum clean all +# Build from EKS minimal base + iptables +FROM public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:latest.2 WORKDIR /app @@ -35,6 +32,6 @@ COPY --from=builder /go/src/github.com/aws/amazon-vpc-cni-k8s/aws-cni \ /go/src/github.com/aws/amazon-vpc-cni-k8s/aws-k8s-agent \ /go/src/github.com/aws/amazon-vpc-cni-k8s/grpc-health-probe \ /go/src/github.com/aws/amazon-vpc-cni-k8s/egress-v4-cni \ - /go/src/github.com/aws/amazon-vpc-cni-k8s/scripts/entrypoint.sh /app/ + /go/src/github.com/aws/amazon-vpc-cni-k8s/aws-vpc-cni /app/ -ENTRYPOINT ["/app/entrypoint.sh"] +ENTRYPOINT ["/app/aws-vpc-cni"] diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh deleted file mode 100755 index 59b12640b37..00000000000 --- a/scripts/entrypoint.sh +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env bash - -# NOTE(jaypipes): Normally, we would prefer *not* to have an entrypoint script -# and instead just start the agent daemon as the container's CMD. However, the -# design of CNI is such that Kubelet looks for the presence of binaries and CNI -# configuration files in specific directories, and the presence of those files -# is the trigger to Kubelet that that particular CNI plugin is "ready". -# -# In the case of the AWS VPC CNI plugin, we have two components to the plugin. -# The first component is the actual CNI binary that is execve'd from Kubelet -# when a container is started or destroyed. The second component is the -# aws-k8s-agent daemon which houses the IPAM controller. -# -# As mentioned above, Kubelet considers a CNI plugin "ready" when it sees the -# binary and configuration file for the plugin in a well-known directory. For -# the AWS VPC CNI plugin binary, we only want to copy the CNI plugin binary -# into that well-known directory AFTER we have successfully started the IPAM -# daemon and know that it can connect to Kubernetes and the local EC2 metadata -# service. This is why this entrypoint script exists; we start the IPAM daemon -# and wait until we know it is up and running successfully before copying the -# CNI plugin binary and its configuration file to the well-known directory that -# Kubelet looks in. - -# turn on exit on subprocess error and exit on undefined variables -set -eu -# turn on bash's job control -set -m - -log_in_json() -{ - FILENAME="${0##*/}" - LOGTYPE=$1 - MSG=$2 - TIMESTAMP=$(date +%FT%T.%3NZ) - printf '{"level":"%s","ts":"%s","caller":"%s","msg":"%s"}\n' "$LOGTYPE" "$TIMESTAMP" "$FILENAME" "$MSG" -} - -unsupported_prefix_target_conf() -{ - if [ "${WARM_PREFIX_TARGET}" -le "0" ] && [ "${WARM_IP_TARGET}" -le "0" ] && [ "${MINIMUM_IP_TARGET}" -le "0" ];then - true - else - false - fi -} - -is_prefix_delegation_enabled() -{ - if [ "${ENABLE_PREFIX_DELEGATION}" == "true" ]; then - true - else - false - fi -} - -validate_env_var() -{ - log_in_json info "Validating env variables ..." - if [[ "${AWS_VPC_K8S_PLUGIN_LOG_FILE,,}" == "stdout" ]]; then - log_in_json error "AWS_VPC_K8S_PLUGIN_LOG_FILE cannot be set to stdout" - exit 1 - fi - - if [[ ${#AWS_VPC_K8S_CNI_VETHPREFIX} -gt 4 ]]; then - log_in_json error "AWS_VPC_K8S_CNI_VETHPREFIX cannot be longer than 4 characters" - exit 1 - fi - - case ${AWS_VPC_K8S_CNI_VETHPREFIX} in - eth|vlan|lo) - log_in_json error "AWS_VPC_K8S_CNI_VETHPREFIX cannot be set to reserved values eth or vlan or lo" - exit 1 - ;; - esac - - case ${POD_SECURITY_GROUP_ENFORCING_MODE} in - strict|standard) - ;; - *) - log_in_json error "POD_SECURITY_GROUP_ENFORCING_MODE must be set to either strict or standard" - exit 1 - ;; - esac - - if is_prefix_delegation_enabled && unsupported_prefix_target_conf ; then - log_in_json error "Setting WARM_PREFIX_TARGET = 0 is not supported while WARM_IP_TARGET/MINIMUM_IP_TARGET is not set. Please configure either one of the WARM_{PREFIX/IP}_TARGET or MINIMUM_IP_TARGET env variables" - exit 1 - fi -} - -# Check for all the required binaries before we go forward -if [ ! -f aws-k8s-agent ]; then - log_in_json error "Required aws-k8s-agent executable not found." - exit 1 -fi -if [ ! -f grpc-health-probe ]; then - log_in_json error "Required grpc-health-probe executable not found." - exit 1 -fi - -AGENT_LOG_PATH=${AGENT_LOG_PATH:-"aws-k8s-agent.log"} -HOST_CNI_BIN_PATH=${HOST_CNI_BIN_PATH:-"/host/opt/cni/bin"} -HOST_CNI_CONFDIR_PATH=${HOST_CNI_CONFDIR_PATH:-"/host/etc/cni/net.d"} -AWS_VPC_K8S_CNI_VETHPREFIX=${AWS_VPC_K8S_CNI_VETHPREFIX:-"eni"} -AWS_VPC_K8S_CNI_RANDOMIZESNAT=${AWS_VPC_K8S_CNI_RANDOMIZESNAT:-"prng"} -AWS_VPC_ENI_MTU=${AWS_VPC_ENI_MTU:-"9001"} -POD_SECURITY_GROUP_ENFORCING_MODE=${POD_SECURITY_GROUP_ENFORCING_MODE:-"strict"} -AWS_VPC_K8S_PLUGIN_LOG_FILE=${AWS_VPC_K8S_PLUGIN_LOG_FILE:-"/var/log/aws-routed-eni/plugin.log"} -AWS_VPC_K8S_PLUGIN_LOG_LEVEL=${AWS_VPC_K8S_PLUGIN_LOG_LEVEL:-"Debug"} -AWS_VPC_K8S_EGRESS_V4_PLUGIN_LOG_FILE=${AWS_VPC_K8S_EGRESS_V4_PLUGIN_LOG_FILE:-"/var/log/aws-routed-eni/egress-v4-plugin.log"} -NODE_IP=${NODE_IP:=""} - -AWS_VPC_K8S_CNI_CONFIGURE_RPFILTER=${AWS_VPC_K8S_CNI_CONFIGURE_RPFILTER:-"true"} -ENABLE_PREFIX_DELEGATION=${ENABLE_PREFIX_DELEGATION:-"false"} -WARM_IP_TARGET=${WARM_IP_TARGET:-"0"} -MINIMUM_IP_TARGET=${MINIMUM_IP_TARGET:-"0"} -WARM_PREFIX_TARGET=${WARM_PREFIX_TARGET:-"0"} -ENABLE_BANDWIDTH_PLUGIN=${ENABLE_BANDWIDTH_PLUGIN:-"false"} -TMP_AWS_CONFLIST_FILE="/tmp/10-aws.conflist" -TMP_AWS_BW_CONFLIST_FILE="/tmp/10-aws-bandwidth-plugin.conflist" - -validate_env_var - -# Check for ipamd connectivity on localhost port 50051 -wait_for_ipam() { - while : - do - if ./grpc-health-probe -addr 127.0.0.1:50051 >/dev/null 2>&1; then - return 0 - fi - log_in_json info "Retrying waiting for IPAM-D" - done -} - -#NodeIP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4) -get_node_primary_v4_address() { - while : - do - token=$(curl -Ss -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60") - NODE_IP=$(curl -H "X-aws-ec2-metadata-token: $token" -Ss http://169.254.169.254/latest/meta-data/local-ipv4) - if [[ "${NODE_IP}" != "" ]]; then - return 0 - fi - # We sleep for 1 second between each retry - sleep 1 - log_in_json info "Retrying fetching node-IP" - done -} - -# If there is no init container, copy the required files -if [[ "$AWS_VPC_K8S_CNI_CONFIGURE_RPFILTER" != "false" ]]; then - # Copy files - log_in_json info "Copying CNI plugin binaries ... " - PLUGIN_BINS="loopback portmap bandwidth host-local aws-cni-support.sh" - for b in $PLUGIN_BINS; do - # Install the binary - install "$b" "$HOST_CNI_BIN_PATH" - done -fi - -log_in_json info "Install CNI binaries.." -install aws-cni "$HOST_CNI_BIN_PATH" -install egress-v4-cni "$HOST_CNI_BIN_PATH" - -log_in_json info "Starting IPAM daemon in the background ... " -./aws-k8s-agent | tee -i "$AGENT_LOG_PATH" 2>&1 & - -log_in_json info "Checking for IPAM connectivity ... " - -if ! wait_for_ipam; then - log_in_json error "Timed out waiting for IPAM daemon to start:" - cat "$AGENT_LOG_PATH" >&2 - exit 1 -fi - -get_node_primary_v4_address -log_in_json info "Copying config file ... " - -# modify the static config to populate it with the env vars -sed \ - -e s~__VETHPREFIX__~"${AWS_VPC_K8S_CNI_VETHPREFIX}"~g \ - -e s~__MTU__~"${AWS_VPC_ENI_MTU}"~g \ - -e s~__PODSGENFORCINGMODE__~"${POD_SECURITY_GROUP_ENFORCING_MODE}"~g \ - -e s~__PLUGINLOGFILE__~"${AWS_VPC_K8S_PLUGIN_LOG_FILE}"~g \ - -e s~__PLUGINLOGLEVEL__~"${AWS_VPC_K8S_PLUGIN_LOG_LEVEL}"~g \ - -e s~__EGRESSV4PLUGINLOGFILE__~"${AWS_VPC_K8S_EGRESS_V4_PLUGIN_LOG_FILE}"~g \ - -e s~__EGRESSV4PLUGINENABLED__~"${ENABLE_IPv6}"~g \ - -e s~__RANDOMIZESNAT__~"${AWS_VPC_K8S_CNI_RANDOMIZESNAT}"~g \ - -e s~__NODEIP__~"${NODE_IP}"~g \ - 10-aws.conflist > "$TMP_AWS_CONFLIST_FILE" - -if [[ "$ENABLE_BANDWIDTH_PLUGIN" == "true" ]]; then - jq '.plugins += [{"type": "bandwidth","capabilities": {"bandwidth": true}}]' "$TMP_AWS_CONFLIST_FILE" > "$TMP_AWS_BW_CONFLIST_FILE" - mv "$TMP_AWS_BW_CONFLIST_FILE" "$TMP_AWS_CONFLIST_FILE" -fi - -mv "$TMP_AWS_CONFLIST_FILE" "$HOST_CNI_CONFDIR_PATH/10-aws.conflist" - -log_in_json info "Successfully copied CNI plugin binary and config file." - -if [[ -f "$HOST_CNI_CONFDIR_PATH/aws.conf" ]]; then - rm "$HOST_CNI_CONFDIR_PATH/aws.conf" -fi - -# Bring the aws-k8s-agent process back into the foreground -log_in_json info "Foregrounding IPAM daemon ..." -fg %1 >/dev/null 2>&1 || { log_in_json error "failed (process terminated)" && cat "$AGENT_LOG_PATH" && exit 1; } diff --git a/scripts/init.sh b/scripts/init.sh deleted file mode 100755 index ae734a17787..00000000000 --- a/scripts/init.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -get_metadata() -{ - TOKEN=$(curl -Ss -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60") - attempts=60 - false - while [ "${?}" -gt 0 ]; do - if [ "${attempts}" -eq 0 ]; then - echo "Failed to get metdata" - exit 1 - fi - meta=$(curl -Ss -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/${1}) - if [ "${?}" -gt 0 ]; then - let attempts-- - sleep 0.5 - false - fi - done - echo "$meta" -} - -PLUGIN_BINS="loopback portmap bandwidth aws-cni-support.sh" - -for b in $PLUGIN_BINS; do - if [ ! -f "$b" ]; then - echo "Required $b executable not found." - exit 1 - fi -done - -HOST_CNI_BIN_PATH=${HOST_CNI_BIN_PATH:-"/host/opt/cni/bin"} - -# Copy files -echo "Copying CNI plugin binaries ... " - -for b in $PLUGIN_BINS; do - # Install the binary - install "$b" "$HOST_CNI_BIN_PATH" -done - -# Configure rp_filter -echo "Configure rp_filter loose... " - -HOST_IP=$(get_metadata 'local-ipv4') -PRIMARY_MAC=$(get_metadata 'mac') -PRIMARY_IF=$(ip -o link show | grep -F "link/ether $PRIMARY_MAC" | awk -F'[ :]+' '{print $2}') -sysctl -w "net.ipv4.conf.$PRIMARY_IF.rp_filter=2" -cat "/proc/sys/net/ipv4/conf/$PRIMARY_IF/rp_filter" - -# Set DISABLE_TCP_EARLY_DEMUX to true to enable kubelet to pod-eni TCP communication -# https://lwn.net/Articles/503420/ and https://github.com/aws/amazon-vpc-cni-k8s/pull/1212 for background -if [ "${DISABLE_TCP_EARLY_DEMUX:-false}" == "true" ]; then - sysctl -w "net.ipv4.tcp_early_demux=0" -else - sysctl -e -w "net.ipv4.tcp_early_demux=1" -fi - -# If IPv6 is enabled,set `disable_ipv6` to `0` and ipv6 `forwarding` to `1` -# We also set `accept_ra` to `2` on primary interface to allow it to honor RA packets. -if [ "${ENABLE_IPv6:-false}" == "true" ]; then - sysctl -w "net.ipv6.conf.all.disable_ipv6=0" - sysctl -w "net.ipv6.conf.all.forwarding=1" - sysctl -w "net.ipv6.conf.$PRIMARY_IF.accept_ra=2" - cat "/proc/sys/net/ipv6/conf/all/disable_ipv6" - cat "/proc/sys/net/ipv6/conf/all/forwarding" - cat "/proc/sys/net/ipv6/conf/$PRIMARY_IF/accept_ra" -fi - -echo "CNI init container done" diff --git a/scripts/run-cni-release-tests.sh b/scripts/run-cni-release-tests.sh index 444bcc1f64e..3a0366be31e 100755 --- a/scripts/run-cni-release-tests.sh +++ b/scripts/run-cni-release-tests.sh @@ -16,7 +16,6 @@ set -e SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" INTEGRATION_TEST_DIR="$SCRIPT_DIR/../test/integration" -CALICO_TEST_DIR="$SCRIPT_DIR/../test/e2e/calico" source "$SCRIPT_DIR"/lib/cluster.sh source "$SCRIPT_DIR"/lib/integration.sh diff --git a/utils/cp/cp.go b/utils/cp/cp.go new file mode 100644 index 00000000000..338ad103d6f --- /dev/null +++ b/utils/cp/cp.go @@ -0,0 +1,76 @@ +package cp + +import ( + "fmt" + "io" + "os" +) + +func TouchFile(filePath string) error { + file, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644) + if err != nil { + return err + } + return file.Close() +} + +func cp(src, dst string) error { + sourceFileStat, err := os.Stat(src) + if err != nil { + return err + } + + if !sourceFileStat.Mode().IsRegular() { + return fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return err + } + defer destination.Close() + _, err = io.Copy(destination, source) + return err +} + +func CopyFile(src, dst string) (err error) { + dstTmp := fmt.Sprintf("%s.tmp", dst) + if err := cp(src, dstTmp); err != nil { + return fmt.Errorf("failed to copy file: %s", err) + } + + err = os.Rename(dstTmp, dst) + if err != nil { + return fmt.Errorf("failed to rename file: %s", err) + } + + si, err := os.Stat(src) + if err != nil { + return fmt.Errorf("failed to stat file: %s", err) + } + err = os.Chmod(dst, si.Mode()) + if err != nil { + return fmt.Errorf("failed to chmod file: %s", err) + } + + return nil +} + +func InstallBinaries(pluginBins []string, hostCNIBinPath string) error { + for _, plugin := range pluginBins { + target := fmt.Sprintf("%s/%s", hostCNIBinPath, plugin) + source := fmt.Sprintf("%s", plugin) + + if err := CopyFile(source, target); err != nil { + return fmt.Errorf("Failed to install %s: %s", target, err) + } + fmt.Printf("Installed %s\n", target) + } + return nil +} diff --git a/utils/imds/imds.go b/utils/imds/imds.go new file mode 100644 index 00000000000..92a77be6049 --- /dev/null +++ b/utils/imds/imds.go @@ -0,0 +1,28 @@ +package imds + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + ec2metadatasvc "github.com/aws/aws-sdk-go/aws/ec2metadata" + "github.com/aws/aws-sdk-go/aws/session" +) + +// EC2Metadata wraps the methods from the amazon-sdk-go's ec2metadata package +type EC2Metadata interface { + GetMetadata(path string) (string, error) + Region() (string, error) +} + +func GetMetaData(key string) (string, error) { + awsSession := session.Must(session.NewSession(aws.NewConfig(). + WithMaxRetries(10), + )) + var ec2Metadata EC2Metadata + ec2Metadata = ec2metadatasvc.New(awsSession) + requestedData, err := ec2Metadata.GetMetadata(key) + if err != nil { + return "", fmt.Errorf("get instance metadata: failed to retrieve %s - %s", key, err) + } + return requestedData, nil +} diff --git a/utils/sysctl/sysctl.go b/utils/sysctl/sysctl.go new file mode 100644 index 00000000000..29e2a6bad38 --- /dev/null +++ b/utils/sysctl/sysctl.go @@ -0,0 +1,49 @@ +// Ref: https://github.com/kubernetes/kubernetes/blob/cb2ea4bf7c029e595f44ee62013c982626fb5bd4/staging/src/k8s.io/component-helpers/node/utils/sysctl/sysctl.go + +package sysctl + +import ( + "io/ioutil" + "path" + "strconv" + "strings" +) + +const ( + sysctlBase = "/proc/sys" +) + +// Interface is an injectable interface for running sysctl commands. +type Interface interface { + // GetSysctl returns the value for the specified sysctl setting + GetSysctl(sysctl string) (int, error) + // SetSysctl modifies the specified sysctl flag to the new value + SetSysctl(sysctl string, newVal int) error +} + +// New returns a new Interface for accessing sysctl +func New() Interface { + return &procSysctl{} +} + +// procSysctl implements Interface by reading and writing files under /proc/sys +type procSysctl struct { +} + +// GetSysctl returns the value for the specified sysctl setting +func (*procSysctl) GetSysctl(sysctl string) (int, error) { + data, err := ioutil.ReadFile(path.Join(sysctlBase, sysctl)) + if err != nil { + return -1, err + } + val, err := strconv.Atoi(strings.Trim(string(data), " \n")) + if err != nil { + return -1, err + } + return val, nil +} + +// SetSysctl modifies the specified sysctl flag to the new value +func (*procSysctl) SetSysctl(sysctl string, newVal int) error { + return ioutil.WriteFile(path.Join(sysctlBase, sysctl), []byte(strconv.Itoa(newVal)), 0640) +}