Серверные службы Google Cloud external Load Balancer случайным образом выходят из строя с ошибкой сервера 502

У меня есть следующая конфигурация внешнего балансировщика нагрузки Google Cloud:

  • GlobalNetworkEndpointGroupToClusterByIp — это Интернет NEG с типомINTERNET_IP_PORTуказывающий на IP-адрес кластера Kubernetes.
  • GlobalNetworkEndpointGroupToManagedS3 — это Интернет NEG с типомINTERNET_FQDN_PORTуказывая на управляемый Яндексом сервис S3.

По какой-то причине некоторые серверные службы не работают, и когда я пытаюсь подключиться к ним, они отвечают HTML-страницей, на которой отображается ошибка сервера 502:

Ошибка: Ошибка сервера

На сервере произошла временная ошибка, и он не смог выполнить ваш запрос.

Пожалуйста, повторите попытку через 30 секунд.

В журналах неудачных серверных служб всегда присутствуют следующие ошибки:

      jsonPayload: {
  cacheId: "GRU-c0ee45d8"
  @type: "type.googleapis.com/google.cloud.loadbalancing.type.LoadBalancerLogEntry"
  statusDetails: "failed_to_pick_backend"
}

Запросы к серверным службам терпят неудачу в течение 1 мс (как отмечено в журналах), поэтому кажется, что они даже не пытаются подключиться к IP-адресу моего кластера Kubernetes или управляемому S3 и мгновенно завершаются ошибкой.

На момент публикации этого вопроса серверные службы S3 и Imgproxy находятся в хорошем состоянии, но другие не работают:

Если я все переразверну, некоторые другие службы могут выйти из строя, например:

  • API и Документы будут работать, другие — нет.
  • API, Docs, FPS и Imgproxy будут работать, S3 не работает.
  • S3 будет работать, другие не сработают

Так что это абсолютно случайно, и я не могу понять, почему это происходит. Если мне очень повезет, после повторного развертывания все серверные службы будут работать нормально. Также возможно, что ни один из них не будет работать.

Кластер Kubernetes работает, соединения принимает, Managed S3 тоже работает хорошо. Похоже на ошибку, но в гугле ничего по этому поводу не нашел.

Вот как выглядит моя конфигурация Terraform:

      resource "google_compute_global_network_endpoint_group" "kubernetes-cluster" {
  name                  = "kubernetes-cluster-${var.ENVIRONMENT_NAME}"
  network_endpoint_type = "INTERNET_IP_PORT"

  depends_on = [
    module.kubernetes-resources
  ]
}

resource "google_compute_global_network_endpoint" "kubernetes-cluster" {
  global_network_endpoint_group = google_compute_global_network_endpoint_group.kubernetes-cluster.name
  port                          = 80
  ip_address                    = yandex_vpc_address.kubernetes.external_ipv4_address.0.address
}

resource "google_compute_global_network_endpoint_group" "s3" {
  name                  = "s3-${var.ENVIRONMENT_NAME}"
  network_endpoint_type = "INTERNET_FQDN_PORT"
}

resource "google_compute_global_network_endpoint" "s3" {
  global_network_endpoint_group = google_compute_global_network_endpoint_group.s3.name
  port                          = 443
  fqdn                          = trimprefix(local.s3.endpoint, "https://")
}

resource "google_compute_backend_service" "s3" {
  name = "s3-${var.ENVIRONMENT_NAME}"

  backend {
    group = google_compute_global_network_endpoint_group.s3.self_link
  }

  custom_request_headers = [
    "Host:${google_compute_global_network_endpoint.s3.fqdn}"
  ]

  cdn_policy {
    cache_key_policy {
      include_host         = true
      include_protocol     = false
      include_query_string = false
    }
  }

  enable_cdn            = true
  load_balancing_scheme = "EXTERNAL"

  log_config {
    enable      = true
    sample_rate = 1.0
  }

  port_name   = "https"
  protocol    = "HTTPS"
  timeout_sec = 60
}

resource "google_compute_backend_service" "imgproxy" {
  name = "imgproxy-${var.ENVIRONMENT_NAME}"

  backend {
    group = google_compute_global_network_endpoint_group.kubernetes-cluster.self_link
  }

  cdn_policy {
    cache_key_policy {
      include_host         = true
      include_protocol     = false
      include_query_string = false
    }
  }

  enable_cdn            = true
  load_balancing_scheme = "EXTERNAL"

  log_config {
    enable      = true
    sample_rate = 1.0
  }

  port_name   = "http"
  protocol    = "HTTP"
  timeout_sec = 60
}

resource "google_compute_backend_service" "api" {
  name = "api-${var.ENVIRONMENT_NAME}"

  custom_request_headers = [
    "Access-Control-Allow-Origin:${var.ALLOWED_CORS_ORIGIN}"
  ]

  backend {
    group = google_compute_global_network_endpoint_group.kubernetes-cluster.self_link
  }

  load_balancing_scheme = "EXTERNAL"

  log_config {
    enable      = true
    sample_rate = 1.0
  }

  port_name   = "http"
  protocol    = "HTTP"
  timeout_sec = 60
}

resource "google_compute_backend_service" "front" {
  name = "front-${var.ENVIRONMENT_NAME}"

  backend {
    group = google_compute_global_network_endpoint_group.kubernetes-cluster.self_link
  }

  cdn_policy {
    cache_key_policy {
      include_host         = true
      include_protocol     = false
      include_query_string = true
    }
  }

  enable_cdn            = true
  load_balancing_scheme = "EXTERNAL"

  log_config {
    enable      = true
    sample_rate = 1.0
  }

  port_name   = "http"
  protocol    = "HTTP"
  timeout_sec = 60
}

resource "google_compute_url_map" "default" {
  name            = "default-${var.ENVIRONMENT_NAME}"
  default_service = google_compute_backend_service.front.self_link

  host_rule {
    hosts = [
      local.hosts.api,
      local.hosts.fps
    ]
    path_matcher = "api"

  }

  host_rule {
    hosts = [
      local.hosts.s3
    ]
    path_matcher = "s3"
  }

  host_rule {
    hosts = [
      local.hosts.imgproxy
    ]
    path_matcher = "imgproxy"
  }

  path_matcher {
    default_service = google_compute_backend_service.api.self_link
    name            = "api"
  }

  path_matcher {
    default_service = google_compute_backend_service.s3.self_link
    name            = "s3"
  }

  path_matcher {
    default_service = google_compute_backend_service.imgproxy.self_link
    name            = "imgproxy"
  }

  test {
    host    = local.hosts.docs
    path    = "/"
    service = google_compute_backend_service.front.self_link
  }

  test {
    host    = local.hosts.api
    path    = "/"
    service = google_compute_backend_service.api.self_link
  }

  test {
    host    = local.hosts.fps
    path    = "/"
    service = google_compute_backend_service.api.self_link
  }

  test {
    host    = local.hosts.s3
    path    = "/"
    service = google_compute_backend_service.s3.self_link
  }

  test {
    host    = local.hosts.imgproxy
    path    = "/"
    service = google_compute_backend_service.imgproxy.self_link
  }
}

# See: https://github.com/hashicorp/terraform-provider-google/issues/5356
resource "random_id" "managed-certificate-name" {
  byte_length = 4
  prefix      = "default-${var.ENVIRONMENT_NAME}-"

  keepers = {
    domains = join(",", values(local.hosts))
  }
}

resource "google_compute_managed_ssl_certificate" "default" {
  name = random_id.managed-certificate-name.hex

  lifecycle {
    create_before_destroy = true
  }

  managed {
    domains = values(local.hosts)
  }
}

resource "google_compute_ssl_policy" "default" {
  name    = "default-${var.ENVIRONMENT_NAME}"
  profile = "MODERN"
}

resource "google_compute_target_https_proxy" "default" {
  name       = "default-${var.ENVIRONMENT_NAME}"
  url_map    = google_compute_url_map.default.self_link
  ssl_policy = google_compute_ssl_policy.default.self_link
  ssl_certificates = [
    google_compute_managed_ssl_certificate.default.self_link
  ]
}

resource "google_compute_global_forwarding_rule" "default" {
  name                  = "default-${var.ENVIRONMENT_NAME}"
  load_balancing_scheme = "EXTERNAL"
  port_range            = "443-443"
  target                = google_compute_target_https_proxy.default.self_link
}

УПД. Я понял, что воссоздание NEG решит проблему:

  1. Подождите, пока Terraform завершит развертывание.
  2. Создайте через консоль Google Cloud Platform NEG с теми же конфигурациями.
  3. Измените серверные службы, чтобы они использовали вновь созданные NEG.
  4. Оно работает!

Но это определенно хак, и похоже, что с помощью Terraform его невозможно автоматизировать. Я продолжу расследование этого вопроса.

1 ответ

Рад слышать, что ваша проблема решена, и я понимаю, что вы добились этого, вручную создав NEG через консоль GCP и впоследствии отредактировав серверные службы, а не используя Terraform. Наиболее вероятной причиной этой проблемы является состояние гонки, т. е. в Terraform мы обычно определяем ресурсы в цепочке, и, следовательно, каждый определяемый ресурс зависит от другого ресурса. Обычно при определении ресурсов через Terraform создание серверных служб и вложений NE зависит от создания NEG. Как создание серверных служб, так и операции подключения конечной точки сети (NE), как правило, выполняются параллельно, и в таком случае процесс подключения NE не ссылается на внутреннюю службу правильно, поскольку состояние NEG Интернета будет считываться именно во время создания внутренней службы. /update (поэтому вложение NE должно произойти до создания серверной части).
Итак, в Terraform при создании серверной службы мы должны определить ее как зависящую от (мета-аргумент) [1] вложения NE (т. е. серверная служба должна запускаться только после подключения NE).

[1] https://www.terraform.io/docs/language/meta-arguments/dependents_on.html

Надеюсь, это проясняет ваши сомнения.

Другие вопросы по тегам