AWS

AWS CDK - API Gateway Fix Note 1

kahnco 2023. 11. 7. 17:55

요구사항

  1. 별도 Stack으로 API Gateway를 생성해야 한다.
  2. 1번에서 별도 생성된 API Gateway를 다른 스택에서 참조하여서, Lambda Function의 트리거로 사용해야한다.
  3. Dev, Prod 별로 Stage를 분기해서 배포해야한다.

문제상황

 요구사항 1, 2번 까지는 수월하게 진행되었다.
 

// API Gateway Stack
import {Construct} from 'constructs'
import * as cdk from 'aws-cdk-lib'
import {Deployment, Stage} from "aws-cdk-lib/aws-apigateway";

export class ApiGatewayStack extends cdk.Stack {
    public readonly apiGateway: cdk.aws_apigateway.RestApi
    public readonly stage: Stage

    constructor(scope: Construct, id: string, customProps: CustomStackProps, props?: cdk.StackProps) {
        super(scope, id, props)
        const apiGatewayId = 'api-gateway'

        this.apiGateway = new cdk.aws_apigateway.RestApi(this, apiGatewayId, {
            restApiName: apiGatewayId,
            deploy: false,
        })
        this.apiGateway.root.addMethod('OPTIONS');	// 아무것도 없으면 게이트웨이 생성 불가

        return this
    }
}
// bin/cdk.ts
const apiGateway = new ApiGatewayStack(app, 'ApiGatewayName', {},
{
    env: commonEnv,
})

const lambdaFunctionStack = new LambdaFunctionStack(app, 'LambdaFunctionName', {
    apiRootResourceId: `${apiGateway.apiGateway.restApiRootResourceId}`,
    apiId: `${apiGateway.apiGateway.restApiId}`,
}, {
    env: commonEnv,
})
// Lambda Function Stack
import {Construct} from 'constructs'
import * as cdk from 'aws-cdk-lib'
import {HttpMethod} from 'aws-cdk-lib/aws-events'
import {Deployment, LambdaIntegration, RestApi} from "aws-cdk-lib/aws-apigateway"

export interface CustomStackProps {
  apiRootResourceId: string,
  apiId: string,
}

export class LambdaFunctionStack extends cdk.Stack {
  constructor(scope: Construct, id: string, customProps: CustomStackProps, props?: cdk.StackProps) {
    super(scope, id, props)

    const LambdaFunction = new cdk.aws_lambda.Function(this, id, {
      /*함수 상세 정보*/
    })

    const referredApiGateway = RestApi.fromRestApiAttributes(this, 'referredApiGateway', {
        rootResourceId: customProps.apiRootResourceId,
        restApiId: customProps.apiId,
    })

    const apiGatewayIntegration = new aws_apigateway.LambdaIntegration(LambdaFunction, {
        allowTestInvoke: false,
    })
    const resource = referredApiGateway.root.addResource('endpoint')
    resource.addMethod(HttpMethod.POST, apiGatewayIntegration)

    const deployment = new aws_apigateway.Deployment(this, 'deployment', {
        api: referredApiGateway,
        retainDeployments: false,
    });

	// 실패하는 코드 (중복)
    new aws_apigateway.Stage(this, 'develop-stage', {
        deployment,
        stageName: 'develop',
    });
    
    // 실패하는 코드 (Resource와 동기화 되지 않음)
    const stage = Stage.fromStageAttributes~~~
  }
}

 
 문제는 3번이었다. Resource, Deployment를 생성하는 것은 잘 작동하지만, 이것만 있어서는 실제 API Gateway의 접속 도메인을 통해서 Lambda를 호출할 수가 없었다. (ex. https://{resourceId}.execute-api.{regionName}.amazonaws.com/{stageName}/{resourceName}). Resource에서는 해당 Method가 존재했지만, 해당 Resource가 특정 Stage로 배포되어있지 않은 상태여서 문제가 되는 상황이었다.
 
 위의 코드처럼 new 생성자를 활용해서 Stage를 새로 생성하려고 하면 기존에 존재하는 Stage와 충돌되어서 생성되지 않고, 단순히 Stage.fromStageAttributes 함수로 존재하는 Stage를 참조하는 경우에는 Resource의 스냅샷과 동기화되지 않았다.
 
 이를 해결하기 위해서 검색하던 중, aws-cdk git repository에서 다음과 같은 이슈를 발견하였다. https://github.com/aws/aws-cdk/issues/25582

 

api-gateway: Allow to use an existent stage for deployments · Issue #25582 · aws/aws-cdk

Describe the feature Ability to specify existent environments name when creating a new deployment for an API Gateway. Currently is possible to do that bypassing cdk lib type definition using the su...

github.com

 
 원하던 기능이 아직 추상화되어있지 않아서 이를 추가해달라고 하는 내용과, 현재 임시방편으로 사용하고 있는 방법이 적혀있었다. (2023.11.07 현재에도 업데이트되지 않고 있다)
 
 해당 답변을 적용한 코드는 다음과 같다.

// Lambda Function Stack
import {Construct} from 'constructs'
import * as cdk from 'aws-cdk-lib'
import {HttpMethod} from 'aws-cdk-lib/aws-events'
import {Deployment, LambdaIntegration, RestApi} from "aws-cdk-lib/aws-apigateway"

export interface CustomStackProps {
  apiRootResourceId: string,
  apiId: string,
}

export class LambdaFunctionStack extends cdk.Stack {
  constructor(scope: Construct, id: string, customProps: CustomStackProps, props?: cdk.StackProps) {
    super(scope, id, props)

    const LambdaFunction = new cdk.aws_lambda.Function(this, id, {
      /*함수 상세 정보*/
    })

    const referredApiGateway = RestApi.fromRestApiAttributes(this, 'referredApiGateway', {
        rootResourceId: customProps.apiRootResourceId,
        restApiId: customProps.apiId,
    })

    const referredApiGateway = RestApi.fromRestApiAttributes(this, 'referredApiGateway', {
      rootResourceId: customProps.apiRootResourceId,
      restApiId: customProps.apiId,
    });
    const resource = referredApiGateway.root.addResource('endpoint');
    const integration = new LambdaIntegration(LambdaFunction, {
      allowTestInvoke: false
    });
    resource.addMethod(HttpMethod.POST, integration);

    const deployment = new Deployment(this, 'deployment', {
      api: referredApiGateway,
      description: 'deployment',
      retainDeployments: true,
    });
    (deployment as any).resource.stageName = "develop";	// 여기가 핵심
  }
}

 
 Deployment 객체를 any 타입으로 캐스팅한 이후에, 하위 resource의 stageName을 강제로 지정해주는 방법이다. 이 방법을 사용하면 Stage를 지정한 채로 API Gateway 생성, Lambda 생성 및 API Gateway 트리거 연동이 가능하다.
 
 하지만 위 방법 또한 기존 Stage 이외에 다른 Stage를 생성하는 것이 불가하다. 해당 부분에 대해서 AWS 측에 문의해보니 Stack 레벨에서 분리해야한다고, Stage를 2개 이상 사용하는 것은 불가하다는 답변을 받았으니 참고바란다.

반응형

'AWS' 카테고리의 다른 글

[WriterSide] Publishing and Access Control  (0) 2024.04.24
Make a Scheduler With AWS EventBridge  (0) 2023.11.21
Deploy Selenium to the AWS Machine  (0) 2023.11.18