Серверные службы 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 решит проблему:
- Подождите, пока Terraform завершит развертывание.
- Создайте через консоль Google Cloud Platform NEG с теми же конфигурациями.
- Измените серверные службы, чтобы они использовали вновь созданные NEG.
- Оно работает!
Но это определенно хак, и похоже, что с помощью 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
Надеюсь, это проясняет ваши сомнения.