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

API Gateway | New config change forces replacement of the Gateway #8532

Labels
Milestone

Comments

@murbano83
Copy link

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request.
  • Please do not leave +1 or me too comments, they generate extra noise for issue followers and do not help prioritize the request.
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment.
  • If an issue is assigned to the modular-magician user, it is either in the process of being autogenerated, or is planned to be autogenerated soon. If an issue is assigned to a user, that user is claiming responsibility for the issue. If an issue is assigned to hashibot, a community member has claimed the issue already.

Terraform Version

Affected Resource(s)

  • google_api_gateway_gateway

Terraform Configuration Files

resource "google_api_gateway_api_config" "api_gw_api_config" {
  project       = var.project_id
  for_each      = var.configs
  provider      = google-beta
  api           = google_api_gateway_api.api_gateway.api_id
  api_config_id = each.key

  openapi_documents {
    document {
      path     = each.value.path
      contents = each.value.contents
    }
  }

  lifecycle {
    create_before_destroy = true
  }
}

resource "google_api_gateway_gateway" "api_gw_gw" {
  project    = var.project_id
  provider   = google-beta
  api_config = google_api_gateway_api_config.api_gw_api_config[var.gateway_api_config].id
  gateway_id = var.gateway_id
  region     = var.region

  lifecycle {
    ignore_changes = [gateway_id]
  }
}

Debug Output

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # module.*******.google_api_gateway_gateway.api_gw_gw must be replaced
-/+ resource "google_api_gateway_gateway" "api_gw_gw" {
      ~ api_config       = "projects/*******/locations/global/apis/*******/configs/config" -> "projects/*******-networking/locations/global/apis/*******/configs/configtwo" # forces replacement
      ~ default_hostname = "*******.ew.gateway.dev" -> (known after apply)
      ~ display_name     = "*******" -> (known after apply)
      ~ id               = "projects/*******/locations/europe-west1/gateways/*******" -> (known after apply)
      - labels           = {} -> null
      ~ name             = "projects/*******/locations/europe-west1/gateways/*******" -> (known after apply)
        # (3 unchanged attributes hidden)
    }

Plan: 1 to add, 0 to change, 1 to destroy.

------------------------------------------------------------------------

Panic Output

Expected Behavior

Run an update in place.

gcloud alpha api-gateway gateways update ******** --api-config "projects/********/locations/global/apis/********/configs/configtwo" --location "europe-west1"
Waiting for API Gateway [********] to be updated...done. 

Actual Behavior

Forces replacement of the Gateway to new one.

Steps to Reproduce

  1. terraform plan
  2. terraform apply

Important Factoids

References

  • #0000
@ghost ghost added the bug label Feb 23, 2021
@venkykuberan venkykuberan self-assigned this Feb 23, 2021
@venkykuberan
Copy link
Contributor

api_config is set to ForceNew, thus not accepting the update. We will fix it, thanks for filing the issue.

@ScottSuarez
Copy link
Collaborator

reopening this topic after reversion.

In practice adapting this required too many prerequisites to change the underlying api_config. Namely the apii had to remain consistent and the api_config that you are moving from must remain in the config during the transition leading to a two step apply. If these conditions are not met the user would encounter an error. Given that an error is likely to occur frequently we thought this was not an entirely health change for the terraform provider, nor a great fit.

@kostacasa
Copy link

@ScottSuarez is the suggestion not to use Terraform at all for managing the APIGW then? At least for the time being until a zero-downtime update model can be figured out.

@rileykarson rileykarson added this to the Backlog milestone Mar 29, 2021
@agola11
Copy link

agola11 commented May 12, 2021

@ScottSuarez -- is there any way to do an update to a config in-place right now, or a plan to add in this feature later?

@kostacasa
Copy link

kostacasa commented May 12, 2021

This is possibly outside of the hands of the Terraform team. As mentioned, the API config is immutable and it creates a problem for how Terraform manages state. An API specification can't be updated in-place, a new one must be created, but terraform will opt for a create/destroy cycle, taking the associated gateway with it.

I was planning to explore the option of adding API config resources using terraform and issuing a gcloud command using the terraform gcloud provider module. Haven't had time to fully explore this but it might be a viable solution.

Either way, configuring the api_config resource to use a for_each block so that you can incrementally create new specs without going through a create/destroy cycle is probably a prerequisite for all of the other approaches.

We have something like this. The local spec names are constructed using a fileset command.

resource "google_api_gateway_api_config" "external" {
  for_each = local.spec_names
  provider = google-beta
  api = google_api_gateway_api.external.api_id
  api_config_id = each.value

  openapi_documents {
    document {
      path = "specs/${each.value}.yaml"
      contents = base64encode(templatefile("${path.module}/specs/${each.value}.yaml",
      {
        services = var.services
        environment = var.workspaceName
        security_definitions = local.env.security_definitions
      }))
    }
  }

  gateway_config {
    backend_config {
      google_service_account = var.apigw_svc_account.id
    }
  }

  lifecycle {
    create_before_destroy = true
  }
}

Another approach could be to suffix the api config name with the current timestamp/date and treat it as a new resource, but still trying to come up with a way to maintain that state throughout applies. I'll post ideas as we find approaches.

The one we have working today is by continuously maintaining two gateways and doing a blue/green deployment at the load balancer level.

The fact that this many hoops need to be jumped through to achieve something this straightforward is truly maddening though. We've spent an unreasonable amount of time trying to shape this into a usable service. Google really dropped the ball here in a surprisingly obvious way.

@ScottSuarez
Copy link
Collaborator

ScottSuarez commented May 12, 2021

Since this topic continues to generate significant interest I'll take another stab at considering working with this.

Ultimately you would need to set a lifecycle rule for creation before destroy on the new google_api_gateway_api_config in order for terraform to engage with the intended behavior. I agree even with this complexity the change will significantly improve your workflows. My job is to make your lives easier so I'll see what I can do. No promises, if more complexities arise it might not be feasible.

@agola11
Copy link

agola11 commented May 13, 2021

@kostacasa @ScottSuarez -- thank you so much for your comments and help!

@upodroid
Copy link
Contributor

FYI, you can use api_config_id_prefix field to have terraform generate a unique name when it creates a new api config similar to how instance templates work in terraform.

There are a few other immutable resources that have the prefix field but the underlying Google APIs work properly in the sense that the returned operation for the creation is done once it is ready to be used(eg, certificates, instance templates) and you can safely delete the old one.

API Gateway transition should be seamless like it is for GCE APIs.

@kostacasa
Copy link

kostacasa commented May 13, 2021

Unfortunately, even using prefixes, a gateway destroy/create cycle is performed when the config is updated. This will cause downtime while the gateway is being re-created.

  # module.apigw-bilt-external.google_api_gateway_api_config.bilt_external must be replaced
+/- resource "google_api_gateway_api_config" "bilt_external" {
      ~ api_config_id        = "bilt-external20210513174550666500000001" -> (known after apply)
      ~ display_name         = "bilt-external20210513174550666500000001" -> (known after apply)
      ~ id                   = "projects/xxx/locations/global/apis/bilt-external/configs/bilt-external20210513174550666500000001" -> (known after apply)
      - labels               = {} -> null
      ~ name                 = "projects/xxx/locations/global/apis/bilt-external/configs/bilt-external20210513174550666500000001" -> (known after apply)
      ~ project              = "xxx" -> (known after apply)
      ~ service_config_id    = "bilt-external20210513174550666500000-00p0et6x7m1dx" -> (known after apply)
        # (2 unchanged attributes hidden)
    }

  # module.apigw-bilt-external.google_api_gateway_gateway.bilt_external must be replaced
-/+ resource "google_api_gateway_gateway" "bilt_external" {
      ~ api_config       = "projects/xxx/locations/global/apis/bilt-external/configs/bilt-external20210513174550666500000001" -> (known after apply) # forces replacement
      ~ default_hostname = "bilt-external-xxx.uc.gateway.dev" -> (known after apply)
      ~ display_name     = "bilt-external" -> (known after apply)
      ~ id               = "projects/xxx/locations/us-central1/gateways/bilt-external" -> (known after apply)
      - labels           = {} -> null
      ~ name             = "projects/xxx/locations/us-central1/gateways/bilt-external" -> (known after apply)
      ~ project          = "xxx" -> (known after apply)
      + region           = (known after apply)
        # (1 unchanged attribute hidden)

        # (1 unchanged block hidden)
    }

@kostacasa
Copy link

Changing the gateway lifecycle to create before destroy results in:

│ Error: Error creating Gateway: googleapi: Error 409: Resource 'projects/xxx/locations/us-central1/gateways/bilt-external' already exists
│ Details:
│ [
│   {
│     "@type": "type.googleapis.com/google.rpc.ResourceInfo",
│     "resourceName": "projects/xxx/locations/us-central1/gateways/bilt-external"
│   }
│ ]
│
│   with module.apigw-bilt-external.google_api_gateway_gateway.bilt_external,
│   on modules/apigw-bilt-external/main.tf line 31, in resource "google_api_gateway_gateway" "bilt_external":

@ScottSuarez
Copy link
Collaborator

I've checked in a potential change. Going to monitor our test pipeline. If everything is green and pending no revert you should see this change in 3.69.0

@kostacasa
Copy link

Thank you @ScottSuarez ! What will the new behavior be?

@ScottSuarez
Copy link
Collaborator

ScottSuarez commented May 13, 2021

You will be able to update google_api_gateway_gateway.api_config in place. When doing so you should ensure the api config is a new entry (not an overwrite of the original resource) and the life_cycle rule create_before_destroy is set.
https://www.terraform.io/docs/language/meta-arguments/lifecycle.html

@kostacasa
Copy link

kostacasa commented May 24, 2021

Hey @ScottSuarez just tried this out and it worked perfectly!

module.apigw-external.google_api_gateway_api_config.external: Creating...
module.apigw-external.google_api_gateway_api_config.external: Still creating... [10s elapsed]
...
module.apigw-external.google_api_gateway_api_config.external: Still creating... [1m40s elapsed]
module.apigw-external.google_api_gateway_api_config.external: Creation complete after 1m47s [id=projects/../locations/global/apis/external/configs/external-20210524180716977500000001]

module.apigw-external.google_api_gateway_gateway.external: Modifying... [id=projects/.../locations/us-central1/gateways/external-gateway]
module.apigw-external.google_api_gateway_gateway.external: Still modifying... [id=projects/.../locations/us-central1/gateways/external-gateway, 10s elapsed]
module.apigw-external.google_api_gateway_gateway.external: Modifications complete after 1m3s [id=projects/.../locations/us-central1/gateways/external-gateway]

module.apigw-external.google_api_gateway_api_config.external (3c499c94): Destroying... [id=projects/.../locations/global/apis/external/configs/external-20210523194550821000000001]
module.apigw-external.google_api_gateway_api_config.external: Still destroying... [id=projects/.../locations/external-20210523194550821000000001, 10s elapsed]
module.apigw-external.google_api_gateway_api_config.external: Destruction complete after 11s
Apply complete! Resources: 1 added, 1 changed, 1 destroyed.

@ScottSuarez
Copy link
Collaborator

nice ! happy to see it's working well for you :)

@github-actions
Copy link

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 24, 2021
@github-actions github-actions bot added service/apigateway forward/review In review; remove label to forward labels Jan 14, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.