我有一个 Api Gateway Rest Api,它受 Cognito Authorizer 保护,并通过 VPCLink 集成来访问运行 NodeJs Express 应用程序的容器。
完整流程如下
Api 网关 -> VPCLink -> NLB(网络负载均衡器) -> ALB(应用程序负载均衡器) -> 容器
这是我的 CDK 设置
Fargate 设置
import * as path from 'path';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecr from "aws-cdk-lib/aws-ecr";
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns';
import { DockerImageAsset } from 'aws-cdk-lib/aws-ecr-assets';
import { DockerImageName, ECRDeployment } from 'cdk-ecr-deployment';
import { StackProps } from 'aws-cdk-lib';
import { NetworkLoadBalancer, NetworkTargetGroup, TargetType } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
export const setupFargate = ($this: any, cognitoBetaUserpool: any, cognitoGammaUserpool: any, cognitoProdUserpool: any, props?: StackProps) => {
const betaFargate = setupService($this, 'beta', cognitoBetaUserpool, props);
const gammaFargate = setupService($this, 'gamma', cognitoGammaUserpool, props);
const prodFargate = setupService($this, 'prod', cognitoProdUserpool, props);
return {betaFargate, gammaFargate, prodFargate};
};
const setupService = ($this: any, stage: string, userPool: any, props?: StackProps) => {
const vpc = new ec2.Vpc($this, `BackendVpc-${stage}`, {
ip_addresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
enableDnsSupport: true,
subnetConfiguration: [
{
cidrMask: 24,
name: 'private',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
{
cidrMask: 24,
name: 'public',
subnetType: ec2.SubnetType.PUBLIC,
}
],
} as ec2.VpcProps);
configVPC(vpc);
const cluster = new ecs.Cluster($this, `BackendCluster-${stage}`, { vpc: vpc, enableFargateCapacityProviders: true });
const imageRepo = new ecr.Repository($this, `BackendRepo-${stage}`, {
repositoryName: `backend-repo-${stage}`
});
const dockerImage = new DockerImageAsset($this,`DockerImage-${stage}`,{
directory: path.join(__dirname, '..', '..', 'backend'),
});
const deployment = new ECRDeployment($this, `DeployDockerImage-${stage}`, {
src: new DockerImageName(dockerImage.imageUri),
dest: new DockerImageName(`${imageRepo.repositoryUri}:latest`),
});
dockerImage.node.addDependency(imageRepo);
deployment.node.addDependency(imageRepo);
const service = new ApplicationLoadBalancedFargateService($this, `BackendService-${stage}`, {
serviceName: `BackendService-${stage}`,
cluster: cluster,
cpu: 256,
memoryLimitMiB: 2048,
taskImageOptions: {
image: ecs.ContainerImage.fromEcrRepository(imageRepo, 'latest'),
containerName: `backend-repo-${stage}`,
containerPort: 80,
environment: {
PORT: "80",
STAGE: stage,
AWS_DEFAULT_REGION: props?.env?.region || "",
USERPOOLID: userPool.userPoolId,
}
},
desiredCount: 1,
publicLoadBalancer: false,
});
const nlb = new NetworkLoadBalancer($this, `BackendNLB-${stage}`, {
vpc: vpc,
crossZoneEnabled: true,
internetFacing: false,
vpcSubnets: {
subnets: vpc.privateSubnets
}
});
const nlbListener = nlb.addListener(`BackendNLBListener-${stage}`, {
port: 80,
});
const nlbTargetGroup = new NetworkTargetGroup($this, `BackendNLBTargetGroup-${stage}`, {
port: 80,
vpc: vpc,
targetType: TargetType.ALB,
// add target manually in aws console since cant find way to do it with cdk
});
nlbListener.addTargetGroups(`BackendNLBAddingTargetGroup-${stage}`, nlbTargetGroup);
return {service, imageRepo, nlb};
};
const configVPC = (vpc: any) => {
// Configure VPC for required services
// ECR images are stored in s3, and thus s3 is needed
vpc.addGatewayEndpoint('S3Endpoint', {
service: ec2.GatewayVpcEndpointAwsService.S3,
});
vpc.addGatewayEndpoint('DynamoDbEndpoint', {
service: ec2.GatewayVpcEndpointAwsService.DYNAMODB,
});
vpc.addInterfaceEndpoint('EcrEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.ECR,
privateDnsEnabled: true,
open: true,
});
vpc.addInterfaceEndpoint('EcrDockerEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER,
privateDnsEnabled: true,
open: true,
});
vpc.addInterfaceEndpoint('LogsEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS,
privateDnsEnabled: true,
open: true,
});
vpc.addInterfaceEndpoint('ApiGatewayEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.APIGATEWAY,
privateDnsEnabled: true,
open: true,
});
vpc.addInterfaceEndpoint('EcsEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.ECS,
privateDnsEnabled: true,
open: true,
});
};
API网关设置
import { RestApi, CorsOptions, CognitoUserPoolsAuthorizer, HttpIntegration, AuthorizationType, VpcLink, PassthroughBehavior } from 'aws-cdk-lib/aws-apigateway';
export const setupApiGateway = ($this: any, betaUserPool: any, gammaUserPool: any, prodUserPool: any, betaFargate: any, gammaFargate: any, prodFargate: any) => {
// Create the API Gateway for each stage
const betaGateway = setupIndividualApiGateway($this, 'beta', betaUserPool, betaFargate);
const gammaGateway = setupIndividualApiGateway($this, 'gamma', gammaUserPool, gammaFargate);
const prodGateway = setupIndividualApiGateway($this, 'prod', prodUserPool, prodFargate);
};
const setupIndividualApiGateway = ($this: any, stage: any, userPool: any, fargate: any) => {
const corsOptions: CorsOptions = {
allowOrigins: ['*'], // Update with the appropriate origins
allowMethods: ['*'], // Add other allowed methods as needed
allowHeaders: ['*'], // Add other allowed headers as needed
exposeHeaders: ['*'],
allowCredentials: true, // Enable credentials (cookies) in CORS requests
};
const gateway = new RestApi($this, `BackendGateway${stage}`, {
restApiName: `Backend${stage}`,
defaultCorsPreflightOptions: corsOptions,
});
const authorizer = new CognitoUserPoolsAuthorizer($this, `${stage}CognitoAuthorizer`, {
cognitoUserPools: [userPool]
});
const vpcLink = new VpcLink($this, `BackendVpcLink-${stage}`, {
vpcLinkName: `BackencVpcLink-${stage}`,
targets: [fargate.nlb]
});
const integration = new HttpIntegration(`http://${fargate.nlb.loadBalancerDnsName}`, {
proxy: true,
options: {
vpcLink: vpcLink,
passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,
}
});
gateway.root.addMethod('GET', integration, {
authorizer: authorizer,
authorizationType: AuthorizationType.COGNITO,
});
gateway.root.addMethod('POST', integration, {
authorizer: authorizer,
authorizationType: AuthorizationType.COGNITO,
});
return gateway;
};
她是我的 Node js Express 服务器
const express = require("express");
const ip = require('ip');
const ipAddress = ip.address();
const bodyParser = require('body-parser');
const cors = require('cors');
const port = process.env.PORT || 80;
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Enable CORS for all methods
app.use(cors());
app.options('*', cors());
// Enable CORS for all methods
app.use((req, res, next) => {
console.log(req.originalUrl);
res.header("Access-Control-Allow-Headers", "*");
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "*");
res.header("Access-Control-Expose-Headers", "*");
res.header("Access-Control-Allow-Credentials", "true");
next();
});
app.get("/api", (req,res ) => {
console.log(req.originalUrl);
res.status(200).send({data: `OK this is the /api path`});
});
app.get("/prod/api", (req,res ) => {
console.log(req.originalUrl);
res.status(200).send({data: `OK this is the /prod/api path`});
});
app.get("/health", (req,res ) => {
console.log(req.originalUrl);
res.status(200).send({data: `OK this is the /health`});
});
app.get("/", (req,res ) => {
console.log(req.originalUrl);
res.status(200).send({data: `OK this is the / path`});
});
app.use((req, res, next) => {
try {
const result = `Please try GET on /posts, /post?id=xyz, or a POST to /post with JSON {\"id\":\"123\",\"title\":\"Fargate test\"}`;
res.contentType("application/json").send(result);
} catch (err) {
next(err);
}
});
// Error middleware must be defined last
app.use((err, req, res, next) => {
console.error(err.message);
if (!err.statusCode) err.statusCode = 500; // If err has no specified error code, set error code to 'Internal Server Error (500)'
res
.status(err.statusCode)
.json({ message: err.message })
.end();
});
app.listen(port, () => {
console.log('app listening at ip ' + ipAddress + ' and port ' + port);
});
问题是,无论我尝试从API网关访问哪条路径
/
/API
/健康
它们都被路由到基本路径/
我认为这与 NLB 到 ALB 有关,但我还没有找到任何其他方法可以将 Api 网关与私有 ALB 集成。
想通了!这与我的 Api 网关的集成有关。我必须创建一个代理(又名 {proxy+} 路由),然后在负载均衡器端点中,您必须将参数作为 url {proxy} 的一部分包含进来,然后您必须进行一些请求参数映射,以便 {proxy} 实际上被替换为请求路径中有什么。希望这有帮助。
这是我为使其工作而更改的代码。
const integration = new HttpIntegration(`http://${fargate.nlb.loadBalancerDnsName}/{proxy}`, {
proxy: true,
options: {
vpcLink: vpcLink,
passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,
requestParameters: {
'integration.request.path.proxy': 'method.request.path.proxy'
}
}
});
gateway.root.addProxy({
defaultIntegration: integration,
defaultMethodOptions: {
operationName: 'ANY',
authorizationType: AuthorizationType.COGNITO,
authorizer: authorizer,
requestParameters: {'method.request.path.proxy': true}
},
defaultCorsPreflightOptions: corsOptions
});