App-FargateStack

 view release on metacpan or  search on metacpan

lib/App/FargateStack/Pod.pm  view on Meta::CPAN

these resources:

=over 4

=item *  A certificate for your domain

=item * A Fargate cluster

=item * IAM roles and policies

=item * A listener and listener rules

=item * A CloudWatch log group

=item * Security groups

=item * A target group

=item * A task definition

=item * An ALB if one is not detected

=back

Once again, launching a Fargate service requires a
lot of fiddling with AWS resources! Getting all of the plumbing
installed and working requires a lot of what and how knowledge.

=head2 Adding or Changing Resources

Adding or updating resources for an existing application should also
be easy. Updating the infrastructure should just be a matter of
updating the configuration and re-running the framework's script. When
you update the configuration the C<App::FargateStack> will detect the
changes and update the necessary resources.

Currently the framework supports adding a single SQS queue, a single
S3 bucket, volumes using EFS mount points, environment variables and
secrets from AWS Secrets Manager.

 my-worker:
   image: my-worker:latest
   command: /usr/local/bin/my-worker.pl
   type: task
   schedule: cron(00 15 * * * *)   
   bucket:
     name: my-worker-bucket
   queue:
     name: my-worker-queue
   environment:
     ENVIRONMENT=prod
   secrets:
     db_password:DB_PASSWORD
   efs:
     id: fs-abcde12355
     path: /
     mount_point: /mnt/my-worker

Adding new resources would normally require you to update your
policies to allow your worker to access these resource. However, the
framework automatically detects that the policy needs to be updated
when new resources are added (even secrets) and takes care of that for
you.

See C<app-Fargate help configuration> for more information about
resources and options.

=head2 Configuration as State

The framework attempts to be as transparent as possible regarding what
it is doing, how long it takes, what the result was and most
importantly I<what defaults were used during resource
provisioning>. Every time the framework is run, the configuration file
is updated based on any new resources provisioned or configured.  For
example, if you did not specify subnets, they are inferred by
inspecting your VPC and automatically added to the configuration file.

This gives you a single view into your Fargate application

=head1 CLI OPTION DEFAULTS

When enabled, C<App::FargateStack> automatically remembers the most recently
used values for several CLI options between runs. This feature helps streamline
repetitive workflows by eliminating the need to re-specify common arguments
such as the AWS profile, region, or config file.

The following options are tracked and persisted:

=over 4

=item * C<--profile>

=item * C<--region>

=item * C<--config>

=item * C<--route53-profile>

=item * C<--max-events>

=back

These values are stored in F<.fargatestack/defaults.json> within your current
project directory. If you omit any of these options on subsequent runs, the
most recently used value will be reused.

Typically, you would create a dedicated project directory for your
stack and place your configuration file there. Once you invoke a
command that includes any of the tracked CLI options, the
F<.fargatestack/defaults.json> file will be created
automatically. Future commands run from that directory can then omit
those options. A typical workflow to create a new stack with a
scheduled job might look like this:

 mkdir my-project
 cd my-project
 app-FargateStack create-stack foo task:my-cron image:my-project 'schedule:cron(0 10 * * * *)'
 app-FargateStack plan
 app-FargateStack apply

That's it...you just created a scheduled job that will run at 10 AM every day!

=head2 Disabling and Resetting

Use the C<--no-history> option to temporarily disable this feature for a single
run. This allows you to override stored values without modifying or deleting
them.

To clear all saved defaults entirely, use the C<reset-history> command. This
removes all of the tracked values from the F<.fargatestack/defaults.json> file,
but preserves the file itself.

=head2 Notes

lib/App/FargateStack/Pod.pm  view on Meta::CPAN

 ...
 tasks:
   apache:
     type: https

The framework will:

=over 4

=item * ...create a log group named /ecs/my-stack

=item * ...configure the apache task to write log streams with a prefix
like my-stack/apache/*

=back

By default, the log group is set to retain logs for 14 days if
C<retention_days> is not specified. You can override this by
specifying a custom retention period using the C<retention_days> key
in the task's log_group section:

 log_group:
   retention_days: 30

=head2 Log Group Notes

=over 4

=item * The log group is reused if it already exists.

=item * Only numeric values accepted by CloudWatch are valid for
retention_days (e.g., 1, 3, 5, 7, 14, 30, 60, 90, etc.).

=item * You can customize the log group name by setting the name in
the C<log_group:> section (not recommended).

 log_group:
   retention_days: 14
   name: /ecs/my-stack

=item * You can change the retention period by updating the
configuration file and re-running C<apply>.

=item * To retain logs indefinitely, remove the C<retention_days>
entry in your configuration file.

=back

=head1 IAM PERMISSIONS

This framework uses a single IAM role for all tasks defined within an
application stack.  The assumption is that services within the stack
share a trust boundary and operate on shared infrastructure.  This
simplifies IAM management while maintaining strict isolation between
stacks.

IAM roles and policies are automatically created based on your
configuration.  Only the minimum required permissions are granted.
For example, if your configuration defines an S3 bucket, the ECS task
role will be permitted to access only that specific bucket - not all
buckets in your account. The policy is updated when new resources are
added to the configuration file.

The task execution role name and role policy name are found under the
C<role:> key in the configuration. The task role is found under the
C<task_role:> key. Role names and role policy names are automatically
fabricated for you from the name you specified under the C<app:> key.

=head2 Task Execution Role vs. Task Role

It's important to understand that App::FargateStack provisions two
distinct IAM roles for your service. The Task Role, which is detailed
above, grants your application the specific permissions it needs to
interact with other AWS services like S3 or SQS. In addition, the
framework also creates a Task Execution Role. This second role is used
by the Amazon ECS container agent itself and grants it permission to
perform essential actions, such as pulling container images from ECR
and sending logs to CloudWatch. You typically won't need to modify the
Task Execution Role, as the framework manages its permissions
automatically.

=head1 SECURITY GROUPS

A security group is automatically provisioned for your Fargate
cluster.  If you define a task of type C<http> or C<https>, the
security group attached to your Application Load Balancer (ALB) is
automatically authorized for ingress to your Fargate task. This is a
rule allowing ALB-to-Fargate traffic.

=head1 FILESYSTEM SUPPORT

EFS volumes are defined per task and mounted according to the task
definition. This design provides fine-grained control over EFS usage,
rather than treating it as a global, stack-level resource.

Each task that requires EFS support must include both a volume and
mountPoint configuration. The ECS task role is automatically updated
to allow EFS access based on your specification.

To specify EFS support in a task:

 efs:
   id: fs-1234567b
   mount_point: /mnt/my-stack
   path: /
   readonly:

Acceptable values for C<readonly> are "true" and "false".

=head2 Field Descriptions

=over 4

=item id:

The ID of an existing EFS filesystem. The framework does not provision
the EFS, but will validate its existence in the current AWS account
and region.

=item mount_point:

The container path to which the EFS volume will be mounted.

=item path:

The path on the EFS filesystem to map to your container's mount point.

=item readonly:

Optional. Set to C<true> to mount the EFS as read-only. Defaults to
C<false>.

=back

=head2 Additional Notes

=over 4

=item * The ECS role's policy for your task is automatically modified
to allow read/write EFS access. Set C<readonly:> in your task's
C<efs:> section to "true" if only want read support.

=item * Your EFS security group must allow access from private subnets
where the Fargate tasks are placed.

=item * No changes are made to the EFS security group; the framework
assumes access is already configured

=item * Only one EFS volume is currently supported per task configuration.

=item * EFS volumes are task-scoped and reused only where explicitly configured.

=item * The framework does not automatically provision an EFS
filesystem for you. The framework does however validate that the
filesystem exists in the current account and region.

=back

=head1 CONFIGURATION

The C<App::FargateStack> framework defines your application stack
using a YAML configuration file. This file describes your
application's services, their resource needs, and how they should be
deployed. Then configuration is updated whenever your run C<plan> or
C<apply>.

=head2 GETTING STARTED

The fastest way to get up and running with C<App::FargateStack> is to
use the C<create-stack> command to generate a configuration file,
inspect the deployment plan, and then apply it.

=head3 Step 1: Create a Configuration Stub

First, generate a minimal YAML configuration file. The C<create-stack>
command provides a shorthand syntax to do this. You only need to
provide an overall application name, a service type, a service name,
and the container image to use.

This command will create a file named F<my-stack.yml> in your current
directory. Make sure you have your AWS profile configured in your
environment or pass it using the C<--profile> option.

  app-FargateStack create-stack my-stack daemon:my-stack-daemon image:my-stack-daemon:latest

This will produce a configuration stub that looks like this:

  app:
    name: my-stack
  tasks:
    my-stack-daemon:
      image: my-stack-daemon:latest
      type: daemon

This file contains the three key pieces of information you provided:
the application name, the task name, and the image to use.

=head3 Step 2: Plan the Deployment (Dry Run)

Next, run the C<plan> command. This is a crucial step that acts as a
dry run. The framework will:

=over 4

=item * Read your minimal configuration file.

=item * Intelligently discover resources in your AWS account (like your VPC and subnets).

=item * Determine what new resources need to be created (like IAM roles, a security group, an ECS cluster and a CloudWatch log group).

=item * Report a full plan of action without making any actual changes.

=item * Update your configuration file with the discovered values and
sensible defaults.

=back

  app-FargateStack plan

After this command completes, your F<my-stack.yml> file will be fully
populated with all the information needed to provision your stack.

=head3 Step 3: Apply the Plan

lib/App/FargateStack/Pod.pm  view on Meta::CPAN


Notes on internet access and ALBs:

=over 4

=item * Internet-facing ALB

An internet-facing ALB must be created in public subnets. Tasks may (and
usually should) remain in private subnets behind it.

=item * Egress from private subnets

For image pulls and outbound calls, use either a NAT Gateway in each AZ
or VPC endpoints for ECR (api and dkr) and S3.

=item * Egress from public subnets

If tasks are placed in public subnets without endpoints or NAT, they
require C<assignPublicIp=ENABLED> to reach ECR/S3.

=back

=head2 REQUIRED SECTIONS

At minimum, your configuration must include the following:

  app:
    name: my-stack

  tasks:
    my-task:
      image: my-image
      type: daemon | task | http | https

For task types C<http> or C<https>, you must also specify a domain name:

  domain: example.com

=head2 FULL SCHEMA OVERVIEW

The framework will expand and update your configuration file with default values as needed.
Here is the full schema outline. All keys are optional unless otherwise noted:

  ---
  account:
  alb:
    arn:
    name:
    port:
    type:
  app:
    name:             # required
    version:
  certificate_arn:
  cluster:
    arn:
    name:
  default_log_group:
  domain:              # required for http/https tasks
  id:
  last_updated:
  region:
  role:
    arn:
    name:
    policy_name:
  route53:
    profile:
    zone_id:
  security_groups:
    alb:
      group_id:
      group_name:
    fargate:
      group_id:
      group_name:
  subnets:
    private:
    public:
  tasks:
    my-task:
      arn:
      cpu:
      family:
      image:           # required
      log_group:
        arn:
        name:
        retention_days:
      memory:
      name:
      size:
      target_group_arn:
      target_group_name:
      task_definition_arn:
      type:            # required (daemon, task, http, https)
  vpc_id:

=head1 TASK SIZE

To simplify task configuration, the framework supports a shorthand key called
C<size> that maps to common CPU and memory combinations supported by Fargate.

If specified, the C<size> parameter should be one of the following profile names:

  tiny     => 256 CPU, 512 MB memory
  small    => 512 CPU, 1 GB memory
  medium   => 1024 CPU, 2 GB memory
  large    => 2048 CPU, 4 GB memory
  xlarge   => 4096 CPU, 8 GB memory
  2xlarge  => 8192 CPU, 16 GB memory

When a C<size> is provided, the framework will automatically populate the
corresponding C<cpu> and C<memory> values in the task definition. If you
manually specify C<cpu> or C<memory> alongside C<size>, those manual values
will take precedence and override the defaults from the profile.

B<Important:> If you change the C<size> after an initial deployment, you should
remove any manually defined C<cpu> and C<memory> keys in your configuration.
This ensures that the framework can correctly apply the new profile values
without conflict.

lib/App/FargateStack/Pod.pm  view on Meta::CPAN

(301). If you do not want a redirect rule, set the C<redirect_80:> in
the C<alb:> section to "false".

If you want your internal application to listen on a port other than
80, set the C<port:> key in the C<alb:> section to a new port
value.

=head2 Example Minimal Configuration

 app:
   name: http-test
 domain: http-test.example.com
 task:
   apache:
     type: http
     image: http-test:latest

Based on this minimal configuration C<app-FargateStack> will enrich
the configuration with appropriate defaults and proceed to provision
your HTTP service.

To do that, the framework attempts to discover the resources required
for your service. If your environment is not compatible with creating
the service, the framework will report the missing resources and
abort the process.

Given this minimal configuration for an internal ("http") or
external ("https") HTTP service, discovery entails:

=over 4

=item  ...determining your VPC's ID

=item  ...identifying the private subnet IDs

=item ...determining if there is and existing load balancer with the
correct scheme

=item  ...finding your load balancer's security group (if an ALB exists)

=item  ...looking for a listener rule on port 80 (and 443 if type is
"https"), including a default forwarding redirect rule

=item  ...validating that you have a private or public hosted zone
in Route 53 that supports your domain

=item  ...setting other defaults for additional resources to be built (log
groups, cluster, target group, etc)

=item  ...determining if an ACM certificate exists for your domain
(if type is "https")

=back

I<Note: Discovery of these resources is only done when they are
missing from your configuration. If you have multiple VPCs for example
you can should explicitly set C<vpc_id:> in the configuration to
identify the target VPC.  Likewise you can explicitly set other
resource configurations (subnets, ALBs, Route 53, etc).>

Resources are provisioned and your configuration file is updated
incrementally as C<app-FargateStack> compares your environment to the
environment required for your stack. When either plan or
apply complete your configuration is updated giving you complete
insight into what resources were found and what resources will be
provisioned. See L<CONFIGURATION> for complete details on resource
configurations.>

Your environment will be validated against the criteria described
below.

=over 4

=item * You have at least 2 private subnets available for deployment

Technically you can launch a task with only 1 subnet but for services
behind an ALB Fargate requires 2 subnets.

I<When you create a service with a load balancer, you must specify
two or more subnets in different Availability Zones. - AWS Docs>

=item * You have a hosted zone for your domain of the appropriate type
(private for type "http", public for type "https")

=back

As discovery progresses, existing and required resources are logged
and your configuration file is updated. If you are B<NOT> running in
dryrun mode, resources will be created immediately as they are
discovered to be missing from your environment.

=head2 Application Load Balancer

When you provision an HTTP service, whether or not it is secure, the
service will placed behind an application load balancer. Your Fargate
service is created in private subnets, so your VPC must contain at
least two private subnets.  Your load balancer can either be
I<internally> or I<externally facing>.

By default, the framework looks for and will reuse a load balancer
with the correct scheme (internal or internet-facing), in a subnet
aligned with your task type. The ALB will be placed in public subnets
if it is internet-facing. You can override that behavior by either
explicitly setting the ALB arn in the C<alb:> section of the
configuration or pass C<--create-alb> when you run our plan and apply.

If no ALB is found or you passed the C<--create-alb> option, a new ALB
is provisioned. When creating a new ALB, C<app-FargateStack> will also
create the necessary listeners and listener rules for the ports you
have configured.

=head3 Why Does the Framework Force the Use of a Load Balancer?

While it is possible to avoid the use or the creation of a load balancer
for your service, the framework forces you to use one for at least two
reasons. Firstly, the IP address of your service may not be stable and
is not friendly for development or production purposes. The framework
is, after all trying its best to promote best practices while
preventing you from having to know how all the sausage is made.

Secondly, it is almost guaranteed that you will eventually want
a domain name for your production service - whether it is an
internally facing microservice or an externally facing web
application.

Creating an alias in Route 53 for your domain pointing to the ALB
ensures you don't need to update application configurations with the
service's dynamic IP address. Additionally, using a load balancer
allows you to create custom routing rules to your service. If you want
to run multiple tasks for your service to support handling more
traffice a load balancer is required.

With those things in mind the framework automatically uses an ALB for
HTTP services and creates an alias record (A) for your domain for both
internal and external facing services.

=head2 AWS WAF Support

For external-facing HTTPS services, C<App::FargateStack> can automate
the creation and association of an AWS Web Application Firewall (WAF)
to provide an essential layer of security. This protects your
application from common web exploits and bots that could affect
availability or compromise security.

The framework follows a "Hybrid Management Model" for WAF, designed to
provide a secure, sensible baseline out-of-the-box while giving you
full control over fine-grained rule customization.

lib/App/FargateStack/Pod.pm  view on Meta::CPAN

=item * B<php>: Includes rules for applications running on PHP.

=item * B<wordpress>: Includes rules specific to WordPress sites.

=item * B<windows>: Includes rules for Windows Server environments.

=item * B<anonymous>: B<Use with caution.> Blocks traffic from anonymous sources like VPNs and proxies, which may block legitimate users.

=item * B<ddos>: Mitigates application-layer (Layer 7) DDoS attacks like HTTP floods.

=item * B<premium>: B<Warning: Extra Cost.> Enables advanced, paid protections for bot control and account takeover prevention.

=back

=head4 Rule Bundles

=over 4

=item * B<default>: Includes C<base> and C<sql>. This is the recommended starting point for most applications.

=item * B<linux-app>: Includes C<default> and C<linux>.

=item * B<wordpress-app>: Includes C<default>, C<linux>, and C<wordpress>.

=item * B<windows-app>: Includes C<default> and C<windows>.

=item * B<all>: Includes all standard, non-premium rule sets. B<Warning:> This will likely exceed the default WCU quota and may incur additional costs.

=back

=head3 The Bootstrap Process (First Run)

On the first C<apply> run with WAF enabled, the framework will perform
a one-time bootstrap:

=over 4

=item 1.

It generates a default F<web-acl.json> file in your project
directory. This file contains the complete definition of your Web ACL,
including the rules generated from your C<managed_rules> keywords.

=item 2.

It calls C<aws wafv2 create-web-acl> to create a new Web ACL.

=item 3.

It calls C<aws wafv2 associate-web-acl> to link the new Web ACL to
your Application Load Balancer.

=item 4.

It updates your configuration file with the state of the new
WAF resources, including its Name, ID, ARN, LockToken, and a checksum
of the F<web-acl.json> file.

=item 5.

The C<waf> block in your F<fargate-stack.yml> is updated to reflect
the bootstrapped state. If the C<managed_rules> key was not present,
it will be added with the default value of C<[default]>.

=back

=head3 Ongoing Management (Subsequent Runs)

After the initial creation, you take full control of the rules. To
add, remove, or modify rules, you simply edit the F<web-acl.json> file
directly.

On subsequent runs of C<apply>, C<App::FargateStack> will:

=over 4

=item *

Calculate a checksum of your F<web-acl.json> file.

=item *

If the checksum has changed, it will safely update the remote Web ACL
with your new rule set.

=item *

If the checksum has not changed, it will skip the update to avoid
unnecessary API calls.

=back

This model gives you the best of both worlds: the "minimal
configuration, maximum results" of a secure default, and the full
"transparent box" control to customize your security posture as your
application's needs evolve.

=head3 Conflict and Drift Management

The framework includes robust safety checks to prevent accidental data
loss. If it detects that the Web ACL has been modified in the AWS
Console I<and> you have also modified your local F<web-acl.json> file,
it will detect the state conflict, refuse to make any changes, and
provide a clear error message with instructions on how to resolve it.

=head3 Estimated Cost

The default WAF configuration is designed to provide a strong security
baseline while remaining cost-effective. When you enable WAF without
specifying any C<managed_rules>, the framework applies the C<default>
bundle, which includes the C<base> and C<sql> rule sets.

The approximate monthly cost for this default configuration is
B<~$9.00 per month>, plus per-request charges.

The cost is broken down as follows:

=over 4

=item * B<$5.00 / month> for the Web ACL itself.

lib/App/FargateStack/Pod.pm  view on Meta::CPAN


=head3 The Solution: Finding the C<stoppedReason>

To solve this, C<App-FargateStack> provides an optional argument to
the C<list-tasks> command. By default, this command only shows
C<RUNNING> tasks. However, if you add the C<stopped> argument, it will
show recently stopped tasks and, most importantly, the reason they
stopped.

B<The Command:>

 app-FargateStack list-tasks stopped

This will display a table of stopped tasks, including a C<Stopped
Reason> column. This column often contains the detailed, multi-line
error message from the underlying AWS service that caused the failure,
giving you the exact information you need to debug the problem.

For example, if an EFS mount failed, the C<stoppedReason> might
contain:

 ResourceInitializationError: failed to invoke EFS utils
 commands... mount.nfs4: mounting failed, reason given by server: No
 such file or directory

This tells you immediately that the problem is with the EFS path, not
a generic "task failed" message.

=head2 Why is my task or service still using an old image?

This is one of the most common points of confusion when working with
ECS and Fargate.

You may have just built and pushed a new image to ECR using the same
tag (e.g. C<latest>), but when you launch a task or deploy a service,
ECS appears to continue using the old image.  Here's why.

=head3 One-off tasks: C<run-task> uses a fixed image digest

When you run a task using:

  app-FargateStack run-task my-task

ECS uses the exact task definition revision as registered. If the
image was specified using a tag like C<:latest>, ECS resolves that tag
once -- at the time the task starts -- and stores the resolved digest
(e.g. C<sha256:...>).

This means:

=over 4

=item *

Tasks launched this way will continue to run the old image, even if
the C<latest> tag in ECR now points to a newer image.

=item *

The only way to run a task with the new image is to register a new
task definition that references the updated image. You can force a new
task definition by registering the definition.

 app-FargateStack register my-task

=back

=head3 Services: C<create-service> and C<update-service> use frozen images too

When you create or update a service, ECS also resolves any image tags
to their current digest and stores that in the registered task
definition.

This means that ECS services are also tied to the image that existed
at the time of task definition registration.

If you push a new image to ECR using the same tag (e.g. C<:latest>),
the service will not automatically use it.  ECS does not re-resolve
the tag unless you explicitly tell it to.

=head3 C<--force-new-deployment> re-pulls image tags (if not pinned by digest)

If your task definition references the image by tag
(e.g. C<http-service:latest>), and not by digest, then running:

  app-FargateStack redeploy my-service

will cause ECS to:

=over 4

=item *

Stop the currently running tasks

=item *

Start new tasks using the same task definition revision

=item *

Re-resolve and pull the image tag from ECR

=back

This allows your service to pick up a newly pushed image without
registering a new task definition, as long as the task definition used
a tag (not a digest).

=head3 Confirm what your task definition is using

To see whether your task definition uses a tag or a digest, run:

  aws ecs describe-task-definition --task-definition my-task:42

Look at the C<image> field under C<containerDefinitions>. It will either be:

  image: http-service:latest     # tag -- will be re-resolved by --force-new-deployment
  image: http-service@sha256:... # digest -- frozen, cannot be re-resolved

=head3 Best practices



( run in 0.631 second using v1.01-cache-2.11-cpan-e1769b4cff6 )