Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aws ts wordpress fargate rds #1293

Merged
merged 6 commits into from
Oct 14, 2022
Merged
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
2 changes: 2 additions & 0 deletions aws-ts-wordpress-fargate-rds/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bin/
/node_modules/
3 changes: 3 additions & 0 deletions aws-ts-wordpress-fargate-rds/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: aws-ts-wordpress-fargate-rds
description: Deploys Wordpress in ECS Fargate with RDS backend.
runtime: nodejs
69 changes: 69 additions & 0 deletions aws-ts-wordpress-fargate-rds/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# WordPress Site in AWS Fargate with RDS DB Backend

This example serves a WordPress site in AWS ECS Fargate using an RDS MySQL Backend.

It leverages the following Pulumi concepts/constructs:

- [Component Resources](https://www.pulumi.com/docs/intro/concepts/programming-model/#components): Allows one to create custom resources that encapsulate one's best practices. In this example, component resource is used to define a "VPC" custom resource, a "Backend" custom resource that sets up the RDS DB, and a "Frontend" resource that sets up the ECS cluster and load balancer and tasks.
- [Other Providers](https://www.pulumi.com/docs/reference/pkg/): Beyond the providers for the various clouds and Kubernetes, etc, Pulumi allows one to create and manage non-cloud resources. In this case, the program uses the Random provider to create a random password if necessary.

This sample uses the following AWS products (and related Pulumi providers):

- [Amazon VPC](https://aws.amazon.com/vpc): Used to set up a new virtual network in which the system is deployed.
- [Amazon RDS](https://aws.amazon.com/rds): A managed DB service used to provide the MySQL backend for WordPress.
- [Amazon ECS Fargate](https://aws.amazon.com/fargate): A container service used to run the WordPress frontend.

## Getting Started

There are no required configuration parameters for this project since the code will use defaults or generate values as needed - see the beginning of `index.ts` to see the defaults.
However, you can override these defaults by using `pulumi config` to set the following values (e.g. `pulumi config set serviceName my-wp-demo`).

- `serviceName` - This is used as a prefix for resources created by the Pulumi program.
- `dbName` - The name of the MySQL DB created in RDS.
- `dbUser` - The user created with access to the MySQL DB.
- `dbPassword` - The password for the DB user. Be sure to use `--secret` if creating this config value (e.g. `pulumi config set dbPassword --secret`).

## Deploying and running the program

Note: some values in this example will be different from run to run.

1. Create a new stack:

```bash
$ pulumi stack init dev
```

1. Set the AWS region:

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

1. Run `pulumi up` to preview and deploy changes. After the preview is shown you will be
prompted if you want to continue or not. Note: If you set the `dbPassword` in the configuration as described above, you will not see the `RandomPassword` resource below.

```bash
$ pulumi up
+ TBD

```

1. The program outputs the following values:

- `DB Endpoint`: This is the RDS DB endpoint. By default, the DB is deployed to disallow public access. This can be overriden in the resource declaration for the backend.
- `DB Password`: This is managed as a secret. To see the value, you can use `pulumi stack output --show-secrets`
- `DB User Name`: The user name for access the DB.
- `ECS Cluster Name`: The name of the ECS cluster created by the stack.
- `Web Service URL`: This is a link to the load balancer fronting the WordPress container. Note: It may take a few minutes for AWS to complete deploying the service and so you may see a 503 error initially.

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

## Troubleshooting

### 503 Error for the Web Service

AWS can take a few minutes to complete deploying the WordPress container and connect the load balancer to the service. So you may see a 503 error for a few minutes right after launching the stack. You can see the status of the service by looking at the cluster in AWS.

## Deployment Speed

Since the stack creates an RDS instance, ECS cluster, load balancer, ECS service, as well as other elements, the stack can take about 4-5 minutes to launch and become ready.
57 changes: 57 additions & 0 deletions aws-ts-wordpress-fargate-rds/backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2016-2019, Pulumi Corporation. All rights reserved.

import { rds } from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";

// Interface for backend args
export interface DbArgs {
dbName: string;
dbUser: string;
dbPassword: pulumi.Output<string>;
subnetIds: pulumi.Output<string>[];
securityGroupIds: pulumi.Output<string>[];
}

// Creates DB
export class Db extends pulumi.ComponentResource {
public readonly dbAddress: pulumi.Output<string>;
public readonly dbName: pulumi.Output<string>;
public readonly dbUser: pulumi.Output<string>;
public readonly dbPassword: pulumi.Output<string | undefined>;

constructor(name: string, args: DbArgs, opts?: pulumi.ComponentResourceOptions) {

super("custom:resource:DB", name, args, opts);

// Create RDS subnet grup
const rdsSubnetGroupName = `${name}-sng`;
const rdsSubnetGroup = new rds.SubnetGroup(rdsSubnetGroupName, {
subnetIds: args.subnetIds,
tags: { "Name": rdsSubnetGroupName},
}, { parent: this });

// RDS DB
const rdsName = `${name}-rds`;
const db = new rds.Instance(rdsName, {
dbName: args.dbName,
username: args.dbUser,
password: args.dbPassword,
vpcSecurityGroupIds: args.securityGroupIds,
dbSubnetGroupName: rdsSubnetGroup.name,
allocatedStorage: 20,
engine: "mysql",
engineVersion: "5.7",
instanceClass: "db.t2.micro",
storageType: "gp2",
skipFinalSnapshot: true,
publiclyAccessible: false,
}, { parent: this });

this.dbAddress = db.address;
this.dbName = db.dbName;
this.dbUser = db.username;
this.dbPassword = db.password;

this.registerOutputs({});
}
}
146 changes: 146 additions & 0 deletions aws-ts-wordpress-fargate-rds/frontend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright 2016-2019, Pulumi Corporation. All rights reserved.

import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";

// Interface for backend args
export interface WebServiceArgs {
dbHost: pulumi.Output<string>;
dbName: pulumi.Output<string>;
dbUser: pulumi.Output<string>;
dbPassword: pulumi.Output<string | undefined>;
dbPort: string;
vpcId: pulumi.Output<string>;
subnetIds: pulumi.Output<string>[];
securityGroupIds: pulumi.Output<string>[];
}

// Creates DB
export class WebService extends pulumi.ComponentResource {
public readonly dnsName: pulumi.Output<string>;
public readonly clusterName: pulumi.Output<string>;

constructor(name: string, args: WebServiceArgs, opts?: pulumi.ComponentResourceOptions) {

super("custom:resource:WebService", name, args, opts);

// Create ECS cluster to run a container-based service
const cluster = new aws.ecs.Cluster(`${name}-ecs`, {}, { parent: this });

// Create load balancer to listen for HTTP traffic
const alb = new aws.lb.LoadBalancer(`${name}-alb`, {
securityGroups: args.securityGroupIds,
subnets: args.subnetIds,
}, { parent: this });

const atg = new aws.lb.TargetGroup(`${name}-app-tg`, {
port: 80,
protocol: "HTTP",
targetType: "ip",
vpcId: args.vpcId,
healthCheck: {
healthyThreshold: 2,
interval: 5,
timeout: 4,
protocol: "HTTP",
matcher: "200-399",
},
}, { parent: this });

const wl = new aws.lb.Listener(`${name}-listener`, {
loadBalancerArn: alb.arn,
port: 80,
defaultActions: [
{
type: "forward",
targetGroupArn: atg.arn,
},
],
}, { parent: this });

const assumeRolePolicy = {
"Version": "2008-10-17",
"Statement": [{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com",
},
"Action": "sts:AssumeRole",
}],
};

const role = new aws.iam.Role(`${name}-task-role`, {
assumeRolePolicy: JSON.stringify(assumeRolePolicy),
}, { parent: this });

const rpa = new aws.iam.RolePolicyAttachment(`${name}-task-policy`, {
role: role.name,
policyArn: "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
}, { parent: this });

// Spin up a load balanced service running our container image.
const taskName = `${name}-app-task`;
const containerName = `${name}-app-container`;
const taskDefinition = pulumi.all([args.dbHost, args.dbPort, args.dbName, args.dbUser, args.dbPassword]).
apply(([dbHost, dbPort, dbName, dbUser, dbPassword]) =>
new aws.ecs.TaskDefinition(taskName, {
family: "fargate-task-definition",
cpu: "256",
memory: "512",
networkMode: "awsvpc",
requiresCompatibilities: ["FARGATE"],
executionRoleArn: role.arn,
containerDefinitions: JSON.stringify([{
"name": containerName,
"image": "wordpress",
"portMappings": [{
"containerPort": 80,
"hostPort": 80,
"protocol": "tcp",
}],
"environment": [
{
"name": "WORDPRESS_DB_HOST",
"value": `${dbHost}:${dbPort}`,
},
{
"name": "WORDPRESS_DB_NAME",
"value": `${dbName}`,
},
{
"name": "WORDPRESS_DB_USER",
"value": `${dbUser}`,
},
{
"name": "WORDPRESS_DB_PASSWORD",
"value": `${dbPassword}`,
},
],
}]),
}, { parent: this }),
);

const service = new aws.ecs.Service(`${name}-app-svc`, {
cluster: cluster.arn,
desiredCount: 1,
launchType: "FARGATE",
taskDefinition: taskDefinition.arn,
networkConfiguration: {
assignPublicIp: true,
subnets: args.subnetIds,
securityGroups: args.securityGroupIds,
},
loadBalancers: [{
targetGroupArn: atg.arn,
containerName: containerName,
containerPort: 80,
}],
}, { dependsOn: [wl], parent: this});

this.dnsName = alb.dnsName;
this.clusterName = cluster.name;

this.registerOutputs({});
}
}
54 changes: 54 additions & 0 deletions aws-ts-wordpress-fargate-rds/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2016-2019, Pulumi Corporation. All rights reserved.
// Deploys:
// - Network: VPC, Subnets, Security Groups
// - DB Backend: MySQL RDS
// - FrontEnd: WordPress in Fargate

import * as pulumi from "@pulumi/pulumi";
import * as random from "@pulumi/random";
import * as backend from "./backend";
import * as frontend from "./frontend";
import * as network from "./network";

// Get config data
const config = new pulumi.Config();
const serviceName = config.get("serviceName") || "wp-fargate-rds";
const dbName = config.get("dbName") || "wordpress";
const dbUser = config.get("dbUser") || "admin";

// Get secretified password from config or create one using the "random" package
let dbPassword = config.getSecret("dbPassword");
if (!dbPassword) {
dbPassword = new random.RandomPassword("dbPassword", {
length: 16,
special: true,
overrideSpecial: "_%",
}).result;
}

const vpc = new network.Vpc(`${serviceName}-net`, {});

const db = new backend.Db(`${serviceName}-db`, {
dbName: dbName,
dbUser: dbUser,
dbPassword: dbPassword,
subnetIds: vpc.subnetIds,
securityGroupIds: vpc.rdsSecurityGroupIds,
});

const fe = new frontend.WebService(`${serviceName}-fe`, {
dbHost: db.dbAddress,
dbPort: "3306",
dbName: db.dbName,
dbUser: db.dbUser,
dbPassword: db.dbPassword,
vpcId: vpc.vpcId,
subnetIds: vpc.subnetIds,
securityGroupIds: vpc.feSecurityGroupIds,
});

export const webServiceUrl = pulumi.interpolate`https://${fe.dnsName}`;
export const ecsClusterName = fe.clusterName;
export const databaseEndpoint = db.dbAddress;
export const databaseUserName = db.dbUser;
export const databasePassword = db.dbPassword;
Loading