in agent/engine/docker_task_engine.go [1047:1279]
func (engine *DockerTaskEngine) createContainer(task *apitask.Task, container *apicontainer.Container) dockerapi.DockerContainerMetadata {
seelog.Infof("Task engine [%s]: creating container: %s", task.Arn, container.Name)
client := engine.client
if container.DockerConfig.Version != nil {
client = client.WithVersion(dockerclient.DockerVersion(*container.DockerConfig.Version))
}
dockerContainerName := ""
containerMap, ok := engine.state.ContainerMapByArn(task.Arn)
if !ok {
containerMap = make(map[string]*apicontainer.DockerContainer)
} else {
// looking for container that has docker name but not created
for _, v := range containerMap {
if v.Container.Name == container.Name {
dockerContainerName = v.DockerName
break
}
}
}
// Resolve HostConfig
// we have to do this in create, not start, because docker no longer handles
// merging create config with start hostconfig the same; e.g. memory limits
// get lost
dockerClientVersion, versionErr := client.APIVersion()
if versionErr != nil {
return dockerapi.DockerContainerMetadata{Error: CannotGetDockerClientVersionError{versionErr}}
}
hostConfig, hcerr := task.DockerHostConfig(container, containerMap, dockerClientVersion, engine.cfg)
if hcerr != nil {
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(hcerr)}
}
if container.AWSLogAuthExecutionRole() {
err := task.ApplyExecutionRoleLogsAuth(hostConfig, engine.credentialsManager)
if err != nil {
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(err)}
}
}
firelensConfig := container.GetFirelensConfig()
if firelensConfig != nil {
err := task.AddFirelensContainerBindMounts(firelensConfig, hostConfig, engine.cfg)
if err != nil {
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(err)}
}
cerr := task.PopulateSecretLogOptionsToFirelensContainer(container)
if cerr != nil {
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(cerr)}
}
if firelensConfig.Type == firelens.FirelensConfigTypeFluentd {
// For fluentd router, needs to specify FLUENT_UID to root in order for the fluentd process to access
// the socket created by Docker.
container.MergeEnvironmentVariables(map[string]string{
"FLUENT_UID": "0",
})
}
}
// If the container is using a special log driver type "awsfirelens", it means the container wants to use
// the firelens container to send logs. In this case, override the log driver type to be fluentd
// and specify appropriate tag and fluentd-address, so that the logs are sent to and routed by the firelens container.
// Update the environment variables FLUENT_HOST and FLUENT_PORT depending on the supported network modes - bridge
// and awsvpc. For reference - https://docs.docker.com/config/containers/logging/fluentd/.
if hostConfig.LogConfig.Type == logDriverTypeFirelens {
hostConfig.LogConfig = getFirelensLogConfig(task, container, hostConfig, engine.cfg)
if task.IsNetworkModeAWSVPC() {
container.MergeEnvironmentVariables(map[string]string{
fluentNetworkHost: FluentAWSVPCHostValue,
fluentNetworkPort: FluentNetworkPortValue,
})
} else if container.GetNetworkModeFromHostConfig() == "" || container.GetNetworkModeFromHostConfig() == apitask.BridgeNetworkMode {
ipAddress, ok := getContainerHostIP(task.GetFirelensContainer().GetNetworkSettings())
if !ok {
err := apierrors.DockerClientConfigError{Msg: "unable to get BridgeIP for task in bridge mode"}
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(&err)}
}
container.MergeEnvironmentVariables(map[string]string{
fluentNetworkHost: ipAddress,
fluentNetworkPort: FluentNetworkPortValue,
})
}
}
//Apply the log driver secret into container's LogConfig and Env secrets to container.Environment
hasSecretAsEnvOrLogDriver := func(s apicontainer.Secret) bool {
return s.Type == apicontainer.SecretTypeEnv || s.Target == apicontainer.SecretTargetLogDriver
}
if container.HasSecret(hasSecretAsEnvOrLogDriver) {
err := task.PopulateSecrets(hostConfig, container)
if err != nil {
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(err)}
}
}
// Populate credentialspec resource
if container.RequiresCredentialSpec() {
seelog.Debugf("Obtained container %s with credentialspec resource requirement for task %s.", container.Name, task.Arn)
var credSpecResource *credentialspec.CredentialSpecResource
resource, ok := task.GetCredentialSpecResource()
if !ok || len(resource) <= 0 {
resMissingErr := &apierrors.DockerClientConfigError{Msg: "unable to fetch task resource credentialspec"}
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(resMissingErr)}
}
credSpecResource = resource[0].(*credentialspec.CredentialSpecResource)
containerCredSpec, err := container.GetCredentialSpec()
if err == nil && containerCredSpec != "" {
// CredentialSpec mapping: input := credentialspec:file://test.json, output := credentialspec=file://test.json
desiredCredSpecInjection, err := credSpecResource.GetTargetMapping(containerCredSpec)
if err != nil || desiredCredSpecInjection == "" {
missingErr := &apierrors.DockerClientConfigError{Msg: "unable to fetch valid credentialspec mapping"}
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(missingErr)}
}
// Inject containers' hostConfig.SecurityOpt with the credentialspec resource
seelog.Infof("Injecting container %s with credentialspec %s.", container.Name, desiredCredSpecInjection)
if len(hostConfig.SecurityOpt) == 0 {
hostConfig.SecurityOpt = []string{desiredCredSpecInjection}
} else {
for idx, opt := range hostConfig.SecurityOpt {
if strings.HasPrefix(opt, "credentialspec:") {
hostConfig.SecurityOpt[idx] = desiredCredSpecInjection
}
}
}
} else {
emptyErr := &apierrors.DockerClientConfigError{Msg: "unable to fetch valid credentialspec: " + err.Error()}
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(emptyErr)}
}
}
if container.ShouldCreateWithEnvFiles() {
err := task.MergeEnvVarsFromEnvfiles(container)
if err != nil {
seelog.Errorf("Error populating environment variables from specified files into container %s", container.Name)
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(err)}
}
}
if execcmd.IsExecEnabledContainer(container) {
tID, err := task.GetID()
if err != nil {
herr := &apierrors.HostConfigError{Msg: err.Error()}
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(herr)}
}
err = engine.execCmdMgr.InitializeContainer(tID, container, hostConfig)
if err != nil {
seelog.Warnf("Exec Agent initialization: %v . Continuing to start container without enabling exec feature.", err)
// Emit a managedagent state chnage event if exec agent initialization fails
engine.tasksLock.RLock()
mTask, ok := engine.managedTasks[task.Arn]
engine.tasksLock.RUnlock()
if ok {
mTask.emitManagedAgentEvent(mTask.Task, container, execcmd.ExecuteCommandAgentName, fmt.Sprintf("ExecuteCommandAgent Initialization failed - %v", err))
} else {
seelog.Errorf("Task engine [%s]: Failed to update status of ExecCommandAgent Process for container [%s]: managed task not found", task.Arn, container.Name)
}
}
}
config, err := task.DockerConfig(container, dockerClientVersion)
if err != nil {
return dockerapi.DockerContainerMetadata{Error: apierrors.NamedError(err)}
}
// Augment labels with some metadata from the agent. Explicitly do this last
// such that it will always override duplicates in the provided raw config
// data.
config.Labels[labelTaskARN] = task.Arn
config.Labels[labelContainerName] = container.Name
config.Labels[labelTaskDefinitionFamily] = task.Family
config.Labels[labelTaskDefinitionVersion] = task.Version
config.Labels[labelCluster] = engine.cfg.Cluster
if dockerContainerName == "" {
// only alphanumeric and hyphen characters are allowed
reInvalidChars := regexp.MustCompile("[^A-Za-z0-9-]+")
name := reInvalidChars.ReplaceAllString(container.Name, "")
dockerContainerName = "ecs-" + task.Family + "-" + task.Version + "-" + name + "-" + utils.RandHex()
// Pre-add the container in case we stop before the next, more useful,
// AddContainer call. This ensures we have a way to get the container if
// we die before 'createContainer' returns because we can inspect by
// name
engine.state.AddContainer(&apicontainer.DockerContainer{
DockerName: dockerContainerName,
Container: container,
}, task)
seelog.Infof("Task engine [%s]: created container name mapping for task: %s -> %s",
task.Arn, container.Name, dockerContainerName)
}
// Create metadata directory and file then populate it with common metadata of all containers of this task
// Afterwards add this directory to the container's mounts if file creation was successful
if engine.cfg.ContainerMetadataEnabled.Enabled() && !container.IsInternal() {
info, infoErr := engine.client.Info(engine.ctx, dockerclient.InfoTimeout)
if infoErr != nil {
seelog.Warnf("Task engine [%s]: unable to get docker info : %v",
task.Arn, infoErr)
}
mderr := engine.metadataManager.Create(config, hostConfig, task, container.Name, info.SecurityOptions)
if mderr != nil {
seelog.Warnf("Task engine [%s]: unable to create metadata for container %s: %v",
task.Arn, container.Name, mderr)
}
}
createContainerBegin := time.Now()
metadata := client.CreateContainer(engine.ctx, config, hostConfig,
dockerContainerName, engine.cfg.ContainerCreateTimeout)
if metadata.DockerID != "" {
seelog.Infof("Task engine [%s]: created docker container for task: %s -> %s",
task.Arn, container.Name, metadata.DockerID)
dockerContainer := &apicontainer.DockerContainer{DockerID: metadata.DockerID,
DockerName: dockerContainerName,
Container: container}
engine.state.AddContainer(dockerContainer, task)
engine.saveDockerContainerData(dockerContainer)
}
container.SetLabels(config.Labels)
seelog.Infof("Task engine [%s]: created docker container for task: %s -> %s, took %s",
task.Arn, container.Name, metadata.DockerID, time.Since(createContainerBegin))
container.SetRuntimeID(metadata.DockerID)
return metadata
}