diff --git a/Release.toml b/Release.toml index dc63d508ef5..275be1677ff 100644 --- a/Release.toml +++ b/Release.toml @@ -292,4 +292,7 @@ version = "1.20.0" "migrate_v1.20.0_corndog-services-cfg-v0-1-0.lz4", "migrate_v1.20.0_bootstrap-containers-config-file-v0-1-0.lz4", "migrate_v1.20.0_bootstrap-containers-services-cfg-v0-1-0.lz4", + "migrate_v1.20.0_remove-ecs-settings-applier.lz4", + "migrate_v1.20.0_update-ecs-config-path.lz4", + "migrate_v1.20.0_update-ecs-config-template-path.lz4", ] diff --git a/packages/ecs-agent/ecs-agent.spec b/packages/ecs-agent/ecs-agent.spec index 873d9360c73..00448724f7f 100644 --- a/packages/ecs-agent/ecs-agent.spec +++ b/packages/ecs-agent/ecs-agent.spec @@ -35,16 +35,18 @@ Source2: https://%{vpccni_goimport}/archive/%{vpccni_gitrev}/%{vpccni_gorepo}.ta Source101: ecs.service Source102: ecs-tmpfiles.conf Source103: ecs-sysctl.conf -Source104: ecs.config +Source104: ecs-base-conf Source105: pause-image-VERSION Source106: pause-config.json Source107: pause-manifest.json Source108: pause-repositories # Bottlerocket-specific - version data can be set with linker options Source109: version.go +Source110: ecs-defaults.conf +Source111: ecs-nvidia.conf # Mount for writing ECS agent configuration -Source200: etc-ecs.mount +Source200: etc-systemd-system-ecs.service.d.mount # Ecs logdog configuration Source300: logdog.ecs.conf @@ -86,6 +88,21 @@ Requires: %{_cross_os}amazon-ssm-agent %description %{summary}. +%package config +Summary: Base configuration files for the ECS agent +Requires: %{name} + +%description config +%{summary}. + +%package nvidia-config +Summary: NVIDIA specific configuration files for the ECS agent +Requires: %{name} +Requires: %{name}-config + +%description nvidia-config +%{summary}. + %prep # After prep runs, the directory setup looks like this: # %{_builddir} [root] @@ -260,9 +277,14 @@ install -D -p -m 0755 %{vpccni_gorepo}-%{vpccni_gitrev}/vpc-eni %{buildroot}%{_c install -d %{buildroot}%{_cross_unitdir} install -D -p -m 0644 %{S:101} %{S:200} %{buildroot}%{_cross_unitdir} +install -d %{buildroot}%{_cross_unitdir}/ecs.service.d/ +install -D -p -m 0644 %{S:110} %{buildroot}%{_cross_unitdir}/ecs.service.d/00-defaults.conf +install -D -p -m 0644 %{S:111} %{buildroot}%{_cross_unitdir}/ecs.service.d/20-nvidia.conf + install -D -p -m 0644 %{S:102} %{buildroot}%{_cross_tmpfilesdir}/ecs.conf install -D -p -m 0644 %{S:103} %{buildroot}%{_cross_sysctldir}/90-ecs.conf -install -D -p -m 0644 %{S:104} %{buildroot}%{_cross_templatedir}/ecs.config + +install -D -p -m 0644 %{S:104} %{buildroot}%{_cross_templatedir}/ecs-base-conf # Directory for agents used by the ECS agent, e.g. SSM, Service Connect %global managed_agents %{_cross_libexecdir}/amazon-ecs-agent/managed-agents @@ -333,11 +355,17 @@ install -p -m 0644 %{S:300} %{buildroot}%{_cross_datadir}/logdog.d %{_cross_libexecdir}/amazon-ecs-agent/vpc-eni %{_cross_libexecdir}/amazon-ecs-agent/managed-agents %{_cross_unitdir}/ecs.service -%{_cross_unitdir}/etc-ecs.mount +%{_cross_unitdir}/etc-systemd-system-ecs.service.d.mount %{_cross_tmpfilesdir}/ecs.conf %{_cross_sysctldir}/90-ecs.conf -%{_cross_templatedir}/ecs.config %{_cross_libdir}/amazon-ecs-agent/amazon-ecs-pause.tar %{_cross_datadir}/logdog.d/logdog.ecs.conf +%files config +%{_cross_templatedir}/ecs-base-conf +%{_cross_unitdir}/ecs.service.d/00-defaults.conf + +%files nvidia-config +%{_cross_unitdir}/ecs.service.d/20-nvidia.conf + %changelog diff --git a/packages/ecs-agent/ecs-base-conf b/packages/ecs-agent/ecs-base-conf new file mode 100644 index 00000000000..73d294829e6 --- /dev/null +++ b/packages/ecs-agent/ecs-base-conf @@ -0,0 +1,59 @@ +[required-extensions] +autoscaling = "v1" +container-registry = "v1" +os = "v1" +std = { version = "v1", helpers = ["default", "negate_or_else"] } +ecs = { version = "v1", helpers = ["ecs_metadata_service_limits"] } ++++ +# Configurations set through the API; default values match the default values in the agent +[Service] +Environment=ECS_AWSVPC_BLOCK_IMDS="{{default "false" settings.ecs.awsvpc-block-imds}}" +Environment=ECS_BACKEND_HOST="{{default "" settings.ecs.backend-host}}" +Environment=ECS_CONTAINER_STOP_TIMEOUT="{{default "30s" settings.ecs.container-stop-timeout}}" +Environment=ECS_CLUSTER="{{default "" settings.ecs.cluster}}" +Environment=ECS_ENABLE_CONTAINER_METADATA="{{default "false" settings.ecs.enable-container-metadata}}" +Environment=ECS_ENABLE_SPOT_INSTANCE_DRAINING="{{default "false" settings.enable-spot-instance-draining}}" +Environment=ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION="{{default "3h" settings.ecs.task-cleanup-wait}}" +Environment=ECS_IMAGE_CLEANUP_INTERVAL="{{default "30m" settings.ecs.image-cleanup-wait}}" +Environment=ECS_IMAGE_MINIMUM_CLEANUP_AGE="{{default "1h" settings.ecs.image-cleanup-age}}" +Environment=ECS_IMAGE_PULL_BEHAVIOR="{{default "default" settings.ecs.image-pull-behavior}}" +Environment=ECS_LOGLEVEL="{{settings.ecs.loglevel}}" +Environment=ECS_NUM_IMAGES_DELETE_PER_CYCLE="{{default 5 settings.ecs.image-cleanup-delete-per-cycle}}" +Environment=ECS_RESERVED_MEMORY="{{default 0 settings.ecs.reserved-memory}}" +Environment=ECS_TASK_METADATA_RPS_LIMIT="{{ecs_metadata_service_limits settings.ecs.metadata-service-rps settings.ecs.metadata-service-burst}}" +Environment=ECS_WARM_POOLS_CHECK="{{default "false" settings.autoscaling.should-wait}}" + +# Boolean configurations whose values are inverted in the API +Environment=ECS_PRIVILEGED_DISABLED="{{negate_or_else true settings.ecs.allow-privileged-containers}}" +Environment=ECS_DISABLE_IMAGE_CLEANUP="{{negate_or_else false settings.ecs.image-cleanup-enabled}}" + +Environment=ECS_INSTANCE_ATTRIBUTES='{ "bottlerocket.variant": "{{os.variant_id}}" + {{~#if settings.ecs.instance-attributes~}} + {{~#each settings.ecs.instance-attributes}} ,"{{@key}}": "{{this}}" {{~/each~}} + {{~/if~}}}' + +{{#if settings.ecs.logging-drivers }} +Environment=ECS_AVAILABLE_LOGGING_DRIVERS='[ + {{~#each settings.ecs.logging-drivers~}} + {{~#unless @first~}}, {{~/unless~}} + "{{this}}" + {{~/each~}}]' +{{/if}} + +{{#if settings.container-registry.credentials~}} +Environment=ECS_ENGINE_AUTH_TYPE=dockercfg + +Environment=ECS_ENGINE_AUTH_DATA='{ + {{~#each settings.container-registry.credentials~}} + {{~#unless @first~}},{{~/unless~}} + {{~#if (eq registry "docker.io" )~}} + "https://index.docker.io/v1/": + {{~else~}} + "{{registry}}": + {{~/if~}} + {"email": "." + {{~#if auth~}},"auth": "{{{auth}}}"{{/if}} + {{~#if username~}},"username": "{{{username}}}"{{/if}} + {{~#if password~}},"password": "{{{password}}}"}{{/if}} + {{~/each~}}}}' +{{/if}} diff --git a/packages/ecs-agent/ecs-defaults.conf b/packages/ecs-agent/ecs-defaults.conf new file mode 100644 index 00000000000..ec772fbd95e --- /dev/null +++ b/packages/ecs-agent/ecs-defaults.conf @@ -0,0 +1,12 @@ +[Service] +# Path overrides +Environment=ECS_AUDIT_LOGFILE="/var/log/ecs/audit.log" +Environment=ECS_CNI_PLUGINS_PATH="/usr/libexec/amazon-ecs-agent" +Environment=ECS_DATADIR="/var/lib/ecs/data" +Environment=ECS_LOGFILE="/var/log/ecs/ecs-agent.log" +# Default configurations +Environment=ECS_ENABLE_AWSLOGS_EXECUTIONROLE_OVERRIDE="true" +Environment=ECS_ENABLE_TASK_IAM_ROLE="true" +Environment=ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST="true" +Environment=ECS_ENABLE_TASK_ENI="true" +Environment=ECS_SELINUX_CAPABLE="true" diff --git a/packages/ecs-agent/ecs-nvidia.conf b/packages/ecs-agent/ecs-nvidia.conf new file mode 100644 index 00000000000..55fec554f96 --- /dev/null +++ b/packages/ecs-agent/ecs-nvidia.conf @@ -0,0 +1,2 @@ +[Service] +Environment=ECS_ENABLE_GPU_SUPPORT="true" diff --git a/packages/ecs-agent/ecs.config b/packages/ecs-agent/ecs.config deleted file mode 100644 index d7f036d92a8..00000000000 --- a/packages/ecs-agent/ecs.config +++ /dev/null @@ -1,44 +0,0 @@ -[required-extensions] -container-registry = "v1" -ecs = "v1" -+++ -ECS_LOGFILE=/var/log/ecs/ecs-agent.log -ECS_LOGLEVEL="{{settings.ecs.loglevel}}" -ECS_AGENT_CONFIG_FILE_PATH="/etc/ecs/ecs.config.json" -{{#if settings.container-registry.credentials~}} -ECS_ENGINE_AUTH_TYPE=dockercfg -ECS_ENGINE_AUTH_DATA='{ - {{~#each settings.container-registry.credentials~}} - {{~#unless @first~}},{{~/unless~}} - {{~#if (eq registry "docker.io" )~}} - "https://index.docker.io/v1/": - {{~else~}} - "{{registry}}": - {{~/if~}} - {"email": "." - {{~#if auth~}},"auth": "{{{auth}}}"{{/if}} - {{~#if username~}},"username": "{{{username}}}"{{/if}} - {{~#if password~}},"password": "{{{password}}}"}{{/if}} - {{~/each~}}}}' -{{/if}} -{{#if settings.ecs.container-stop-timeout}} -ECS_CONTAINER_STOP_TIMEOUT="{{settings.ecs.container-stop-timeout}}" -{{/if}} -{{#if settings.ecs.task-cleanup-wait}} -ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION="{{settings.ecs.task-cleanup-wait}}" -{{/if}} -{{#if settings.ecs.image-cleanup-wait}} -ECS_IMAGE_CLEANUP_INTERVAL="{{settings.ecs.image-cleanup-wait}}" -{{/if}} -{{# if settings.ecs.image-cleanup-age}} -ECS_IMAGE_MINIMUM_CLEANUP_AGE="{{settings.ecs.image-cleanup-age}}" -{{/if}} -{{#if settings.ecs.backend-host}} -ECS_BACKEND_HOST="{{settings.ecs.backend-host}}" -{{/if}} -{{#if settings.ecs.awsvpc-block-imds}} -ECS_AWSVPC_BLOCK_IMDS="{{settings.ecs.awsvpc-block-imds}}" -{{/if}} -{{#if settings.ecs.enable-container-metadata}} -ECS_ENABLE_CONTAINER_METADATA="{{settings.ecs.enable-container-metadata}}" -{{/if}} diff --git a/packages/ecs-agent/ecs.service b/packages/ecs-agent/ecs.service index 96de5175220..dcd6a56f5c4 100644 --- a/packages/ecs-agent/ecs.service +++ b/packages/ecs-agent/ecs.service @@ -10,7 +10,6 @@ Type=simple Restart=always RestartPreventExitStatus=5 RestartSec=5 -EnvironmentFile=-/etc/ecs/ecs.config EnvironmentFile=/etc/network/proxy.env Environment=ECS_CHECKPOINT=true # Grant ECS tasks access to the ECS task metadata endpoint diff --git a/packages/ecs-agent/etc-ecs.mount b/packages/ecs-agent/etc-systemd-system-ecs.service.d.mount similarity index 73% rename from packages/ecs-agent/etc-ecs.mount rename to packages/ecs-agent/etc-systemd-system-ecs.service.d.mount index 3234b5fc1f8..8a2c20fbf79 100644 --- a/packages/ecs-agent/etc-ecs.mount +++ b/packages/ecs-agent/etc-systemd-system-ecs.service.d.mount @@ -1,5 +1,5 @@ [Unit] -Description=ECS agent Configuration Directory (/etc/ecs) +Description=ECS agent drop-ins Directory (/etc/systemd/system/ecs.service.d) DefaultDependencies=no Conflicts=umount.target Before=local-fs.target umount.target @@ -8,7 +8,7 @@ Wants=selinux-policy-files.service [Mount] What=tmpfs -Where=/etc/ecs +Where=/etc/systemd/system/ecs.service.d Type=tmpfs Options=nosuid,nodev,noexec,noatime,mode=0750,context=system_u:object_r:secret_t:s0 diff --git a/packages/os/os.spec b/packages/os/os.spec index 53a9937b9d7..d4af73474b1 100644 --- a/packages/os/os.spec +++ b/packages/os/os.spec @@ -119,10 +119,6 @@ Requires: %{_cross_os}shibaken Requires: %{_cross_os}cfsignal %endif -%if %{with ecs_runtime} -Requires: %{_cross_os}ecs-settings-applier -%endif - %if %{with nvidia_flavor} Requires: %{_cross_os}driverdog %endif @@ -248,13 +244,6 @@ Summary: Bottlerocket certificates handler %description -n %{_cross_os}certdog %{summary}. -%if %{with ecs_runtime} -%package -n %{_cross_os}ecs-settings-applier -Summary: Settings generator for ECS -%description -n %{_cross_os}ecs-settings-applier -%{summary}. -%endif - %if %{with aws_k8s_family} %package -n %{_cross_os}pluto Summary: Dynamic setting generator for kubernetes @@ -370,7 +359,6 @@ echo "** Output from non-static builds:" -p shimpei \ -p bloodhound \ -p xfscli \ - %{?with_ecs_runtime: -p ecs-settings-applier} \ %{?with_aws_platform: -p shibaken -p cfsignal} \ %{?with_aws_k8s_family: -p pluto} \ %{?with_k8s_runtime: -p static-pods} \ @@ -396,7 +384,6 @@ for p in \ signpost updog metricdog logdog \ ghostdog bootstrap-containers \ shimpei bloodhound bottlerocket-checks \ - %{?with_ecs_runtime: ecs-settings-applier} \ %{?with_aws_platform: shibaken cfsignal} \ %{?with_aws_k8s_family: pluto} \ %{?with_k8s_runtime: static-pods} \ @@ -644,11 +631,6 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir} %files -n %{_cross_os}logdog %{_cross_bindir}/logdog -%if %{with ecs_runtime} -%files -n %{_cross_os}ecs-settings-applier -%{_cross_bindir}/ecs-settings-applier -%endif - %if %{with aws_platform} %files -n %{_cross_os}shibaken %{_cross_bindir}/shibaken diff --git a/sources/Cargo.lock b/sources/Cargo.lock index 4b8707e9842..8b7268bdb06 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -1713,23 +1713,6 @@ dependencies = [ "vmw_backdoor", ] -[[package]] -name = "ecs-settings-applier" -version = "0.1.0" -dependencies = [ - "bottlerocket-variant", - "constants", - "generate-readme", - "log", - "models", - "schnauzer", - "serde", - "serde_json", - "simplelog", - "snafu 0.8.2", - "tokio", -] - [[package]] name = "either" version = "1.10.0" @@ -3370,6 +3353,13 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "remove-ecs-settings-applier" +version = "0.1.0" +dependencies = [ + "migration-helpers", +] + [[package]] name = "reqwest" version = "0.11.26" @@ -4624,6 +4614,20 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "update-ecs-config-path" +version = "0.1.0" +dependencies = [ + "migration-helpers", +] + +[[package]] +name = "update-ecs-config-template-path" +version = "0.1.0" +dependencies = [ + "migration-helpers", +] + [[package]] name = "update_metadata" version = "0.1.0" diff --git a/sources/Cargo.toml b/sources/Cargo.toml index e78bce3d627..80a16d2398e 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -9,7 +9,6 @@ members = [ "api/corndog", "api/datastore", "api/early-boot-config", - "api/ecs-settings-applier", "api/netdog", "api/sundog", "api/schnauzer", @@ -78,6 +77,9 @@ members = [ "api/migration/migrations/v1.20.0/corndog-services-cfg-v0-1-0", "api/migration/migrations/v1.20.0/bootstrap-containers-config-file-v0-1-0", "api/migration/migrations/v1.20.0/bootstrap-containers-services-cfg-v0-1-0", + "api/migration/migrations/v1.20.0/remove-ecs-settings-applier", + "api/migration/migrations/v1.20.0/update-ecs-config-path", + "api/migration/migrations/v1.20.0/update-ecs-config-template-path", "bloodhound", diff --git a/sources/api/ecs-settings-applier/README.md b/sources/api/ecs-settings-applier/README.md deleted file mode 100644 index 0ede9c91b2e..00000000000 --- a/sources/api/ecs-settings-applier/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# ecs-settings-applier - -Current version: 0.1.0 - -## Introduction - -ecs-settings-applier generates a configuration file for the ECS agent from Bottlerocket settings. - -The configuration file for ECS is a JSON-formatted document with conditionally-defined keys and -embedded lists. The structure and names of fields in the document can be found -[here](https://github.com/aws/amazon-ecs-agent/blob/a250409cf5eb4ad84a7b889023f1e4d2e274b7ab/agent/config/types.go). - -## Colophon - -This text was generated using from `README.tpl` [cargo-readme](https://crates.io/crates/cargo-readme), and includes the rustdoc from `src/main.rs`. diff --git a/sources/api/ecs-settings-applier/README.tpl b/sources/api/ecs-settings-applier/README.tpl deleted file mode 100644 index 82592715222..00000000000 --- a/sources/api/ecs-settings-applier/README.tpl +++ /dev/null @@ -1,9 +0,0 @@ -# {{crate}} - -Current version: {{version}} - -{{readme}} - -## Colophon - -This text was generated using from `README.tpl` [cargo-readme](https://crates.io/crates/cargo-readme), and includes the rustdoc from `src/main.rs`. diff --git a/sources/api/ecs-settings-applier/build.rs b/sources/api/ecs-settings-applier/build.rs deleted file mode 100644 index b100fa8f8ed..00000000000 --- a/sources/api/ecs-settings-applier/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -use bottlerocket_variant::Variant; - -fn main() { - let variant = Variant::from_env().unwrap(); - variant.emit_cfgs(); - generate_readme::from_file("src/ecs.rs").unwrap(); -} diff --git a/sources/api/ecs-settings-applier/src/ecs.rs b/sources/api/ecs-settings-applier/src/ecs.rs deleted file mode 100644 index d66c9a16d61..00000000000 --- a/sources/api/ecs-settings-applier/src/ecs.rs +++ /dev/null @@ -1,271 +0,0 @@ -/*! -# Introduction - -ecs-settings-applier generates a configuration file for the ECS agent from Bottlerocket settings. - -The configuration file for ECS is a JSON-formatted document with conditionally-defined keys and -embedded lists. The structure and names of fields in the document can be found -[here](https://github.com/aws/amazon-ecs-agent/blob/a250409cf5eb4ad84a7b889023f1e4d2e274b7ab/agent/config/types.go). -*/ -use log::debug; -use serde::Serialize; -use simplelog::{Config as LogConfig, LevelFilter, SimpleLogger}; -use snafu::{OptionExt, ResultExt}; -use std::fs; -use std::path::Path; -use std::{env, process}; - -const DEFAULT_ECS_CONFIG_PATH: &str = "/etc/ecs/ecs.config.json"; -const VARIANT_ATTRIBUTE_NAME: &str = "bottlerocket.variant"; - -#[derive(Serialize, Debug, Default)] -#[serde(rename_all = "PascalCase")] -struct ECSConfig { - #[serde(skip_serializing_if = "Option::is_none")] - cluster: Option, - - #[serde(skip_serializing_if = "std::collections::HashMap::is_empty")] - instance_attributes: std::collections::HashMap, - - #[serde(skip_serializing_if = "Option::is_none")] - privileged_disabled: Option, - - #[serde(skip_serializing_if = "std::vec::Vec::is_empty")] - available_logging_drivers: Vec, - - #[serde(skip_serializing_if = "Option::is_none")] - spot_instance_draining_enabled: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - warm_pools_support: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - image_pull_behavior: Option, - - #[serde(rename = "TaskIAMRoleEnabled")] - task_iam_role_enabled: bool, - - #[serde(rename = "TaskIAMRoleEnabledForNetworkHost")] - task_iam_role_enabled_for_network_host: bool, - - #[serde(rename = "SELinuxCapable")] - selinux_capable: bool, - - #[serde(rename = "OverrideAWSLogsExecutionRole")] - override_awslogs_execution_role: bool, - - #[serde(rename = "TaskENIEnabled")] - task_eni_enabled: bool, - - #[serde(rename = "GPUSupportEnabled")] - gpu_support_enabled: bool, - - #[serde( - rename = "TaskMetadataSteadyStateRate", - skip_serializing_if = "Option::is_none" - )] - metadata_service_rps: Option, - - #[serde( - rename = "TaskMetadataBurstRate", - skip_serializing_if = "Option::is_none" - )] - metadata_service_burst: Option, - - #[serde(rename = "ReservedMemory", skip_serializing_if = "Option::is_none")] - reserved_memory: Option, - - #[serde( - rename = "NumImagesToDeletePerCycle", - skip_serializing_if = "Option::is_none" - )] - image_cleanup_delete_per_cycle: Option, - - image_cleanup_disabled: bool, - - #[serde(rename = "CNIPluginsPath")] - cni_plugins_path: String, - - credentials_audit_log_file: String, - - data_dir: String, -} - -// Returning a Result from main makes it print a Debug representation of the error, but with Snafu -// we have nice Display representations of the error, so we wrap "main" (run) and print any error. -// https://github.com/shepmaster/snafu/issues/110 -pub(crate) async fn main() { - if let Err(e) = run().await { - eprintln!("{}", e); - process::exit(1); - } -} - -async fn run() -> Result<()> { - let args = parse_args(env::args()); - SimpleLogger::init(LevelFilter::Info, LogConfig::default()).context(error::LoggerSnafu)?; - - // Get all ecs and autoscaling settings values for config file templates - debug!("Requesting ecs and autoscaling settings values"); - let settings = schnauzer::v1::get_settings(&args.socket_path) - .await - .context(error::SettingsSnafu)?; - - debug!("settings = {:#?}", settings.settings); - let ecs = settings - .settings - .as_ref() - .and_then(|s| s.ecs.as_ref()) - .context(error::ModelSnafu)?; - - let autoscaling = settings - .settings - .as_ref() - .and_then(|s| s.autoscaling.as_ref()) - .context(error::ModelSnafu)?; - - let mut config = ECSConfig { - cluster: ecs.cluster.clone(), - privileged_disabled: ecs.allow_privileged_containers.map(|s| !s), - available_logging_drivers: ecs - .logging_drivers - .clone() - .unwrap_or_default() - .iter() - .map(|s| s.to_string()) - .collect(), - spot_instance_draining_enabled: ecs.enable_spot_instance_draining, - warm_pools_support: autoscaling.should_wait, - image_pull_behavior: ecs.image_pull_behavior.as_ref().map(|b| b.as_u8()), - - // Task role support is always enabled - task_iam_role_enabled: true, - task_iam_role_enabled_for_network_host: true, - - // SELinux is always available - selinux_capable: true, - - // Always supported with Docker newer than v17.11.0 - // See https://github.com/docker/engine/commit/c7cc9d67590dd11343336c121e3629924a9894e9 - override_awslogs_execution_role: true, - - // awsvpc mode is always available - task_eni_enabled: true, - - gpu_support_enabled: cfg!(variant_flavor = "nvidia"), - reserved_memory: ecs.reserved_memory, - metadata_service_rps: ecs.metadata_service_rps, - metadata_service_burst: ecs.metadata_service_burst, - image_cleanup_delete_per_cycle: ecs.image_cleanup_delete_per_cycle, - image_cleanup_disabled: !ecs.image_cleanup_enabled.unwrap_or(true), - cni_plugins_path: "/usr/libexec/amazon-ecs-agent".to_string(), - credentials_audit_log_file: "/var/log/ecs/audit.log".to_string(), - data_dir: "/var/lib/ecs/data".to_string(), - ..Default::default() - }; - if let Some(os) = settings.os { - config - .instance_attributes - .insert(VARIANT_ATTRIBUTE_NAME.to_string(), os.variant_id); - } - if let Some(attributes) = &ecs.instance_attributes { - for (key, value) in attributes { - config - .instance_attributes - .insert(key.to_string(), value.to_string()); - } - } - let serialized = serde_json::to_string(&config).context(error::SerializationSnafu)?; - debug!("serialized = {}", serialized); - - write_to_disk(DEFAULT_ECS_CONFIG_PATH, serialized).context(error::FSSnafu { - path: DEFAULT_ECS_CONFIG_PATH, - })?; - Ok(()) -} - -/// Writes the rendered data at the proper location -fn write_to_disk, C: AsRef<[u8]>>(path: P, contents: C) -> std::io::Result<()> { - if let Some(dirname) = path.as_ref().parent() { - fs::create_dir_all(dirname)?; - }; - - fs::write(path, contents) -} - -// Stores user-supplied arguments. -struct Args { - socket_path: String, -} - -fn parse_args(args: env::Args) -> Args { - let mut socket_path = None; - let mut iter = args.skip(1); - while let Some(arg) = iter.next() { - match arg.as_ref() { - "--socket-path" => { - socket_path = Some( - iter.next() - .unwrap_or_else(|| usage_msg("Did not give argument to --socket-path")), - ) - } - _ => usage(), - } - } - Args { - socket_path: socket_path.unwrap_or_else(|| constants::API_SOCKET.to_string()), - } -} - -// Prints a more specific message before exiting through usage(). -fn usage_msg>(msg: S) -> ! { - eprintln!("{}\n", msg.as_ref()); - usage(); -} - -// Informs the user about proper usage of the program and exits. -fn usage() -> ! { - let program_name = env::args().next().unwrap_or_else(|| "program".to_string()); - eprintln!( - r"Usage: {} - [ (-s | --socket-path) PATH ] - - Socket path defaults to {}", - program_name, - constants::API_SOCKET - ); - process::exit(2); -} - -type Result = std::result::Result; - -mod error { - use snafu::Snafu; - - #[derive(Debug, Snafu)] - #[snafu(visibility(pub(super)))] - pub(super) enum Error { - #[snafu(display("Failed to read settings: {}", source))] - Settings { - source: schnauzer::v1::Error, - }, - - #[snafu(display("Logger setup error: {}", source))] - Logger { - source: log::SetLoggerError, - }, - - Model, - - #[snafu(display("Failed to serialize ECS config: {}", source))] - Serialization { - source: serde_json::error::Error, - }, - - #[snafu(display("Filesystem operation for path {} failed: {}", path, source))] - FS { - path: &'static str, - source: std::io::Error, - }, - } -} diff --git a/sources/api/ecs-settings-applier/src/main.rs b/sources/api/ecs-settings-applier/src/main.rs deleted file mode 100644 index 538f3681be6..00000000000 --- a/sources/api/ecs-settings-applier/src/main.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[cfg(variant_family = "aws-ecs")] -mod ecs; - -#[cfg(variant_family = "aws-ecs")] -#[tokio::main] -async fn main() { - ecs::main().await -} - -#[cfg(not(variant_family = "aws-ecs"))] -fn main() {} diff --git a/sources/api/migration/migrations/v1.20.0/remove-ecs-settings-applier/Cargo.toml b/sources/api/migration/migrations/v1.20.0/remove-ecs-settings-applier/Cargo.toml new file mode 100644 index 00000000000..dfa69e1afd3 --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/remove-ecs-settings-applier/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "remove-ecs-settings-applier" +version = "0.1.0" +edition = "2021" +authors = ["Arnaldo Garcia "] +license = "Apache-2.0 OR MIT" +publish = false +exclude = ["README.md"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +migration-helpers = { path = "../../../migration-helpers", version = "0.1.0" } diff --git a/sources/api/migration/migrations/v1.20.0/remove-ecs-settings-applier/src/main.rs b/sources/api/migration/migrations/v1.20.0/remove-ecs-settings-applier/src/main.rs new file mode 100644 index 00000000000..b3b5a69b0cd --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/remove-ecs-settings-applier/src/main.rs @@ -0,0 +1,23 @@ +use migration_helpers::common_migrations::{ListReplacement, ReplaceListsMigration}; +use migration_helpers::{migrate, Result}; +use std::process; + +/// We updated the 'affected-services' list metadata for 'settings.ecs' to remove +/// ecs-settings-applier on upgrade, and to add it on downgrade. +fn run() -> Result<()> { + migrate(ReplaceListsMigration(vec![ListReplacement { + setting: "services.ecs.restart-commands", + old_vals: &[ + "/usr/bin/ecs-settings-applier", + "/bin/systemctl try-reload-or-restart ecs.service", + ], + new_vals: &["/bin/systemctl try-reload-or-restart ecs.service"], + }])) +} + +fn main() { + if let Err(e) = run() { + eprintln!("{}", e); + process::exit(1); + } +} diff --git a/sources/api/migration/migrations/v1.20.0/update-ecs-config-path/Cargo.toml b/sources/api/migration/migrations/v1.20.0/update-ecs-config-path/Cargo.toml new file mode 100644 index 00000000000..bb7710d543e --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/update-ecs-config-path/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "update-ecs-config-path" +version = "0.1.0" +edition = "2021" +authors = ["Arnaldo Garcia "] +license = "Apache-2.0 OR MIT" +publish = false +exclude = ["README.md"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +migration-helpers = { path = "../../../migration-helpers", version = "0.1.0" } diff --git a/sources/api/migration/migrations/v1.20.0/update-ecs-config-path/src/main.rs b/sources/api/migration/migrations/v1.20.0/update-ecs-config-path/src/main.rs new file mode 100644 index 00000000000..bed7e5fe1fe --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/update-ecs-config-path/src/main.rs @@ -0,0 +1,19 @@ +use migration_helpers::common_migrations::ReplaceStringMigration; +use migration_helpers::{migrate, Result}; +use std::process; + +/// We updated the 'path' string for 'ecs-config' +fn run() -> Result<()> { + migrate(ReplaceStringMigration { + setting: "configuration-files.ecs-config.path", + old_val: "/etc/ecs/ecs.config", + new_val: "/etc/systemd/system/ecs.service.d/10-base.conf", + }) +} + +fn main() { + if let Err(e) = run() { + eprintln!("{}", e); + process::exit(1); + } +} diff --git a/sources/api/migration/migrations/v1.20.0/update-ecs-config-template-path/Cargo.toml b/sources/api/migration/migrations/v1.20.0/update-ecs-config-template-path/Cargo.toml new file mode 100644 index 00000000000..fe708b0f934 --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/update-ecs-config-template-path/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "update-ecs-config-template-path" +version = "0.1.0" +edition = "2021" +authors = ["Arnaldo Garcia "] +license = "Apache-2.0 OR MIT" +publish = false +exclude = ["README.md"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +migration-helpers = { path = "../../../migration-helpers", version = "0.1.0" } diff --git a/sources/api/migration/migrations/v1.20.0/update-ecs-config-template-path/src/main.rs b/sources/api/migration/migrations/v1.20.0/update-ecs-config-template-path/src/main.rs new file mode 100644 index 00000000000..08046bb79eb --- /dev/null +++ b/sources/api/migration/migrations/v1.20.0/update-ecs-config-template-path/src/main.rs @@ -0,0 +1,19 @@ +use migration_helpers::common_migrations::ReplaceStringMigration; +use migration_helpers::{migrate, Result}; +use std::process; + +/// We updated the 'template-path' for 'ecs-config' +fn run() -> Result<()> { + migrate(ReplaceStringMigration { + setting: "configuration-files.ecs-config.template-path", + old_val: "/usr/share/templates/ecs.config", + new_val: "/usr/share/templates/ecs-base-conf", + }) +} + +fn main() { + if let Err(e) = run() { + eprintln!("{}", e); + process::exit(1); + } +} diff --git a/sources/api/schnauzer/src/helpers.rs b/sources/api/schnauzer/src/helpers.rs index 1e5fc8a9984..cda3b93abde 100644 --- a/sources/api/schnauzer/src/helpers.rs +++ b/sources/api/schnauzer/src/helpers.rs @@ -158,6 +158,9 @@ const KUBE_RESERVE_ADDITIONAL: f32 = 2.5; const IPV4_LOCALHOST: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); const IPV6_LOCALHOST: IpAddr = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); +const DEFAULT_ECS_METADATA_SERVICE_RPS: i32 = 40; +const DEFAULT_ECS_METADATA_SERVICE_BURST: i32 = 60; + /// Potential errors during helper execution mod error { use handlebars::RenderError; @@ -317,6 +320,12 @@ mod error { source: serde_plain::Error, runtime: String, }, + + #[snafu(display("Invalid metadata service limits '{},{}'", rps, burst))] + InvalidMetadataServiceLimits { + rps: handlebars::JsonValue, + burst: handlebars::JsonValue, + }, } // Handlebars helpers are required to return a RenderError. @@ -1368,6 +1377,129 @@ pub fn etc_hosts_entries( Ok(()) } +/// This helper negates a boolean value, or returns a default value when the provided key wasn't +/// set. +/// +/// The first argument for the helper is the default value; the second argument is the key to +/// negate. Both values must be booleans, otherwise the helper will return an error. The default +/// value will be returned as it is if the provided key is missing. + +pub fn negate_or_else( + helper: &Helper<'_, '_>, + _: &Handlebars, + _: &Context, + renderctx: &mut RenderContext<'_, '_>, + out: &mut dyn Output, +) -> Result<(), RenderError> { + // To give context to our errors, get the template name, if available. + trace!("Starting negate_or_else helper"); + let template_name = template_name(renderctx); + trace!("Template name: {}", &template_name); + + // Check number of parameters, must be exactly two (the value to negate and the default value) + trace!("Number of params: {}", helper.params().len()); + check_param_count(helper, template_name, 2)?; + + let fallback_value = get_param(helper, 0)?; + let value_to_negate = get_param(helper, 1)?; + + let fallback = match fallback_value { + Value::Bool(b) => b, + _ => { + return Err(RenderError::from( + error::TemplateHelperError::InvalidTemplateValue { + expected: "boolean", + value: fallback_value.to_owned(), + template: template_name.to_owned(), + }, + )) + } + }; + + let output = match value_to_negate { + Value::Bool(b) => !b, + Value::Null => *fallback, + _ => { + return Err(RenderError::from( + error::TemplateHelperError::InvalidTemplateValue { + expected: "boolean", + value: value_to_negate.to_owned(), + template: template_name.to_owned(), + }, + )) + } + }; + + out.write(&(output).to_string()) + .context(error::TemplateWriteSnafu { + template: template_name.to_owned(), + })?; + + Ok(()) +} + +/// This helper returns the valid values for the ECS metadata service limits +/// +/// This helper returns the properly formatted ECS metadata service limits. When either RPS, burst +/// or both are missing, the default values in the ECS agent are used instead. +pub fn ecs_metadata_service_limits( + helper: &Helper<'_, '_>, + _: &Handlebars, + _: &Context, + renderctx: &mut RenderContext<'_, '_>, + out: &mut dyn Output, +) -> Result<(), RenderError> { + // To give context to our errors, get the template name, if available. + trace!("Starting ecs_metadata_service_limits helper"); + let template_name = template_name(renderctx); + trace!("Template name: {}", &template_name); + + // Check number of parameters, must be exactly two (metadata_service_rps and + // metadata_service_burst) + trace!("Number of params: {}", helper.params().len()); + check_param_count(helper, template_name, 2)?; + + let metadata_service_rps = helper + .param(0) + .map(|v| v.value()) + .context(error::ParamUnwrapSnafu {})?; + + let metadata_service_burst = helper + .param(1) + .map(|v| v.value()) + .context(error::ParamUnwrapSnafu {})?; + + let output = match (metadata_service_rps, metadata_service_burst) { + (Value::Number(rps), Value::Number(burst)) => { + format!("{},{}", rps, burst) + } + (Value::Number(rps), Value::Null) => { + format!("{},{}", rps, DEFAULT_ECS_METADATA_SERVICE_BURST) + } + (Value::Null, Value::Number(burst)) => { + format!("{},{}", DEFAULT_ECS_METADATA_SERVICE_RPS, burst) + } + (Value::Null, Value::Null) => format!( + "{},{}", + DEFAULT_ECS_METADATA_SERVICE_RPS, DEFAULT_ECS_METADATA_SERVICE_BURST + ), + (rps, burst) => { + return Err(RenderError::from( + error::TemplateHelperError::InvalidMetadataServiceLimits { + rps: rps.to_owned(), + burst: burst.to_owned(), + }, + )) + } + }; + + out.write(&output).context(error::TemplateWriteSnafu { + template: template_name.to_owned(), + })?; + + Ok(()) +} + // This helper checks if any objects have '"enabled": true' in their properties. // // any_enabled takes one argument that is expected to be an array of objects, @@ -3074,3 +3206,108 @@ mod test_oci_spec { ); } } + +#[cfg(test)] +mod test_negate_or_else { + use crate::helpers::negate_or_else; + use handlebars::{Handlebars, RenderError}; + use serde::Serialize; + use serde_json::json; + + fn setup_and_render_template(tmpl: &str, data: &T) -> Result + where + T: Serialize, + { + let mut registry = Handlebars::new(); + registry.register_helper("negate_or_else", Box::new(negate_or_else)); + + registry.render_template(tmpl, data) + } + + #[test] + fn test_negated_values() { + let template: &str = r#"{{negate_or_else false settings.value}}"#; + + let test_cases = vec![ + (json!({"settings": {"value": true}}), "false"), + (json!({"settings": {"value": false}}), "true"), + (json!({"settings": {"value": None::}}), "false"), + ]; + + test_cases.iter().for_each(|test_case| { + let (config, expected) = test_case; + let rendered = setup_and_render_template(template, config).unwrap(); + assert!(expected == &rendered); + }); + } + + #[test] + fn test_fails_when_not_booleans() { + let test_cases = vec![ + json!({"settings": {"value": []}}), + json!({"settings": {"value": {}}}), + json!({"settings": {"value": ""}}), + ]; + + let template: &str = r#"{{negate_or_else false settings.value}}"#; + + test_cases.iter().for_each(|test_case| { + let rendered = setup_and_render_template(template, test_case); + assert!(rendered.is_err()); + }); + } +} + +#[cfg(test)] +mod test_ecs_metadata_service_limits { + use crate::helpers::ecs_metadata_service_limits; + use handlebars::{Handlebars, RenderError}; + use serde::Serialize; + use serde_json::json; + + const TEMPLATE: &str = r#"{{ecs_metadata_service_limits settings.rps settings.burst}}"#; + + fn setup_and_render_template(tmpl: &str, data: &T) -> Result + where + T: Serialize, + { + let mut registry = Handlebars::new(); + registry.register_helper( + "ecs_metadata_service_limits", + Box::new(ecs_metadata_service_limits), + ); + + registry.render_template(tmpl, data) + } + + #[test] + fn test_valid_ecs_metadata_service_limits() { + let test_cases = vec![ + (json!({"settings": {"rps": 1, "burst": 1}}), r#"1,1"#), + (json!({"settings": {"rps": 1}}), r#"1,60"#), + (json!({"settings": {"burst": 1}}), r#"40,1"#), + (json!({"settings": {}}), r#"40,60"#), + ]; + + test_cases.iter().for_each(|test_case| { + let (config, expected) = test_case; + let rendered = setup_and_render_template(TEMPLATE, config).unwrap(); + assert!(expected == &rendered); + }); + } + + #[test] + fn test_invalid_ecs_metadata_service_limits() { + let test_cases = vec![ + json!({"settings": {"rps": [], "burst": 1}}), + json!({"settings": {"rps": 1, "burst": []}}), + json!({"settings": {"rps": [], "burst": []}}), + json!({"settings": {"rps": {}, "burst": {}}}), + ]; + + test_cases.iter().for_each(|test_case| { + let rendered = setup_and_render_template(TEMPLATE, test_case); + assert!(rendered.is_err()); + }); + } +} diff --git a/sources/api/schnauzer/src/v1.rs b/sources/api/schnauzer/src/v1.rs index 3ec4497f41a..118cd5208b2 100644 --- a/sources/api/schnauzer/src/v1.rs +++ b/sources/api/schnauzer/src/v1.rs @@ -135,6 +135,11 @@ pub fn build_template_registry() -> Result> { template_registry.register_helper("etc_hosts_entries", Box::new(helpers::etc_hosts_entries)); template_registry.register_helper("any_enabled", Box::new(helpers::any_enabled)); template_registry.register_helper("oci_defaults", Box::new(helpers::oci_defaults)); + template_registry.register_helper("negate_or_else", Box::new(helpers::negate_or_else)); + template_registry.register_helper( + "ecs_metadata_service_limits", + Box::new(helpers::ecs_metadata_service_limits), + ); Ok(template_registry) } diff --git a/sources/api/schnauzer/src/v2/import/helpers.rs b/sources/api/schnauzer/src/v2/import/helpers.rs index f73bb8e11bd..a71f1874aac 100644 --- a/sources/api/schnauzer/src/v2/import/helpers.rs +++ b/sources/api/schnauzer/src/v2/import/helpers.rs @@ -37,6 +37,10 @@ fn all_helpers() -> HashMap helper!(handlebars_helpers::ecr_prefix), }, + "ecs" => hashmap! { + "ecs_metadata_service_limits" => helper!(handlebars_helpers::ecs_metadata_service_limits), + }, + "kubernetes" => hashmap! { "join_node_taints" => helper!(handlebars_helpers::join_node_taints), "kube_reserve_cpu" => helper!(handlebars_helpers::kube_reserve_cpu), @@ -69,6 +73,7 @@ fn all_helpers() -> HashMap helper!(handlebars_helpers::join_map), "if_not_null" => Box::new(handlebars_helpers::IfNotNullHelper), "goarch" => helper!(handlebars_helpers::goarch), + "negate_or_else" => helper!(handlebars_helpers::negate_or_else), }, } } diff --git a/sources/models/shared-defaults/ecs.toml b/sources/models/shared-defaults/ecs.toml index 48061b3c376..ffe54b0dcbd 100644 --- a/sources/models/shared-defaults/ecs.toml +++ b/sources/models/shared-defaults/ecs.toml @@ -1,11 +1,11 @@ # ECS [services.ecs] -restart-commands = ["/usr/bin/ecs-settings-applier", "/bin/systemctl try-reload-or-restart ecs.service"] +restart-commands = ["/bin/systemctl try-reload-or-restart ecs.service"] configuration-files = ["ecs-config"] [configuration-files.ecs-config] -path = "/etc/ecs/ecs.config" -template-path = "/usr/share/templates/ecs.config" +path = "/etc/systemd/system/ecs.service.d/10-base.conf" +template-path = "/usr/share/templates/ecs-base-conf" [metadata.settings.ecs] affected-services = ["ecs"] diff --git a/variants/aws-ecs-1-nvidia/Cargo.toml b/variants/aws-ecs-1-nvidia/Cargo.toml index 19e63fc84cf..015f348d414 100644 --- a/variants/aws-ecs-1-nvidia/Cargo.toml +++ b/variants/aws-ecs-1-nvidia/Cargo.toml @@ -25,7 +25,7 @@ included-packages = [ "docker-engine", "docker-init", # ecs - "ecs-agent", + "ecs-agent-nvidia-config", # NVIDIA support "ecs-gpu-init", "nvidia-container-toolkit-ecs", diff --git a/variants/aws-ecs-1/Cargo.toml b/variants/aws-ecs-1/Cargo.toml index 7c63d3460e1..3d53e68b097 100644 --- a/variants/aws-ecs-1/Cargo.toml +++ b/variants/aws-ecs-1/Cargo.toml @@ -22,7 +22,7 @@ included-packages = [ "docker-engine", "docker-init", # ecs - "ecs-agent", + "ecs-agent-config", ] [lib] diff --git a/variants/aws-ecs-2-nvidia/Cargo.toml b/variants/aws-ecs-2-nvidia/Cargo.toml index d1274aed288..2ace1284361 100644 --- a/variants/aws-ecs-2-nvidia/Cargo.toml +++ b/variants/aws-ecs-2-nvidia/Cargo.toml @@ -25,7 +25,7 @@ included-packages = [ "docker-engine", "docker-init", # ecs - "ecs-agent", + "ecs-agent-nvidia-config", # NVIDIA support "ecs-gpu-init", "nvidia-container-toolkit-ecs", diff --git a/variants/aws-ecs-2/Cargo.toml b/variants/aws-ecs-2/Cargo.toml index c0a7fcc47a0..d307bb3abc5 100644 --- a/variants/aws-ecs-2/Cargo.toml +++ b/variants/aws-ecs-2/Cargo.toml @@ -24,7 +24,7 @@ included-packages = [ "docker-engine", "docker-init", # ecs - "ecs-agent", + "ecs-agent-config", ] kernel-parameters = [ "console=tty0",