-
Notifications
You must be signed in to change notification settings - Fork 1
Cloud Architecture
Linker1 se ejecuta en Oracle Cloud Infrastructure (OCI), región sa-bogota-1. La arquitectura sigue un modelo de VM única con acceso controlado mediante Bastion, sin IP pública expuesta directamente.
┌─────────────────────────────────────────────────────────────────────────────┐
│ INTERNET │
│ │
│ Usuarios ──────────────────────────────────────────────────┐ │
│ │ │
│ Grafana Cloud (Observabilidad) LaunchDarkly │ │
│ ┌─────────────────────────┐ ┌──────────┐ │ │
│ │ Loki (Logs) │◀───OTLP────│ Feature │ │ │
│ │ Mimir (Métricas) │ http/ │ Flags │ │ │
│ │ Tempo (Traces) │ protobuf └──────────┘ │ │
│ │ Synthetic Monitoring │ │ │
│ └─────────────────────────┘ │ │
│ │ │
└──────────────────────────────────────────────────────────────┼──────────────┘
│
┌──────────────────────────────────────────────────────────────┼──────────────┐
│ OCI — sa-bogota-1 │ │
│ │ │
│ ┌───────────────┐ ┌───────────────────────────────┼────────────┐ │
│ │ OCI Bastion │ │ VCN / Subnet (privada) │ │ │
│ │ │─SSH───▶ │ │ │ │
│ │ (Managed SSH │ │ ┌───────────────────────────▼──────────┐ │ │
│ │ Sessions) │ │ │ VM (vm-linker1-app) │ │ │
│ └───────┬───────┘ │ │ Ubuntu + Java 21 │ │ │
│ │ │ │ │ │ │
│ │ │ │ ┌──────────┐ ┌─────────────┐ │ │ │
│ │ │ │ │ Nginx │:80──▶│ Javalin │ │ │ │
│ │ │ │ │ (proxy) │ │ :8080 │ │ │ │
│ │ │ │ └──────────┘ │ │ │ │ │
│ │ │ │ │ ┌────────┐│ │ │ │
│ ┌───────▼───────┐ │ │ │ │ SQLite ││ │ │ │
│ │ GitHub Actions│ │ │ │ │ (.db) ││ │ │ │
│ │ (deploy via │ │ │ │ └────────┘│ │ │ │
│ │ bastion) │ │ │ │ │ │ │ │
│ └───────────────┘ │ │ ┌──────────────┐ │ systemd │ │ │ │
│ │ │ │ MySQL │ │ service │ │ │ │
│ │ │ │ (healthcheck) │◀──│ │ │ │ │
│ │ │ └──────────────┘ └────────────┘ │ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ OCI Vault │ │
│ │ (SSH keys, passwords) │ │
│ └─────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
| Componente | Función |
|---|---|
| Repositorio |
co-eiv-devsecops/linker1 — código fuente, IaC, workflows, documentación |
| GitHub Actions | Tres workflows: CI (ci.yml), CD (pipeline.yml), Release (release.yml) |
| GitHub Packages | Registro Maven para publicar artefactos JAR con versión CI |
| GitHub Releases | Artefactos binarios versionados con SemVer para rollback |
| Branch Protection |
main y DEV protegidas: PR + approval + CI verde obligatorio |
| Aspecto | Detalle |
|---|---|
| Propósito | Acceso SSH a la VM sin exponer IP pública |
| Tipo de sesión | Managed SSH (bastion session create-managed-ssh) |
| Usado por |
pipeline.yml (deploy + rollback), manual vía Cloud Shell |
| TTL | 1800 segundos (30 minutos) por sesión |
| Autenticación | Par de claves SSH (DEPLOYMENT_PRIVATE_KEY / DEPLOYMENT_PUBLIC_KEY) |
El Bastion es el único punto de entrada a la VM. No existe ninguna otra forma de acceder: ni IP pública, ni VPN, ni port forwarding directo.
| Aspecto | Detalle |
|---|---|
| Shape |
VM.Standard.E5.Flex (1 OCPU, 4 GB RAM) |
| OS | Ubuntu (cloud-init provisioning) |
| IP Pública |
assign_public_ip = false — sin IP pública directa |
| Acceso público | A través del dominio 1.n-la-c.app (DNS configurado por el instructor) |
| Provisioning |
cloud-init.yaml instala Java 21, Maven, Nginx; clona el repo; build inicial |
server {
listen 80 default_server;
server_name _;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}- Escucha en puerto 80 (HTTP público)
- Reenvía todo el tráfico a Javalin en puerto 8080
- Propaga headers de IP real y protocolo para logging
| Aspecto | Detalle |
|---|---|
| Ubicación del JAR | /opt/linker1/linker1.jar |
| Base de datos |
/var/lib/linker1/linker1.db (SQLite) |
| Frontend | Servido desde recursos empaquetados en el JAR |
| Puerto |
8080 (configurable vía LINKER_PORT) |
| Proceso | Gestionado por systemd (linker1.service) |
[Unit]
Description=Linker1 URL Shortener
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/linker1
ExecStart=/usr/bin/java -jar /opt/linker1/linker1.jar
Restart=always
RestartSec=5
User=ubuntu
Environment="LINKER_PORT=8080"
Environment="LINKER_DB_PATH=/var/lib/linker1/linker1.db"
Environment="LD_SDK_KEY=..."
Environment="OTEL_SERVICE_NAME=linker1"
Environment="OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf"
# ... más variables opcionales
[Install]
WantedBy=multi-user.target-
Restart=always: si el proceso muere, se reinicia automáticamente. -
RestartSec=5: espera 5 segundos antes del restart (evita restart loops). - Variables de entorno inyectan toda la configuración en runtime.
| Servicio | Función | Protocolo |
|---|---|---|
| Loki | Almacenamiento y consulta de logs | OTLP → http/protobuf
|
| Mimir | Almacenamiento y consulta de métricas | OTLP → http/protobuf
|
| Tempo | Almacenamiento y consulta de traces distribuidos | OTLP → http/protobuf
|
| Synthetic Monitoring | Polling cada 5 min a /healthz
|
HTTP |
El endpoint OTLP se configura como https://otlp-gateway-<region>.grafana.net/otlp, autenticado con headers Authorization=Basic <token>.
- SDK server-side (Java) conectado con
LD_SDK_KEY - Evaluación de flags en tiempo real sin restart de la aplicación
- Modo offline disponible para testing (
LD_OFFLINE=true) - Feature flag actual:
new-ui(toggle entre interfaz V1 y V2)
- Usado exclusivamente para el healthcheck (
GET /healthz→SELECT 1) - No almacena datos de la aplicación (eso lo hace SQLite)
- Si no está configurado,
/healthzretorna 503 pero la app sigue funcional
La infraestructura está definida en Terraform (infra/):
| Archivo | Contenido |
|---|---|
provider.tf |
Configuración del provider OCI |
variables.tf |
15 variables (región, shape, secrets, etc.) |
main.tf |
Instancia OCI (oci_core_instance) + cloud-init |
cloud-init.yaml |
Provisioning: Java 21, Maven, Nginx, clone + build |
outputs.tf |
Output del OCID de la instancia |
terraform.tfvars.example |
Template de variables |
Los secrets que contienen caracteres especiales (%, ") son escapados antes de ser inyectados en la unidad systemd:
systemd_escape = { for k, v in {
ld_sdk_key = var.ld_sdk_key
// ...
} : k => replace(replace(v, "%", "%%"), "\"", "\\\"")
}Esta misma lógica de escape existe en deploy.sh y pipeline.yml, asegurando consistencia entre los tres mecanismos de despliegue.
Usuario DNS/CDN VM
│ │ │
│──GET 1.n-la-c.app/abc──▶│ │
│ │──:80──▶ Nginx │
│ │ │ │
│ │ proxy_pass │
│ │ │ │
│ │ :8080 ▼ │
│ │ Javalin │
│ │ │ │
│ │ LinkRoutes │
│ │ ├─ traceResolve │ ← span: link.resolve
│ │ ├─ service.get │ ← span: link.resolve.lookup
│ │ ├─ SQLite query │
│ │ └─ redirect 301 │
│ │ │ │
│◀────301 Location: url────│◀────────┘ │
│ │
│ Grafana Cloud │
│ ├─ trace exportado │
│ ├─ métrica contada │
│ └─ log registrado │
| Mecanismo | Qué protege |
|---|---|
systemd Restart=always |
Auto-recuperación si el proceso Java muere |
| Rollback automático | Si el deploy falla, se restaura la versión anterior |
Health check /healthz |
Monitoreo activo de backing services |
| Synthetic Monitoring | Detección proactiva de caídas cada 5 minutos |
| Bastion (sin IP pública) | Superficie de ataque reducida |
| Graceful degradation | OTLP/MySQL ausentes = features degradados, no crash |
| Branch protection | Previene que código roto llegue a producción |