Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1a4a84d24 | ||
|
|
a9c57d4985 | ||
|
|
584a42ea72 | ||
|
|
a6a92f75ea | ||
|
|
0eea6234d6 | ||
|
|
77bf6089fb | ||
|
|
0134a4dd0b | ||
|
|
3fe63591ec | ||
|
|
c6cf394c26 | ||
|
|
7a09202c27 | ||
|
|
a902dc0a26 | ||
|
|
04dc6bd8bc | ||
|
|
60072cfcbe | ||
|
|
c9b376c4e6 | ||
|
|
cea41ce428 | ||
|
|
381e6ab6ab | ||
|
|
e9c93bfb82 | ||
|
|
f25b10a2f9 | ||
|
|
7a83ed61b9 | ||
|
|
987ef04b0d |
@@ -0,0 +1,44 @@
|
|||||||
|
# Known issues
|
||||||
|
|
||||||
|
## Blackhole de red cross-node en el cluster k3d (MTU/PMTUD, flannel VXLAN)
|
||||||
|
|
||||||
|
**Estado:** workaround aplicado, fix definitivo pendiente.
|
||||||
|
|
||||||
|
**Síntoma:** cualquier pod que necesite hablar con `commerce-postgres-0`
|
||||||
|
(StatefulSet, sin réplicas, fijo a un nodo) falla de forma consistente y
|
||||||
|
reproducible al 100% cuando queda agendado en un nodo **distinto** al de
|
||||||
|
Postgres. El handshake TCP (SYN/ACK, `nc`, `ping`) funciona perfecto entre
|
||||||
|
nodos, pero el primer paquete de datos real de la sesión (protocolo Postgres,
|
||||||
|
`pg_isready` incluido) nunca llega — se cuelga hasta hacer timeout.
|
||||||
|
|
||||||
|
**Diagnóstico:**
|
||||||
|
|
||||||
|
- Confirmado aislando el nodo con `nodeName` en pods de prueba: 100% de
|
||||||
|
fallos (186/186 intentos) agendando cross-node (`agent-0` → Postgres en
|
||||||
|
`server-0`); 100% de éxito agendando en el mismo nodo.
|
||||||
|
- MTU dentro de los pods (interfaz `flannel.1`/VXLAN) es 1450 en ambos nodos
|
||||||
|
— consistente, no es un mismatch obvio a nivel flannel.
|
||||||
|
- `ping` sin `-M do` (permite fragmentación) no muestra pérdida de paquetes
|
||||||
|
hasta payloads de 2000 bytes entre los contenedores Docker de los nodos.
|
||||||
|
- Encaja con un blackhole de PMTU Discovery: el bridge Docker externo que
|
||||||
|
conecta los contenedores de los nodos k3d probablemente tiene un MTU real
|
||||||
|
menor a 1500, y los paquetes TCP con el bit DF puesto (como los de la
|
||||||
|
sesión de Postgres) se pierden en vez de fragmentarse o generar el ICMP
|
||||||
|
"fragmentation needed" que permitiría el ajuste automático.
|
||||||
|
|
||||||
|
**Workaround temporal (aplicado en `workloads/ecommerce/commerce/medusa.yaml`):**
|
||||||
|
|
||||||
|
`podAffinity` con `requiredDuringSchedulingIgnoredDuringExecution` sobre
|
||||||
|
`medusa-deploy`, forzando que sus pods se agenden siempre en el mismo nodo
|
||||||
|
que `commerce-postgres-0` (`matchLabels: app: commerce-postgres`,
|
||||||
|
`topologyKey: kubernetes.io/hostname`). Esto evita el tráfico cross-node
|
||||||
|
entre Medusa y Postgres, pero no resuelve el problema de fondo — cualquier
|
||||||
|
otro workload que necesite cruzar nodos para hablar con Postgres (u otro
|
||||||
|
servicio) puede pisar el mismo blackhole.
|
||||||
|
|
||||||
|
**TODO — fix definitivo pendiente:**
|
||||||
|
|
||||||
|
Ajustar el MTU del cluster k3d a un valor seguro (`1400`) de forma
|
||||||
|
consistente en la red Docker del cluster y en flannel, para eliminar el
|
||||||
|
blackhole de raíz y poder quitar el `podAffinity` (o dejarlo como
|
||||||
|
optimización, no como requisito de disponibilidad).
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
# Incidente: medusa-deploy atascado sin levantar (2026-08)
|
||||||
|
|
||||||
|
**Ventana del incidente:** ~2026-08-08 (onset) → 2026-08-10 05:31 UTC (resuelto)
|
||||||
|
**Servicio afectado:** `medusa-deploy` en namespace `ecommerce`
|
||||||
|
**Impacto:** el rollout nuevo nunca llegaba a estar listo; el pod anterior
|
||||||
|
(`medusa-deploy-59878b956f-hf45b`) siguió sirviendo tráfico sin interrupción
|
||||||
|
durante todo el incidente (`maxUnavailable: 0`), por lo que no hubo downtime
|
||||||
|
de cara al usuario — el impacto fue "no se puede desplegar", no "el servicio
|
||||||
|
está caído".
|
||||||
|
|
||||||
|
## Síntoma inicial reportado vs. estado real
|
||||||
|
|
||||||
|
El incidente se reportó como **CrashLoopBackOff** ("sigue igual desde ayer,
|
||||||
|
más de 20-24 horas"). Al correr `kubectl get pods -n ecommerce -o wide` esa
|
||||||
|
descripción resultó **inexacta**: no había ningún pod en CrashLoopBackOff.
|
||||||
|
El estado real era:
|
||||||
|
|
||||||
|
```
|
||||||
|
commerce-postgres-0 1/1 Running 0 4d9h
|
||||||
|
medusa-deploy-59878b956f-hf45b 1/1 Running 0 21h (revisión vieja, sana)
|
||||||
|
medusa-deploy-9d8784d7b-g9jql 0/1 Init:0/2 0 3h56m (revisión nueva, atascada)
|
||||||
|
```
|
||||||
|
|
||||||
|
0 *restarts* en el pod nuevo — no estaba crasheando y reiniciando, estaba
|
||||||
|
**colgado indefinidamente en el primer init container** (`wait-for-postgres`),
|
||||||
|
sin timeout ni backoff que lo sacara de ese estado. Esta distinción importa:
|
||||||
|
un pod en `Init:0/2` con 0 restarts no aparece en las alertas típicas de
|
||||||
|
CrashLoopBackOff, lo que probablemente explica por qué pasó desapercibido
|
||||||
|
tanto tiempo.
|
||||||
|
|
||||||
|
**Lección:** verificar siempre el estado real con `kubectl get pods` antes de
|
||||||
|
asumir el tipo de falla que describe quien reporta el incidente. Un
|
||||||
|
`Init:X/Y` con 0 restarts y un `CrashLoopBackOff` requieren líneas de
|
||||||
|
investigación distintas.
|
||||||
|
|
||||||
|
## Hipótesis descartadas (en orden)
|
||||||
|
|
||||||
|
### 1. Credenciales / secret inválido o vacío
|
||||||
|
|
||||||
|
- `kubectl get secret -n ecommerce commerce-secrets` → la clave
|
||||||
|
`DATABASE_URL` existe.
|
||||||
|
- Se verificó longitud del valor (`~126` caracteres, no vacío ni truncado)
|
||||||
|
sin volcar el contenido en texto plano.
|
||||||
|
- Se probó el `DATABASE_URL` real contra Postgres con `pg_isready` ejecutado
|
||||||
|
**dentro del propio pod `commerce-postgres-0`**: `accepting connections`,
|
||||||
|
exit 0.
|
||||||
|
- **Descartada:** el secret existe, tiene el formato correcto y las
|
||||||
|
credenciales son válidas.
|
||||||
|
|
||||||
|
### 2. NetworkPolicy bloqueando tráfico
|
||||||
|
|
||||||
|
- `kubectl describe networkpolicy -n ecommerce ecommerce-network-security`
|
||||||
|
→ `PodSelector: <none>` (aplica a todos los pods), `Allowing ingress
|
||||||
|
traffic: To Port: <any>, From: NamespaceSelector: <none>` (permite todo el
|
||||||
|
ingress desde cualquier namespace), `Not affecting egress traffic`.
|
||||||
|
- **Descartada:** la policy es efectivamente allow-all para ingress y no
|
||||||
|
restringe egress. No podía estar bloqueando la conexión de `medusa` hacia
|
||||||
|
`postgres-svc`.
|
||||||
|
|
||||||
|
### 3. Salud de Postgres
|
||||||
|
|
||||||
|
- `kubectl exec -n ecommerce commerce-postgres-0 -- psql -U medusa -c
|
||||||
|
"SELECT count(*) FROM pg_stat_activity;"` → `8` conexiones activas,
|
||||||
|
respuesta inmediata.
|
||||||
|
- `kubectl get endpoints -n ecommerce postgres-svc` → apunta correctamente a
|
||||||
|
`10.42.0.98:5432`, la IP real del pod.
|
||||||
|
- **Descartada:** Postgres estaba sano, aceptando conexiones y con el
|
||||||
|
Endpoint del Service correctamente resuelto.
|
||||||
|
|
||||||
|
### 4. ResourceQuota / LimitRange del namespace
|
||||||
|
|
||||||
|
- `kubectl describe resourcequota -n ecommerce` → `pods: 7/12`,
|
||||||
|
`requests.cpu: 625m/3`, `requests.memory: 1504Mi/4Gi`, `limits.cpu:
|
||||||
|
4250m/8`, `limits.memory: 4480Mi/8Gi` — todo muy por debajo de los topes.
|
||||||
|
- `kubectl describe limitrange -n ecommerce` → rangos por-contenedor
|
||||||
|
(16Mi–1Gi mem, 10m–2 cpu) compatibles con los `resources` definidos en
|
||||||
|
`medusa.yaml`.
|
||||||
|
- Sin eventos de `exceeded quota` en el namespace.
|
||||||
|
- **Descartada:** no había presión de cuota ni un contenedor rechazado por
|
||||||
|
LimitRange.
|
||||||
|
|
||||||
|
### 5. DNS
|
||||||
|
|
||||||
|
- `kubectl exec` en el pod viejo (sano) → `getent hosts postgres-svc`
|
||||||
|
resolvía a `10.42.0.98` correctamente.
|
||||||
|
- CoreDNS (`kube-system`) → `Running`, sano.
|
||||||
|
- Repetido el mismo chequeo **dentro del propio init container atascado**
|
||||||
|
(`wait-for-postgres` del pod nuevo) → misma resolución correcta, y
|
||||||
|
`/etc/resolv.conf` con `nameserver 10.43.0.10` normal.
|
||||||
|
- **Descartada:** la resolución DNS funcionaba de forma idéntica en el pod
|
||||||
|
sano y en el pod atascado.
|
||||||
|
|
||||||
|
### 6. MTU/PMTUD cross-node (blackhole conocido, ver `docs/known-issues.md`)
|
||||||
|
|
||||||
|
- Existe un blackhole de red confirmado (186/186 fallos reproducidos) cuando
|
||||||
|
un pod que necesita hablar con `commerce-postgres-0` queda agendado en un
|
||||||
|
nodo distinto al de Postgres, mitigado hoy con `podAffinity` en
|
||||||
|
`medusa.yaml`.
|
||||||
|
- `kubectl get pods -o wide` mostró que **los tres pods relevantes
|
||||||
|
(`commerce-postgres-0`, el pod viejo y el pod nuevo) estaban en el mismo
|
||||||
|
nodo** (`k3d-lab-cluster-server-0`).
|
||||||
|
- **Descartada para este incidente puntual:** al no haber tráfico
|
||||||
|
cross-node involucrado, el blackhole de MTU no podía ser la causa. Sigue
|
||||||
|
siendo una condición latente real del cluster (ver sección de deuda
|
||||||
|
técnica).
|
||||||
|
|
||||||
|
## Causa raíz real
|
||||||
|
|
||||||
|
El init container `wait-for-postgres` ejecutaba:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
until pg_isready -d "$DATABASE_URL" -t 5; do ...
|
||||||
|
```
|
||||||
|
|
||||||
|
pasando la URI completa de `DATABASE_URL` a `pg_isready`. En algún momento
|
||||||
|
se agregó el query param `?sslmode=disable` al `DATABASE_URL` generado por
|
||||||
|
`create-commerce-secrets.sh` (repo `scripts`, branch
|
||||||
|
`fix/medusa-database-url-sslmode`, ya ejecutada contra el secret vivo del
|
||||||
|
cluster). Con ese query param, el parser de conninfo de `pg_isready` en la
|
||||||
|
imagen `postgres:17-alpine` no lograba interpretar la URI y devolvía:
|
||||||
|
|
||||||
|
```
|
||||||
|
postgres-svc:5432 - no attempt
|
||||||
|
```
|
||||||
|
|
||||||
|
`exit 3` — **`no attempt`** es un status propio de `pg_isready` que indica
|
||||||
|
que el cliente **ni siquiera intentó conectar** por un problema en los
|
||||||
|
parámetros de conexión (a diferencia de `no response`, que sí implica un
|
||||||
|
intento de conexión fallido). Esto se reprodujo de forma determinística
|
||||||
|
ejecutando el mismo comando **dentro del propio init container atascado**
|
||||||
|
(sin crear pods efímeros nuevos), y se aisló la causa probando la misma URI:
|
||||||
|
|
||||||
|
- con `pg_isready -h postgres-svc -p 5432 -U medusa -d medusa` (sin URI) →
|
||||||
|
`accepting connections`, exit 0.
|
||||||
|
- con la URI completa pero sin el `?sslmode=disable` → también exitosa.
|
||||||
|
- con la URI completa incluyendo `?sslmode=disable` → reproducía el fallo.
|
||||||
|
|
||||||
|
Como el `until` no tiene timeout ni backoff máximo, el init container
|
||||||
|
quedaba reintentando indefinidamente sin nunca fallar de forma visible
|
||||||
|
(de ahí que no apareciera como CrashLoopBackOff).
|
||||||
|
|
||||||
|
## Fix aplicado
|
||||||
|
|
||||||
|
`workloads/ecommerce/commerce/medusa.yaml`, init container
|
||||||
|
`wait-for-postgres`:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- until pg_isready -d "$DATABASE_URL" -t 5; do
|
||||||
|
+ until pg_isready -h postgres-svc -p 5432 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -t 5; do
|
||||||
|
```
|
||||||
|
|
||||||
|
`postgres-svc` y `5432` son fijos (el Service de Postgres); `POSTGRES_USER`
|
||||||
|
y `POSTGRES_DB` ya llegan al contenedor vía el mismo `envFrom:
|
||||||
|
secretRef: commerce-secrets`. El chequeo de disponibilidad deja de depender
|
||||||
|
por completo del formato de `DATABASE_URL`, incluyendo cualquier query
|
||||||
|
param futuro.
|
||||||
|
|
||||||
|
Commit `e9c93bf` en `main` de `apps-registry`, pusheado y sincronizado por
|
||||||
|
Argo CD (`ecommerce-app`, revisión `e9c93bfb82ec09b8ea2cc3468040ef9d72416542`,
|
||||||
|
sync a las `2026-08-10T05:29:32Z`). El pod atascado
|
||||||
|
`medusa-deploy-9d8784d7b-g9jql` fue reemplazado automáticamente por
|
||||||
|
`medusa-deploy-56c7c878b6-xgnkb`, que completó `wait-for-postgres` en
|
||||||
|
segundos y quedó `1/1 Running` sin intervención manual adicional.
|
||||||
|
|
||||||
|
## Lecciones aprendidas
|
||||||
|
|
||||||
|
**Separar el chequeo de disponibilidad (`wait-for-postgres`) de las
|
||||||
|
migraciones (`migrations`) en init containers distintos fue lo correcto** y
|
||||||
|
ya estaba así desde que se introdujo este init container
|
||||||
|
(commit `f4ad140`). Eso permitió aislar el fallo al primer init container
|
||||||
|
sin ambigüedad — la falla nunca llegó a tocar `migrations`. La lección no es
|
||||||
|
cambiar esa separación, sino mantenerla: cualquier chequeo de
|
||||||
|
disponibilidad debe usar el mínimo de parámetros necesarios (host/puerto/
|
||||||
|
usuario/db) en vez de reusar una URI de conexión pensada para la app,
|
||||||
|
que puede cambiar de formato por razones ajenas al chequeo.
|
||||||
|
|
||||||
|
**No fue necesario crear pods de diagnóstico efímeros, y no crearlos evitó
|
||||||
|
ruido adicional en el cluster.** Toda la reproducción y aislamiento de la
|
||||||
|
causa raíz (DNS, `pg_isready` con distintos argumentos, lectura de
|
||||||
|
`/etc/resolv.conf`) se hizo con `kubectl exec` sobre pods que ya existían
|
||||||
|
(el pod viejo sano, el pod nuevo atascado, y `commerce-postgres-0`). Crear
|
||||||
|
pods de prueba repetidos consume cuota (`ecommerce-quota` está en `pods:
|
||||||
|
7/12`, con margen pero no ilimitado) y puede introducir su propio ruido de
|
||||||
|
scheduling/red — como muestra `docs/known-issues.md`, el diagnóstico previo
|
||||||
|
del blackhole de MTU sí necesitó pods de prueba dedicados (186 intentos)
|
||||||
|
porque el fenómeno dependía del nodo de scheduling, algo que no se puede
|
||||||
|
observar desde un pod ya corriendo. La regla práctica: usar pods
|
||||||
|
existentes cuando el fallo es reproducible ahí, y reservar pods efímeros
|
||||||
|
para cuando la variable a probar (ej. nodo, imagen, versión) no se puede
|
||||||
|
cambiar en un pod ya desplegado — midiendo el impacto (cuota, ruido) de
|
||||||
|
cada corrida.
|
||||||
|
|
||||||
|
## Deuda técnica pendiente (no resuelta por este incidente)
|
||||||
|
|
||||||
|
El blackhole de red cross-node por MTU/PMTUD documentado en
|
||||||
|
`docs/known-issues.md` sigue sin arreglo de fondo (ajustar MTU a `1400` en
|
||||||
|
la red Docker/flannel del cluster k3d). El workaround actual es el
|
||||||
|
`podAffinity` en `medusa.yaml` que fija `medusa-deploy` al mismo nodo que
|
||||||
|
`commerce-postgres-0`.
|
||||||
|
|
||||||
|
Existe una branch abierta, `fix/medusa-wait-for-postgres`, que:
|
||||||
|
|
||||||
|
- Elimina ese `podAffinity`.
|
||||||
|
- Borra `docs/known-issues.md` por completo.
|
||||||
|
- **No incluye el fix de MTU real** (no toca configuración de flannel ni de
|
||||||
|
la red Docker del cluster).
|
||||||
|
|
||||||
|
Mergear esa branch tal como está day-1 reintroduciría el blackhole cross-node
|
||||||
|
sin red de contención y borraría la única documentación del problema. No se
|
||||||
|
tocó como parte de este incidente. Queda pendiente decidir si se cierra sin
|
||||||
|
mergear, o si se retoma agregando primero el fix real de MTU antes de poder
|
||||||
|
quitar el `podAffinity` con seguridad.
|
||||||
@@ -25,6 +25,16 @@ module.exports = defineConfig({
|
|||||||
jwtSecret: required("JWT_SECRET"),
|
jwtSecret: required("JWT_SECRET"),
|
||||||
cookieSecret: required("COOKIE_SECRET"),
|
cookieSecret: required("COOKIE_SECRET"),
|
||||||
},
|
},
|
||||||
|
// Todos los Ingress del lab terminan en Traefik por HTTP plano
|
||||||
|
// (entrypoint "web", sin TLS); Cloudflare es quien atiende HTTPS de
|
||||||
|
// cara al navegador. Con `secure: true` (el default en producción),
|
||||||
|
// express-session nunca emite Set-Cookie porque no detecta la
|
||||||
|
// conexión como HTTPS, y el dashboard admin queda con 401 en
|
||||||
|
// /admin/users/me pese a loguear bien. La API con Bearer token no se
|
||||||
|
// ve afectada por esto.
|
||||||
|
cookieOptions: {
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
admin: {
|
admin: {
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework";
|
||||||
|
import { ContainerRegistrationKeys } from "@medusajs/framework/utils";
|
||||||
|
|
||||||
|
export default async function revalidateStorefrontHandler({
|
||||||
|
container,
|
||||||
|
}: SubscriberArgs) {
|
||||||
|
const logger = container.resolve(ContainerRegistrationKeys.LOGGER);
|
||||||
|
|
||||||
|
const storefrontUrl = process.env.STOREFRONT_URL;
|
||||||
|
const secret = process.env.REVALIDATE_SECRET;
|
||||||
|
|
||||||
|
if (!storefrontUrl || !secret) {
|
||||||
|
logger.warn(
|
||||||
|
"STOREFRONT_URL o REVALIDATE_SECRET no configurados; se omite la revalidación del storefront.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${storefrontUrl}/api/revalidate`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "x-revalidate-secret": secret },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
logger.warn(
|
||||||
|
`Revalidación del storefront respondió HTTP ${response.status}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(
|
||||||
|
`No se pudo revalidar el storefront: ${(error as Error).message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config: SubscriberConfig = {
|
||||||
|
event: [
|
||||||
|
"product.updated",
|
||||||
|
"product-variant.updated",
|
||||||
|
"product-variant.created",
|
||||||
|
"product-variant.deleted",
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
const secret = request.headers.get("x-revalidate-secret");
|
||||||
|
|
||||||
|
if (!secret || secret !== process.env.REVALIDATE_SECRET) {
|
||||||
|
return Response.json({ message: "Invalid secret" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
revalidateTag("medusa-products");
|
||||||
|
|
||||||
|
return Response.json({ revalidated: true, tag: "medusa-products" }, { status: 200 });
|
||||||
|
}
|
||||||
@@ -9,10 +9,17 @@ import { getProductBySlug, getProducts } from "@/lib/headless/client";
|
|||||||
|
|
||||||
interface ProductPageProps { params: Promise<{ slug: string }> }
|
interface ProductPageProps { params: Promise<{ slug: string }> }
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
// generateStaticParams + revalidate quedaban aquí para pre-renderizar en
|
||||||
const products = await getProducts();
|
// build time, pero HEADLESS_PROVIDER/MEDUSA_* no existen en el contexto del
|
||||||
return products.map((product) => ({ slug: product.slug }));
|
// Docker build: el snapshot estático quedaba congelado con el catálogo mock
|
||||||
}
|
// (todos los productos con price: null) hasta que ISR lo revalidara. Con
|
||||||
|
// frontend-deploy corriendo 2 réplicas y sin cacheHandler compartido, cada
|
||||||
|
// pod revalida su propia caché de forma independiente, así que un producto
|
||||||
|
// podía mostrar "Consultar precio" en un pod y el precio real en el otro
|
||||||
|
// según cuál hubiera sido "calentado". force-dynamic elimina esa caché por
|
||||||
|
// pod y deja esta ruta en vivo contra Medusa, igual que el listado
|
||||||
|
// (dinámico por searchParams).
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export async function generateMetadata({ params }: ProductPageProps): Promise<Metadata> {
|
export async function generateMetadata({ params }: ProductPageProps): Promise<Metadata> {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ spec:
|
|||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: gitea-registry-secret
|
- name: gitea-registry-secret
|
||||||
|
|
||||||
|
affinity:
|
||||||
|
podAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: commerce-postgres
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: wait-for-postgres
|
- name: wait-for-postgres
|
||||||
image: postgres:17-alpine
|
image: postgres:17-alpine
|
||||||
@@ -28,7 +36,7 @@ spec:
|
|||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
until pg_isready -d "$DATABASE_URL" -t 5; do
|
until pg_isready -h postgres-svc -p 5432 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -t 5; do
|
||||||
echo "postgres no disponible aun, reintentando..."
|
echo "postgres no disponible aun, reintentando..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
@@ -44,7 +52,7 @@ spec:
|
|||||||
memory: 64Mi
|
memory: 64Mi
|
||||||
|
|
||||||
- name: migrations
|
- name: migrations
|
||||||
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.85
|
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.88
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command:
|
command:
|
||||||
- npx
|
- npx
|
||||||
@@ -65,7 +73,7 @@ spec:
|
|||||||
|
|
||||||
containers:
|
containers:
|
||||||
- name: medusa
|
- name: medusa
|
||||||
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.85
|
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.88
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ stringData:
|
|||||||
|
|
||||||
JWT_SECRET: CAMBIAR
|
JWT_SECRET: CAMBIAR
|
||||||
COOKIE_SECRET: CAMBIAR
|
COOKIE_SECRET: CAMBIAR
|
||||||
|
|
||||||
|
# Debe ser el MISMO valor que REVALIDATE_SECRET en el Secret
|
||||||
|
# commerce-storefront: el subscriber de Medusa lo envía como header
|
||||||
|
# x-revalidate-secret al invocar POST /api/revalidate en el storefront.
|
||||||
|
REVALIDATE_SECRET: CAMBIAR
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
@@ -30,3 +35,6 @@ metadata:
|
|||||||
type: Opaque
|
type: Opaque
|
||||||
stringData:
|
stringData:
|
||||||
MEDUSA_PUBLISHABLE_KEY: pk_CAMBIAR_DESPUES_DE_CREARLA_EN_MEDUSA
|
MEDUSA_PUBLISHABLE_KEY: pk_CAMBIAR_DESPUES_DE_CREARLA_EN_MEDUSA
|
||||||
|
|
||||||
|
# Mismo valor que REVALIDATE_SECRET en commerce-secrets.
|
||||||
|
REVALIDATE_SECRET: CAMBIAR
|
||||||
|
|||||||
@@ -16,9 +16,19 @@ spec:
|
|||||||
- name: gitea-registry-secret
|
- name: gitea-registry-secret
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.77
|
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.89
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
|
env:
|
||||||
|
- name: HEADLESS_PROVIDER
|
||||||
|
value: medusa
|
||||||
|
- name: MEDUSA_BACKEND_URL
|
||||||
|
value: http://medusa-svc:9000
|
||||||
|
- name: MEDUSA_REGION_ID
|
||||||
|
value: reg_01KZT86WPAH7A7ZZRDSX3350V2
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: commerce-storefront
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
|||||||
@@ -258,6 +258,14 @@ function backendUrl(): string {
|
|||||||
return value.replace(/\/$/, "");
|
return value.replace(/\/$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function regionId(): string {
|
||||||
|
const value = process.env.MEDUSA_REGION_ID;
|
||||||
|
if (!value) {
|
||||||
|
throw new Error("MEDUSA_REGION_ID no está configurado.");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
async function medusaFetch<T>(
|
async function medusaFetch<T>(
|
||||||
path: string,
|
path: string,
|
||||||
searchParams?: URLSearchParams,
|
searchParams?: URLSearchParams,
|
||||||
@@ -515,7 +523,10 @@ async function fetchProducts(
|
|||||||
): Promise<Product[]> {
|
): Promise<Product[]> {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
limit: String(limit),
|
limit: String(limit),
|
||||||
currency_code: "cop",
|
// El validador de /store/products no acepta currency_code (400
|
||||||
|
// "Unrecognized fields"); el precio calculado solo se resuelve con
|
||||||
|
// region_id.
|
||||||
|
region_id: regionId(),
|
||||||
fields:
|
fields:
|
||||||
"+metadata,+images,+tags,+categories,+collection,+variants.inventory_quantity,+variants.calculated_price,+variants.options",
|
"+metadata,+images,+tags,+categories,+collection,+variants.inventory_quantity,+variants.calculated_price,+variants.options",
|
||||||
});
|
});
|
||||||
@@ -543,7 +554,10 @@ export const medusaCatalogProvider: CatalogProvider = {
|
|||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
handle: slug,
|
handle: slug,
|
||||||
limit: "1",
|
limit: "1",
|
||||||
currency_code: "cop",
|
// El validador de /store/products no acepta currency_code (400
|
||||||
|
// "Unrecognized fields"); el precio calculado solo se resuelve con
|
||||||
|
// region_id.
|
||||||
|
region_id: regionId(),
|
||||||
fields:
|
fields:
|
||||||
"+metadata,+images,+tags,+categories,+collection,+variants.inventory_quantity,+variants.calculated_price,+variants.options",
|
"+metadata,+images,+tags,+categories,+collection,+variants.inventory_quantity,+variants.calculated_price,+variants.options",
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user