Close

Container Workloads

Overview

  • Container workloads are designed to provide users with automatically managed container clusters, with configurable auto-scaling and health-checking available out of the box.

  • Containers are running securely within your virtual private cloud (VPC). You can expose ports of your containers by routing traffic from http api gateways and load balancers using event integrations.

  • On the background, containers are running on ECS cluster using AWS Fargate. AWS Fargate is a technology which empowers users to run containers without having to manage servers or clusters.


When to use

Container workloads are ideal for running containerized applications, running continuously.

If you already have your application containerized you are practically good to go. If not, we can bundle your your code and create containers for you.

Whether your application is made out of a single container or a set of multiple containers, container workloads can meet your needs.

Advantages

  • Pay-per-use - You only pay for containers running at any given moment. If there is no load coming to your containers, you can choose to scale down to 0 (and pay nothing). However, be aware of slower scaling speeds.
  • Auto-scaling - You can easily setup horizontal autoscaling, by defining to scale-out when memory or cpu utlization rises above specified threshold. Conversly, your cluster scales down if the value is below the threshold for sustainable amount of time to avoid under-utilization.
  • Health-checking - You can easily setup internal healthcheck command which periodically checks health of the containers from within. Unhealthy, broken instancies are automatically decomissioned and replaced with new ones.
  • High availability - Container workloads run your containers in multiple Availability Zones.
  • Secure by default - Underlying environment is securely managed by AWS.

Disadvantages

  • Scaling speed - Depending on situation scaling of your service can take from seconds to few minutes. Compared to this, scaling of lambda functions is much faster. On the other hand, when using container workloads, you are effectively avoiding slow cold starts.

Usage

➡️ Definition breakdown

StpContainerWorkload
Property nameType
!containers[ CwContainer, ... ]
array of specifications of containers that make up your containerWorkload.
resourcesCwResourcesConfig
specification of resources (memory, cpu) each instance of your containerWorkload will have.
events[ CwLoadBalancerEventCwHttpApiEvent, ... ]
array of event integrations which will be delivering events(requests) to workload's containers
scalingCwScaling
scaling specifications(number of instances, scaling conditions) for you workload.
accessControlAccessControl
configure/permit access to other resources of your stack such as databases, buckets, eventBuses, etc.

➡️ Containers

For each container of the workload you can specify following properties.

CwContainer
Referenced in: StpContainerWorkload
Property nameType
!namestring
arbitrary name for the container of your workload. must be unique in respect to other container names of the workload
essentialboolean
determines whether the container is essential within the workload. Default value is true (i.e container is essential).
dependsOn[ ContainerDependency, ... ]
list of dependencies on other containers of the container workload.
!imageConfigFilePathBasedImageCwDockerfileBasedImageCwImageBasedImage
specifications for image used by the container
environment[ EnvironmentVar, ... ]
list of environment variables injected into container. environment variables are often used to inject information about other parts of infrastrucutre into the container.
ports[ CwPort, ... ]
list of port objects determining which ports of container are exposed within container workload.
internalHealthCheckContainerHealthCheck
specifications for internal healthcheck for a container. if container is essential and deemed unhealthy, the entire container workload instance is killed and replaced with new instance.

↳ Image config

There are three ways you can specify container image:

  1. Defining container image by specifing filePath. In this scenario Stacktape bundles your code and creates the image for you.
  2. Defining container image by specifing Dockerfile. In this scenario Stacktape creates the image according to specified dockerfile.
  3. Defining container image by specifing image name. In this scenario Stacktape uses specified image from docker hub repository.

Specifying filePath image

FilePathBasedImage
Referenced in: CwContainer
Property nameType
!filePathstring
path to file containing code of your application (relative to stacktape config file)
includeFiles[ string, ... ]
list of filenames, which should be explicityly copied into container
baseImagestring
image which should be used as an underlying base image for the final app image

resources:
containerWorkloads:
singleContainer:
containers:
- name: 'myAppContainer'
imageConfig:
# REQUIRED path to root file of your app.
filePath: '_example-configs/containers/ts-container.ts'
# OPTIONAL you can provide path to files (static assets) that should be included in your bundle
includeFiles: '_example-configs/include/explicitly-included.txt'
environment:
- name: port
value: 80
ports:
- containerPort: 80
resources:
cpu: 0.25
memory: 512
events:
- httpApi:
httpApiGateway: 'myApiGw'
targetContainerPort: 80
path: '/my-path'
method: 'GET'


Specifying dockerfile image

CwDockerfileBasedImage
Referenced in: CwContainer
Property nameType
entryPoint[ string, ... ]
A string array representing the entryPoint of the container. This overrides ENTRYPOINT instruction in Dockerfile
dockerfilePathstring
path to Dockerfile (relative to buildContextPath) used to build your app image
!buildContextPathstring
path to directory (relative to stacktape config file) used as build context
buildArgs[ DockerBuildArg, ... ]
list of docker build arguments
command[ string, ... ]
A string array representing the command that the container runs on start-up. This overrides CMD instruction in Dockerfile

resources:
containerWorkloads:
singleContainer:
containers:
- name: 'myAppContainer'
imageConfig:
# OPTIONAL path to dockerfile
dockerfilePath: 'my-container/Dockerfile'
# provide context path for building image
dockerBuildContextPath: 'my-container'
# OPTIONAL provide an entryPoint for execution of the image
# If dockerfile contains ENTRYPOINT directive this overrides it.
entryPoint: ['node']
# OPTIONAL provide a command for execution of the image
# If dockerfile contains CMD drective, this overrides it.
command: ['index.js']
environment:
- name: port
value: 80
ports:
- containerPort: 80
resources:
cpu: 0.25
memory: 512
events:
- httpApi:
httpApiGateway: 'myApiGw'
targetContainerPort: 80
path: '/my-path'
method: 'GET'


Optionally, it is possible to specify docker build arguments.
DockerBuildArg
Referenced in: CwDockerfileBasedImage
Property nameType
!argNamestring
Name of argument
!valuestring
Value of argument

Specifying image by name

CwImageBasedImage
Referenced in: CwContainer
Property nameType
repositoryCredentialsSecretArnstring
An arn of secret containing credentials to the remote docker repository
entryPoint[ string, ... ]
A string array representing the entryPoint of the container. This overrides ENTRYPOINT instruction of image
!imagestring
Image available from docker hub.
command[ string, ... ]
A string array representing the command that the container runs on start-up. This overrides CMD instruction of image

resources:
containerWorkloads:
singleContainer:
containers:
- name: 'myAppContainer'
imageConfig:
# REQUIRED name of the image stored in docker repository
image: 'mypublicrepo/my-container'
# OPTIONAL provide an entryPoint for execution of the image
entryPoint: ['node']
# OPTIONAL provide a command for execution of the image
command: ['index.js']
environment:
- name: port
value: 80
ports:
- containerPort: 80
resources:
cpu: 0.25
memory: 512
events:
- httpApi:
httpApiGateway: 'myApiGw'
targetContainerPort: 80
path: '/my-path'
method: 'GET'


↳ Environment variables

Environment property is used to set environment variables of container.

Environment variables are often used to injectinformation about other parts of infrastructure into the container.

EnvironmentVar
Referenced in: CwContainer
Property nameType
!namestring
No description
!valuestringnumberboolean
No description

resources:
containerWorkloads:
singleContainer:
containers:
- name: 'myAppContainer'
imageConfig:
filePath: '_example-configs/containers/ts-container.ts'
includeFiles: '_example-configs/include/explicitly-included.txt'
environment:
- name: 'port'
value: 80
# we are injecting bucket name of the bucket defined below
- name: 'BUCKET_NAME'
value: "$GetParam('myStorageBucket', 'Bucket::Name')"
ports:
- containerPort: 80
resources:
cpu: 0.25
memory: 512
events:
- httpApi:
httpApiGateway: 'myApiGw'
targetContainerPort: 80
path: '/my-path'
method: 'GET'
buckets:
myStorageBucket:
accessibility: 'private'


↳ Ports

Specified ports are only opened for communicating with other containers of container workload.
In order to make containers accessible from outer internet, use events property of container workload to route traffic to these ports.

CwPort
Referenced in: CwContainer
Property nameType
!containerPortnumber
port number to expose on the container
protocolstring - ENUM
port protocol. default is tcp

Example:
  • Following snippet demonstrates usage of ports.

  • Example also includes event integration with httpApi (httpApi routes traffic to the exposed ports).

  • Figure below the snippet depicts the architecture of this example.

resources:
containerWorkloads:
multiContainer:
containers:
- name: 'frontend'
imageConfig:
filePath: '_example-configs/frontend/index.ts'
includeFiles: _example-configs/include/explicitly-included.txt
dependsOn:
- containerName: 'backend'
condition: 'STARTED'
environment:
- name: 'port'
value: 80
- name: 'BACKEND_PORT'
value: 3000
ports:
- containerPort: 80
- name: 'backend'
imageConfig:
filePath: '_example-configs/backend/index.ts'
environment:
- name: 'port'
value: 3000
ports:
- containerPort: 3000
resources:
cpu: 0.5
memory: 1024
events:
- httpApi:
httpApiGateway: 'myApiGw'
targetContainerPort: 80
path: '*'
method: '*'
httpApigateways:
myApiGw: {}


Graphical representation of the example
Graphical representation of the example


↳ Container dependencies

Containers in a workload often rely on each other. In many cases one needs to be started or successfully finish its execution before the other container can start.

ContainerDependency
Referenced in: CwContainer
Property nameType
!containerNamestring
name of the container
!conditionstring - ENUM
The dependency condition of the container.

Example:
  • Following snippet demonstrates usage of container dependency

  • Frontend container is dependent on backend container to start first.

resources:
containerWorkloads:
multiContainer:
containers:
- name: 'frontend'
imageConfig:
filePath: '_example-configs/frontend/index.ts'
includeFiles: _example-configs/include/explicitly-included.txt
dependsOn:
- containerName: 'backend'
condition: 'STARTED'
environment:
- name: 'port'
value: 80
- name: 'BACKEND_PORT'
value: 3000
ports:
- containerPort: 80
- name: 'backend'
imageConfig:
filePath: '_example-configs/backend/index.ts'
environment:
- name: 'port'
value: 3000
ports:
- containerPort: 3000
resources:
cpu: 0.5
memory: 1024
events:
- httpApi:
httpApiGateway: 'myApiGw'
targetContainerPort: 80
path: '*'
method: '*'
httpApigateways:
myApiGw: {}


↳ Healthcheck

The purpose of container health check is to monitor health of your container from inside.

Once the essetial container of an instance is determined UNHEALTHY, instance is automatically replaced with new one.

This provides you with reliable failover capability.

ContainerHealthCheck
Referenced in: CwContainer
Property nameType
!healthCheckCommand[ string, ... ]
A string array representing the command that the container runs to determine if it is healthy.
intervalSecondsnumber
The time period in seconds between each health check execution.
timeoutSecondsnumber
The time period in seconds to wait for a health check to succeed before it is considered a failure.
startPeriodSecondsnumber
Grace period within which to provide containers time to bootstrap before failed health checks count towards the maximum number of retries.
retriesnumber
The number of times to retry a failed health check before the container is considered unhealthy.

Example:
  • Following snippet demonstrates usage of healthcheck

  • In this example healthcheck command curls local address, to check for availability of the service.

resources:
containerWorkloads:
singleContainer:
containers:
- name: 'myAppContainer'
imageConfig:
filePath: '_example-configs/containers/ts-container.ts'
includeFiles: '_example-configs/include/explicitly-included.txt'
environment:
- name: port
value: 80
ports:
- containerPort: 80
internalHealthCheck:
# REQUIRED A string array representing the command that the container runs to determine if it is healthy.
# The string array must start with "CMD" to execute the command arguments directly, or "CMD-SHELL" to run the command with the container's default shell
healthCheckCommand: ['CMD-SHELL', 'curl -f http://localhost/ || exit 1']
# OPTIONAL The time period in seconds between each health check execution.
# DEFAULT is 30
intervalSeconds: 20
# OPTIONAL The time period in seconds to wait for a health check to succeed before it is considered a failure.
# DEFAULT is 5
timeoutSeconds: 5
# OPTIONAL The optional grace period within which to provide containers time to bootstrap before failed health checks count towards the maximum number of retries.
# DEFAULT is 0
startPeriodSeconds: 150
# OPTIONAL The number of times to retry a failed health check before the container is considered unhealthy.
# DEFAULT is 3
retries: 2
resources:
cpu: 0.25
memory: 512
events:
- httpApi:
httpApiGateway: 'myApiGw'
targetContainerPort: 80
path: '/my-path'
method: 'GET'


➡️ Resources

Resources section gives you ability to configure compute resources for your workload.
If you are using multi-container workload, the assigned resources are shared between containers.
CwResourcesConfig
Referenced in: StpContainerWorkload
Property nameType
cpunumber - ENUM
Number of vCpus containers of workload have assigned
memorynumber
Memory in MB available to containers of container workload

resources:
containerWorkloads:
singleContainer:
containers:
- name: 'myAppContainer'
imageConfig:
filePath: '_example-configs/containers/ts-container.ts'
includeFiles: '_example-configs/include/explicitly-included.txt'
environment:
- name: port
value: 80
ports:
- containerPort: 80
resources:
cpu: 0.25
memory: 512
events:
- httpApi:
httpApiGateway: 'myApiGw'
targetContainerPort: 80
path: '/my-path'
method: 'GET'


➡️ Scaling

Scaling section gives you ability to configure scaling for your container workload.

In scaling section you can specify:

  • lower and upper bounds for workload scaling (i.e maximum and minimum amount of workload instancies).
  • conditions which trigger scaling using scaling policy.
CwScaling
Referenced in: StpContainerWorkload
Property nameType
!minCountnumber
minimum number of instances for container workload. Workload will not scale below this value.
!maxCountnumber
maximum number of instances for container workload. Workload will not scale above this value.
initialCountnumber
initial number of instances for container workload. Workload will have this amount of instances after deploy.
!scalingPolicyCwScalingPolicy
conditions when scaling is triggered

Example:

  • Following snippet uses scaling property to define scaling action when memory utilization is above 80%.
  • For completion, the example also includes httpApiGateway and event integration.
  • Figure below the snippet depicts the process of scaling.

resources:
containerWorkloads:
singleContainer:
containers:
- name: 'frontend'
imageConfig:
filePath: '_example-configs/frontend/index.ts'
includeFiles: _example-configs/include/explicitly-included.txt
dependsOn:
- containerName: 'backend'
condition: 'STARTED'
environment:
- name: 'port'
value: 80
- name: 'BACKEND_PORT'
value: 3000
ports:
- containerPort: 80
- name: 'backend'
imageConfig:
filePath: '_example-configs/backend/index.ts'
environment:
- name: 'port'
value: 3000
ports:
- containerPort: 3000
resources:
cpu: 0.5
memory: 1024
scaling:
# minimum amount of workload "instances" to be deployed at any time
minCount: 1
# maximum amount of workload "instances" to be deployed at any time
maxCount: 3
scalingPolicy:
keepAvgMemoryUtilizationUnder: 80
events:
- httpApi:
httpApiGateway: 'myApiGw'
targetContainerPort: 80
path: '/my-path'
method: 'GET'
httpApigateways:
myApiGw: {}


Graphical representation of autoscaling process
Graphical representation of autoscaling process


Generally autoscaling of container workloads has following characterstics:

  • Scaling is always horizontal (see example). To scale your workload vertically (i.e adding cpu power or memory), you need to re-deploy your stack. More on that in section Specify resources.

  • To ensure application availability, workload scales out proportionally to the metric as fast as it can, but scales in more gradually.

    Scaling policy is more aggressive in adding capacity when utilization increases than it is in removing capacity when utilization decreases. For example, if the policy's specified metric reaches its target value, the policy assumes that your application is already heavily loaded. So it responds by adding capacity proportional to the metric value as fast as it can. The higher the metric, the more capacity is added.

    When the metric falls below the target value, the policy expects that utilization will eventually increase again. So it slows down scaling by removing capacity only when utilization passes a threshold that is far enough below the target value (usually 20% lower) for utilization to be considered to have slowed. The intention of this more conservative behavior is to ensure that removing capacity only happens when the application is no longer experiencing demand at the same high level that it was previously. This is currently the default behavior for all scaling policies (though the behavior could change in the future).


↳ Scaling policy

Within scaling policy you can define cpu and memory metric thresholds which trigger scaling process.

Depending on this metrics, the workload can either scale-out(add instances) or scale-in(remove instances)

CwScalingPolicy
Referenced in: CwScaling
Property nameType
keepAvgCpuUtilizationUndernumber
percentual cpu threshold for triggering scaling
keepAvgMemoryUtilizationUndernumber
percentual memory threshold for triggering scaling

If you define both keepAvgCpuUtilizationUnder and keepAvgMemoryUtilizationUnder, your workload will scale out if one of the metrics is above target value. However, to scale in, both of the metrics need to be below their respective target values.


➡️ Events

Events property can be used to setup event integrations for delivering events(request) to the containers in your workload.

Currently following integrations are supported:


↳ httpApi integration

Http api integration integrates your workload with specified http api gateway.

CwHttpApiEvent
Referenced in: StpContainerWorkload
Property nameType
!httpApiCwHttpApiIntegration
Object property containing all information about httpApi gateway integration

CwHttpApiIntegration
Referenced in: CwHttpApiEvent
Property nameType
!targetContainerPortnumber
Determines which port (exposed in ports of containers) will receive traffic from the integration.
!httpApiGatewayNamestring
name of the httpApiGateway defined within stack
!methodstring - ENUM
HTTP method for which this integration forwards events (requests)
!pathstring
url path for which this integration forwards events (requests)
authorizerStpAuthorizer
options for configuring authorizer for this integration
payloadFormatstring - ENUM
payload format used with this integration. Defaults to '1.0'

Example:
  • Following snippet demonstrates usage of http api integration
  • All GET requests incoming to myApiGw to url path /my-path will be forwarded to myAppContainer
  • For completion httpApiGateway myApiGw is shown within the configuration.

resources:
containerWorkloads:
singleContainer:
containers:
- name: 'myAppContainer'
imageConfig:
filePath: '_example-configs/containers/ts-container.ts'
includeFiles: '_example-configs/include/explicitly-included.txt'
environment:
- name: port
value: 80
ports:
- containerPort: 80
resources:
cpu: 0.25
memory: 512
events:
- httpApi:
httpApiGateway: 'myApiGw'
targetContainerPort: 80
path: '/my-path'
method: 'GET'
httpApigateways:
myApiGw: {}


↳ loadBalancer integration

Load balancer integration integrates your workload with specified load balancer.

CwLoadBalancerEvent
Referenced in: StpContainerWorkload
Property nameType
!loadBalancerCwLoadBalancerIntegration
Object property containing all information about loadBalancer integration

CwLoadBalancerIntegration
Referenced in: CwLoadBalancerEvent
Property nameType
!targetContainerPortnumber
Determines which port (exposed in ports of containers) of will receive traffic from the integration.
!loadBalancerNamestring
name of the loadBalancer defined within stack
!loadBalancerPortnumber
one of the ports of the specified loadBalancer
!prioritynumber
priority of the integration
paths[ string, ... ]
list of url paths for which this integration is valid
methods[ string, ... ]
list of HTTP methods for which this integration forwards events(requests)
hosts[ string, ... ]
list of hostnames for which this integration forwards events(requests).
headers[ LbHeaderCondition, ... ]
list of complex objects determining header conditions for which this integration forwards events(requests).
queryParams[ LbQueryParamCondition, ... ]
list of complex objects determining query parameter conditions for which this integration forwards events(requests).
sourceIps[ string, ... ]
list of ip addresses for which this integration forwards events(requests).

LbHeaderCondition
Property nameType
!headerNamestring
name of the header to which this condition applies
!values[ string, ... ]
values of the header for which integration forwards events(requests).

LbQueryParamCondition
Property nameType
!paramNamestring
name of the query parameter name to which this condition applies
!values[ string, ... ]
values of the query parameter for which integration forwards events(requests).

Example:
  • Following snippet demonstrates usage of load balancer integration
  • All requests incoming to myFirstLb will be forwarded to myAppContainer
  • For completion loadBalancer myFirstLb is shown within the configuration.

resources:
containerWorkloads:
singleContainer:
containers:
- name: 'myAppContainer'
imageConfig:
filePath: '_example-configs/containers/ts-container.ts'
includeFiles: '_example-configs/include/explicitly-included.txt'
environment:
- name: port
value: 80
ports:
- containerPort: 80
resources:
cpu: 0.25
memory: 512
events:
- loadBalancer:
loadBalancerName: 'myFirstLb'
loadBalancerPort: 443
targetContainerPort: 80
priority: 3
paths:
- '*'
loadBalancers:
myFirstLb:
interface: internet
ports:
443:
protocol: HTTPS

➡️ Access control

AccessControl section can be used to control access to other resources of your infrastructure.

This can give you ability to for example access databases, atlas mongo clusters, buckets, eventBuses or other resources.

There are two approaches when granting workload access to a resource:

  1. allowAccessTo property - list of strings. Elements of this list are names of your stack resources (such as eventBuses, buckets...) which workload should be able to access. allowAccessTo gives you ability to easily and transparently control accesses to resources of your stack.

  2. iamRoleStatements property - list of iam role statement objects. Elements of this array are iam role statements, which will be appended to your containerWorkloads role. This is an advanced feature and should be used with caution. iamRoleStatements is useful when you want to have more granular control of accesses or you need to control access to resources that cannot be scoped by (listed in) allowAccessTo property.


Depending on your case you can choose to use allowAccessTo property, iamRoleStatements property or choose to use both
AccessControl
Referenced in: StpContainerWorkload
Property nameType
iamRoleStatements[ StpIamRoleStatement, ... ]
Elements of this array are iam role statements, which will be appended to your workloads role.
allowAccessTo[ string, ... ]
Elements of this array are name of resources defined in resources section.

Example:
  • Following snippet demonstrates usage of both allowAccessTo property and iamRoleStatements property
  • We are using allowAccessTo to give workload access to bucket myBucket and iamRoleStatements to give workload access to DynamoDB table ResultsTable
  • We are injecting name of the bucket and table
  • For completion, resources which we are accessing are shown as well.

resources:
containerWorkloads:
singleContainer:
containers:
- name: 'myAppContainer'
imageConfig:
filePath: '_example-configs/containers/ts-container.ts'
includeFiles: '_example-configs/include/explicitly-included.txt'
# we are injecting Dynamo table name and bucket name into container environment variables
environment:
- name: 'TABLE_NAME'
value: "$GetParam('ResultsTable', 'Name')"
- name: 'BUCKET_NAME'
value: "$GetParam('myBucket', 'Bucket::Name')"
ports:
- containerPort: 80
resources:
cpu: 0.25
memory: 512
accessControl:
allowAccessTo:
- 'myBucket'
iamRoleStatements:
- Resource:
- "$GetParam('ResultsTable', 'Arn')"
Effect: 'Allow'
Action:
- 'dynamodb:PutItem'
- 'dynamodb:Get*'
events:
- httpApi:
httpApiGateway: 'myApiGw'
targetContainerPort: 80
path: '/my-path'
method: 'GET'
buckets:
myBucket:
accessibility: 'private'
cloudformationResources:
ResultsTable:
Type: 'AWS::DynamoDB::Table'
Properties:
TableName: "$Format('{}-results-table', $GetCliArgs().stage)"
BillingMode: 'PAY_PER_REQUEST'
AttributeDefinitions:
- AttributeName: 'resultDate'
AttributeType: 'S'
KeySchema:
- AttributeName: 'resultDate'
KeyType: 'HASH'

StpIamRoleStatement
Referenced in: AccessControl
Property nameType
Sidstring
statement identifier.
Effectstring
Effect of the statement
Action[ string, ... ]
list of actions allowed/denied by the statement
ConditionUNSPECIFIED
specifies circumstances under which the statements grants permissions
!Resource[ string, ... ]
list of resources we want to access
You can use iamRoleStatements to give containerWorkload access to cloudformation resources defined incloudformationResources section.
🗄️ Resources — Previous
Functions
Next — 🗄️ Resources
Batch Jobs