Skip to content
Open
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
8 changes: 8 additions & 0 deletions aws-py-voting-app/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: webserver-py
runtime: python
description: Basic example of an AWS web server accessible over HTTP (in Python!)
template:
config:
aws:region:
description: The AWS region to deploy into
default: us-west-2
82 changes: 82 additions & 0 deletions aws-py-voting-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)

# Web Server Using Amazon EC2

An example based on the Amazon sample at:
http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/deploying.applications.html. The example deploys an EC2 instance and opens port 80.

## Prerequisites

1. [Install Pulumi](https://www.pulumi.com/docs/get-started/install/)
1. [Configure Pulumi for AWS](https://www.pulumi.com/docs/intro/cloud-providers/aws/setup/)
1. [Configure Pulumi for Python](https://www.pulumi.com/docs/intro/languages/python/)

## Deploying and running the program

1. Install dependencies (a `virtualenv` is recommended - see [Pulumi Python docs](https://www.pulumi.com/docs/intro/languages/python/)):

```bash
$ pip install -r requirements.txt
```

1. Create a new stack:

```bash
$ pulumi stack init python-webserver-testing
```

1. Set the AWS region:

```bash
$ pulumi config set aws:region us-west-2
```

1. Create a Python virtualenv, activate it, and install dependencies:

This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program.

```bash
$ python3 -m venv venv
$ source venv/bin/activate
$ pip3 install -r requirements.txt
```

1. Run `pulumi up` to preview and deploy changes:

```bash
$ pulumi up
Previewing stack 'python-webserver-testing'
Previewing changes:
...

Do you want to proceed? yes
Updating stack 'python-webserver-testing'
Performing changes:

#: Resource Type Name Status Extra Info
1: pulumi:pulumi:Stack webserver-py-python-webserver-testing + created
2: aws:ec2:SecurityGroup web-secgrp + created
3: aws:ec2:Instance web-server-www + created

info: 3 changes performed:
+ 3 resources created
Update duration: 26.470339302s

Permalink: https://pulumi.com/lindydonna/examples/webserver-py/python-webserver-testing/updates/1
```

1. View the host name and IP address of the instance via `stack output`:

```bash
$ pulumi stack output
Current stack outputs (2):
OUTPUT VALUE
public_dns ec2-34-217-176-141.us-west-2.compute.amazonaws.com
public_ip 34.217.176.141
```

1. Verify that the EC2 instance exists, by either using the AWS Console or running `aws ec2 describe-instances`.

## Clean up

To clean up resources, run `pulumi destroy` and answer the confirmation question at the prompt.
290 changes: 290 additions & 0 deletions aws-py-voting-app/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.

import json
import pulumi
import pulumi_aws as aws
import pulumi_docker as docker

# Get the password to use for Redis from the pulumi config
config = pulumi.Config()
redis_password = config.require("redis-password")
redis_port = 6379

# The ECS cluster in which our application and databse will run
app_cluster = aws.ecs.Cluster("app-cluster")

# Creating a VPC and a public subnet
app_vpc = aws.ec2.Vpc("app-vpc",
cidr_block="172.31.0.0/16",
enable_dns_hostnames=True)

app_vpc_subnet = aws.ec2.Subnet("app-vpc-subnet",
cidr_block="172.31.32.0/20",
vpc_id=app_vpc)

# Creating a gateway to the web for the VPC
app_gateway = aws.ec2.InternetGateway("app-gateway",
vpc_id=app_vpc.id)

app_routetable = aws.ec2.RouteTable("app-routetable",
routes=[
{
"cidr_block": "0.0.0.0/0",
"gateway_id": app_gateway.id,
}
],
vpc_id=app_vpc.id)

# Associating our gateway with our VPC, to allow our app to communicate with the greater internet
app_routetable_association = aws.ec2.MainRouteTableAssociation("app_routetable_association",
route_table_id=app_routetable.id,
vpc_id=app_vpc)

# Creating a Security Group that restricts incoming traffic to HTTP
app_security_group = aws.ec2.SecurityGroup("security-group",
vpc_id=app_vpc.id,
description="Enables HTTP access",
ingress=[{
'protocol': 'tcp',
'from_port': 0,
'to_port': 65535,
'cidr_blocks': ['0.0.0.0/0'],
}],
egress=[{
'protocol': '-1',
'from_port': 0,
'to_port': 0,
'cidr_blocks': ['0.0.0.0/0'],
}])

# Creating an IAM role used by Fargate to execute all our services
app_exec_role = aws.iam.Role("app-exec-role",
assume_role_policy="""{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}]
}""")

# Attaching execution permissions to the exec role
exec_policy_attachment = aws.iam.RolePolicyAttachment("app-exec-policy", role=app_exec_role.name,
policy_arn="arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy")

# Creating an IAM role used by Fargate to manage tasks
app_task_role = aws.iam.Role("app-task-role",
assume_role_policy="""{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}]
}""")

# Attaching execution permissions to the task role
exec_policy_attachment = aws.iam.RolePolicyAttachment("app-access-policy", role=app_exec_role.name,
policy_arn="arn:aws:iam::aws:policy/AmazonEC2ContainerServiceFullAccess")

exec_policy_attachment = aws.iam.RolePolicyAttachment("app-lambda-policy", role=app_exec_role.name,
policy_arn="arn:aws:iam::aws:policy/AWSLambdaFullAccess")

# Creating storage space to upload a docker image of our app to
app_ecr_repo = aws.ecr.Repository("app-ecr-repo",
image_tag_mutability="MUTABLE")

# Attaching an application life cycle policy to the storage
app_lifecycle_policy = aws.ecr.LifecyclePolicy("app-lifecycle-policy",
repository=app_ecr_repo.name,
policy="""{
"rules": [
{
"rulePriority": 10,
"description": "Remove untagged images",
"selection": {
"tagStatus": "untagged",
"countType": "imageCountMoreThan",
"countNumber": 1
},
"action": {
"type": "expire"
}
}
]
}""")

# The application's backend and data layer: Redis

# Creating a target group through which the Redis backend receives requests
redis_targetgroup = aws.lb.TargetGroup("redis-targetgroup",
port=redis_port,
protocol="TCP",
target_type="ip",
stickiness= {
"enabled": False,
"type": "lb_cookie",
},
vpc_id=app_vpc.id)

# Creating a load balancer to spread out incoming requests
redis_balancer = aws.lb.LoadBalancer("redis-balancer",
load_balancer_type="network",
internal= False,
security_groups=[],
subnets=[app_vpc_subnet.id])

# Forwards internal traffic with the Redis port number to the Redis target group
redis_listener = aws.lb.Listener("redis-listener",
load_balancer_arn=redis_balancer.arn,
port=redis_port,
protocol="TCP",
default_actions=[{
"type": "forward",
"target_group_arn": redis_targetgroup.arn
}])

# Creating a task definition for the Redis instance.
redis_task_definition = aws.ecs.TaskDefinition("redis-task-definition",
family="redis-task-definition-family",
cpu="256",
memory="512",
network_mode="awsvpc",
requires_compatibilities=["FARGATE"],
execution_role_arn=app_exec_role.arn,
task_role_arn=app_task_role.arn,
container_definitions=json.dumps([{
"name": "redis-container",
"image": "redis:alpine", # A pre-built docker image with a functioning redis server
"memory": 512,
"essential": True,
"portMappings": [{
"containerPort": redis_port,
"hostPort": redis_port,
"protocol": "tcp"
}],
"command": ["redis-server", "--requirepass", redis_password],
}]))

# Launching our Redis service on Fargate, using our configurations and load balancers
redis_service = aws.ecs.Service("redis-service",
cluster=app_cluster.arn,
desired_count=1,
launch_type="FARGATE",
task_definition=redis_task_definition.arn,
wait_for_steady_state=False,
network_configuration={
"assign_public_ip": "true",
"subnets": [app_vpc_subnet.id],
"security_groups": [app_security_group.id]
},
load_balancers=[{
"target_group_arn": redis_targetgroup.arn,
"container_name": "redis-container",
"container_port": redis_port,
}],
opts=pulumi.ResourceOptions(depends_on=[redis_listener]),
)

# Creating a special endpoint for the Redis backend, which we will provide
# to the Flask frontend as an environment variable
redis_endpoint = {"host": str(redis_balancer.dns_name), "port": str(redis_port)}

# The application's frontend: A Flask service

# Creating a target group through which the Flask frontend receives requests
flask_targetgroup = aws.lb.TargetGroup("flask-targetgroup",
port=80,
protocol="TCP",
target_type="ip",
stickiness= {
"enabled": False,
"type": "lb_cookie",
},
vpc_id=app_vpc.id)

# Creating a load balancer to spread out incoming requests
flask_balancer = aws.lb.LoadBalancer("flask-balancer",
load_balancer_type="network",
internal=False,
security_groups=[],
subnets=[app_vpc_subnet.id])

# Forwards all public traffic using port 80 to the Flask target group
flask_listener = aws.lb.Listener("flask-listener",
load_balancer_arn=flask_balancer.arn,
port=80,
protocol="TCP",
default_actions=[{
"type": "forward",
"target_group_arn": flask_targetgroup.arn
}])

# Creating a Docker image from "./frontend/Dockerfile", which we will use
# to upload our app
flask_image = docker.Image("flask-dockerimage",
image_name="flask-dockerimage",
build=docker.DockerBuild(
context="./frontend",
),
skip_push=False,
registry=app_ecr_repo
)
# docker.build_and_push_image("pulumi-user/flask-dockerimage:v1.0.0", "./frontend", app_ecr_repo.repository_url, flask_listener, flask_listener)

# Creating a task definition for the Flask instance.
flask_task_definition = aws.ecs.TaskDefinition("flask-task-definition",
family="frontend-task-definition-family",
cpu="256",
memory="512",
network_mode="awsvpc",
requires_compatibilities=["FARGATE"],
execution_role_arn=app_exec_role.arn,
task_role_arn=app_task_role.arn,
container_definitions=json.dumps([{
"name": "flask-container",
"image": "flask-dockerimage",
"memory": 512,
"essential": True,
"portMappings": [{
"containerPort": 80,
"hostPort": 80,
"protocol": "tcp"
}],
"environment": [ # The Redis endpoint we created is given to Flask, allowing it to communicate with the former
{ "name": "REDIS", "value": redis_endpoint["host"] },
{ "name": "REDIS_PORT", "value": redis_endpoint["port"] },
{ "name": "REDIS_PWD", "value": redis_password },
],
}]))

# Launching our Redis service on Fargate, using our configurations and load balancers
flask_service = aws.ecs.Service("flask-service",
cluster=app_cluster.arn,
desired_count=1,
launch_type="FARGATE",
task_definition=flask_task_definition.arn,
wait_for_steady_state=False,
network_configuration={
"assign_public_ip": "true",
"subnets": [app_vpc_subnet.id],
"security_groups": [app_security_group.id]
},
load_balancers=[{
"target_group_arn": flask_targetgroup.arn,
"container_name": "flask-container",
"container_port": 80,
}],
opts=pulumi.ResourceOptions(depends_on=[flask_listener]),
)

# Exporting the url of our Flask frontend. We can now connect to our app
pulumi.export("app-url", flask_balancer.dns_name)
Loading