Skip to content
Draft
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
140 changes: 5 additions & 135 deletions compute/ecs_service/rvn-ecs-web-definition.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1546,143 +1546,13 @@ module:
timeout: 1800
ui:
metrics:
- id: cpu_utilization
name: CPU utilization
type: line
source:
type: cloudwatch
- $template: ../../partials/templates/ecs-service-metrics.yml
with:
aws_account_id: << module.input.aws_account_id >>
dimensions:
ClusterName: << stack.output.cluster_name >>
ServiceName: << stack.output.service_name >>
name: CPUUtilization
namespace: AWS/ECS
cluster_name: << stack.output.cluster_name >>
region: << stack.output.region >>
statistic: Average
- id: memory_utilization
name: Memory utilization
type: line
source:
type: cloudwatch
aws_account_id: << module.input.aws_account_id >>
dimensions:
ClusterName: << stack.output.cluster_name >>
ServiceName: << stack.output.service_name >>
name: MemoryUtilization
namespace: AWS/ECS
region: << stack.output.region >>
statistic: Average
- id: running_tasks
name: Running tasks
type: line
source:
type: cloudwatch
aws_account_id: << module.input.aws_account_id >>
dimensions:
ClusterName: << stack.output.cluster_name >>
ServiceName: << stack.output.service_name >>
name: RunningTaskCount
namespace: ECS/ContainerInsights
region: << stack.output.region >>
statistic: Average
- id: request_count
name: Request count
type: line
source:
type: cloudwatch
aws_account_id: << module.input.aws_account_id >>
dimensions:
TargetGroup: << stack.output.target_group_arn_suffix >>
name: RequestCount
namespace: AWS/ApplicationELB
region: << stack.output.region >>
statistic: Sum
- id: target_5xx_errors
name: 5xx errors
type: line
source:
type: cloudwatch
aws_account_id: << module.input.aws_account_id >>
dimensions:
TargetGroup: << stack.output.target_group_arn_suffix >>
name: HTTPCode_Target_5XX_Count
namespace: AWS/ApplicationELB
region: << stack.output.region >>
statistic: Sum
- id: target_4xx_errors
name: 4xx errors
type: line
source:
type: cloudwatch
aws_account_id: << module.input.aws_account_id >>
dimensions:
TargetGroup: << stack.output.target_group_arn_suffix >>
name: HTTPCode_Target_4XX_Count
namespace: AWS/ApplicationELB
region: << stack.output.region >>
statistic: Sum
- id: target_response_time
name: Target response time
type: line
source:
type: cloudwatch
aws_account_id: << module.input.aws_account_id >>
dimensions:
TargetGroup: << stack.output.target_group_arn_suffix >>
name: TargetResponseTime
namespace: AWS/ApplicationELB
region: << stack.output.region >>
statistic: Average
- id: healthy_hosts
name: Healthy hosts
type: line
source:
type: cloudwatch
aws_account_id: << module.input.aws_account_id >>
dimensions:
TargetGroup: << stack.output.target_group_arn_suffix >>
name: HealthyHostCount
namespace: AWS/ApplicationELB
region: << stack.output.region >>
statistic: Average
- id: unhealthy_hosts
name: Unhealthy hosts
type: line
source:
type: cloudwatch
aws_account_id: << module.input.aws_account_id >>
dimensions:
TargetGroup: << stack.output.target_group_arn_suffix >>
name: UnHealthyHostCount
namespace: AWS/ApplicationELB
region: << stack.output.region >>
statistic: Average
- id: network_in
name: Network in
type: line
source:
type: cloudwatch
aws_account_id: << module.input.aws_account_id >>
dimensions:
ClusterName: << stack.output.cluster_name >>
ServiceName: << stack.output.service_name >>
name: NetworkRxBytes
namespace: ECS/ContainerInsights
region: << stack.output.region >>
statistic: Sum
- id: network_out
name: Network out
type: line
source:
type: cloudwatch
aws_account_id: << module.input.aws_account_id >>
dimensions:
ClusterName: << stack.output.cluster_name >>
ServiceName: << stack.output.service_name >>
name: NetworkTxBytes
namespace: ECS/ContainerInsights
region: << stack.output.region >>
statistic: Sum
service_name: << stack.output.service_name >>
target_group: << stack.output.target_group_arn_suffix >>
readme: |
Web server ECS service for running an HTTP application behind an ECS cluster load balancer.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
definition:
type: rvn-ecs-existing-service
name: Existing ECS Service
description: Deploys releases to an existing, externally managed Amazon ECS service using a user-provided task definition template.
release:
Comment on lines +1 to +5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Root README.md not updated

AGENTS.md explicitly marks it as critical to update the root README.md Module Directory table whenever a module is added, modified, or removed. This PR introduces a new module (rvn-ecs-existing-service) in a new category (modules_without_stack) but the root README.md is not updated, leaving it out of sync.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: modules_without_stack/ecs_existing_service/rvn-ecs-existing-service-definition.yml
Line: 1-5

Comment:
**Root README.md not updated**

`AGENTS.md` explicitly marks it as critical to update the root `README.md` Module Directory table whenever a module is added, modified, or removed. This PR introduces a new module (`rvn-ecs-existing-service`) in a new category (`modules_without_stack`) but the root `README.md` is not updated, leaving it out of sync.

**Context Used:** AGENTS.md ([source](https://app.greptile.com/flightcontrol/github/flightcontrolhq/modules/-/custom-context?memory=79f61682-c3c3-4650-8eed-6406e8edd026))

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

version: 0.0.1
description: Initial existing ECS service module definition.
module:
inputs:
- $include: ../../partials/inputs/aws-account.yml
- $include: ../../partials/inputs/aws-region.yml
- id: section_service
label: ECS service
type: section
description: Identify the existing ECS cluster and service Ravion should deploy to. Ravion does not create, change, or destroy these resources.
- id: cluster_name
immutable: true
label: Cluster name
type: string
description: Name of the existing ECS cluster that contains the service.
placeholder: production-cluster
required: true
patterns:
- message: "Use 1-255 characters: letters, numbers, hyphens, and underscores only."
pattern: ^[A-Za-z0-9_-]{1,255}$
- id: service_name
immutable: true
label: Service name
type: string
description: Name of the existing ECS service to update on each deployment.
placeholder: my-service
required: true
patterns:
- message: "Use 1-255 characters: letters, numbers, hyphens, and underscores only."
pattern: ^[A-Za-z0-9_-]{1,255}$
- id: target_group_arn_suffix
label: Target group ARN suffix
type: string
description: ARN suffix of the load balancer target group serving this service, such as `targetgroup/my-tg/1234567890abcdef`. Used only for load balancer metrics; leave blank if the service is not behind a load balancer.
collapsible: true
placeholder: targetgroup/my-tg/1234567890abcdef
required: false
- id: section_image
label: Image registry
type: section
description: Images are built outside Ravion. Configure the registry repository here and provide only the tag or digest at deploy time.
- id: image_repository
label: Image repository
type: string
description: Image repository without a tag or digest, such as `nginx`, `ghcr.io/org/app`, or `123456789012.dkr.ecr.us-east-1.amazonaws.com/app`.
placeholder: 123456789012.dkr.ecr.us-east-1.amazonaws.com/app
required: true
- id: image_registry_credentials_secret_arn
label: Registry credentials secret ARN
type: string
description: Secrets Manager secret ARN for private registries such as GHCR or Docker Hub. The secret must use the ECS repository credentials JSON format. Not needed for public images or normal same-account ECR.
collapsible: true
placeholder: arn:aws:secretsmanager:us-east-1:123456789012:secret:registry-creds
required: false
- id: section_task_definition
label: Task definition
type: section
description: Each deployment registers a new task definition revision from the template below, in the template's family, and updates the service to use it.
- id: container_name
label: Deploy target container name
type: string
description: Name of the container in the task definition template that receives the deployed image. The template image value for this container is overridden at deploy time.
placeholder: app
required: true
patterns:
- message: "Use 1-255 characters: letters, numbers, hyphens, and underscores only."
pattern: ^[A-Za-z0-9_-]{1,255}$
- id: task_definition_template
label: Task definition template
type: object
description: Task definition body registered on every deployment, using snake_case keys (family, container_definitions, port_mappings, log_configuration, and so on). Must include family, usually the family the service already uses. The whole template is registered as written, except the image of the deploy target container is replaced at deploy time.
placeholder: |-
family: my-service
container_definitions:
- name: app
image: overridden-at-deploy-time
essential: true
port_mappings:
- container_port: 80
protocol: tcp
environment:
- name: NODE_ENV
value: production
secrets:
- name: DATABASE_URL
value_from: arn:aws:ssm:us-east-1:123456789012:parameter/database-url
log_configuration:
log_driver: awslogs
options:
awslogs-group: /ecs/my-service
awslogs-region: us-east-1
awslogs-stream-prefix: app
cpu: "512"
memory: "1024"
network_mode: awsvpc
requires_compatibilities:
- FARGATE
runtime_platform:
cpu_architecture: X86_64
operating_system_family: LINUX
execution_role_arn: arn:aws:iam::123456789012:role/my-execution-role
task_role_arn: arn:aws:iam::123456789012:role/my-task-role
required: true
deploy:
type: aws:ecs
concurrency:
queue_overflow: oldest
queue_size: 1
infrastructure:
ecs_cluster_arn: arn:aws:ecs:<<module.input.aws_region>>:<<module.input.aws_account_id>>:cluster/<<module.input.cluster_name>>
ecs_service_arns:
- arn:aws:ecs:<<module.input.aws_region>>:<<module.input.aws_account_id>>:service/<<module.input.cluster_name>>/<<module.input.service_name>>
inputs:
- id: image_ref
label: Image tag or digest
type: string
description: Image tag or digest to deploy, resolved in the image repository configured on the module. Do not pass a full image URI.
placeholder: sha256:... or v1.2.3
required: true
task_definition: >-
<< merge(module.input.task_definition_template, {"container_definitions":
(module.input.task_definition_template.container_definitions != nil ?
map(module.input.task_definition_template.container_definitions, #.name ==
module.input.container_name ? merge(#,
{"image": imageUri(module.input.image_repository, deploy.input.image_ref)},
module.input.image_registry_credentials_secret_arn ? {"repository_credentials":
{"credentials_parameter": module.input.image_registry_credentials_secret_arn}} : nil) : #) : [])}) >>
timeout: 1800
ui:
links:
- name: ECS service console
href: https://<<module.input.aws_region>>.console.aws.amazon.com/ecs/v2/clusters/<<module.input.cluster_name>>/services/<<module.input.service_name>>
metrics:
- $template: ../../partials/templates/ecs-service-metrics.yml
with:
aws_account_id: << module.input.aws_account_id >>
cluster_name: << module.input.cluster_name >>
region: << module.input.aws_region >>
service_name: << module.input.service_name >>
target_group: << module.input.target_group_arn_suffix >>
readme: |
Deploys releases to an existing, externally managed Amazon ECS service using a user-provided task definition template.

## Overview

The Existing ECS Service module connects Ravion deployments to an ECS service that was created outside Ravion. There is no infrastructure stack: Ravion does not create, change, or destroy the cluster, service, load balancer, IAM roles, or networking. You provide the AWS account, region, cluster name, service name, and a task definition template.

On each deployment, Ravion renders the template, replaces the image of the deploy target container with the image provided at deploy time, registers a new task definition revision in the template's family, and updates the existing ECS service to use it.

## What you must provide

| Field | Required | Description |
| ---------------------------- | -------- | ------------------------------------------------------------------ |
| AWS account | Yes | Connected AWS account that owns the ECS service |
| Region | Yes | Region where the cluster and service run |
| Cluster name | Yes | Existing ECS cluster name |
| Service name | Yes | Existing ECS service name |
| Target group ARN suffix | No | Enables load balancer metrics for the service |
| Image repository | Yes | Image repository without a tag or digest |
| Registry credentials secret ARN | No | ECS repository credentials secret for private registries |
| Deploy target container name | Yes | Container in the template that receives the deployed image |
| Task definition template | Yes | Full task definition body registered on every deployment |

## Image registry

Configure the image repository on the module without a tag or digest, such as `nginx`, `ghcr.io/org/app`, or `123456789012.dkr.ecr.us-east-1.amazonaws.com/app`. For private registries such as GHCR or Docker Hub, provide a Secrets Manager secret ARN in the ECS repository credentials JSON format; it is attached to the deploy target container as repository credentials. Same-account ECR repositories normally need no credentials, but the template's execution role must be able to pull the image.

## Task definition template

The template is the task definition body in snake_case, mirroring the ECS RegisterTaskDefinition API: family, container_definitions, cpu, memory, network_mode, requires_compatibilities, runtime_platform, task_role_arn, execution_role_arn, volumes, and so on. It must include family, usually the family the service already uses, so new revisions land in the right place.

The entire template is passed through as the registered task definition. Ravion overrides only the deploy target container: its image is replaced with the image resolved at deploy time, and registry credentials are attached to it when a registry credentials secret ARN is configured. Everything else, including additional containers and sidecars, is registered exactly as written.

Because the service and its IAM roles are externally managed, the template must reference an execution role that can pull the deployed image and write to the configured log destination, and a task role with whatever AWS permissions the application needs.

## Deployment

At deploy time, provide only the image tag or digest, such as `v1.2.3` or `sha256:...`. It is resolved in the image repository configured on the module: digests are joined with `@` and tags with `:`. Image builds happen outside Ravion in your own pipeline.

Deployments are queued one at a time per module instance; stale queued deployments are collapsed in favor of the newest.

## Design decisions

- No stack: the module never owns or mutates the underlying AWS resources, so destroying the module instance leaves the ECS service untouched.
- No build: images come from an external pipeline. The registry repository is module configuration; deploys only choose the tag or digest, mirroring the prebuilt-image mode of the ECS Web Server module.
- The task definition template is the single source of truth for everything except the deployed image, keeping drift between Ravion and the external service explicit and reviewable.
- The Running tasks, Network in, and Network out metrics use ECS Container Insights and only report data when Container Insights is enabled on the cluster.
- Load balancer metrics (request count, 4xx/5xx errors, response time, healthy/unhealthy hosts) read from the optional target group ARN suffix and stay empty when it is not configured.

## Learn more

- [Amazon ECS services](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs_services.html)
- [RegisterTaskDefinition API](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RegisterTaskDefinition.html)
- [Amazon ECS Container Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/ContainerInsights.html)
Comment on lines +192 to +199

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 ALB metrics emitted when target group is absent

target_group_arn_suffix is optional (required: false), so it can be empty when the service has no load balancer. The shared ecs-service-metrics.yml template has no conditional guard and always emits all four ALB metrics (request count, 4xx/5xx errors, response time, healthy/unhealthy hosts) with TargetGroup: $with.target_group. When target_group_arn_suffix is blank, the compiled output will carry an empty CloudWatch dimension, which may result in API errors or permanently empty metric graphs visible to users. The README acknowledges this as intentional, but it is worth confirming the platform silently suppresses metrics with nil/empty dimensions rather than surfacing errors.

Prompt To Fix With AI
This is a comment left during a code review.
Path: modules_without_stack/ecs_existing_service/rvn-ecs-existing-service-definition.yml
Line: 196-203

Comment:
**ALB metrics emitted when target group is absent**

`target_group_arn_suffix` is optional (`required: false`), so it can be empty when the service has no load balancer. The shared `ecs-service-metrics.yml` template has no conditional guard and always emits all four ALB metrics (request count, 4xx/5xx errors, response time, healthy/unhealthy hosts) with `TargetGroup: $with.target_group`. When `target_group_arn_suffix` is blank, the compiled output will carry an empty CloudWatch dimension, which may result in API errors or permanently empty metric graphs visible to users. The README acknowledges this as intentional, but it is worth confirming the platform silently suppresses metrics with nil/empty dimensions rather than surfacing errors.

How can I resolve this? If you propose a fix, please make it concise.

Loading
Loading