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

DO-1704: add nginx to the mesh #1396

Merged
merged 1 commit into from
Sep 10, 2024
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
3 changes: 3 additions & 0 deletions packages/graphql-mesh-server/assets/nginx/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM nginx:stable-alpine

COPY nginx.conf /etc/nginx/nginx.conf
53 changes: 53 additions & 0 deletions packages/graphql-mesh-server/assets/nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
events {
worker_connections 1024;
}

http {
log_format json_combined escape=json

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON logging threw me, but this seems correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it looks a bit funky in the config but it seems to work. We want to use JSON so we have better CloudWatch query support: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_AnalyzeLogData-discoverable-fields.html

'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"remote_user":"$remote_user",'
'"request":"$request",'
'"status": "$status",'
'"body_bytes_sent":"$body_bytes_sent",'
'"request_time":"$request_time",'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent"'
'}';

gzip on;
gzip_proxied any;
gzip_types text/plain application/json;
gzip_min_length 1000;

server {
listen 80;
access_log /var/log/nginx/access.log json_combined;

location /graphql {
# Reject requests with unsupported HTTP method
if ($request_method !~ ^(GET|POST|HEAD|OPTIONS|PUT|DELETE)$) {
return 405;
}

# Only requests matching the expectations will
# get sent to the application server
proxy_pass http://localhost:4000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}

location /healthcheck {
return 200;
}

location / {
return 403;
}
}
}
121 changes: 88 additions & 33 deletions packages/graphql-mesh-server/lib/fargate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { CfnIPSet, CfnWebACL } from "aws-cdk-lib/aws-wafv2";
import { ScalingInterval, AdjustmentType } from "aws-cdk-lib/aws-autoscaling";
import { ApplicationLoadBalancer } from "aws-cdk-lib/aws-elasticloadbalancingv2";
import { LogGroup } from "aws-cdk-lib/aws-logs";
import path = require("path");

export interface MeshServiceProps {
/**
Expand Down Expand Up @@ -182,6 +183,25 @@ export interface MeshServiceProps {
* @default - AWS generated task definition family name
*/
taskDefinitionFamilyName?: string;

/**
* Nginx image to use
*
* @default ecs.ContainerImage.fromRegistry("nginx:stable-alpine")
*/
nginxImage?: ecs.ContainerImage;

/**
* Disable the nginx sidecar container
*
* @default - false
*/
disableNginx?: boolean;

/**
* Optional manual overrides for nginx sidecar container
*/
nginxConfigOverride?: Partial<ecs.ContainerDefinitionOptions>;
}

export class MeshService extends Construct {
Expand All @@ -203,6 +223,8 @@ export class MeshService extends Construct {
)
: undefined;

if (!certificate) throw Error("Must pass certificate");

this.vpc =
props.vpc ||
new Vpc(this, "vpc", {
Expand Down Expand Up @@ -294,6 +316,65 @@ export class MeshService extends Construct {
logGroup: this.logGroup,
});

const taskDefinition = new ecs.FargateTaskDefinition(this, "TaskDef", {
memoryLimitMiB: props.memory || 1024,
cpu: props.cpu || 512,
});

// Configure nginx
if (!props.disableNginx) {
taskDefinition.addContainer("nginx", {
image:
props.nginxImage ||
ecs.ContainerImage.fromAsset(
path.resolve(__dirname, "../assets/nginx")
),
containerName: "nginx",
essential: true,
healthCheck: {
command: [
"CMD-SHELL",
"curl -f http://localhost || echo 'Health check failed'",
],
startPeriod: Duration.seconds(5),
},
logging: logDriver,
portMappings: [{ containerPort: 80 }],
...props.nginxConfigOverride,
});
}

// Add the main mesh container
taskDefinition.addContainer("mesh", {
image: ecs.ContainerImage.fromEcrRepository(this.repository),
containerName: "mesh",
environment: environment,
secrets: props.secrets ? props.secrets : ssmSecrets,
healthCheck: {
command: [
"CMD-SHELL",
"curl -f http://localhost || echo 'Health check failed'",
],
startPeriod: Duration.seconds(5),
},
logging: logDriver,
portMappings: [{ containerPort: 4000 }], // Main application listens on port 4000
});

// Configure x-ray
taskDefinition.addContainer("xray", {
image: ecs.ContainerImage.fromRegistry("amazon/aws-xray-daemon"),
cpu: 32,
containerName: "xray",
memoryReservationMiB: 256,
essential: false,
healthCheck: {
command: ["CMD-SHELL", "pgrep xray || echo 'Health check failed'"],
startPeriod: Duration.seconds(5),
},
portMappings: [{ containerPort: 4000, protocol: ecs.Protocol.UDP }],
});

// Create a load-balanced Fargate service and make it public
const fargateService =
new ecsPatterns.ApplicationLoadBalancedFargateService(this, `fargate`, {
Expand All @@ -304,22 +385,8 @@ export class MeshService extends Construct {
enableExecuteCommand: true,
cpu: props.cpu || 512, // 0.5 vCPU
memoryLimitMiB: props.memory || 1024, // 1 GB
taskImageOptions: {
image: ecs.ContainerImage.fromEcrRepository(this.repository),
enableLogging: true, // default
containerPort: 4000, // graphql mesh gateway port
secrets: props.secrets ? props.secrets : ssmSecrets, // Prefer v2 secrets using secrets manager
environment: environment,
logDriver: logDriver,
taskRole: new iam.Role(this, "MeshTaskRole", {
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
}),
family:
props.taskDefinitionFamilyName !== undefined
? props.taskDefinitionFamilyName
: undefined,
},
publicLoadBalancer: true, // default,
taskDefinition: taskDefinition,
publicLoadBalancer: true, // defult,
taskSubnets: {
subnets: [...this.vpc.privateSubnets],
},
Expand All @@ -329,23 +396,11 @@ export class MeshService extends Construct {
this.service = fargateService.service;
this.loadBalancer = fargateService.loadBalancer;

// Configure x-ray
const xray = this.service.taskDefinition.addContainer("xray", {
image: ecs.ContainerImage.fromRegistry("amazon/aws-xray-daemon"),
cpu: 32,
memoryReservationMiB: 256,
essential: false,
});
xray.addPortMappings({
containerPort: 2000,
protocol: ecs.Protocol.UDP,
});

this.service.taskDefinition.taskRole.addManagedPolicy({
taskDefinition.taskRole.addManagedPolicy({
managedPolicyArn:
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
});
this.service.taskDefinition.taskRole.addManagedPolicy({
taskDefinition.taskRole.addManagedPolicy({
managedPolicyArn: "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess",
});

Expand Down Expand Up @@ -539,15 +594,15 @@ export class MeshService extends Construct {

this.firewall.addAssociation(
"loadbalancer-association",
fargateService.loadBalancer.loadBalancerArn
this.loadBalancer.loadBalancerArn
);

fargateService.targetGroup.configureHealthCheck({
path: "/healthcheck",
});

// Setup auto scaling policy
const scaling = fargateService.service.autoScaleTaskCount({
const scaling = this.service.autoScaleTaskCount({
minCapacity: props.minCapacity || 1,
maxCapacity: props.maxCapacity || 5,
});
Expand All @@ -558,7 +613,7 @@ export class MeshService extends Construct {
{ lower: 85, change: +3 },
];

const cpuUtilization = fargateService.service.metricCpuUtilization();
const cpuUtilization = this.service.metricCpuUtilization();
scaling.scaleOnMetric("auto-scale-cpu", {
metric: cpuUtilization,
scalingSteps: cpuScalingSteps,
Expand Down
16 changes: 9 additions & 7 deletions packages/graphql-mesh-server/lib/maintenance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,15 @@ export class Maintenance extends Construct {
readOnly: true,
sourceVolume: "maintenanceVolume",
};
props.fargateService.taskDefinition.defaultContainer?.addMountPoints(
mountPoint
);
props.fargateService.taskDefinition.defaultContainer?.addEnvironment(
"MAINTENANCE_FILE_PATH",
`${efsVolumeMountPath}/maintenance.enabled`
);
props.fargateService.taskDefinition
.findContainer("mesh")
?.addMountPoints(mountPoint);
props.fargateService.taskDefinition
.findContainer("mesh")
?.addEnvironment(
"MAINTENANCE_FILE_PATH",
`${efsVolumeMountPath}/maintenance.enabled`
);

const api = new apigateway.RestApi(this, "maintenance-apigw");
const apiKey = api.addApiKey("maintenance-api-key", {
Expand Down
Loading