diff --git a/aws-ts-hello-fargate/.gitignore b/aws-ts-hello-fargate/.gitignore new file mode 100644 index 000000000..c6958891d --- /dev/null +++ b/aws-ts-hello-fargate/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/node_modules/ diff --git a/aws-ts-hello-fargate/Pulumi.yaml b/aws-ts-hello-fargate/Pulumi.yaml new file mode 100644 index 000000000..587da8b33 --- /dev/null +++ b/aws-ts-hello-fargate/Pulumi.yaml @@ -0,0 +1,3 @@ +name: aws-ts-hello-fargate +runtime: nodejs +description: A minimal "Hello, World" container in Fargate. diff --git a/aws-ts-hello-fargate/README.md b/aws-ts-hello-fargate/README.md new file mode 100644 index 000000000..7f9421a68 --- /dev/null +++ b/aws-ts-hello-fargate/README.md @@ -0,0 +1,120 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Get Started with Docker on AWS Fargate + +This example, inspired by the [Docker Getting Started Tutorial](https://docs.docker.com/get-started/), builds, deploys, +and runs a simple containerized application to a private container registry, and scales out five load balanced replicas, +all in just a handful of lines of Node.js code, and leveraging modern and best-in-class AWS features. + +To do this, we use Pulumi infrastructure as code to provision an +[Elastic Container Service (ECS)](https://aws.amazon.com/ecs/) cluster, build our `Dockerfile` and deploy the +resulting image to a private [Elastic Container Registry (ECR)](https://aws.amazon.com/ecr/) repository, and then create +a scaled-out [Fargate](https://aws.amazon.com/fargate/) service behind an +[Elastic Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/) that allows traffic from the Internet +on port 80. Because this example using AWS services directly, you can mix in other resources, like S3 buckets, RDS +databases, and so on. + +# Prerequisites + +- [Node.js](https://nodejs.org/en/download/) +- [Download and install the Pulumi CLI](https://pulumi.io/install) +- [Connect Pulumi with your AWS account](https://pulumi.io/quickstart/aws/setup.html) (if your AWS CLI is + configured, this will just work) + +# Running the Example + +After cloning this repo, `cd` into it and run these commands: + +1. Create a new stack, which is an isolated deployment target for this example: + + ```bash + $ pulumi stack init dev + ``` + +2. Set your desired AWS region: + + ```bash + $ pulumi config set aws:region us-east-1 # any valid AWS region will work + ``` + +3. Deploy everything with a single `pulumi up` command. This will show you a preview of changes first, which + includes all of the required AWS resources (clusters, services, and the like). Don't worry if it's more than + you expected -- this is one of the benefits of Pulumi, it configures everything so that so you don't need to! + + ```bash + $ pulumi up + ``` + + After being prompted and selecting "yes", your deployment will begin. It'll complete in a few minutes: + + ``` + Updating (dev): + + Type Name Status + + pulumi:pulumi:Stack aws-ts-hello-fargate-dev created + + ├─ awsx:x:ecs:Cluster cluster created + + │ ├─ awsx:x:ec2:SecurityGroup cluster created + + │ │ ├─ awsx:x:ec2:EgressSecurityGroupRule cluster-egress created + + │ │ │ └─ aws:ec2:SecurityGroupRule cluster-egress created + + │ │ ├─ awsx:x:ec2:IngressSecurityGroupRule cluster-ssh created + + │ │ │ └─ aws:ec2:SecurityGroupRule cluster-ssh created + + │ │ ├─ awsx:x:ec2:IngressSecurityGroupRule cluster-containers created + + │ │ │ └─ aws:ec2:SecurityGroupRule cluster-containers created + + │ │ └─ aws:ec2:SecurityGroup cluster created + + │ └─ aws:ecs:Cluster cluster created + + ├─ awsx:x:elasticloadbalancingv2:ApplicationLoadBalancer net-lb created + + │ ├─ awsx:x:elasticloadbalancingv2:ApplicationTargetGroup web created + + │ │ └─ aws:elasticloadbalancingv2:TargetGroup ca84d134 created + + │ ├─ awsx:x:elasticloadbalancingv2:ApplicationListener web created + + │ │ ├─ awsx:x:ec2:IngressSecurityGroupRule web-external-0-ingress created + + │ │ │ └─ aws:ec2:SecurityGroupRule web-external-0-ingress created + + │ │ └─ aws:elasticloadbalancingv2:Listener web created + + │ └─ aws:elasticloadbalancingv2:LoadBalancer 218ffe37 created + + ├─ awsx:x:ec2:Vpc default-vpc created + + │ ├─ awsx:x:ec2:Subnet default-vpc-public-0 created + + │ ├─ awsx:x:ec2:Subnet default-vpc-public-1 created + > │ ├─ aws:ec2:Subnet default-vpc-public-0 read + > │ └─ aws:ec2:Subnet default-vpc-public-1 read + + ├─ awsx:x:ecs:FargateTaskDefinition app-svc created + + │ ├─ aws:ecr:Repository app-img created + + │ ├─ aws:cloudwatch:LogGroup app-svc created + + │ ├─ aws:iam:Role app-svc-task created + + │ ├─ aws:iam:Role app-svc-execution created + + │ ├─ aws:ecr:LifecyclePolicy app-img created + + │ ├─ aws:iam:RolePolicyAttachment app-svc-task-32be53a2 created + + │ ├─ aws:iam:RolePolicyAttachment app-svc-task-fd1a00e5 created + + │ ├─ aws:iam:RolePolicyAttachment app-svc-execution-9a42f520 created + + │ └─ aws:ecs:TaskDefinition app-svc created + + ├─ awsx:x:ecs:FargateService app-svc created + + │ └─ aws:ecs:Service app-svc created + > └─ aws:ec2:Vpc default-vpc read + + Outputs: + url: "218ffe37-e8023b7-1429118690.us-east-1.elb.amazonaws.com" + + Resources: + + 34 created + + Duration: 3m30s + + Permalink: https://app.pulumi.com/acmecorp/aws-ts-hello-fargate/dev/updates/1 + ``` + +4. At this point, your app is running! The URL was published so it's easy to interact with: + + ```bash + $ curl http://$(pulumi stack output url) +

Hello World!

+ Hostname: ip-172-31-39-18.ec2.internal
+ Visits: cannot connect to Redis, counter disabled + ``` + + For more details on how to enable Redis or advanced options, please see the instructions in the + [Docker Getting Started guide](https://docs.docker.com/get-started/part6/). + +6. Once you are done, you can destroy all of the resources, and the stack: + + ```bash + $ pulumi destroy + $ pulumi stack rm + ``` diff --git a/aws-ts-hello-fargate/app/Dockerfile b/aws-ts-hello-fargate/app/Dockerfile new file mode 100644 index 000000000..51191659f --- /dev/null +++ b/aws-ts-hello-fargate/app/Dockerfile @@ -0,0 +1,20 @@ +# Use an official Python runtime as a parent image +FROM python:2.7-slim + +# Set the working directory to /app +WORKDIR /app + +# Copy the current directory contents into the container at /app +COPY . /app + +# Install any needed packages specified in requirements.txt +RUN pip install --trusted-host pypi.python.org -r requirements.txt + +# Make port 80 available to the world outside this container +EXPOSE 80 + +# Define environment variable +ENV NAME World + +# Run app.py when the container launches +CMD ["python", "app.py"] diff --git a/aws-ts-hello-fargate/app/app.py b/aws-ts-hello-fargate/app/app.py new file mode 100644 index 000000000..f90357485 --- /dev/null +++ b/aws-ts-hello-fargate/app/app.py @@ -0,0 +1,24 @@ +from flask import Flask +from redis import Redis, RedisError +import os +import socket + +# Connect to Redis +redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2) + +app = Flask(__name__) + +@app.route("/") +def hello(): + try: + visits = redis.incr("counter") + except RedisError: + visits = "cannot connect to Redis, counter disabled" + + html = "

Hello {name}!

" \ + "Hostname: {hostname}
" \ + "Visits: {visits}" + return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits) + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=80) diff --git a/aws-ts-hello-fargate/app/requirements.txt b/aws-ts-hello-fargate/app/requirements.txt new file mode 100644 index 000000000..82dfa50b3 --- /dev/null +++ b/aws-ts-hello-fargate/app/requirements.txt @@ -0,0 +1,2 @@ +Flask +Redis diff --git a/aws-ts-hello-fargate/index.ts b/aws-ts-hello-fargate/index.ts new file mode 100644 index 000000000..56e97a7d6 --- /dev/null +++ b/aws-ts-hello-fargate/index.ts @@ -0,0 +1,29 @@ +import * as awsx from "@pulumi/awsx"; + +// Step 1: Create an ECS Fargate cluster. +const cluster = new awsx.ecs.Cluster("cluster"); + +// Step 2: Define the Networking for our service. +const alb = new awsx.elasticloadbalancingv2.ApplicationLoadBalancer( + "net-lb", { external: true, securityGroups: cluster.securityGroups }); +const web = alb.createListener("web", { port: 80, external: true }); + +// Step 3: Build and publish a Docker image to a private ECR registry. +const img = awsx.ecs.Image.fromPath("app-img", "./app"); + +// Step 4: Create a Fargate service task that can scale out. +const appService = new awsx.ecs.FargateService("app-svc", { + cluster, + taskDefinitionArgs: { + container: { + image: img, + cpu: 102 /*10% of 1024*/, + memory: 50 /*MB*/, + portMappings: [ web ], + }, + }, + desiredCount: 5, +}); + +// Step 5: Export the Internet address for the service. +export const url = web.endpoint.hostname; diff --git a/aws-ts-hello-fargate/package.json b/aws-ts-hello-fargate/package.json new file mode 100644 index 000000000..22dacd5e4 --- /dev/null +++ b/aws-ts-hello-fargate/package.json @@ -0,0 +1,12 @@ +{ + "name": "aws-typescript", + "devDependencies": { + "@types/node": "latest" + }, + "dependencies": { + "@pulumi/aws": "latest", + "@pulumi/awsx": "^0.18.3", + "@pulumi/docker": "^0.17.0", + "@pulumi/pulumi": "latest" + } +} diff --git a/aws-ts-hello-fargate/tsconfig.json b/aws-ts-hello-fargate/tsconfig.json new file mode 100644 index 000000000..16caa48c2 --- /dev/null +++ b/aws-ts-hello-fargate/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "outDir": "bin", + "target": "es6", + "lib": [ + "es6" + ], + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true, + "strictNullChecks": true + }, + "files": [ + "index.ts" + ] +}