Arquitectura y Enrutamiento

Glue2 es el proxy inverso central que se sitúa frente a todos los servicios de HolaCloud. Todo el tráfico entrante — ya sea de navegadores, clientes API o servicios internos — pasa a través de Glue2 antes de llegar a su destino.

Arquitectura de Glue2

Proxy Inverso

Glue2 opera como un proxy inverso de capa 7. Termina TLS, inspecciona cada solicitud, autentica al cliente y luego reenvía la solicitud al servicio backend correspondiente. Ningún tráfico externo llega directamente a los servicios de HolaCloud; Glue2 es el único punto de entrada.

1Cliente ──▶ Glue2 (verificación de auth) ──▶ Servicio Backend
23               ├──▶ InceptionDB
4               ├──▶ Lambda
5               ├──▶ Files
6               ├──▶ Config
7               ├──▶ KVNode
8               ├──▶ Scheduler
9               └──▶ Logs

Enrutamiento por Host Virtual

Glue2 enruta las solicitudes según el encabezado Host. Cada proyecto de HolaCloud recibe un subdominio único (<proyecto>.hola.cloud), y Glue2 mapea ese subdominio al servicio backend del proyecto.

Los servicios de plataforma integrados usan subdominios reservados:

Host Servicio
inceptiondb.hola.cloud InceptionDB
api.hola.cloud API pública (Lambda, Files, Config, etc.)
console.hola.cloud Consola de HolaCloud
auth.hola.cloud Autenticación / Servicio de sesión
logs.hola.cloud InstantLogs

Cuando llega una solicitud para mi-proyecto.hola.cloud, Glue2 busca el proyecto con ese slug, resuelve su servicio backend principal y reenvía la solicitud.

Flujo del Proxy

  1. El cliente envía una solicitud HTTP a <proyecto>.hola.cloud.
  2. Glue2 termina la conexión TLS y lee el encabezado Host.
  3. Glue2 verifica la autenticación (cookie de sesión, clave API o token OAuth). Si el endpoint requiere autenticación y no se proporciona, la solicitud es rechazada con 401 Unauthorized.
  4. Glue2 resuelve el host virtual a un ID de proyecto y un servicio backend destino.
  5. Glue2 inyecta los encabezados de autenticación y proyecto en la solicitud.
  6. La solicitud se reenvía al servicio backend.
  7. La respuesta del backend se transmite de vuelta al cliente y Glue2 registra la transacción.

Encabezados Inyectados

Al reenviar a un backend, Glue2 inyecta:

Encabezado Descripción
X-Glue-Authentication Contexto de autenticación en JSON plano
X-Holacloud-Project-Id El UUID del proyecto derivado del host virtual
X-Holacloud-Tenant-Project-Id ID de proyecto con alcance de inquilino para aislamiento multiinquilino
X-Forwarded-For Dirección IP original del cliente
X-Forwarded-Proto Protocolo original (http o https)

Los servicios backend usan estos encabezados para identificar al cliente y aplicar autorización. X-Glue-Authentication es JSON plano.

Endpoints de Administración V0

Glue2 expone endpoints de administración bajo /v0/:

Listar Hosts Virtuales

curl "https://api.hola.cloud/v0/virtualhosts" \
  -H "Api-Key: TU_API_KEY" \
  -H "Api-Secret: TU_API_SECRET"
GET /v0/virtualhosts HTTP/1.1
Host: api.hola.cloud
Api-Key: TU_API_KEY
Api-Secret: TU_API_SECRET
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.hola.cloud/v0/virtualhosts", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Api-Key", "TU_API_KEY")
	req.Header.Set("Api-Secret", "TU_API_SECRET")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	responseBody, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(responseBody))
}
<?php
$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://api.hola.cloud/v0/virtualhosts',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'Api-Key: TU_API_KEY',
        'Api-Secret: TU_API_SECRET',
    ],
]);

$response = curl_exec($ch);
if ($response === false) {
    throw new Exception(curl_error($ch));
}
curl_close($ch);

echo $response;
import requests

headers = {
    "Api-Key": "TU_API_KEY",
    "Api-Secret": "TU_API_SECRET",
}

response = requests.request(
    "GET",
    "https://api.hola.cloud/v0/virtualhosts",
    headers=headers
)

print(response.text)
const response = await fetch("https://api.hola.cloud/v0/virtualhosts", {
  method: "GET",
  headers: {
    "Api-Key": "TU_API_KEY",
    "Api-Secret": "TU_API_SECRET"
  }
});

console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v0/virtualhosts", {
  method: "GET",
  headers: {
    "Api-Key": "TU_API_KEY",
    "Api-Secret": "TU_API_SECRET"
  }
});

const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        var request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.hola.cloud/v0/virtualhosts"))
            .method("GET", HttpRequest.BodyPublishers.noBody())
            .header("Api-Key", "TU_API_KEY")
            .header("Api-Secret", "TU_API_SECRET")
            .build();

        var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }
}
1["mi-proyecto.hola.cloud", "api.mi-proyecto.hola.cloud"]

Estadísticas de Tráfico

curl "https://api.hola.cloud/v0/stats" \
  -H "Api-Key: TU_API_KEY" \
  -H "Api-Secret: TU_API_SECRET"
GET /v0/stats HTTP/1.1
Host: api.hola.cloud
Api-Key: TU_API_KEY
Api-Secret: TU_API_SECRET
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.hola.cloud/v0/stats", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Api-Key", "TU_API_KEY")
	req.Header.Set("Api-Secret", "TU_API_SECRET")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	responseBody, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(responseBody))
}
<?php
$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://api.hola.cloud/v0/stats',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'Api-Key: TU_API_KEY',
        'Api-Secret: TU_API_SECRET',
    ],
]);

$response = curl_exec($ch);
if ($response === false) {
    throw new Exception(curl_error($ch));
}
curl_close($ch);

echo $response;
import requests

headers = {
    "Api-Key": "TU_API_KEY",
    "Api-Secret": "TU_API_SECRET",
}

response = requests.request(
    "GET",
    "https://api.hola.cloud/v0/stats",
    headers=headers
)

print(response.text)
const response = await fetch("https://api.hola.cloud/v0/stats", {
  method: "GET",
  headers: {
    "Api-Key": "TU_API_KEY",
    "Api-Secret": "TU_API_SECRET"
  }
});

console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v0/stats", {
  method: "GET",
  headers: {
    "Api-Key": "TU_API_KEY",
    "Api-Secret": "TU_API_SECRET"
  }
});

const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        var request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.hola.cloud/v0/stats"))
            .method("GET", HttpRequest.BodyPublishers.noBody())
            .header("Api-Key", "TU_API_KEY")
            .header("Api-Secret", "TU_API_SECRET")
            .build();

        var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }
}

Estado de Salud del Backend

curl "https://api.hola.cloud/v0/status" \
  -H "Api-Key: TU_API_KEY" \
  -H "Api-Secret: TU_API_SECRET"
GET /v0/status HTTP/1.1
Host: api.hola.cloud
Api-Key: TU_API_KEY
Api-Secret: TU_API_SECRET
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.hola.cloud/v0/status", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Api-Key", "TU_API_KEY")
	req.Header.Set("Api-Secret", "TU_API_SECRET")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	responseBody, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(responseBody))
}
<?php
$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => 'https://api.hola.cloud/v0/status',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'Api-Key: TU_API_KEY',
        'Api-Secret: TU_API_SECRET',
    ],
]);

$response = curl_exec($ch);
if ($response === false) {
    throw new Exception(curl_error($ch));
}
curl_close($ch);

echo $response;
import requests

headers = {
    "Api-Key": "TU_API_KEY",
    "Api-Secret": "TU_API_SECRET",
}

response = requests.request(
    "GET",
    "https://api.hola.cloud/v0/status",
    headers=headers
)

print(response.text)
const response = await fetch("https://api.hola.cloud/v0/status", {
  method: "GET",
  headers: {
    "Api-Key": "TU_API_KEY",
    "Api-Secret": "TU_API_SECRET"
  }
});

console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v0/status", {
  method: "GET",
  headers: {
    "Api-Key": "TU_API_KEY",
    "Api-Secret": "TU_API_SECRET"
  }
});

const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        var request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.hola.cloud/v0/status"))
            .method("GET", HttpRequest.BodyPublishers.noBody())
            .header("Api-Key", "TU_API_KEY")
            .header("Api-Secret", "TU_API_SECRET")
            .build();

        var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }
}
1{
2  "backends": [
3    {
4      "name": "inceptiondb",
5      "status": "saludable",
6      "latency_ms": 12
7    }
8  ]
9}

Siguientes Pasos

Comentarios

Deja un comentario