Pull to refresh

Managed SSL for TCP Loadbalancer in GKE

Level of difficultyMedium
Reading time5 min
Views1.2K

At Altenar, we often use RabbitMQ as an entry point for our products. In this specific case, we will discuss a system that calculates sports data and provides it to other systems within the organization as well as outside the perimeter. When designing the system, the RabbitMQ component had the following requirements:

For this project, we are using Google Cloud. Like Let’s Encrypt, you can use a Google Managed SSL certificate for any public HTTP load balancer in Google Cloud. However, it has several limitations, with the key ones being:

  • It only works for HTTP Load balancers.

  • It only works for public Load balancers.

According to RabbitMQ documentation, there are two common approaches for client connections:

  • Configure RabbitMQ to handle TLS connections

  • Use a proxy or load balancer (such as HAproxy) to perform TLS termination of client connections and use plain TCP connections to RabbitMQ nodes.

Our idea was to use a Google Managed certificate for TCP/SSL proxy load balancer in Google Cloud. However, due to the aforementioned limitations, a managed certificate is not supported for TCP load balancers. On the other hand, Google allows you to use an SSL certificate for several load balancers, which we decided to explore. Overall the blueprint would look like this:

  • We wanted to have a default dummy HTTP service that we would expose over port 443 and use for the sake of managing the SSL certificate and automatic renewals.

  • We would have a separate endpoint reusing the same IP address and SSL certificate.

Let’s examine each part of the solution in detail.


GKE Cluster

As the main hosting platform, we use GKE. We use the Google Terraform module for the private GKE cluster, configure Workload Identity and install an AutoNEG controller to the cluster.

In this article, we won’t delve into the specifics of how Workload Identity works with AutoNEG, as they are used as described in the official documentation.

With all preparations in place, we deploy a dummy “Hello World” service to the cluster and attach it to the Google Load balancer via AutoNEG. Below is the YAML configuration for that:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gcp-tls-certificate-issuer
  labels:
    app: gcp-tls-certificate-issuer
  annotations:
    deployment.kubernetes.io/revision: '1'
spec:
  replicas: 2
  selector:
    matchLabels:
      app: gcp-tls-certificate-issuer
  template:
    metadata:
      labels:
        app: gcp-tls-certificate-issuer
    spec:
      containers:
        - name: ok
          image: assemblyline/ok:latest
          ports:
            - containerPort: 8888
              protocol: TCP
          imagePullPolicy: Always
          securityContext:
            capabilities:
              drop:
                - ALL
            runAsUser: 1000
            runAsGroup: 3000
            runAsNonRoot: true
            readOnlyRootFilesystem: true
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 25%
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600
---
apiVersion: v1
kind: Service
metadata:
  name: gcp-tls-certificate-issuer
  labels:
    app: gcp-tls-certificate-issuer
  annotations:
    cloud.google.com/neg: '{"exposed_ports": {"8888":{"name": "gcp-tls-certificate-issuer"}}}'
    controller.autoneg.dev/neg: '{"backend_services":{"8888":[{"name":"envcode-rabbit-https-backend-service","max_connections_per_endpoint":10000}]}}'
spec:
  ports:
    - name: http
      protocol: TCP
      port: 8888
      targetPort: 8888
  selector:
    app: gcp-tls-certificate-issuer
  clusterIP: 10.10.12.130
  clusterIPs:
    - 10.10.12.130
  type: ClusterIP

Notice the service's annotation. This is how AutoNEG adds it to the Load balancer as a backend.

Google load balancer

The next part is managed outside GKE and created separately. Google Load balancer is not a single object but rather a set of different objects combined. Below is the Terraform code with comments:

resource "google_compute_managed_ssl_certificate" "rabbitmq" {
  project = var.project
  name    = "${var.environment_name}-google-managed-certificate-rabbitmq"

  managed {
    domains = ["rabitmq.example.com."]  # Replace with your domain
  }
}

# reserved IP address
resource "google_compute_global_address" "default" {
  project = var.project
  name    = "tcp-proxy-xlb-ip"
}

output "rabbitmq-ip" {
  value = google_compute_global_address.default.address
}

# forwarding rule for TCP Loadbalanser
resource "google_compute_global_forwarding_rule" "default" {
  project               = var.project
  name                  = "${var.environment_name}-tcp-global-loadbalancer"
  provider              = google
  ip_protocol           = "TCP"
  load_balancing_scheme = "EXTERNAL"
  port_range            = "5671"
  target                = google_compute_target_ssl_proxy.default.id
  ip_address            = google_compute_global_address.default.id
}
# https://cloud.google.com/load-balancing/docs/ssl
# When you use Google-managed SSL certificates with SSL Proxy Load Balancing, the frontend port for traffic must be 443 to enable the Google-managed SSL certificates to be provisioned and renewed.

# forwarding rule for HTTPS Loadbalanser
resource "google_compute_global_forwarding_rule" "https" {
  project               = var.project
  name                  = "${var.environment_name}-https-global-loadbalancer"
  provider              = google
  ip_protocol           = "TCP"
  load_balancing_scheme = "EXTERNAL"
  port_range            = "443"
  target                = google_compute_target_ssl_proxy.https.id
  ip_address            = google_compute_global_address.default.id
}

resource "google_compute_target_ssl_proxy" "default" {
  project          = var.project
  name             = "${var.environment_name}-global-loadbalancer-tcp-proxy"
  backend_service  = google_compute_backend_service.default.id
  ssl_certificates = [google_compute_managed_ssl_certificate.rabbitmq.id]
}

resource "google_compute_target_ssl_proxy" "https" {
  project          = var.project
  name             = "${var.environment_name}-global-loadbalancer-https-proxy"
  backend_service  = google_compute_backend_service.https.id
  ssl_certificates = [google_compute_managed_ssl_certificate.rabbitmq.id]
}

# backend service For RabbitMQ Autoneg
resource "google_compute_backend_service" "default" {
  project = var.project
  name    = "${var.environment_name}-tcp-backend-service"
  protocol              = "TCP"
  port_name             = "tcp"
  load_balancing_scheme = "EXTERNAL"
  timeout_sec           = 10
  health_checks         = [google_compute_health_check.default.id]
  session_affinity      = "CLIENT_IP"

  # We don't want TF to remove whatever was configured by AutoNEG
  lifecycle {
    ignore_changes = [backend]
  }
}

# backend service For HTTPS Autoneg
resource "google_compute_backend_service" "https" {
  project               = var.project

                          #that's what you use in the service annotations
  name                  = "${var.environment_name}-https-backend-service"  
  protocol              = "TCP"
  port_name             = "tcp"
  load_balancing_scheme = "EXTERNAL"
  timeout_sec           = 10
  health_checks         = [google_compute_health_check.https.id]

  # We don't want TF to remove whatever was configured by AutoNEG
  lifecycle {
    ignore_changes = [backend]
  }
}

resource "google_compute_health_check" "default" {
  project            = var.project
  name               = "tcp-proxy-health-check"
  description        = "Backend service for AutoNEG"
  timeout_sec        = 1
  check_interval_sec = 1

  tcp_health_check {
    port = "5672" #use container port
  }
}

resource "google_compute_health_check" "https" {
  project            = var.project
  name               = "https-proxy-health-check"
  description        = "Backend service for AutoNEG"
  timeout_sec        = 1
  check_interval_sec = 1

  tcp_health_check {
    port = "8888" #use container port
  }
}

]

As you can see, we created one IP address and one SSL certificate and then used them in two forwarding rules. This allowed us to have a managed SSL certificate used for the TCP Load Balancer.

Don’t forget to configure DNS and point the IP to the right hostname for the whole thing to work.

Tip: GKE has thel7-default-backend deployment. Perhaps it would’ve been enough just creating a service with AutoNEG annotations and pointing it to that deployment’s pods. Try that and let me know in the comments if it works.

Tags:
Hubs:
Rating0
Comments0

Articles