簡體   English   中英

ConflictException:階段已經存在於 aws_api_gateway_deployment 和 stage_name

[英]ConflictException: Stage already exist from aws_api_gateway_deployment with stage_name

問題

首次使用階段名稱創建 API 網關部署,並創建階段以配置 X-RAY 或 CloudWatch 日志記錄時,將導致“階段已存在”。

resource "aws_api_gateway_deployment" "this" {
  rest_api_id = aws_api_gateway_rest_api.mysfit.id
  stage_name  = "${var.ENV}"
  variables = {
    deployed_at = timestamp()
  }
  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_api_gateway_stage" "this" {
  stage_name    = var.ENV
  rest_api_id   = aws_api_gateway_rest_api.mysfit.id
  deployment_id = aws_api_gateway_deployment.this.id

  dynamic "access_log_settings" {
    for_each = var.enable_apigw_stage_cloudwatch_access_log ? [1] : []
    content {
      destination_arn = module.cloudwatch.cloudwatch_loggroup_arn
      format          = file("${path.module}/apigw_access_log_format.json")
    }
  }

  xray_tracing_enabled = var.xray_tracing_enabled

  tags = {
    Project     = var.PROJECT
    Environment = var.ENV
  }
}

解決方法是省略 aws_api_gateway_deployment 中的 stage_name,因為 stage 對於 API 網關部署是可選的。 但是,部署的invoke_url在URL路徑中沒有stage部分。

這是 Terraform 特定問題還是 API 網關問題?

參考

據我了解,API網關部署設計存在邏輯缺陷。

API 部署是部署到一個階段,如在 Amazon ZDB974238714CA8DE634ACE 網關中部署 REST API 中一樣

要部署 API,請創建 API 部署並將其與階段相關聯。 階段是對 API 的生命周期 state 的邏輯引用(例如,dev、prod、beta、v2)。

但是,創建階段 API 需要現有部署。 我相信,部署和階段之間的這種循環依賴是問題的根源。

問題 1

創建階段 API 需要使用 --deployment-id 參數進行部署。 因此,我們需要首先創建一個 API 部署。

這是第一個問題。 如果我們為創建部署指定一個階段,它會創建該階段。 那我們就不能自己創造舞台。

當我們使用 CloudFormation 或 Terraform 等配置管理工具時,這會導致“Stage already exists”異常,因為我們將嘗試自己創建階段資源。

因此,當我們第一次創建 API 部署時,我們無法指定階段。

問題 2

由於問題 1,我們要管理階段資源的創建,我們需要首先創建一個虛擬部署,以便我們可以使用虛擬創建一個階段。 這一步浪費部署創建是第二個問題。 雖然stage指向deployment,但deployment並不能完全識別stage,因為如果嘗試從deployment中獲取invoke URL,它不包括stage。

創建階段后,最后我們可以創建另一個指定階段的 API 部署。 由於階段已經存在,部署將引用階段並且調用 URL 將包含階段。

例子

Terraform

#--------------------------------------------------------------------------------
# Dummy API Deployment
#--------------------------------------------------------------------------------
resource "aws_api_gateway_deployment" "dummy" {
  rest_api_id = "${aws_api_gateway_rest_api.this.id}"

  #--------------------------------------------------------------------------------
  # To avoid State already exists
  # https://github.com/terraform-providers/terraform-provider-aws/issues/2918
  #--------------------------------------------------------------------------------
  #stage_name  = "${var.ENV}"

  #--------------------------------------------------------------------------------
  # Force re-deployment at each run. Alternative is to verify MD5 of API GW files.
  #--------------------------------------------------------------------------------
  # https://medium.com/coryodaniel/til-forcing-terraform-to-deploy-a-aws-api-gateway-deployment-ed36a9f60c1a
  # https://github.com/hashicorp/terraform/issues/6613
  # Terraform’s aws_api_gateway_deployment won’t deploy subsequent releases in the event
  # that something has changed in an integration, method, etc
  #--------------------------------------------------------------------------------
  stage_description = "Deployment at ${timestamp()}"

  lifecycle {
    create_before_destroy = true
  }

  depends_on = [
    #--------------------------------------------------------------------------------
    # [aws_api_gateway_account.this]
    # To avoid the error: Updating API Gateway Stage failed:
    # BadRequestException: CloudWatch Logs role ARN must be set in account settings to enable logging.
    #--------------------------------------------------------------------------------
    "aws_api_gateway_account.this",

    #--------------------------------------------------------------------------------
    # To avoid NotFoundException: Invalid Integration identifier specified
    #--------------------------------------------------------------------------------
    "aws_api_gateway_integration.ping_put",
  ]
  #--------------------------------------------------------------------------------
}

#--------------------------------------------------------------------------------
# Create a stage refering to the dummy.
# The 2nd/true deployment will later refer to this stage
#--------------------------------------------------------------------------------
resource "aws_api_gateway_stage" "this" {
  stage_name    = var.ENV
  rest_api_id   = aws_api_gateway_rest_api.this.id
  deployment_id = aws_api_gateway_deployment.dummy.id

  xray_tracing_enabled = var.apigw_xray_tracing_enabled

  tags = {
    Project     = var.PROJECT
    Environment = var.ENV
  }

  depends_on = [
    aws_api_gateway_deployment.dummy
  ]
}

#--------------------------------------------------------------------------------
# Legitimate API Deployment
#--------------------------------------------------------------------------------
resource "aws_api_gateway_deployment" "this" {
  rest_api_id = aws_api_gateway_rest_api.this.id
  stage_name  = aws_api_gateway_stage.this.stage_name

  lifecycle {
    create_before_destroy = true
  }
}

雲形成

AWSTemplateFormatVersion: "2010-09-09"
Description: "My API Gateway and Lambda function"

Parameters:
  apiGatewayStageName:
    Type: "String"
    Default: "devStage"

  lambdaFunctionName:
    Type: "String"
    Default: "my-lambda-function"

Resources:
  apiGateway:
    Type: "AWS::ApiGateway::RestApi"
    Properties:
      Name: "test-api"
      Description: "My Test API"

  apiGatewayRootMethod:
    Type: "AWS::ApiGateway::Method"
    Properties:
      AuthorizationType: "NONE"
      HttpMethod: "GET"
      Integration:
        IntegrationHttpMethod: "POST"
        Type: "AWS_PROXY"
        Uri: !Sub
          - "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations"
          - lambdaArn: !GetAtt "lambdaFunction.Arn"
      ResourceId: !GetAtt "apiGateway.RootResourceId"
      RestApiId: !Ref "apiGateway"

  apiGatewayDeployment:
    Type: "AWS::ApiGateway::Deployment"
    DependsOn:
      - "apiGatewayRootMethod"
    Properties:
      RestApiId: !Ref "apiGateway"
      StageName: ""

  apiGatewayStage:
    Type: "AWS::ApiGateway::Stage"
    Properties:
      StageName: !Ref "apiGatewayStageName"
      RestApiId: !Ref "apiGateway"
      DeploymentId: !Ref "apiGatewayDeployment"
      MethodSettings:
        - ResourcePath: /
          HttpMethod: "GET"
          MetricsEnabled: 'true'
          DataTraceEnabled: 'true'

  lambdaFunction:
    Type: "AWS::Lambda::Function"
    Properties:
      Code:
        ZipFile: |
          def handler(event,context):
            return {
              'body': 'Hello World from Lambda',
              'headers': {
                'Content-Type': 'text/plain'
              },
              'statusCode': 200
            }
      Description: "My function"
      FunctionName: !Ref "lambdaFunctionName"
      Handler: "index.handler"
      MemorySize: 256
      Role: !GetAtt "lambdaIAMRole.Arn"
      Runtime: "python3.7"
      Timeout: 30

  lambdaApiGatewayInvoke:
    Type: "AWS::Lambda::Permission"
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !GetAtt "lambdaFunction.Arn"
      Principal: "apigateway.amazonaws.com"
      SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/*/GET/"

  lambdaIAMRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - "sts:AssumeRole"
            Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
      Policies:
        - PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Effect: "Allow"
                Resource:
                  - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${lambdaFunctionName}:*"
          PolicyName: "lambda"

  lambdaLogGroup:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: !Sub "/aws/lambda/${lambdaFunctionName}"
      RetentionInDays: 90

Outputs:
  apiGatewayInvokeURL:
    Value: !Sub "https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName}"

  lambdaArn:
    Value: !GetAtt "lambdaFunction.Arn"

引用文檔

AWS::ApiGateway::Deployment 資源將 API 網關 RestApi 資源部署到stage

這意味着,如果您正在創建一個 AWS::ApiGateway::Stage 資源,其階段名稱與您傳遞給 AWS::ApiGateway::Deployment 的階段名稱相同,那么 Cloudformation 腳本將嘗試創建兩個具有相同名稱的階段 - 因此提示錯誤。

因此,解決方案是not將階段名稱傳遞到部署資源中。 這將起作用:

Parameters:
  ENVIRONMENT:
    Type: String

Resources:
  ContactsStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      StageName: !Ref ENVIRONMENT
      RestApiId: !Ref MyApiGateway
      DeploymentId: !Ref MyDeployment
      MethodSettings:
        - ResourcePath: /
          HttpMethod: POST
          MetricsEnabled: 'true'
          DataTraceEnabled: 'false'

  MyDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn: MyMethod
    Properties:
      RestApiId: !Ref MyApiGateway
#      StageName: !Ref ENVIRONMENT      # uncommenting this will cause the error

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM