Skip to content

Commit

Permalink
1537 split services 1 (#361)
Browse files Browse the repository at this point in the history
* feat(infra): adding first service group to staging

* feat(infra): have moved the creation of target group, service, and also autocaling setup to gitcoin.js file

* feat(infra): trying to move the gitcoin.ts to a shared lib file ...

* feat(infra): reuse service helpers across prod and staging

* feat(infra): use single package.json

* chore(infra): default public url

* chore(ci): run from infra folder

* chore(ci): correct infra package

* fix(ci): cd into staging for pulumi

* chore(ci): update prod for new pulumi setup

* chore(infra): remove superuser task def

* chore(infra): protect staging and prod dbs

---------

Co-authored-by: schultztimothy <schultz.timothy52@gmail.com>
  • Loading branch information
nutrina and tim-schultz authored Aug 25, 2023
1 parent 139facd commit 1504cc3
Show file tree
Hide file tree
Showing 16 changed files with 504 additions and 6,868 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/api-promote-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ jobs:
uses: actions/setup-node@v2
with:
cache: "yarn"
cache-dependency-path: infra/prod/package-lock.json
cache-dependency-path: infra/package-lock.json

################################################################################################################
# TODO: to be fixed: only doing the aws-actions/configure-aws-credentials and aws-actions/amazon-ecr-login
Expand All @@ -172,9 +172,10 @@ jobs:
# Update the pulumi stack with new image
- run: |
npm install
cd prod
pulumi stack select -c gitcoin/prod/scorer-production
pulumi config -s gitcoin/prod/scorer-production set aws:region us-west-2 --non-interactive
working-directory: infra/prod
working-directory: infra
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/api-promote-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ jobs:
uses: actions/setup-node@v2
with:
cache: "yarn"
cache-dependency-path: infra/staging/package-lock.json
cache-dependency-path: infra/package-lock.json

################################################################################################################
# TODO: to be fixed: only doing the aws-actions/configure-aws-credentials and aws-actions/amazon-ecr-login
Expand All @@ -181,9 +181,10 @@ jobs:
# Update the pulumi stack with new image
- run: |
npm install
cd staging
pulumi stack select -c gitcoin/passport-scorer/staging
pulumi config -s gitcoin/passport-scorer/staging set aws:region us-west-2 --non-interactive
working-directory: infra/staging
working-directory: infra
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
Expand Down
File renamed without changes.
290 changes: 290 additions & 0 deletions infra/lib/scorer/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
import { LogGroup } from "@pulumi/aws/cloudwatch/logGroup";
import { Cluster, Container } from "@pulumi/awsx/ecs";
import { Role } from "@pulumi/aws/iam/role";
import * as awsx from "@pulumi/awsx";
import { Input, interpolate } from "@pulumi/pulumi";
import { TargetGroup, ListenerRule } from "@pulumi/aws/lb";
import * as aws from "@pulumi/aws";

let SCORER_SERVER_SSM_ARN = `${process.env["SCORER_SERVER_SSM_ARN"]}`;

export type ScorerService = {
dockerImageScorer: Input<string>;
dockerImageVerifier: Input<string>;
executionRole: Role;
cluster: Cluster;
logGroup: LogGroup;
subnets: Input<Input<string>[]>;
needsVerifier: boolean;
httpListenerArn: Input<string>;
httpListenerRulePaths?: Input<Input<string>[]>;
listenerRulePriority?: Input<number>;
targetGroup: TargetGroup;
autoScaleMaxCapacity: number;
autoScaleMinCapacity: number;
};

export type ScorerEnvironmentConfig = {
domain: Input<string>;
rdsConnectionUrl: Input<string>;
uiDomains: Input<string>;
allowedHosts: Input<string>;
csrfTrustedOrigins: Input<string>;
redisCacheOpsConnectionUrl: Input<string>;
debug?: Input<string>;
readReplicaConnectionUrl?: Input<string>;
passportPublicUrl?: Input<string>;
};

export const secrets = [
{
name: "SECRET_KEY",
valueFrom: `${SCORER_SERVER_SSM_ARN}:SECRET_KEY::`,
},
{
name: "GOOGLE_OAUTH_CLIENT_ID",
valueFrom: `${SCORER_SERVER_SSM_ARN}:GOOGLE_OAUTH_CLIENT_ID::`,
},
{
name: "GOOGLE_CLIENT_SECRET",
valueFrom: `${SCORER_SERVER_SSM_ARN}:GOOGLE_CLIENT_SECRET::`,
},
{
name: "RATELIMIT_ENABLE",
valueFrom: `${SCORER_SERVER_SSM_ARN}:RATELIMIT_ENABLE::`,
},
{
name: "TRUSTED_IAM_ISSUER",
valueFrom: `${SCORER_SERVER_SSM_ARN}:TRUSTED_IAM_ISSUER::`,
},
{
name: "CERAMIC_CACHE_SCORER_ID",
valueFrom: `${SCORER_SERVER_SSM_ARN}:CERAMIC_CACHE_SCORER_ID::`,
},
{
name: "FF_API_ANALYTICS",
valueFrom: `${SCORER_SERVER_SSM_ARN}:FF_API_ANALYTICS::`,
},
{
name: "FF_DEDUP_WITH_LINK_TABLE",
valueFrom: `${SCORER_SERVER_SSM_ARN}:FF_DEDUP_WITH_LINK_TABLE::`,
},
{
name: "CGRANTS_API_TOKEN",
valueFrom: `${SCORER_SERVER_SSM_ARN}:CGRANTS_API_TOKEN::`,
},
{
name: "S3_DATA_AWS_SECRET_KEY_ID",
valueFrom: `${SCORER_SERVER_SSM_ARN}:S3_DATA_AWS_SECRET_KEY_ID::`,
},
{
name: "S3_DATA_AWS_SECRET_ACCESS_KEY",
valueFrom: `${SCORER_SERVER_SSM_ARN}:S3_DATA_AWS_SECRET_ACCESS_KEY::`,
},
{
name: "S3_WEEKLY_BACKUP_BUCKET_NAME",
valueFrom: `${SCORER_SERVER_SSM_ARN}:S3_WEEKLY_BACKUP_BUCKET_NAME::`,
},
{
name: "REGISTRY_API_READ_DB",
valueFrom: `${SCORER_SERVER_SSM_ARN}:REGISTRY_API_READ_DB::`,
},
];

export function getEnvironment(config: ScorerEnvironmentConfig) {
return [
{
name: "DEBUG",
value: config.debug || "off",
},
{
name: "DATABASE_URL",
value: config.rdsConnectionUrl,
},
{
name: "READ_REPLICA_0_URL",
value: config.readReplicaConnectionUrl || config.rdsConnectionUrl,
},
{
name: "UI_DOMAINS",
value: config.uiDomains,
},
{
name: "ALLOWED_HOSTS",
value: JSON.stringify([config.domain, "*"]),
},
{
name: "CSRF_TRUSTED_ORIGINS",
value: JSON.stringify([`https://${config.domain}`]),
},
{
name: "CELERY_BROKER_URL",
value: config.redisCacheOpsConnectionUrl,
},
{
name: "CERAMIC_CACHE_CACAO_VALIDATION_URL",
value: "http://localhost:8001/verify",
},
{
name: "SECURE_SSL_REDIRECT",
value: "off",
},
{
name: "SECURE_PROXY_SSL_HEADER",
value: JSON.stringify(["HTTP_X_FORWARDED_PROTO", "https"]),
},
{
name: "LOGGING_STRATEGY",
value: "structlog_json",
},
{
name: "PASSPORT_PUBLIC_URL",
value: config.passportPublicUrl || "https://passport.gitcoin.co/",
},
];
}

export function createTargetGroup(
name: string,
vpcId: Input<string>
): TargetGroup {
return new TargetGroup(name, {
port: 80,
protocol: "HTTP",
vpcId: vpcId,
targetType: "ip",
healthCheck: { path: "/health/", unhealthyThreshold: 5 },
});
}

export function createScorerECSService(
name: string,
config: ScorerService,
envConfig: ScorerEnvironmentConfig
): awsx.ecs.FargateService {
//////////////////////////////////////////////////////////////
// Create target group and load balancer rules
//////////////////////////////////////////////////////////////

if (config.httpListenerRulePaths) {
const targetPassportRule = new ListenerRule(`lrule-${name}`, {
tags: { name: name },
listenerArn: config.httpListenerArn,
priority: config.listenerRulePriority,
actions: [
{
type: "forward",
targetGroupArn: config.targetGroup.arn,
},
],
conditions: [
{
pathPattern: {
values: config.httpListenerRulePaths,
},
},
],
});
}

//////////////////////////////////////////////////////////////
// Create the task definition and the service
//////////////////////////////////////////////////////////////

const containers: Record<string, Container> = {
scorer: {
image: config.dockerImageScorer,
memory: 4096,
cpu: 4000,
portMappings: [{ containerPort: 80, hostPort: 80 }],
command: [
"gunicorn",
"-w",
"4",
"-k",
"uvicorn.workers.UvicornWorker",
"scorer.asgi:application",
"-b",
"0.0.0.0:80",
],
links: [],
secrets: secrets,
environment: getEnvironment(envConfig),
linuxParameters: {
initProcessEnabled: true,
},
},
};

if (config.needsVerifier) {
containers.verifier = {
image: config.dockerImageVerifier,
memory: 512,
links: [],
portMappings: [
{
containerPort: 8001,
hostPort: 8001,
},
],
environment: [
{
name: "VERIFIER_PORT",
value: "8001",
},
],
linuxParameters: {
initProcessEnabled: true,
},
};
}

const service = new awsx.ecs.FargateService(name, {
cluster: config.cluster,
desiredCount: 1,
subnets: config.subnets,
loadBalancers: [
{
containerName: "scorer",
containerPort: 80,
targetGroupArn: config.targetGroup.arn,
},
],
taskDefinitionArgs: {
logGroup: config.logGroup,
executionRole: config.executionRole,
containers,
},
});

const ecsScorerServiceAutoscalingTarget = new aws.appautoscaling.Target(
`autoscale-target-${name}`,
{
maxCapacity: 20,
minCapacity: 2,
resourceId: interpolate`service/${config.cluster.cluster.name}/${service.service.name}`,
scalableDimension: "ecs:service:DesiredCount",
serviceNamespace: "ecs",
}
);

const ecsScorerServiceAutoscaling = new aws.appautoscaling.Policy(
`autoscale-policy-${name}`,
{
policyType: "TargetTrackingScaling",
resourceId: ecsScorerServiceAutoscalingTarget.resourceId,
scalableDimension: ecsScorerServiceAutoscalingTarget.scalableDimension,
serviceNamespace: ecsScorerServiceAutoscalingTarget.serviceNamespace,
targetTrackingScalingPolicyConfiguration: {
predefinedMetricSpecification: {
predefinedMetricType: "ECSServiceAverageCPUUtilization",
},
targetValue: 30,
scaleInCooldown: 300,
scaleOutCooldown: 300,
},
}
);

return service;
}
4 changes: 2 additions & 2 deletions infra/prod/package-lock.json → infra/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions infra/prod/package.json → infra/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"name": "prod",
"main": "index.ts",
"name": "infra",
"devDependencies": {
"@types/node": "^14"
},
Expand Down
Loading

0 comments on commit 1504cc3

Please sign in to comment.