Skip to content
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

Add daemon manager with basic instance task creation #3789

Merged
merged 1 commit into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions agent/api/container/containertype.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const (
// ContainerServiceConnectRelay represents the internal container type
// for the relay to share connections to management infrastructure.
ContainerServiceConnectRelay

// ContainerManagedDaemon represents the internal container type
// for Managed Daemons
ContainerManagedDaemon
)

// ContainerType represents the type of the internal container created
Expand Down
31 changes: 31 additions & 0 deletions agent/engine/daemonmanager/daemon_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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 daemonmanager

import (
"context"

apitask "github.com/aws/amazon-ecs-agent/agent/api/task"
"github.com/aws/amazon-ecs-agent/agent/config"
"github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi"
"github.com/aws/amazon-ecs-agent/agent/utils/loader"
"github.com/docker/docker/api/types"
)

type DaemonManager interface {
loader.Loader
CreateDaemonTask() (*apitask.Task, error)
LoadImage(ctx context.Context, _ *config.Config, dockerClient dockerapi.DockerClient) (*types.ImageInspect, error)
IsLoaded(dockerClient dockerapi.DockerClient) (bool, error)
}
158 changes: 158 additions & 0 deletions agent/engine/daemonmanager/daemon_manager_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//go:build linux
// +build linux

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
fierlion marked this conversation as resolved.
Show resolved Hide resolved
//
// 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 daemonmanager

import (
fierlion marked this conversation as resolved.
Show resolved Hide resolved
"context"
"encoding/json"
"fmt"
"io/fs"
"os"

apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container"
apicontainerstatus "github.com/aws/amazon-ecs-agent/agent/api/container/status"
apitask "github.com/aws/amazon-ecs-agent/agent/api/task"
apitaskstatus "github.com/aws/amazon-ecs-agent/agent/api/task/status"
"github.com/aws/amazon-ecs-agent/agent/config"
"github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi"
"github.com/aws/amazon-ecs-agent/agent/taskresource"
utils "github.com/aws/amazon-ecs-agent/agent/utils"
"github.com/aws/amazon-ecs-agent/agent/utils/loader"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger/field"
md "github.com/aws/amazon-ecs-agent/ecs-agent/manageddaemon"

"github.com/aws/aws-sdk-go/aws"
"github.com/docker/docker/api/types"
dockercontainer "github.com/docker/docker/api/types/container"
"github.com/pborman/uuid"
)

const (
// all daemons will share the same user id
// note: AppNetUID is 20000
daemonUID = 20001
daemonMountPermission fs.FileMode = 0700
ecsAgentLogFileENV = "ECS_LOGFILE"
defaultECSAgentLogPathContainer = "/log"
)

var mkdirAllAndChown = utils.MkdirAllAndChown

// each daemon manager manages one single daemon container
type daemonManager struct {
managedDaemon *md.ManagedDaemon
}

func NewDaemonManager(manageddaemon *md.ManagedDaemon) DaemonManager {
return &daemonManager{managedDaemon: manageddaemon}
}

func (dm *daemonManager) CreateDaemonTask() (*apitask.Task, error) {
imageName := dm.managedDaemon.GetImageName()
loadedImageRef := dm.managedDaemon.GetLoadedDaemonImageRef()
containerRunning := apicontainerstatus.ContainerRunning
dockerHostConfig := dockercontainer.HostConfig{
singholt marked this conversation as resolved.
Show resolved Hide resolved
NetworkMode: apitask.HostNetworkMode,
// the default value of 0 for MaximumRetryCount means retry indefinitely
RestartPolicy: dockercontainer.RestartPolicy{
Name: "on-failure",
MaximumRetryCount: 0,
},
}
if !dm.managedDaemon.IsValidManagedDaemon() {
return nil, fmt.Errorf("%s is an invalid managed daemon", imageName)
}
for _, mount := range dm.managedDaemon.GetMountPoints() {
err := mkdirAllAndChown(mount.SourceVolumeHostPath, daemonMountPermission, daemonUID, os.Getegid())
if err != nil {
return nil, err
}
dockerHostConfig.Binds = append(dockerHostConfig.Binds,
fmt.Sprintf("%s:%s", mount.SourceVolumeHostPath, mount.ContainerPath))
}
rawHostConfig, err := json.Marshal(&dockerHostConfig)
if err != nil {
return nil, err
}
healthConfig := dm.managedDaemon.GetDockerHealthConfig()
rawHealthConfig, err := json.Marshal(&healthConfig)
if err != nil {
return nil, err
}
// The raw host config needs to be created this way - if we marshal the entire config object
singholt marked this conversation as resolved.
Show resolved Hide resolved
// directly, and the object only contains healthcheck, all other fields will be written as empty/nil
// in the result string. This will override the configurations that comes with the container image
// (CMD for example)
rawConfig := fmt.Sprintf("{\"Healthcheck\":%s}", string(rawHealthConfig))

daemonTask := &apitask.Task{
Arn: fmt.Sprintf("arn:::::/%s-%s", dm.managedDaemon.GetImageName(), uuid.NewUUID()),
DesiredStatusUnsafe: apitaskstatus.TaskRunning,
Containers: []*apicontainer.Container{{
Name: dm.managedDaemon.GetImageName(),
Image: loadedImageRef,
ContainerArn: fmt.Sprintf("arn:::::/instance-%s", imageName),
Type: apicontainer.ContainerManagedDaemon,
TransitionDependenciesMap: make(map[apicontainerstatus.ContainerStatus]apicontainer.TransitionDependencySet),
Essential: true,
SteadyStateStatusUnsafe: &containerRunning,
DockerConfig: apicontainer.DockerConfig{
Config: aws.String(rawConfig),
HostConfig: aws.String(string(rawHostConfig)),
},
HealthCheckType: "DOCKER",
}},
LaunchType: "EC2",
NetworkMode: apitask.HostNetworkMode,
ResourcesMapUnsafe: make(map[string][]taskresource.TaskResource),
IsInternal: true,
}
// add managed daemon environment to daemon task container
daemonTask.Containers[0].MergeEnvironmentVariables(dm.managedDaemon.GetEnvironment())
return daemonTask, nil
}

// LoadImage loads the daemon's latest image
func (dm *daemonManager) LoadImage(ctx context.Context, _ *config.Config, dockerClient dockerapi.DockerClient) (*types.ImageInspect, error) {
singholt marked this conversation as resolved.
Show resolved Hide resolved
var loadErr error
daemonImageToLoad := dm.managedDaemon.GetImageName()
daemonImageTarPath := dm.managedDaemon.GetImageTarPath()
if _, err := os.Stat(daemonImageTarPath); err != nil {
logger.Warn(fmt.Sprintf("%s container tarball unavailable at path: %s", daemonImageToLoad, daemonImageTarPath), logger.Fields{
field.Error: err,
})
fierlion marked this conversation as resolved.
Show resolved Hide resolved
}
logger.Debug(fmt.Sprintf("Loading %s container image from tarball: %s", daemonImageToLoad, daemonImageTarPath))
if loadErr = loader.LoadFromFile(ctx, daemonImageTarPath, dockerClient); loadErr != nil {
logger.Warn(fmt.Sprintf("Unable to load %s container image from tarball: %s", daemonImageToLoad, daemonImageTarPath), logger.Fields{
field.Error: loadErr,
})
}
dm.managedDaemon.SetLoadedDaemonImageRef(dm.managedDaemon.GetImageCanonicalRef())
loadedImageRef := dm.managedDaemon.GetLoadedDaemonImageRef()
logger.Info(fmt.Sprintf("Successfully loaded %s container image from tarball: %s", daemonImageToLoad, daemonImageTarPath),
logger.Fields{
field.Image: loadedImageRef,
})
return loader.GetContainerImage(loadedImageRef, dockerClient)
}

// isImageLoaded uses the image ref with its tag
func (dm *daemonManager) IsLoaded(dockerClient dockerapi.DockerClient) (bool, error) {
return loader.IsImageLoaded(dm.managedDaemon.GetImageCanonicalRef(), dockerClient)
}
Loading