Skip to content

Commit

Permalink
Add awsinfra example. (pulumi#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi committed Jan 9, 2019
1 parent e3dd441 commit 00221d0
Show file tree
Hide file tree
Showing 19 changed files with 547 additions and 0 deletions.
8 changes: 8 additions & 0 deletions aws-ts-containers/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: container-quickstart
description: NGINX container example
runtime: nodejs
template:
config:
aws:region:
description: The AWS region to deploy into
default: us-west-2
65 changes: 65 additions & 0 deletions aws-ts-containers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)

# Easy container example

Companion to the tutorial [Provision containers on AWS](https://pulumi.io/quickstart/aws-containers.html).

## Prerequisites

To run this example, make sure [Docker](https://docs.docker.com/engine/installation/) is installed and running.

## Running the App

Note: some values in this example will be different from run to run. These values are indicated
with `***`.

1. Create a new stack:

```
$ pulumi stack init containers-dev
```

1. Configure Pulumi to use an AWS region that supports Fargate. This is currently only available in `us-east-1`, `us-east-2`, `us-west-2`, and `eu-west-1`:

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

1. Restore NPM modules via `npm install` or `yarn install`.

1. Preview and deploy the app via `pulumi up`. The preview will take a few minutes, as it builds a Docker container. A total of 19 resources are created.

```
$ pulumi up
```

1. View the endpoint URL, and run curl:

```bash
$ pulumi stack output
Current stack outputs (1)
OUTPUT VALUE
hostname http:https://***.elb.us-west-2.amazonaws.com

$ curl $(pulumi stack output hostname)
<html>
<head><meta charset="UTF-8">
<title>Hello, Pulumi!</title></head>
<body>
<p>Hello, S3!</p>
<p>Made with ❤️ with <a href="https://pulumi.com">Pulumi</a></p>
</body></html>
```

1. To view the runtime logs from the container, use the `pulumi logs` command. To get a log stream, use `pulumi logs --follow`.

```
$ pulumi logs --follow
Collecting logs for stack container-quickstart-dev since 2018-05-22T14:25:46.000-07:00.
2018-05-22T15:33:22.057-07:00[ pulumi-nginx] 172.31.13.248 - - [22/May/2018:22:33:22 +0000] "GET / HTTP/1.1" 200 189 "-" "curl/7.54.0" "-"
```

## Clean up

To clean up resources, run `pulumi destroy` and answer the confirmation question at the prompt.

2 changes: 2 additions & 0 deletions aws-ts-containers/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM nginx
COPY content /usr/share/nginx/html
Binary file added aws-ts-containers/app/content/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions aws-ts-containers/app/content/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html>
<head><meta charset="UTF-8">
<title>Hello, Pulumi!</title></head>
<body>
<p>Hello, containers!</p>
<p>Made with ❤️ with <a href="https://pulumi.com">Pulumi</a></p>
</body></html>
24 changes: 24 additions & 0 deletions aws-ts-containers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as awsx from "@pulumi/aws-infra";

// Create an elastic network listener to listen for requests and route them to the container.
// See https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html
// for more details.
let listener = new awsx.elasticloadbalancingv2.NetworkListener("nginx", { port: 80 });

// Define the service to run. We pass in the listener to hook up the network load balancer
// to the containers the service will launch.
let service = new awsx.ecs.FargateService("nginx", {
desiredCount: 2,
taskDefinitionArgs: {
containers: {
nginx: {
image: awsx.ecs.Image.fromPath("./app"),
memory: 512,
portMappings: [listener],
},
},
},
});

// export just the hostname property of the container frontend
export const hostname = listener.endpoint().apply(e => `http:https://${e.hostname}`);
9 changes: 9 additions & 0 deletions aws-ts-containers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "container-quickstart",
"main": "index.js",
"dependencies": {
"@pulumi/pulumi": "dev",
"@pulumi/aws": "dev",
"@pulumi/aws-infra": "=0.16.3-dev.1546999531"
}
}
11 changes: 11 additions & 0 deletions aws-ts-voting-app/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: voting-app
runtime: nodejs
description: Voting app that uses containers
template:
config:
aws:region:
description: The AWS region to deploy into
default: us-west-2
redisPassword:
description: The Redis password
secret: true
111 changes: 111 additions & 0 deletions aws-ts-voting-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)

# Voting app with two containers

A simple voting app that uses Redis for a data store and a Python Flask app for the frontend. The example has been ported from https://github.com/Azure-Samples/azure-voting-app-redis.

The example shows how easy it is to deploy containers into production and to connect them to one another. Since the example defines a custom container, Pulumi does the following:
- Builds the Docker image
- Provisions AWS Container Registry (ECR) instance
- Pushes the image to the ECR instance
- Creates a new ECS task definition, pointing to the ECR image definition

## Prerequisites

To use this example, make sure [Docker](https://docs.docker.com/engine/installation/) is installed and running.

## Deploying and running the program

### Configure the deployment

Note: some values in this example will be different from run to run. These values are indicated
with `***`.

1. Login via `pulumi login`.

1. Create a new stack:

```
$ pulumi stack init voting-app-testing
```

1. Set AWS as the provider:

```
$ pulumi config set cloud:provider aws
```

1. Configure Pulumi to use an AWS region that supports Fargate, which is currently only available in `us-east-1`, `us-east-2`, `us-west-2`, and `eu-west-1`:

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

1. Set a value for the Redis password. The value can be an encrypted secret, specified with the `--secret` flag. If this flag is not provided, the value will be saved as plaintext in `Pulumi.testing.yaml` (since `testing` is the current stack name).

```
$ pulumi config set --secret redisPassword S3cr37Password
```

### Install dependencies

1. Restore NPM modules via `npm install` or `yarn install`.

### Preview and deploy

1. Ensure the Docker daemon is running on your machine, then preview and deploy the program with `pulumi up`. The program deploys 24 resources and takes about 10 minutes to complete.

1. View the stack output properties via `pulumi stack output`. The stack output property `frontendUrl` is the URL and port of the deployed app:

```bash
$ pulumi stack output frontendURL
***.elb.us-west-2.amazonaws.com
```

1. In a browser, navigate to the URL for `frontendURL`. You should see the voting app webpage.

![Voting app screenshot](./voting-app-webpage.png)

### Delete resources

When you're done, run `pulumi destroy` to delete the program's resources:

```
$ pulumi destroy
This will permanently destroy all resources in the 'testing' stack!
Please confirm that this is what you'd like to do by typing ("testing"): testing
```

## About the code

At the start of the program, the following lines retrieve the value for the Redis password by reading a [configuration value](https://pulumi.io/reference/config.html). This is the same value that was set above with the command `pulumi config set redisPassword <value>`:

```typescript
let config = new pulumi.Config();
let redisPassword = config.require("redisPassword");
```

In the program, the value can be used like any other variable.

### Resources

The program provisions two top-level resources with the following commands:

```typescript
let redisCache = new awsx.ecs.FargateService("voting-app-cache", ... )
let frontend = new awsx.ecs.FargateService("voting-app-frontend", ... )
```

The definition of `redisCache` uses the `image` property of `FargateService.taskDefinitionArgs` to point to an existing Docker image. In this case, this is the image `redis` at tag `alpine` on Docker Hub. The `redisPassword` variable is passed to the startup command for this image.

The definition of `frontend` is more interesting, as it uses `image` property of `FargateService.taskDefinitionArgs` to point to a folder with a Dockerfile, which in this case is a Python Flask app. Pulumi automatically invokes `docker build` for you and pushes the container to ECR.

So that the `frontend` container can connect to `redisCache`, the environment variables `REDIS`, `REDIS_PORT` are defined. Using the `redisListenre.endpoint` property, it's easy to declare the connection between the two containers.

The Flask app uses these environment variables to connect to the Redis cache container. See the following in [`frontend/app/main.py`](frontend/app/main.py):

```python
redis_server = os.environ['REDIS']
redis_port = os.environ['REDIS_PORT']
redis_password = os.environ['REDIS_PWD']
```
3 changes: 3 additions & 0 deletions aws-ts-voting-app/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM tiangolo/uwsgi-nginx-flask:python3.6
RUN pip install redis
COPY /app /app
21 changes: 21 additions & 0 deletions aws-ts-voting-app/frontend/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) Microsoft Corporation. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
5 changes: 5 additions & 0 deletions aws-ts-voting-app/frontend/app/config_file.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# UI Configurations
TITLE = 'Pulumi Voting App'
VOTE1VALUE = 'Tabs'
VOTE2VALUE = 'Spaces'
SHOWHOST = 'false'
71 changes: 71 additions & 0 deletions aws-ts-voting-app/frontend/app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copied from https://github.com/Azure-Samples/azure-voting-app-redis

from flask import Flask, request, render_template
import os
import random
import redis
import socket
import sys

app = Flask(__name__)

# Load configurations
app.config.from_pyfile('config_file.cfg')
button1 = app.config['VOTE1VALUE']
button2 = app.config['VOTE2VALUE']
title = app.config['TITLE']

# Redis configurations
redis_server = os.environ['REDIS']
redis_port = os.environ['REDIS_PORT']
redis_password = os.environ['REDIS_PWD']

# Redis Connection
try:
r = redis.StrictRedis(host=redis_server, port=redis_port, password=redis_password)
r.ping()
except redis.ConnectionError:
exit('Failed to connect to Redis, terminating.')

# Init Redis
if not r.get(button1): r.set(button1,0)
if not r.get(button2): r.set(button2,0)

@app.route('/', methods=['GET', 'POST'])
def index():

if request.method == 'GET':

# Get current values
vote1 = r.get(button1).decode('utf-8')
vote2 = r.get(button2).decode('utf-8')

# Return index with values
return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)

elif request.method == 'POST':

if request.form['vote'] == 'reset':

# Empty table and return results
r.set(button1,0)
r.set(button2,0)
vote1 = r.get(button1).decode('utf-8')
vote2 = r.get(button2).decode('utf-8')
return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)

else:

# Insert vote result into DB
vote = request.form['vote']
r.incr(vote,1)

# Get current values
vote1 = r.get(button1).decode('utf-8')
vote2 = r.get(button2).decode('utf-8')

# Return results
return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)

if __name__ == "__main__":
app.run()
Loading

0 comments on commit 00221d0

Please sign in to comment.