examples/jamstack: implemented backend deployment running on ecs

Signed-off-by: Sam Alba <sam.alba@gmail.com>
This commit is contained in:
Sam Alba 2021-04-09 16:00:38 -07:00
parent 9d6551e0bf
commit 90936c450f
4 changed files with 346 additions and 0 deletions

View File

@ -0,0 +1,125 @@
package main
import (
"dagger.io/dagger"
"dagger.io/aws"
"dagger.io/aws/ecs"
)
infra: {
// AWS auth & default region
awsConfig: aws.#Config
// VPC Id
vpcId: string
// ECR Image repository
ecrRepository: string
// ECS cluster name
ecsClusterName: string
// Execution Role ARN used for all tasks running on the cluster
ecsTaskRoleArn?: string
// ELB listener ARN
elbListenerArn: string
}
// Backend configuration
backend: {
// Source code to build this container
source: dagger.#Artifact
// Container environment variables
environment: [string]: string
// Public hostname (need to match the master domain configures on the loadbalancer)
hostname: string
// Container configuration
container: {
// Desired number of running containers
desiredCount: *1 | int
// Time to wait for the HTTP timeout to complete
healthCheckTimeout: *10 | int
// HTTP Path to perform the healthcheck request (HTTP Get)
healthCheckPath: *"/" | string
// Number of times the health check needs to fail before recycling the container
healthCheckUnhealthyThreshold: *2 | int
// Port used by the process inside the container
port: *80 | int
// Memory to allocate
memory: *1024 | int
// Override the default container command
command: [...string]
// Custom dockerfile path
dockerfilePath: *"" | string
// docker build args
dockerBuildArgs: [string]: string
}
// Init container runs only once when the main container starts
initContainer: {
command: [...string]
environment: [string]: string
}
}
// Backend deployment logic
backend: {
let slug = name
// Docker image built from source, pushed to ECR
image: #ECRImage & {
source: source
repository: infra.ecrRepository
tag: slug
awsConfig: infra.awsConfig
if backend.container.dockerfilePath != "" {
dockerfilePath: backend.container.dockerfilePath
}
buildArgs: backend.container.dockerBuildArgs
}
// Creates an ECS Task + Service + deploy via Cloudformation
app: #ECSApp & {
awsConfig: infra.awsConfig
"slug": slug
clusterName: infra.ecsClusterName
vpcId: infra.vpcId
elbListenerArn: infra.elbListenerArn
if infra.ecsTaskRoleArn != _|_ {
taskRoleArn: infra.ecsTaskRoleArn
}
hostname: hostname
healthCheck: {
timeout: backend.container.healthCheckTimeout
path: backend.container.healthCheckPath
unhealthyThresholdCount: backend.container.healthCheckUnhealthyThreshold
}
desiredCount: backend.container.desiredCount
container: {
command: backend.container.command
environment: environment
port: backend.container.port
memory: backend.container.memory
"image": image.ref
}
}
// Optional container to run one-time during the deploy (eg. db migration)
if len(backend.initContainer.command) > 0 {
initContainer: ecs.#RunTask & {
config: infra.awsConfig
containerName: slug
cluster: infra.ecsClusterName
if infra.ecsTaskRoleArn != _|_ {
roleArn: infra.ecsTaskRoleArn
}
containerEnvironment: backend.initContainer.environment
containerCommand: backend.initContainer.command
taskArn: app.taskArn
}
}
}

View File

@ -0,0 +1,55 @@
package main
import (
"dagger.io/dagger"
"dagger.io/dagger/op"
"dagger.io/aws"
"dagger.io/aws/ecr"
)
// Build an image and push it to ECR
#ECRImage: {
source: dagger.#Artifact
// Path of the Dockerfile
dockerfilePath?: string
repository: string
tag: string
awsConfig: aws.#Config
buildArgs: [string]: string
pushTarget: "\(repository):\(tag)"
// Build the image
buildImage: op.#DockerBuild & {
context: source
if dockerfilePath != _|_ {
"dockerfilePath": dockerfilePath
}
buildArg: buildArgs
}
// Use these credentials to push
ecrCreds: ecr.#Credentials & {
config: awsConfig
target: pushTarget
}
push: #up: [
op.#DockerBuild & {
context: source
if dockerfilePath != _|_ {
"dockerfilePath": dockerfilePath
}
buildArg: buildArgs
},
op.Export & {
format: "string"
source: op.#PushContainer & {
ref: pushTarget
}
},
]
// FIXME: ref does not include the sha256: https://github.com/dagger/dagger/issues/303
ref: pushTarget
}

162
examples/jamstack/ecs.cue Normal file
View File

@ -0,0 +1,162 @@
package main
import (
"encoding/json"
"dagger.io/aws"
"dagger.io/aws/elb"
"dagger.io/aws/cloudformation"
)
#ECSApp: {
awsConfig: aws.#Config
slug: string
clusterName: string
vpcId: string
elbListenerArn: string
tlsCertificateArn: string
taskRoleArn: *"" | string
hostname: string
healthCheck: {
timeout: *10 | int
path: *"/" | string
unhealthyThresholdCount: *2 | int
}
desiredCount: int
container: {
command: [...string]
environment: [string]: string
port: *80 | int
cpu: *256 | int
memory: *1024 | int
image: string
}
taskArn: cfnStack.outputs.TaskArn
elbRulePriority: elb.#RandomRulePriority & {
config: awsConfig
listenerArn: elbListenerArn
vhost: hostname
}
cfnStack: cloudformation.#Stack & {
config: awsConfig
stackName: slug
onFailure: "DO_NOTHING"
parameters: {
ELBRulePriority: elbRulePriority.out
ImageRef: container.image
ELBListenerArn: elbListenerArn
TLSCertificateArn: tlsCertificateArn
}
source: json.Marshal(template)
}
template: {
AWSTemplateFormatVersion: "2010-09-09"
Description: "Blocklayer deployed app"
Parameters: {
ELBRulePriority: Type: "Number"
ImageRef: Type: "String"
ELBListenerArn: Type: "String"
TLSCertificateArn: Type: "String"
}
Resources: {
TLSCertificate: {
Type: "AWS::ElasticLoadBalancingV2::ListenerCertificate"
Properties: {
Certificates: [ {CertificateArn: Ref: "TLSCertificateArn"}]
ListenerArn: Ref: "ELBListenerArn"
}
}
ECSTaskDefinition: {
Type: "AWS::ECS::TaskDefinition"
Properties: {
Cpu: "\(container.cpu)"
Memory: "\(container.memory)"
if taskRoleArn != "" {
TaskRoleArn: taskRoleArn
}
NetworkMode: "bridge"
ContainerDefinitions: [{
if len(container.command) > 0 {
Command: container.command
}
Name: slug
Image: Ref: "ImageRef"
Essential: true
Environment: [ for k, v in container.environment {
Name: k
Value: v
}]
PortMappings: [{
ContainerPort: container.port
}]
StopTimeout: 5
LogConfiguration: {
LogDriver: "awslogs"
Options: {
"awslogs-group": "bl/provider/ecs/\(clusterName)"
"awslogs-region": Ref: "AWS::Region"
"awslogs-create-group": "true"
"awslogs-stream-prefix": slug
}
}
}]
}
}
ECSListenerRule: {
Type: "AWS::ElasticLoadBalancingV2::ListenerRule"
Properties: {
ListenerArn: Ref: "ELBListenerArn"
Priority: Ref: "ELBRulePriority"
Conditions: [{
Field: "host-header"
Values: [hostname]}]
Actions: [{
Type: "forward"
TargetGroupArn: Ref: "ECSTargetGroup"
}]}}
ECSTargetGroup: {
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties: {
Protocol: "HTTP"
VpcId: vpcId
Port: 80
HealthCheckPath: healthCheck.path
UnhealthyThresholdCount: healthCheck.unhealthyThresholdCount
HealthCheckTimeoutSeconds: healthCheck.timeout
HealthCheckIntervalSeconds: healthCheck.timeout + 1
HealthyThresholdCount: 3
TargetGroupAttributes: [{
Value: "10"
Key: "deregistration_delay.timeout_seconds"
}]}}
ECSService: {
Type: "AWS::ECS::Service"
Properties: {
Cluster: clusterName
DesiredCount: desiredCount
LaunchType: "EC2"
LoadBalancers: [{
ContainerPort: container.port
TargetGroupArn: Ref: "ECSTargetGroup"
ContainerName: slug
}]
ServiceName: slug
TaskDefinition: Ref: "ECSTaskDefinition"
DeploymentConfiguration: {
DeploymentCircuitBreaker: {
Enable: true
Rollback: true
}
MaximumPercent: 200
MinimumHealthyPercent: 100
}}
DependsOn: "ECSListenerRule"
}
}
Outputs: TaskArn: Value: Ref: "ECSTaskDefinition"
}
}

View File

@ -0,0 +1,4 @@
package main
// Name of the application
name: string & =~"[a-z0-9-]+"