//go:build linux // +build linux // 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. package app import ( "os" asmfactory "github.com/aws/amazon-ecs-agent/agent/asm/factory" "github.com/aws/amazon-ecs-agent/agent/config" "github.com/aws/amazon-ecs-agent/agent/ecscni" "github.com/aws/amazon-ecs-agent/agent/engine" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" "github.com/aws/amazon-ecs-agent/agent/eni/watcher" "github.com/aws/amazon-ecs-agent/agent/gpu" s3factory "github.com/aws/amazon-ecs-agent/agent/s3/factory" ssmfactory "github.com/aws/amazon-ecs-agent/agent/ssm/factory" "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" "github.com/aws/amazon-ecs-agent/ecs-agent/ecs_client/model/ecs" "github.com/aws/amazon-ecs-agent/agent/statechange" "github.com/aws/amazon-ecs-agent/agent/taskresource" cgroup "github.com/aws/amazon-ecs-agent/agent/taskresource/cgroup/control" "github.com/aws/amazon-ecs-agent/agent/utils/ioutilwrapper" "github.com/cihub/seelog" "github.com/pkg/errors" ) // initPID defines the process identifier for the init process const initPID = 1 // awsVPCCNIPlugins is a list of CNI plugins required by the ECS Agent // to configure the ENI for a task var awsVPCCNIPlugins = []string{ecscni.ECSENIPluginName, ecscni.ECSBridgePluginName, ecscni.ECSIPAMPluginName, ecscni.ECSAppMeshPluginName, ecscni.ECSBranchENIPluginName, } // startWindowsService is not supported on Linux func (agent *ecsAgent) startWindowsService() int { seelog.Error("Windows Services are not supported on Linux") return 1 } var getPid = os.Getpid // initializeTaskENIDependencies initializes all of the dependencies required by // the Agent to support the 'awsvpc' networking mode. A non nil error is returned // if an error is encountered during this process. An additional boolean flag to // indicate if this error is considered terminal is also returned func (agent *ecsAgent) initializeTaskENIDependencies(state dockerstate.TaskEngineState, taskEngine engine.TaskEngine) (error, bool) { // Check if the Agent process's pid == 1, which means it's running without an init system if getPid() == initPID { // This is a terminal error. Bad things happen with invoking the // the ENI plugin when there's no init process in the pid namespace. // Specifically, the DHClient processes that are started as children // of the Agent will not be reaped leading to the ENI device // disappearing until the Agent is killed. return errors.New("agent is not started with an init system"), true } // Set VPC and Subnet IDs for the instance if err, ok := agent.setVPCSubnet(); err != nil { return err, ok } // Validate that the CNI plugins exist in the expected path and that // they possess the right capabilities if err := agent.verifyCNIPluginsCapabilities(); err != nil { // An error here is terminal as it means that the plugins // do not support the ENI capability return err, true } if err := agent.startENIWatcher(state, taskEngine.StateChangeEvents()); err != nil { // If udev watcher was not initialized in this run because of the udev socket // file not being available etc, the Agent might be able to retry and succeed // on the next run. Hence, returning a false here for terminal bool return err, false } return nil, false } // verifyCNIPluginsCapabilities returns an error if there's an error querying // capabilities or if the required capability is absent from the capabilities // of the following plugins: // a. ecs-eni // b. ecs-bridge // c. ecs-ipam // d. aws-appmesh // e. vpc-branch-eni func (agent *ecsAgent) verifyCNIPluginsCapabilities() error { // Check if we can get capabilities from each plugin for _, plugin := range awsVPCCNIPlugins { // skip verifying branch cni plugin if eni trunking is not enabled if plugin == ecscni.ECSBranchENIPluginName && agent.cfg != nil && !agent.cfg.ENITrunkingEnabled.Enabled() { continue } capabilities, err := agent.cniClient.Capabilities(plugin) if err != nil { return err } // appmesh plugin is not needed for awsvpc networking capability if plugin == ecscni.ECSAppMeshPluginName { continue } if !contains(capabilities, ecscni.CapabilityAWSVPCNetworkingMode) { return errors.Errorf("plugin '%s' doesn't support the capability: %s", plugin, ecscni.CapabilityAWSVPCNetworkingMode) } } return nil } // startENIWatcher starts the udev monitor and the watcher for receiving // notifications from the monitor func (agent *ecsAgent) startENIWatcher(state dockerstate.TaskEngineState, stateChangeEvents chan<- statechange.Event) error { seelog.Debug("Setting up ENI Watcher") if agent.eniWatcher == nil { eniWatcher, err := watcher.New(agent.ctx, agent.mac, state, stateChangeEvents) if err != nil { return errors.Wrapf(err, "unable to create ENI watcher") } agent.eniWatcher = eniWatcher if err := agent.eniWatcher.Init(); err != nil { return errors.Wrapf(err, "unable to initialize eni watcher") } go agent.eniWatcher.Start() } return nil } // initializeResourceFields exists mainly for testing doStart() to use mock Control // object func (agent *ecsAgent) initializeResourceFields(credentialsManager credentials.Manager) { agent.resourceFields = &taskresource.ResourceFields{ Control: cgroup.New(), ResourceFieldsCommon: &taskresource.ResourceFieldsCommon{ IOUtil: ioutilwrapper.NewIOUtil(), ASMClientCreator: asmfactory.NewClientCreator(), SSMClientCreator: ssmfactory.NewSSMClientCreator(), S3ClientCreator: s3factory.NewS3ClientCreator(), CredentialsManager: credentialsManager, EC2InstanceID: agent.getEC2InstanceID(), }, Ctx: agent.ctx, DockerClient: agent.dockerClient, NvidiaGPUManager: gpu.NewNvidiaGPUManager(), } } func (agent *ecsAgent) cgroupInit() error { err := agent.resourceFields.Control.Init() // When task CPU and memory limits are enabled, all tasks are placed // under the '/ecs' cgroup root. if err == nil { return nil } if agent.cfg.TaskCPUMemLimit.Value == config.ExplicitlyEnabled { return errors.Wrapf(err, "unable to setup '/ecs' cgroup") } seelog.Warnf("Disabling TaskCPUMemLimit because agent is unable to setup '/ecs' cgroup: %v", err) agent.cfg.TaskCPUMemLimit.Value = config.ExplicitlyDisabled return nil } func (agent *ecsAgent) initializeGPUManager() error { if agent.resourceFields != nil && agent.resourceFields.NvidiaGPUManager != nil { return agent.resourceFields.NvidiaGPUManager.Initialize() } return nil } func (agent *ecsAgent) getPlatformDevices() []*ecs.PlatformDevice { if agent.cfg.GPUSupportEnabled { if agent.resourceFields != nil && agent.resourceFields.NvidiaGPUManager != nil { return agent.resourceFields.NvidiaGPUManager.GetDevices() } } return nil } func (agent *ecsAgent) loadPauseContainer() error { // Load the pause container's image from the 'disk' _, err := agent.pauseLoader.LoadImage(agent.ctx, agent.cfg, agent.dockerClient) return err }