Versiones y actualizaciones
DeviceChain se distribuye como un conjunto de imágenes de contenedor precompiladas y versionadas, más un chart de Helm. No necesita compilar nada para ejecutarlo: descargue una versión publicada, instale el chart y actualice in situ sin tiempo de inactividad.
Hasta ahora, tres puntos de la historia exigen recrear la instancia en lugar de actualizarla:
- La
v0.9.0reemplazó la cadena de migraciones de cada servicio por una única línea base congelada, por lo que una base de datosv0.8.xfalla conalready existsal encontrarla. Consulte La compactación de la línea base de la v0.9.0. - La
v0.10.0cambió la clave primaria de las tablas de eventos para corregir un defecto que descartaba telemetría de forma silenciosa. Consulte El cambio de clave de eventos de la v0.10.0. - Cualquier instancia creada por la
v0.16.0o una versión anterior, que no registró ninguna declaración de lo que es la instancia: el registro que una actualización lee ahora para saber qué desplegar. Consulte Instancias creadas por la v0.16.0 y anteriores.
Si está en cualquiera de ellos, lea la sección correspondiente más abajo antes de hacer nada más.
La v0.12.0 se actualiza en el sitio, pero cambia el tema en el que un dispositivo responde
a un comando, mueve un permiso y cambia varias cosas cuya forma se mantuvo igual. Un
La actualización informará éxito en cualquier caso. Lea
v0.12.0: una actualización que cambia contratos antes de empezar.
Esto se aplica a cualquier actualización que cruce la v0.12.0, no solo a la que se
detiene ahí: pasar de la v0.11.0 directamente a un parche posterior no omite esos cambios.
Modelo de versionado
Cada versión es una única etiqueta git de versión semántica (vX.Y.Z). Ese único número cubre
todo en conjunto: cada imagen de servicio, el operador, el chart de Helm y la CLI
dcctl se publican todos con la misma versión. No hay desfase de versión por servicio
del que preocuparse: un despliegue es un único número coherente.
Un solo comando mueve todo ello a la vez: el operador no forma parte del chart, así que algo externo al chart tiene que ser lo que mueva ambas cosas. Consulte Actualizaciones sin tiempo de inactividad para el procedimiento.
- Las versiones estables son
vX.Y.Z(por ejemplo,v1.2.0). La etiqueta:latestsigue a la versión estable más reciente. - Las versiones preliminares son
vX.Y.Z-rc.N(por ejemplo,v1.2.0-rc.1). Estas nunca mueven:latest.
Estabilidad previa a 1.0
Hasta la v1.0.0, cualquier versión —incluida una versión de parche— puede cambiar APIs, esquemas o comportamiento sin una capa de compatibilidad. Esto es deliberado: mientras el modelo de datos aún se está asentando, preferimos un cambio limpio antes que cargar con una capa de compatibilidad que tendríamos que mantener para siempre.
Cada cambio disruptivo se indica al principio de las notas de esa versión. Léalas antes de actualizar. Son la lista autorizada; el número de versión por sí solo no le indica si una versión es segura para su despliegue.
En concreto, antes de la v1.0.0 debe esperar que una versión pueda:
- endurecer la validación, de modo que una solicitud que antes tenía éxito ahora sea rechazada, por lo general porque se estaba aceptando silenciosamente o descartando silenciosamente
- cambiar o eliminar un campo GraphQL, en lugar de marcarlo obsoleto durante un ciclo
- alterar el esquema de la base de datos de formas que una reversión no deshará
- reemplazar por completo la línea base de migraciones, lo que elimina por entero la ruta de
actualización en lugar de limitarse a hacerla unidireccional. Cuando eso ocurre, las notas de la versión lo
indican al principio, y la única vía es recrear la instancia. La
v0.9.0y lav0.10.0son versiones de este tipo - dejar de admitir la actualización desde una instancia más antigua por un motivo que no
tiene nada que ver con el esquema: la versión posterior a la
v0.16.0lee un registro de lo que es una instancia que las versiones anteriores nunca escribieron, y se niega en lugar de inventárselo
La propiedad de "actualizar in situ sin tiempo de inactividad" descrita arriba describe la mecánica de una actualización progresiva. No es una promesa de que sus llamadas a la API existentes conserven el mismo significado a través de un incremento de versión previo a 1.0.
Una vez que se publique la v1.0.0, esta sección se reemplaza por una promesa de compatibilidad de versionado semántico normal: cambios disruptivos solo en una versión mayor.
Debido a que las versiones son frecuentes antes de la disponibilidad general (GA), la versión menor marca un hito (una funcionalidad o subsistema significativo que se lanza) y la versión de parche lleva el ritmo continuo de correcciones y endurecimiento. Una versión de parche no es automáticamente una actualización de bajo riesgo durante este período; nuevamente, las notas de la versión son las que se lo indican.
Imágenes
Las imágenes se publican en el Registro de Contenedores de GitHub público bajo
ghcr.io/devicechain-io, por ejemplo, ghcr.io/devicechain-io/device-management. Son
multiarquitectura (linux/amd64 y linux/arm64) y se construyen sobre una base distroless sin
privilegios de root, por lo que se ejecutan como un usuario sin privilegios, sin shell y con una superficie de ataque mínima.
Debido a que el registro es público, no se requieren credenciales para descargar las imágenes publicadas.
Instalación de una versión específica
Fije la etiqueta de imagen a la versión que desea:
DC_ROOT_KEY, más abajo, es la clave raíz del almacén de secretos de la instancia:
la requiere el perfil default, se genera una sola vez con openssl rand -base64 32 y se
pasa sin cambios en cada instalación y actualización. Consulte
Desplegando con Helm para saber por qué.
Sustituya <version> por una etiqueta realmente publicada —la
página de versiones las lista, y un
valor no publicado falla al descargar la imagen, no en el momento de la instalación—.
helm install dc deploy/helm/devicechain \
--set instance.id=devicechain \
--set instance.config.infrastructure.secrets.rootKey="$DC_ROOT_KEY" \
--set image.tag=<version>
El chart de Helm en sí también se publica como un artefacto OCI, por lo que puede instalarlo sin una
copia local del repositorio. El chart se versiona por separado de las imágenes y no lleva la
v inicial; helm show chart oci://ghcr.io/devicechain-io/charts/devicechain imprime la
última, y --version rechaza cualquier valor que nunca se haya publicado:
helm install dc oci://ghcr.io/devicechain-io/charts/devicechain \
--version <chart-version> \
--set instance.id=devicechain \
--set instance.config.infrastructure.secrets.rootKey="$DC_ROOT_KEY" \
--set image.tag=<version>
El chart también está publicado en Artifact Hub, que muestra cada versión publicada junto con sus valores predeterminados y sus plantillas renderizadas.
Actualizar una instalación hecha solo con el chart
Una instancia instalada con helm install en lugar de dcctl bootstrap se actualiza con
helm upgrade, y conserva una trampa que la ruta de dcctl no tiene.
dcctl upgrade no se aplica a ella. Ese comando vuelve a leer del clúster la declaración de
una instancia y su documento de configuración, y una instalación hecha solo con el chart no
tiene ninguno de los dos; tampoco instala ningún operador, así que no hay una segunda mitad
que mover.
El release de abajo se llama dc porque ese es el nombre que eligió el helm install de más
arriba. Una instancia instalada con dcctl bootstrap lleva un release con el nombre de la
instancia —devicechain se instala como dc-devicechain—, así que cualquier comando helm
dirigido a una de esas necesita ese nombre en su lugar.
helm get values dc -n default -o yaml > dc-values.yaml
helm upgrade dc deploy/helm/devicechain \
-n default \
-f dc-values.yaml \
--set image.tag=<new-version>
rm dc-values.yaml # este archivo contiene los secretos de su instancia
--set image.tag=… por sí solo no funcionaráLa regla de Helm es la trampa. Una actualización que no pasa ningún valor reutiliza los que
ya están en la versión desplegada. Pero en cuanto pasa cualquier valor —incluido el único
--set que cambia la versión, que es justamente el objetivo de una actualización— Helm parte
de los valores predeterminados del chart y todo lo que usted fijó al instalar desaparece. Eso
incluye la clave raíz de la instancia, sin la cual los secretos almacenados de una instancia en
funcionamiento no se pueden leer.
Cuando eso ocurre no se corrompe nada, porque el chart se niega a renderizar sin la clave raíz:
Error: UPGRADE FAILED: execution error at (devicechain/templates/instance-config.yaml:27:4): instance.config.infrastructure.secrets.rootKey is required: area "notification-management" owns an envelope-encrypted secret store and cannot form its KEK without it, so it would crash-loop. Set it to a base64 256-bit key (openssl rand -base64 32); dcctl bootstrap mints one automatically.
--reuse-values también funciona, pero conserva en silencio entradas obsoletas cuando los
valores predeterminados del chart cambian entre versiones, así que es preferible volcar los
valores y pasarlos con -f, donde puede verlos.
Actualizaciones sin tiempo de inactividad
Actualizar una instancia que usted arrancó con el bootstrap es un solo comando, y el chart
y los servicios están diseñados para hacer avanzar a los clientes sin perder tráfico. Hay
cuatro excepciones, todas documentadas más abajo: la transición a la ingesta duradera, que
sigue siendo una actualización corriente pero tiene un efecto secundario visible, y la
v0.9.0, la v0.10.0 y cualquier instancia creada por la v0.16.0 o una versión anterior,
a las que no se puede actualizar en absoluto. Consulte las notas de la versión a la que va a
migrar antes de ejecutarlo:
dcctl upgrade local devicechain --version <new-version>
Una versión de DeviceChain es un único número que abarca las imágenes de los servicios, el
chart, el operador y dcctl, y ese comando los mueve todos juntos, en el orden en que tienen
que moverse:
- el operador — su namespace, sus CRD, su RBAC y su controlador, aplicados a partir de
manifiestos incrustados en
dcctl. No forma parte del chart de Helm, así que nada dentro del chart puede alcanzarlo. Se aplica el flujo renderizado completo y no solo la imagen del controlador, porque los CRD van en él: un esquema que se quedara en la versión con la que se arrancó la instancia descartaría en silencio cualquier campo que añadiera una versión posterior; - el documento de configuración del que cada servicio lee sus credenciales y sus
endpoints, recompuesto a partir del chart de esta versión y escrito por
dcctl, que es su dueño; - la versión desplegada de Helm que ejecuta los servicios, que los hace avanzar a las imágenes nuevas y espera a que cada área termine.
Ejecútelo con --dry-run primero si quiere ver qué movería. Toma el clúster de destino del
propio registro de la instancia en lugar de adivinarlo, y dice cuál es.
dcctl upgrade conserva aquello sobre lo que la instancia está funcionando: las contraseñas
propietarias de las bases de datos, la autoridad y los inicios de sesión del bróker, el secreto
entre servicios, la clave raíz del almacén de secretos y el secreto de cliente del inicio de
sesión único. Un cambio de versión no puede convertirse en un cambio de credenciales.
Esto está verificado, no solo afirmado. Se comprobó la actualización de una instancia en funcionamiento comparando un resumen criptográfico (digest) de cada una de esas credenciales antes y después, y lo único que había cambiado era la etiqueta de imagen: en todos los servicios, en la consola y en el operador.
Como las conserva por diseño, no rota nada. Si necesita cambiar una credencial, una actualización no lo hará — y para varias de ellas hoy no existe un procedimiento admitido.
El perfil, la topología y las áreas funcionales habilitadas provienen de la propia declaración
de la instancia —lo que dcctl bootstrap registró en el clúster—, no de flags escritos aquí.
Cambiar lo que una instancia es es una pregunta distinta con respuestas distintas: subir el
número de réplicas, por ejemplo, no vuelve a replicar los streams de mensajería que se crearon
con el número anterior.
Otras dos cosas quedan deliberadamente fuera de este comando. No ejecuta la aplicación de infraestructura, porque dos de las entradas de esa aplicación no se pueden recuperar del clúster: los nombres de endpoint y de bucket de un destino de respaldo externo, y el texto claro del secreto de cliente del inicio de sesión único. Y no toca las bases de datos más allá de dejar que los servicios ejecuten sus propias migraciones.
Qué más comprueba una actualización
Dos cosas viajan con ella, porque un cambio de versión es lo que le ocurre de forma fiable a una instancia en funcionamiento, y un calendario no.
El certificado del bróker. El bróker de mensajería sirve un certificado válido durante un
año, emitido por una autoridad que dcctl acuña en el arranque inicial y conserva en el
clúster. Una actualización vuelve a emitir ese certificado cuando está dentro de sus últimos 30
días, o cuando ya no cubre todos los nombres por los que los bróker se llaman entre sí —que es
lo que le hace escalar una instancia a un certificado que, por lo demás, sigue holgadamente en
vigor. La reemisión se hace bajo la misma autoridad, así que nada tiene que volver a
confiar en nada, y el bróker se reinicia para que sirva de verdad el certificado nuevo en lugar
de conservar el antiguo hasta que algo ajeno lo reinicie. Fuera de esas condiciones la
comprobación se ejecuta y no hace nada.
Una instancia arrancada antes de que dcctl conservara esa autoridad no puede tener su
certificado reemitido en sitio. La actualización lo indica y continúa en lugar de fallar;
recrear la instancia es lo que acuña una autoridad y un certificado nuevos.
El depósito (escrow) de la clave raíz. Cada actualización comprueba que el artefacto de depósito de esta instancia siga protegiendo la clave sobre la que la instancia está realmente funcionando. Esa comprobación no necesita contraseña: el artefacto registra una huella de la clave que protege, así que compararla con la que está en uso no abre nada.
| Qué encuentra | Qué hace |
|---|---|
| El artefacto protege la clave en uso | Lo indica y lo deja intacto |
| El artefacto protege una clave distinta | Avisa con claridad. Lo más habitual es que pertenezca a una instancia anterior con el mismo nombre, y restaurar desde él recuperaría un clúster incapaz de leer sus propios secretos |
| No hay artefacto | Escribe uno, si usted pasó --escrow-passphrase-file (o fijó DCCTL_ESCROW_PASSPHRASE). Si no, avisa de que la única copia de la clave raíz está dentro del clúster |
Así es como una instancia creada al principio con --no-escrow obtiene un depósito más tarde.
Ninguno de esos desenlaces hace fallar la actualización: un problema de depósito trata de un
desastre futuro y la actualización que tiene delante trata de la instancia en funcionamiento, y
un operador que no puede actualizar rodeará la comprobación en lugar de arreglarla.
helm upgradeEl procedimiento era: volcar a un archivo los valores de la versión desplegada actual con helm get values, volver a pasarlos con -f junto a la nueva etiqueta de imagen, borrar el archivo
porque contenía sus secretos, y después ejecutar dcctl upgrade una segunda vez para el
operador.
Ese baile existía únicamente porque la versión desplegada de Helm era donde vivían las
credenciales generadas de la instancia, y Helm parte de los valores predeterminados del chart
en cuanto se le pasa cualquier valor —de modo que una actualización que no las trasladara a
mano las perdía. Ahora dcctl es el dueño del documento de configuración, la versión desplegada
ya no contiene esas credenciales, y el paso que le decía que escribiera sus secretos en un
archivo simplemente desaparece.
También cierra un hueco que tenía la forma de dos comandos: una actualización que se detenía
tras la mitad de helm dejaba los servicios nuevos ejecutándose contra el controlador con el
que se arrancó la instancia por primera vez, indefinidamente y sin ningún error que lo
indicara.
Lo que hace que el despliegue sea seguro:
- Aumentar antes de terminar. Cada Deployment usa una estrategia
RollingUpdateconmaxUnavailable: 0ymaxSurge: 1, de modo que un pod nuevo debe pasar su sonda de disponibilidad/readyzantes de que se elimine un pod antiguo. La capacidad nunca disminuye durante el despliegue. - Apagado ordenado / drenaje de conexiones. Cuando se le pide a un pod que termine, primero
informa "no listo" (de modo que el Service deje de enrutarle nuevas solicitudes), espera una breve
ventana de drenaje para que ese cambio se propague, y solo entonces termina el trabajo en curso y
se apaga. Configure la ventana con
shutdownDrainSeconds(por defecto5), mantenida de forma segura por debajo determinationGracePeriodSeconds(por defecto30). Ambos forman un único presupuesto y los servicios lo verifican: el drenaje puede ocupar como máximo la mitad del período de gracia, porque la ventana solo espera — terminar las solicitudes en curso, drenar los consumidores del bróker y cerrar el pool de base de datos ocurren después de ella, y el kubelet envía SIGKILL cuando el período de gracia expira, haya terminado eso o no. Una ventana mayor se rechaza al arrancar el servicio (y endcctl bootstrap, antes de instalar nada), en lugar de descubrirse cuando un pod ya se está apagando. PongashutdownDrainSeconds: 0para omitir el drenaje por completo, lo que encaja con una ejecución de una sola instancia sin ningún Service del que retirarse. - Migraciones de esquema coordinadas. Los servicios ejecutan migraciones de base de datos bajo un bloqueo a nivel de base de datos, de modo que cuando varias réplicas se inician a la vez, exactamente una aplica las migraciones y el resto espera; sin condiciones de carrera, sin DDL duplicado.
Para lograr un verdadero cero tiempo de inactividad, ejecute replicas: 2 (o más) para cada área, de modo que el despliegue siempre tenga
un pod activo sirviendo tráfico. Una sola réplica igualmente tiene una breve brecha mientras se reemplaza su único pod.
Configúrelo globalmente con --set replicas=2, o por área bajo
functionalAreas.<area>.replicas. Un PodDisruptionBudget se genera automáticamente para cualquier
área con más de una réplica, de modo que los drenajes de nodo no puedan expulsar a todas las réplicas a la vez.
La compactación de la línea base de la v0.9.0
La v0.9.0 es la primera de las dos versiones a las que no se puede llegar actualizando en sitio
(la otra es la v0.10.0).
Antes de ella, el esquema de cada servicio se construía mediante una cadena de migraciones aplicadas en orden.
La v0.9.0 reemplaza todas esas cadenas por una única línea base congelada: una migración por servicio que
crea el esquema completo tal y como está. Una base de datos creada por v0.8.x ya aplicó la cadena antigua, así
que cuando se encuentra con la línea base intenta crear tablas que ya existen y falla con already exists. El
fallo es evidente y ocurre en el arranque; no corrompe nada.
No hay ruta de migración y, antes de la v1.0.0, no la habrá. Mantener una capa de compatibilidad para una forma
de esquema que todavía se está asentando es precisamente el coste que este proyecto ha decidido no asumir
mientras todas las instalaciones siguen siendo tempranas.
Para pasar a la v0.9.0, recree la instancia:
# Exporte antes lo que necesite: esto descarta las bases de datos.
dcctl destroy local devicechain
dcctl bootstrap local devicechain
La protección de destrucción protege las bases de datos frente a una operación normal de
helm, no frente a un dcctl destroy deliberado. Si la instancia contiene telemetría, definiciones de
dispositivos o paneles que le importan, expórtelos antes de empezar. No existe una ruta in situ que los
conserve a través de esta versión.
Normalmente, un cambio de esquema añade una nueva migración sobre la línea base, lo que es una actualización in situ corriente. Esa es la regla, y se cumple en casi todas las versiones.
Decía que la compactación describía «una única versión, no una nueva política». Después, la
v0.10.0 también necesitó recrear la instancia, por un motivo distinto. La versión honesta de la
regla es: añadir migraciones es lo normal y, antes de la v1.0.0, una versión todavía puede exigir
recrear la instancia cuando un defecto no se puede corregir de otra forma. Toda versión que lo
exija lo indicará en sus notas y aquí. Consulte ambas antes de actualizar, en lugar de deducirlo
del número de versión.
El cambio de clave de eventos de la v0.10.0
La v0.10.0 es la segunda versión a la que no se puede llegar actualizando en sitio, por un motivo
distinto al de la compactación.
Un evento se identificaba por la combinación de su inquilino, dispositivo, tipo y marca de tiempo. Esa combinación no es única: un dispositivo que muestrea dos sensores y publica cada uno como su propio mensaje bajo una misma marca de tiempo produce dos eventos realmente distintos que a la base de datos le parecen idénticos. El segundo se descartaba de forma silenciosa: sus lecturas quedaban guardadas contra el registro del primero y, una vez descartado, ya nunca podía reconocerse como repetido, de modo que cada reintento posterior de ese mensaje añadía otra copia de sus lecturas.
Cualquier dispositivo que marque el tiempo en segundos enteros podía provocarlo emitiendo dos veces en un mismo segundo, y el SDK de .NET publicado lo hacía así hasta esta versión.
La v0.10.0 otorga a cada evento, a cada lectura y a cada registro de relación una identidad
derivada de su propio contenido, y convierte esa identidad en la clave. Almacenar la telemetría
correctamente implica cambiar la clave primaria de las tablas más grandes del sistema, y esas tablas
están comprimidas: un motor de base de datos no altera una clave sobre datos comprimidos in situ. No
existe ninguna ruta de actualización que conserve las filas existentes.
Para migrar a la v0.10.0, recree la instancia:
# Exporte antes lo que necesite: esto descarta las bases de datos.
dcctl destroy local devicechain
dcctl bootstrap local devicechain
Se aplica la misma advertencia que más arriba: recrear la instancia descarta su telemetría, las definiciones de dispositivos y los paneles. Exporte lo que necesite antes de empezar.
La acompañan dos cambios en cómo la API informa del tiempo, y ninguno requiere acción alguna:
- Las marcas de tiempo se devuelven ahora con la precisión con la que se registraron; antes se redondeaban hacia abajo al segundo entero al salir, por lo que dos lecturas separadas por 200 milisegundos volvían pareciendo simultáneas. Las marcas de tiempo de segundo entero no cambian.
- Las peticiones que usan el valor
updatedAtde un registro para evitar sobrescribir la edición de otra persona se comprueban ahora con esa misma precisión. Antes, dos ediciones dentro de un mismo segundo podían superar ambas la comprobación, y la posterior sobrescribía en silencio un cambio que nunca había visto.
v0.11.0: de nuevo una actualización normal
La v0.11.0 es la primera versión desde la v0.8.5 a la que se puede llegar en sitio.
Su cambio de esquema añade tres migraciones en lugar de reemplazar una línea base, por lo que
una base de datos v0.10.0 existente se traslada con sus filas intactas en vez de tener que
recrearse.
Lo que llega a la base de datos:
- dos tablas nuevas que registran el progreso y el historial de la eliminación de un inquilino, y
- dos columnas en la tabla de inquilinos que siguen su estado de ciclo de vida.
Todos los inquilinos que ya existen quedan en el estado activo normal al añadirse la columna, así que nada cambia en una instancia en funcionamiento hasta que elimine realmente un inquilino.
Antes de la publicación se ejecutaron dos comprobaciones, y conviene mantenerlas separadas porque midieron cosas distintas.
Las migraciones, contra una base de datos con datos dentro. Se construyó un esquema v0.10.0,
se llenó con filas representativas y se trasladó hacia adelante. Todas esas filas llegaron
idénticas byte a byte, y el esquema resultante es idéntico al de una instalación nueva de
v0.11.0, para todas las áreas funcionales, no solo para la que cambió.
La actualización en sí, sobre una instancia en funcionamiento. Se construyó una instancia
v0.10.0 a partir de las imágenes v0.10.0 publicadas, se le dieron inquilinos e identidades
reales y luego se actualizó con el comando de arriba. Todos los servicios se desplegaron, el
recuento de filas de las 67 tablas no varió salvo por las nuevas entradas de migración y los
registros de auditoría que escribieron, el inicio de sesión siguió funcionando para una cuenta
creada bajo v0.10.0, y la nueva API de eliminación de inquilinos respondió en la instancia
actualizada.
Cuatro límites, indicados con claridad:
- Solo se verificaron las bases de datos. El estado del bróker (JetStream), el almacenamiento de objetos y el estado clave-valor no están cubiertos por ninguna de las dos comprobaciones.
- Solo PostgreSQL 16. Las instalaciones nuevas se verifican en ambas versiones principales admitidas; la ruta de actualización en sí se midió en la 16.
- La comparación fila a fila proviene de la primera comprobación, no de la segunda. La instancia en funcionamiento se verificó por recuentos de filas, que no detectarían una fila modificada en el sitio en lugar de eliminada.
- La consola web se dejó con su imagen
v0.10.0durante la segunda comprobación, así que la consolav0.11.0no se ejercitó contra una instancia actualizada.
v0.12.0: una actualización que cambia contratos
Se puede llegar a la v0.12.0 en sitio. Su cambio de esquema añade
migraciones en lugar de reemplazar una línea base, así que una base de datos v0.11.0
existente se conserva con sus filas intactas, y esto se midió sobre una instancia en
ejecución en lugar de razonarse.
Lo que sí cambia son los contratos: el tema MQTT en el que un dispositivo responde a un comando, unas cuantas operaciones GraphQL y el significado de varias cosas cuya forma no cambió en absoluto. Nada de eso se ve en una actualización que informa éxito, así que lea esta sección antes de ejecutarlo.
Haga esto antes de actualizar
1. Actualice todo dispositivo que responda a comandos. El tema en el que un dispositivo publica la respuesta a un comando ahora está acotado a ese dispositivo:
# antes
{instanceId}/{tenant}/command-responses
# ahora
{instanceId}/{tenant}/command-responses/{deviceToken}
Las credenciales que se emiten a un dispositivo ya no permiten el tema anterior, así que a un dispositivo sin actualizar se le rechazarán las respuestas en el broker: seguirá recibiendo los comandos y actuando sobre ellos, pero la plataforma nunca registrará que lo hizo, y todos acabarán leyéndose como caducados por tiempo.
El motivo del cambio es que el tema anterior permitía que cualquier dispositivo del inquilino publicara una respuesta nombrando cualquier comando, incluido uno emitido a otro dispositivo. Nada en la respuesta decía quién la enviaba, así que nada podía distinguirlo. El token del dispositivo forma ahora parte del tema, que forma parte de lo que el broker firma, de modo que un dispositivo solo puede responder por sí mismo.
Actualice primero los dispositivos si puede. Las respuestas enviadas en el tema anterior durante la transición se rechazan, no se encolan, y el pequeño número de respuestas en vuelo justo en el momento de la actualización se descarta en lugar de entregarse.
2. Renombre una fuente de eventos cuyo id sea exactamente lwm2m. Ese es el valor bajo
el que el servicio LwM2M archiva la presencia de sus propios dispositivos, y los registros
de presencia se comparan por igualdad exacta, así que su fuente y ese servicio se
sobrescriben mutuamente las filas. event-sources ahora se niega a arrancar con ese id, lo
que detiene toda la ingesta de la instancia.
Un id que solo se lee como un transporte, como sparkplug:plant-a o lwm2m:site-a, ahora
arranca con una advertencia en lugar de negarse. Renómbrelos cuando le venga bien. En ambos
casos tenga en cuenta la trampa al renombrar: la presencia ya registrada bajo el id anterior
no se traslada, y nada la rellena después.
3. Compruebe quién lee el historial de ubicaciones. Las consultas que devuelven
posiciones de dispositivos ahora requieren el permiso location:read en lugar de
event:read. Ese permiso no está en la base de solo lectura que recibe un visor, así que
una cuenta que podía leer el historial de posiciones en la v0.11.0 no puede en la
v0.12.0. Concédalo explícitamente a los roles que lo necesiten.
Ese mismo permiso pasa a controlar además la vista previa de una regla que comprueba la
contención en una geocerca. Una vista previa de ese tipo devuelve, por dispositivo, cuándo
entró en una región y cuándo salió — una lectura de posición, se pida como se pida — así que
previewRule requiere location:read además del device:read que toma toda vista previa. A
un autor de reglas que en la v0.11.0 podía previsualizar cualquier borrador se le rechazan
los borradores con contención hasta que se le conceda ese permiso. Las vistas previas que no
comprueban contención no se ven afectadas.
Y compruebe quién lo lee a través de un asistente de IA. El servidor MCP incorporó una
herramienta query_locations que devuelve las posiciones reportadas de un dispositivo, y llegar
a ella exige dos concesiones que se mantienen separadas de forma deliberada. La autorización
del agente debe incluir un alcance OAuth nuevo, location, y la persona que lo autorizó debe
tener un rol que conceda location:read. Ninguna de las dos basta por sí sola: el alcance es un
techo sobre lo que un token puede portar, no una concesión de nada.
Un agente autorizado solo con read-only no puede leer posiciones, por mucho que tenga su
usuario. Ese es justamente el sentido de un alcance aparte en lugar de un read-only más
amplio: la pantalla de consentimiento le muestra a la persona la cadena de alcance en crudo, así
que meter la posición dentro de read-only habría significado una autorización idéntica antes y
después que ahora incluye dónde han estado los dispositivos — y, con bastante frecuencia, dónde
han estado las personas que los llevan. Mantenerlo separado hace que conceder observabilidad a
un agente no sea el mismo acto que concederle el historial de ubicaciones, y permite a un
usuario permitir lo uno reteniendo lo otro.
Un cliente MCP que ya tenga registrado seguirá funcionando y seguirá recibiendo un rechazo en
las posiciones hasta que su petición de autorización pida read-only location y el usuario lo
vuelva a autorizar. La base del visor no cambia: location:read sigue sin ser algo que un
miembro reciba de forma predeterminada. Vea Acceso de IA (MCP).
4. Busque estas operaciones GraphQL en cualquier cosa que haya escrito contra la API:
| Operación | Qué cambió |
|---|---|
createCommand | Devuelve CreateCommandResult! en lugar de Command!. El comando está ahora bajo un campo command, junto a un campo rejection que explica un rechazo. |
updateDeviceType | Su argumento request es ahora un DeviceTypeUpdateRequest! obligatorio, y con él cambió la semántica: esto es una actualización parcial. Un campo omitido ahora CONSERVA su valor almacenado en lugar de borrarlo, y un null explícito lo limpia. Así que un cliente que limpiaba un campo dejándolo fuera ahora tiene que enviarle null — y, en el otro sentido, renombrar un tipo ya no desvincula el perfil a través del cual sus dispositivos resuelven sus capacidades. Un cliente escrito contra el comportamiento anterior de registro completo — uno que lee el tipo y devuelve todos los campos — sigue funcionando y sigue escribiendo lo que envía. token también ha desaparecido de la entrada, así que una actualización ya no puede mover el token de un tipo. Los campos no reconocidos dentro de la solicitud se rechazan en lugar de ignorarse. |
assertedActiveDeviceStates | Sustituida por assertedDeviceStates, que toma activeOnly y pagina mediante afterId y pageSize. |
deviceCredentials, deviceCredentialsById, deviceCredentialsByToken | Ahora requieren device:write. Para un tipo de credencial el identificador legible es el token portador, así que device:read — que tiene todo miembro habilitado — bastaba para abrir una sesión en el broker como cualquier dispositivo del inquilino. |
locationEvents | Ahora requiere location:read, como arriba. |
geoFenceSetSnapshot, currentGeoFenceSet | Su campo fences ahora está paginado: toma un argumento pagination obligatorio y devuelve results junto a un registro pagination, en lugar de una lista simple. Lea páginas hasta que pageEnd alcance totalRecords. Un conjunto de geocercas en los límites documentados es mayor de lo que puede transportar una sola respuesta, así que la forma de lista no podía devolverse en absoluto para los inquilinos con más probabilidad de pedirla. |
Cualquier consulta ...ById(ids: []) | Una lista de ids vacía ahora no devuelve nada. Antes devolvía la tabla entera, sin paginar. |
5. Deje de rellenar los ids con ceros a la izquierda. Un argumento id se interpreta
ahora como un número decimal y nada más. Antes se interpretaba deduciendo la base del propio
literal, así que un "017" rellenado con ceros — exactamente lo que envía un cliente que
formatea los ids a un ancho fijo — se leía como octal y resolvía a la fila 15: la entidad
equivocada, devuelta con éxito y sin ningún error que lo delatara. "0x2", "0b101" y "1_0"
se aceptaban de la misma manera. Las cuatro formas se rechazan ahora de plano. Envíe "17".
6. Cuente con que todos los pods de servicio se reinicien, una vez. El documento de
configuración de instancia que se entrega a los servicios tiene ahora eliminada la coordenada
de cualquier área funcional que este despliegue no habilitó. En un despliegue sin
ai-inference — es decir, todos los perfiles salvo full — eso cambia los bytes del documento
y por tanto la anotación de suma de verificación que reinicia los pods, así que el
la actualización reinicia todos los servicios y no solo aquellos cuya imagen se movió. Es una
actualización progresiva normal y no requiere nada de usted; figura aquí para que un reinicio
completo no se lea como un síntoma.
El motivo de eliminar la coordenada es que un nombre de host de un servicio que nadie desplegó era peor que ningún nombre de host: la superficie de autoría de reglas construía su puerta de lenguaje natural «Describe» contra él, fallaba al resolver el nombre e informaba de que el inquilino no había consentido el enrutamiento externo de IA — culpando a un ajuste del inquilino por un servicio que el operador nunca instaló. Ahora dice que la función no está habilitada en este despliegue, que es la verdad.
Cambios sin cambio de firma
Estos son los que un cliente no puede detectar mirando el esquema.
Actualizar un perfil de dispositivo borra su declaración de ubicación. Un perfil puede
ahora declarar que sus dispositivos informan de su posición, y updateDeviceProfile
reemplaza el perfil completo. Un cliente escrito contra la v0.11.0 no envía el campo
nuevo, así que actualizar un perfil por cualquier motivo — renombrarlo, editar su
descripción — deja de declarar la posición para todos los dispositivos que lo usan, en
silencio. El único síntoma es que las superficies de mapa se quedan vacías. Envíe el campo,
o vuelva a establecer la declaración después de cualquier actualización hecha desde un
cliente antiguo.
updateDeviceProfile ha pasado desde entonces a ser una actualización
parcial: una petición que no
dice nada sobre la declaración ahora la deja como está, y limpiarla requiere un null explícito.
El consejo de arriba es lo que hay que hacer en una instancia v0.12.x o v0.13.x; en una actual
no hay nada que arrastrar. Renombrar un perfil es una mutación
propia.
Las reglas de detección con ventana ya no cuentan lecturas acumuladas de fuera de su
ventana. Las reglas de repetición, de agregado deslizante y de correlación incorporaban una
lectura de cualquier punto del pasado, lo que permitía que una regla que dice «tres lecturas
en diez segundos» se disparara con lecturas separadas por una hora — el detonante habitual
era un dispositivo subiendo su búfer acumulado. Ahora esas reglas descartan una lectura que
llega después de que haya pasado la ventana a la que pertenecía, igual que ya hacían los
agregados de ventana fija y las reglas de sesión. Espere menos alarmas de esos tipos de
regla en cualquier flota que suba por lotes, y consulte detect_late_samples_total para ver
cuánto se está descartando. Las lecturas se almacenan y grafican exactamente igual que antes;
esto afecta solo a la detección. Véase ejecutar el motor de
detección.
La geometría de una geocerca se valida de forma más estricta y se almacena tal como queda escrita, no tal como se envía. Tres cambios, todos en el momento de crear o actualizar una geocerca:
- Una posición debe ser exactamente
[longitud, latitud]. Antes se aceptaba e ignoraba una tercera ordenada o posteriores. - El documento de geometría solo puede llevar las claves que la plataforma lee —
kindygeometryen el nivel superior,typeycoordinatesdentro. Cualquier otra clave antes se almacenaba y nunca se consultaba. - Las coordenadas se reescriben en notación decimal simple antes de almacenarse. Una
coordenada enviada como
1e-300se devuelve como su expansión decimal completa. No se redondea ningún valor y ninguna geocerca cambia de forma, pero un documento leído de vuelta no es idéntico byte a byte al enviado.
Una geocerca también se rechaza ahora si su forma almacenada supera 32 KiB. Eso es aproximadamente el doble del tamaño de una geocerca que use todos los vértices que la plataforma permite, así que la geometría ordinaria no se ve afectada; lo que se rechaza es un documento cuyo tamaño proviene de la notación y no de la forma. La consola siempre ha escrito las posiciones en la forma aceptada, así que las geocercas dibujadas en la consola no se ven afectadas. Las geocercas ya almacenadas no se reescriben y siguen funcionando, pero una que incumpla alguna regla anterior será rechazada la próxima vez que se guarde.
Cancelar un comando registra CANCELLED. Antes registraba EXPIRED, que compartía con
un comando que simplemente agotó su tiempo. Si se bifurca sobre EXPIRED para detectar su
propia cancelación, ya no estará ahí.
Los comandos pueden quedarse ahora en HELD o PARKED. Un comando dirigido a un
dispositivo que la plataforma sabe ausente se retiene en lugar de publicarse, y uno que se
despachó a un dispositivo que resultó inalcanzable se aparca. Ambos están esperando, no
terminados, y ambos son nuevos: el código que trate cualquier cosa distinta de QUEUED o
SENT como terminal se equivocará. El conjunto completo es ahora QUEUED, HELD, SENT,
PARKED, SUCCESSFUL, FAILED, TIMEOUT, EXPIRED, CANCELLED.
Una lectura se almacena en el instante en que se tomó. Cuando un mensaje transporta muchas muestras, cada una con su propia marca de tiempo — toda carga de Sparkplug y LwM2M lo hace, y también cualquier dispositivo que almacene mientras está sin conexión — esas muestras se almacenaban en el instante en que llegaba el mensaje. Ahora se almacenan en el suyo propio. Un dispositivo que sube una hora de lecturas almacenadas las escribe a lo largo de esa hora en lugar de en el momento de la subida, de modo que el historial, las gráficas, la retención y la detección las ven donde realmente corresponden.
Una fuente de presencia que deja de ejecutarse ahora devuelve sus dispositivos. Un dispositivo
marcado como ASSERTED conservaba indefinidamente la presencia que tuviera por última vez: el
barrido de inactividad omite los dispositivos afirmados y un evento de datos no puede cambiarlos, así
que un dispositivo que estaba conectado cuando su fuente desapareció figuraba conectado para siempre,
y uno que estaba fuera de línea tenía sus comandos retenidos para siempre. La presencia MQTT afirmada
por el broker ahora libera los dispositivos que afirmó cuando se la desactiva deliberadamente, o
cuando falta su credencial de cuenta de sistema de NATS, devolviéndolos a INFERRED sin afirmar nada
sobre la conectividad. En una instancia donde eso aplique, espere un evento de cambio de estado por
dispositivo, a ritmo pausado, contado bajo presence_events_total{state="demoted"}, y espere que
esos dispositivos vuelvan a quedar bajo el barrido de inactividad de diez minutos. Sparkplug y LwM2M
no tienen liberación automática: dcctl presence demote y la nueva mutación demoteAssertedPresence
de device-state lo hacen a mano, para cualquier fuente. La mutación necesita un permiso nuevo,
state:demote, que ningún rol tiene de forma predeterminada. Un medidor nuevo,
presence_tap_off{reason}, informa de si la presencia afirmada por el broker está funcionando
siquiera — algo que nada reportaba antes, porque desde fuera una flota en silencio y una toma que
nunca arrancó son idénticas. Vea Devolver un dispositivo a presencia
inferida.
Una lectura reentregada ya no duplica sus filas. La identidad de un evento de medición se deriva de un resumen de su propio contenido, y esa identidad es lo que hace inofensiva una reentrega. Para una lectura que lleva más de una métrica sobre un transporte JSON, el resumen se calculaba sobre un orden que inventaba la plataforma y no sobre el que envió el dispositivo, así que la misma lectura resolvía a una identidad distinta aproximadamente cuatro de cada cinco veces. Cuando la plataforma reentregaba uno de esos mensajes — cosa que hace de forma rutinaria, ante una publicación sin confirmar o un fallo de escritura transitorio — el duplicado no se reconocía: las filas de medición se escribían una segunda vez y los resúmenes horarios las contaban dos veces. Las lecturas de una sola métrica, y las que llegan por Sparkplug o LwM2M, nunca se vieron afectadas. La corrección es solo hacia adelante: los duplicados ya escritos antes de la actualización se quedan donde están, y sus resúmenes siguen inflados. Si tiene gráficas que se veían demasiado altas en dispositivos de varias métricas, este es el motivo, y a partir de la actualización se leerán correctamente.
Toda lista paginada devuelve ahora las filas en un orden declarado. De los 37 puntos finales de lista de la plataforma, 31 no nombraban ningún orden, lo que deja a una lectura paginada libre de entregar la misma fila en dos páginas y no mostrar nunca otra — un defecto real que ya se había reportado dos veces como una pantalla que se reordenaba bajo un operador. Cada lista ordena ahora por una clave total y sin ambigüedad. Si tiene código que dependía del orden incidental que una consulta concreta devolvía por casualidad, verá ahora uno estable, que puede no ser el mismo. Un orden se eligió deliberadamente en lugar de mecánicamente: las credenciales de dispositivo se listan con la de mayor margen restante primero, porque una lectura sin acotar de ellas alimenta la reutilización de credenciales, y ordenar por id habría devuelto la credencial más próxima a caducar.
Un comando respondido en texto plano registra ahora su respuesta. Un dispositivo que
respondía a un comando con algo que no es JSON — acknowledged, una palabra de estado suelta —
hacía fallar la escritura con un error de tipo de la base de datos y dejaba el comando en SENT,
reintentando la misma escritura condenada una vez por minuto durante toda la vida de la fila. El
comando acababa caducando por tiempo contra un dispositivo que lo había respondido
correctamente. Una respuesta así se almacena ahora, sin pérdida, como una cadena JSON. Los
valores que suministra un cliente de la API no cambian: esos deben seguir siendo JSON válido,
porque a un cliente que envía JSON mal formado hay que decírselo.
Los comandos a dispositivos Sparkplug fallan ahora de inmediato en lugar de perderse. La
plataforma no tiene ruta de comandos hacia un dispositivo Sparkplug — esos nodos viven en tu
propia infraestructura MQTT y nada tiende un puente entre ambas — y la comprobación que debía
rechazar uno de esos comandos se comparaba contra un valor que ningún dispositivo lleva jamás,
así que no coincidía con nada y todos esos comandos se aceptaban y luego se iban en silencio a
ninguna parte. Ahora se registran FAILED de inmediato con ese motivo, y se cuentan bajo
command_delivery_undeliverable_total. Espere que los comandos que antes se quedaban hasta su
TTL y registraban TIMEOUT aparezcan ahora como fallos inmediatos. Vea
Comandos.
Un comando cuyo rastro perdió la plataforma se rearma en lugar de culpar al dispositivo. Un
comando podía llegar a SENT y luego no ser alcanzable por nada — el pod que lo publicó muere
antes de registrar el desenlace — y SENT no tenía más salida que el TTL, que registraba
TIMEOUT contra un dispositivo al que nunca se le envió nada. Una pasada en segundo plano
encuentra ahora esos casos y los rearma a PARKED, de modo que se entregan en el siguiente
despertar del dispositivo. command_delivery_stranded_recovered_total lleva una etiqueta
{disposition} que dice dónde acabó cada uno. Esto aplica solo a dispositivos LwM2M: en MQTT
plano, un comando que parece no haber llegado a nada no se distingue de uno que llegó y cuya
respuesta se perdió, así que ahí el comportamiento no cambia y
command_delivery_stranded_skipped_total{reason="transport"} mostrará un ritmo constante que no
es un fallo. Vea cuando la plataforma pierde el rastro de un
comando.
Una acción de regla que la plataforma nunca podrá entregar se descarta en lugar de
reintentarse. Cuando una acción REACT se rechaza por un motivo que ningún reintento puede
cambiar — un sendCommand dirigido a un dispositivo que ya no existe, o a un comando fuera del
vocabulario publicado de ese dispositivo — antes se reintentaba hasta el límite de reentregas y
luego se contaba como envenenada, lo que ponía un error de autoría en el mismo estante que un
fallo de infraestructura. Ahora se descarta al primer rechazo de ese tipo y se cuenta bajo
react_actions_permanently_rejected_total, etiquetada por tipo de acción. Un ritmo sostenido en
ese contador significa que una regla apunta a algo que sus dispositivos no pueden aceptar; el
contador de envenenadas que antes inflaba significa ahora lo que dice.
Una respuesta truncada entre servicios se cuenta. Los servicios leen las respuestas de los
demás hasta un tope fijo de 1 MiB, y una respuesta mayor se cortaba en silencio. Ahora la cuenta
devicechain_svcclient_responses_truncated_total, etiquetada por par. La lectura debería ser
plana en cero; una distinta de cero significa que algún servicio está actuando sobre una
respuesta parcial, algo que conviene saber antes de que el síntoma llegue a una pantalla.
Entradas que antes se aceptaban y ahora no
- Una política de notificación que lleve
deviceTypeToken. Acotar una política a un tipo de dispositivo no está implementado; la escritura antes tenía éxito y luego no entregaba nada. - Una regla de notificación cuya
severityno sea uno de los niveles en mayúsculas o*. Una severidad en minúsculas antes se escribía, se releía sin cambios y nunca coincidía con ninguna alarma. - Un
occurredTimede0001-01-01T00:00:00Z. Es una marca de tiempo válida, y la plataforma la reserva para significar que no se informó ninguna hora. - Un encolado que llevaría a un inquilino por encima de su techo de comandos retenidos. Los
comandos retenidos para un dispositivo ausente se acumulan sin freno natural — la acumulación
de una flota dormida puede quedarse días — y antes nada la acotaba. El límite se resuelve desde
la anulación propia del inquilino, si no la de su nivel, si no un valor predeterminado de
plataforma de 10 000, y no hay ningún valor, en ningún nivel, que signifique ilimitado. El
rechazo lleva el código
HELD_CEILING_EXCEEDEDy es el único temporal que produce la puerta de encolado: se libera a medida que esos dispositivos vuelven. Un cliente que trate todo rechazo como permanente debería tratarlo como caso aparte. Vea cuánta acumulación puede retener un inquilino. - Un encolado que llevaría a un inquilino por encima de la parte de ese techo reservada para la
entrega. Una parte del límite — el 20 % de forma predeterminada — se guarda para la entrega de
comandos de la propia plataforma, de modo que una sola escritura de flota no pueda consumirlo
todo y dejar rechazado cada
sendCommandautomatizado de ese inquilino hasta que la acumulación drene. Todo lo que emite comandos en su nombre queda acotado por el resto: la consola, los SDK,dcctly sus propias integraciones por igual. La consecuencia práctica es que un lote grande que antes se habría admitido entero puede ahora rechazarse en parte; cuando el lote pudo desplegarse parcialmente, su registro dice qué dispositivos no cupieron. Vea una parte del techo está reservada para la entrega.
Arranque inicial y la CLI
Estos llegan a una instancia a través de dcctl bootstrap y de la aplicación de la
infraestructura, no de la versión desplegada, así que ninguno se materializa durante la actualización
descrita arriba. Figuran aquí porque cada uno es un cambio en lo que sale mal.
Un cambio en la configuración del bróker reinicia ahora el bróker. nats-server no puede
recargar en caliente su bloque de callout de autorización ni sus límites de JetStream, y su
negativa es total: abandona la recarga entera, incluido todo cambio no relacionado que viajara
en la misma aplicación. Visto desde fuera eso era la peor clase de nada: la aplicación informaba
éxito, el ConfigMap mostraba los valores nuevos, y el bróker en ejecución seguía con la
configuración con la que arrancó, con la única evidencia en una línea dentro del propio registro
del bróker. Los servicios fallaban entonces al autenticarse contra un ConfigMap que demostraba
que sus credenciales eran correctas. El StatefulSet del bróker lleva ahora en su plantilla de pod
un hash de su configuración renderizada, de modo que el servidor siempre arranca con el archivo
que se le dio. El coste es que los cambios de configuración del bróker reinician ahora esos pods,
donde antes solo lo hacía un cambio de chart o de imagen: presupueste unos 50–70 segundos por
pod, lo que en un bróker de un solo servidor es una interrupción total breve y en tres es un
reinicio continuo.
Las versiones de los charts de terceros están fijadas. ingress-nginx y cert-manager se
instalaban con lo último que hubiera publicado su repositorio, lo que convertía al repositorio de
charts en una dependencia de la planificación además de la aplicación: cuando su host de
artefactos devolvió un 503, el plan falló con un error que no nombraba ni el chart ni la red, y
costó dos arranques fallidos hasta dar con la causa. Quedan fijados en 4.15.1 y v1.21.1
respectivamente — las versiones que ejecuta el clúster con el que se hicieron las pruebas. Si
contaba con recoger una más nueva automáticamente, ahora la actualiza deliberadamente.
Un dcctl compilado por usted tiene ahora una etiqueta de imagen predeterminada utilizable.
make -C backend/cli build producía un binario cuya etiqueta de imagen predeterminada salía del
archivo VERSION del repositorio — un valor que ninguna versión establece y bajo el que nunca se
publicó ninguna imagen. Todas las cargas de trabajo acababan en ImagePullBackOff, varios minutos
dentro de un arranque que había informado progreso sano todo el camino. Un dcctl compilado
localmente usa ahora dev de forma predeterminada, que el guardián de versiones no publicadas
reconoce y rechaza pronto con un mensaje legible, en vez de tarde con uno que no lo es. Un dcctl
publicado nunca estuvo afectado: su etiqueta viene de la propia versión.
Configuración
Una clave se movió. maxEventFutureSkewSeconds limitaba cuánto puede adelantarse una marca
de tiempo informada por el dispositivo respecto al reloj de la plataforma; era un ajuste de
event-processing y ahora es de device-management, porque la hora del evento se decide
ahora en un único lugar, tanto para la detección en vivo como para la reproducción.
Una configuración que siga estableciéndola bajo event-processing arranca con
normalidad y registra una advertencia que nombra la nueva ubicación. El valor anterior no
se aplica: establézcalo bajo device-management si lo había cambiado respecto al valor
predeterminado de 300 segundos.
No se eliminó nada de los valores del chart, así que un archivo de valores v0.11.0 se
aplica sin cambios.
El límite se movió, así que mientras dura este despliegue ninguna de las dos partes lo está
aplicando. En v0.11.0 solo el motor de detección limitaba una hora informada por el
dispositivo; en v0.12.0 solo lo hace la resolución de eventos. Los dos servicios se
despliegan como Deployments independientes, así que existe una ventana en la que un
event-processing de v0.11.0 ya ha sido sustituido mientras un device-management de
v0.11.0 sigue publicando, y un evento que cruce en esa ventana no lo comprueba ninguno.
Lo que cuesta si llega uno con una marca de tiempo desmesuradamente futura: la detección mantiene una única frontera temporal para toda la instancia, así que ese único evento la adelanta y todos los temporizadores pendientes de todos los inquilinos se disparan a la vez. Recuperarse implica reiniciar la instantánea del motor.
Es un límite de una sola actualización, no una debilidad permanente: una vez que ambos
servicios están en v0.12.0 concuerdan de forma definitiva, y una instancia que se destruye
y se recrea nunca queda expuesta. Si va a actualizar en caliente con dispositivos enviando
datos, detenga el tráfico de dispositivos durante el despliegue, o prepárese para reiniciar
la instantánea de detección después.
Un servicio que rechaza su propia configuración sale ahora con un estado distinto de cero.
Antes registraba «refusing to start» y terminaba con estado 0, así que el pod informaba
Completed — exactamente lo que informa un apagado ordenado, e indistinguible de uno a simple
vista. Esos pods entrarán ahora en CrashLoopBackOff. No ha cambiado nada sobre qué
configuraciones se rechazan; lo que cambió es que el rechazo se ve ahora en kubectl get pods,
en un contador de reinicios y para cualquier cosa que alerte sobre ellos. Un servicio que no
consigue apagarse limpiamente se informa igual, por el mismo motivo. Si tiene una alerta que
trata un pod de servicio en Completed como benigno, esta es la versión en la que el fallo
subyacente empieza a llegarle.
v0.12.1: un parche, nada que hacer
La v0.12.1 es una actualización en sitio corriente desde la v0.12.0. No añade ninguna migración, por
lo que la base de datos queda intacta, y no cambia ninguna API, tema, permiso ni clave de
configuración: todo lo que describe la sección de la v0.12.0 anterior sigue siendo exactamente
lo que usted está ejecutando.
Vale la pena conocer dos correcciones:
- Los colores de estado de la consola web ahora cumplen el contraste WCAG AA en los temas
claro y oscuro. Las insignias
pendingyonlinefallaban en ambos temas, y el texto de error fallaba en el tema oscuro. Lo que los colores significan no ha cambiado, pero las insignias rellenas son visiblemente más oscuras, porque esa es la única forma de que las letras blancas sobre ellas resulten legibles. - El monitor de inactividad ya no lee en memoria todos los dispositivos de todos los inquilinos en cada pasada, ni emite una ida y vuelta a la base de datos por cada dispositivo que marca; decide y escribe en una sola sentencia. Los dispositivos pasan a inactivos según exactamente el mismo calendario que antes —esto es un cambio de coste, no de comportamiento— y se nota sobre todo en flotas grandes y en los momentos justo después de que una fuente de presencia devuelva sus dispositivos.
v0.13.0 — los límites de geocercas pasan a formar parte de su plan
v0.13.0 es una actualización en sitio corriente, y no cambia ningún tema, permiso ni clave de
configuración.
Sí cambia la base de datos, de forma aditiva: crea una tabla para las formas de las geocercas, añade tres columnas anulables al registro del inquilino y reescribe una sola vez, en su sitio, el historial de geocercas almacenado para adaptarlo al nuevo formato. No se elimina nada y no hay que recrear nada.
Ese último paso es el que conviene conocer si ya utiliza geocercas. A partir de v0.13.0 la
forma de una geocerca se almacena una sola vez y se referencia por su contenido, en lugar de
copiarse en cada versión de su conjunto de geocercas, y la actualización reescribe el historial
que ya tiene para que lo referencie del mismo modo. Sus geocercas y su historial se conservan sin
cambios; lo que cambia es cómo se almacenan. El paso se puede volver a ejecutar sin riesgo y no
hace nada en una instancia que ya lo haya aplicado.
Lo que cambia es que los dos límites de geocercas que antes eran fijos para todos — 512 posiciones en una geocerca, 100 geocercas por inquilino — ahora son ajustes de su plan, junto a un tercero: un límite sobre el total de posiciones de todo su conjunto de geocercas. Los tres conservan sus valores anteriores de forma predeterminada, así que un inquilino al que nunca se le hayan cambiado queda medido exactamente donde estaba y no tiene que hacer nada.
Dos cosas que conviene saber antes de actualizar:
- El límite del conjunto completo es nuevo, y su valor predeterminado es el que los otros dos ya implicaban: 51.200 posiciones, que son 100 geocercas de 512. Así que un inquilino que use geocercas exactamente hasta los límites documentados queda en el nuevo límite, nunca por encima. El total cuenta formas distintas, así que dos geocercas dibujadas de forma idéntica cuestan una.
- Un cambio solo se rechaza cuando hace un número mayor. Si más adelante un operador baja uno de sus límites por debajo de lo que ya tiene, conserva todas sus geocercas. Editar el nombre o la descripción de una geocerca, y eliminar una geocerca, siempre siguen funcionando: la comprobación es sobre el crecimiento, no sobre el tamaño. Esto es lo que evita que un cambio de plan deje varadas geocercas que eran válidas cuando se dibujaron. Hacer una geocerca más pequeña casi siempre funciona también; la excepción es que el total del conjunto cuenta formas distintas, así que editar una de varias geocercas dibujadas igual la separa del resto y puede subir el total aunque esa geocerca se haya encogido.
Una consecuencia que hay que prever: como eliminar una geocerca baja el total almacenado, un inquilino que esté por encima de un límite y elimine una geocerca no podrá volver a crearla. Para mover una geocerca a otro token, cree primero la nueva y elimine después la antigua, lo que requiere un hueco libre de geocerca durante el momento en que ambas existen.
Los operadores que empaqueten planes deben saber que estos tienen topes reales, porque no todos se
gastan solo en el inquilino: el total del conjunto es una porción de una caché de geometría de la
que tiran todos los inquilinos de la instancia, y el número de geocercas acota un anuncio que tiene
que caber en un solo mensaje del broker. Los rechazos
nombran tanto el número como el ajuste que hay que subir, y una métrica
geofence_cap_refusals_total los cuenta según qué límite rechazó.
v0.14.0 — los paquetes contra los que compila
v0.14.0 es una actualización en sitio corriente desde v0.13.x. No añade ninguna migración, así que la base
de datos queda intacta, y no cambia ninguna API, tema, permiso ni clave de configuración. Si
solo ejecuta la plataforma, no hay nada que hacer.
Lo que cambió está a su alrededor: los artefactos contra los que compila y la CLI con la que la ejecuta.
El runtime web está publicado. @devicechain/client, @devicechain/dashboards,
@devicechain/widgets y @devicechain/brand están en npm, así que embeber un panel o un widget en
su propia aplicación es una instalación y ya no una compilación contra nuestro árbol de fuentes.
Los cuatro se publican juntos en una misma versión y están fijados entre sí. Ver
Paquetes de npm para la línea de instalación y la política de
dist-tags.
Si estaba compilando nuestros widgets desde el árbol de fuentes, hay un cambio que le
corresponde hacer. maplibre-gl ahora es una dependencia peer de @devicechain/widgets: su
aplicación proporciona la biblioteca, la URL de su worker y su hoja de estilos, en lugar de que el
paquete de widgets las decida por usted. Eso es lo que hace que el paquete funcione bajo un
empaquetador que no controlamos — pero significa que un widget de mapa sin cableado del anfitrión
por encima ahora muestra un aviso explícito en lugar de un lienzo en blanco, que es el síntoma que
debe esperar si actualiza sin hacerlo. El cableado es corto y está descrito en
Renderizar un mapa. En el servidor no cambia nada.
El SDK cliente para .NET y Unity está publicado en nuget.org como DeviceChain.Sdk.
dcctl ya puede decirle qué ha arrancado, y apagarlo todo.
# cada instancia, el clúster en el que vive y si ese clúster sigue existiendo
dcctl instances list
# destruirlas todas
dcctl destroy --all
🔴 Esto cierra un defecto sobre el que conviene actuar, no solo conocerlo. Hasta ahora nada
registraba en qué clúster se había arrancado una instancia: se derivaba del nombre de la instancia
al crearla y se volvía a derivar al destruirla. Esa derivación es incorrecta para cualquier
instancia arrancada con --kube-context, y el fallo era silencioso en la peor dirección: dcctl destroy pedía al proveedor que borrara un clúster que no existía, lo cual tiene éxito sin decir
nada, eliminaba el estado local e informaba de que la instancia había sido destruida mientras su
clúster real seguía funcionando. Si alguna vez arrancó con --kube-context y después destruyó
esa instancia, es probable que su clúster siga en pie. dcctl instances list no puede
mostrárselas — la destrucción eliminó el registro local, que es justamente el problema — así que
pregunte directamente al proveedor (para el proveedor local, kind get clusters) y borre lo que
reconozca.
A partir de esta versión el clúster se anota al arrancar y se relee al destruir, y la línea final dice cuál de tres cosas ocurrió: se borró el clúster registrado, el clúster ya no estaba y solo se limpió el estado local, o el registro no era fiable y no se tocó nada. Ninguna de ellas es la frase antigua impresa sobre un clúster que sigue funcionando.
Las instancias creadas antes de esta versión no tienen ese registro y aparecen como no record — destroy will guess the cluster. La destrucción sigue funcionando sobre ellas recurriendo a la
derivación antigua, así que la advertencia anterior sigue aplicando a ellas y solo a ellas.
v0.15.0 — las actualizaciones dejan de borrar lo que no envió
v0.15.0 es una actualización en sitio corriente desde v0.14.x. Las migraciones nuevas se
ejecutan solas al arrancar los servicios, no hay nada que recrear y ningún dato debe moverse a
mano.
Los cambios incompatibles están en la API y en el acceso de red saliente, no en la
actualización en sí. Si administra la plataforma y la maneja desde la consola, aquí no hay nada
que deba hacer. Las secciones siguientes son para quienes llaman a la API directamente, quienes
envían notificaciones a través de algo dentro de su propia red, quienes ejecutan el servidor MCP,
o quienes han personalizado la configuración de event-sources.
Las actualizaciones ya no reemplazan el registro completo
Este es el cambio que afecta a más gente, y es la razón por la que esta versión está marcada como incompatible.
Antes, una actualización reemplazaba el registro: cualquier campo que omitiera se borraba.
Ahora un campo que no menciona se deja exactamente como estaba, y borrar un valor requiere un
null explícito.
La petición en sí tiene una forma nueva que ya no lleva el nombre del propio registro —
actualizar y renombrar son operaciones distintas, y ahora existen mutaciones rename… dedicadas
para los cuatro tipos que lo necesitan. Por tanto, una aplicación que llame a la API directamente
debe quitar el nombre de sus peticiones de actualización y regenerar su código cliente.
Una petición con la forma antigua se rechaza de plano, con un error que nombra el campo que ya no se acepta. No se aplica a medias y no falla en silencio: se entera en la primera llamada, y no a partir de un registro que ha perdido la mitad de su contenido.
Una aplicación que borraba un valor omitiendo el campo ahora conserva el valor anterior. Nada
da error; la actualización simplemente hace menos de lo que hacía. Si su código se apoya en la
omisión para borrar un campo, envíe un null explícito en su lugar.
Tenga en cuenta que no todos los campos aceptan null: algunos son obligatorios y lo rechazan con
un error específico. Son campos que nunca podrían borrarse legítimamente.
Si construye una petición de actualización enlazando una variable distinta por campo, una
variable que no suministre llega como null explícito en lugar de como campo ausente — y null
explícito significa borra esto. En el campo rules de una política de notificación eso vacía
todo el conjunto de reglas y devuelve éxito. Enlace el objeto de petición completo como una sola
variable, o incluya únicamente los campos que realmente quiere cambiar.
El id de un evento almacenado ha cambiado
El id de un evento es ahora el identificador propio del evento, en lugar de un valor compuesto a
partir del token del dispositivo, el tipo de evento y la marca de tiempo. Cualquier id que haya
guardado de una versión anterior ya no coincidirá con nada.
La forma anterior tampoco era única: un dispositivo que reportaba dos medidas en el mismo instante producía el mismo id para ambas, de modo que cualquier cliente con una caché normalizada basada en él estaba fusionando esas lecturas en una sola sin avisar. Si guardó ids, vuelva a leerlos; si los usaba como clave, esto es tanto una corrección como una ruptura.
Las conexiones salientes a direcciones privadas ahora se rechazan
Los webhooks de notificación, los relés SMTP y las llamadas HTTP de los conectores ya no pueden alcanzar direcciones de loopback, privadas, de NAT de operador, de enlace local ni de metadatos de nube. La comprobación ocurre en el momento de conectar, y un rechazo es definitivo: no se reintenta.
Esto está activo por omisión y no hay ningún interruptor para desactivarlo.
Este es el fallo con más probabilidad de sorprenderle, porque nada en él se parece a un cambio de política de red: las notificaciones simplemente dejan de llegar, y el fallo queda registrado como permanente en lugar de pendiente. Autorice las direcciones concretas que utiliza:
instance:
config:
infrastructure:
egress:
allowedDestinations:
- 10.96.0.25/32 # el relé SMTP dentro del clúster
Indique cada destino como su propio /32. Los destinos en la internet pública no se ven afectados
y no necesitan entrada.
Si ejecuta el servidor MCP
Dos cambios requieren acción, y uno de ellos impide que el servicio arranque:
- Una URL de recurso con una barra final ahora se rechaza al arrancar. Un identificador se compara de forma exacta, así que una barra final significaba que los tokens quedaban ligados a una dirección que nunca terminaba de coincidir. Antes se aceptaba y luego fallaba de forma silenciosa; ahora falla de forma visible al arrancar. Quite la barra.
- Los metadatos de recurso protegido han cambiado de ubicación, a la que define la
especificación, con el segmento well-known entre el host y la ruta. El chart los enruta por
usted. Si usted mismo termina el ingress, añada una ruta para el prefijo
/.well-known/que no reescriba la ruta.
Se han eliminado dos claves de configuración, y se comportan de forma distinta
debug, dentro de una entrada deeventSources. La configuración se valida de forma estricta, así que dejarla impide queevent-sourcesarranque, con un error que nombra el campo. Elimínela.inboundEventBatchingy susmaxBatchSize/batchTimeoutMs. Esta queda retirada, no rechazada: se descarta al cargar la configuración con una advertencia, así que el servicio arranca con normalidad. Elimínela cuando le venga bien.
La diferencia no es arbitraria: una clave retirada es una que todavía podemos reconocer por su nombre, de modo que podemos descartarla por usted. Una clave anidada dentro de una entrada de una lista no lo es, y por eso la primera tiene que detener el servicio.
También en esta versión
Los comandos se despachan ahora en el momento en que se encolan, en lugar de esperar al siguiente barrido, y el intervalo de ese barrido es configurable si quiere cambiar cada cuánto se ejecuta la red de seguridad. Los mensajes no entregados (dead letters) se pueden leer y consultar, no solo contar. Hay una vista de informes a la que puede apuntar una herramienta de BI. Los activos incorporan jerarquía padre/hijo y un contrato de propiedades documentado, los dispositivos incorporan una operación de sustitución, las alarmas incorporan reconocimiento masivo, y un inquilino puede elegir el idioma con el que abre su consola.
Los paquetes npm publicados y el SDK para .NET/Unity no llevan cambios de código en esta versión. Ahora bien, si su propio código envía mutaciones de actualización a través de ellos, ese código sí tendrá que regenerarlo usted.
v0.16.0 — los dispositivos deben nombrar el despacho que responden
v0.16.0 es una actualización en sitio corriente desde v0.15.x. Una migración nueva se
ejecuta sola al arrancar command-delivery: añade una columna con un valor por defecto, rellena
las filas existentes en la misma sentencia y no requiere nada de usted.
Hay una comprobación previa que conviene hacer antes de actualizar y un cambio incompatible que afecta a los dispositivos, no a quienes llaman a la API. Más allá de eso, esta versión trata sobre todo de servicios que se niegan a arrancar ante configuraciones que antes se aceptaban y se ignoraban en silencio — lo cual es más seguro, y también puede detener un pod que llevaba meses funcionando sin problemas.
Antes de actualizar: revise si sus puertos de escucha colisionan
event-sources ejecuta más de un servidor HTTP en un mismo proceso — GraphQL en su propio puerto,
más cada fuente de eventos HTTP que tenga configurada. Hasta ahora, que dos de ellos coincidieran
en el mismo puerto mataba un transporte de ingesta en silencio: el servidor perdedor moría
dentro de una gorutina y nunca se volvía a mencionar.
Ahora los enlaces son síncronos y un fallo es fatal, de modo que una colisión que llevaba meses rota hace que el despliegue entre en ciclo de reinicios. Ese es el comportamiento correcto y es también el cambio que con más probabilidad le sorprenderá, porque hoy nada le avisa de ello.
Compare el port de cada fuente con los demás y con el puerto de GraphQL, y compruebe que la
entrada extraPorts del chart coincide con el port de cada fuente. Un port: "0" de cara al
dispositivo también se rechaza ahora.
Todo dispositivo que responda a un comando debe devolver el nonce del despacho
Este es el único cambio incompatible de protocolo de la versión, y la población a la que afecta son los dispositivos construidos fuera de este repositorio — firmware, pasarelas, cualquier cosa que hable directamente el protocolo de comandos.
El sobre de entrega lleva un dispatchNonce. Un dispositivo que responda a ese comando debe ahora
devolver el mismo valor en el sobre de su respuesta. Una respuesta que lo omita, o que nombre un
despacho que el comando ya ha abandonado, se rechaza y se registra como carta muerta en lugar
de resolver el comando.
El motivo es un defecto real, no una cuestión de orden: el mismo comando puede publicarse legítimamente más de una vez — devuelto a la cola y despachado de nuevo — y sin un nonce no hay forma de saber a qué despacho pertenece una respuesta. Una respuesta a un despacho ya superado estaba resolviendo el más reciente con el resultado del anterior.
La plataforma no puede enumerar dispositivos construidos en otro lugar, así que los cuenta por usted. Después de actualizar, vigile:
devicechain_commanddelivery_command_delivery_responses_without_nonce_total— respuestas que no nombraron ningún despacho. Sobre todo dispositivos sin actualizar, y en una flota donde todos hablan el contrato actual debería ser cero. Cuenta cualquier respuesta sin nonce, así que una respuesta duplicada o tardía a un comando ya resuelto también acaba aquí.devicechain_commanddelivery_command_delivery_responses_stale_nonce_total— respuestas que nombran un despacho que el comando ya abandonó. La lectura habitual es que se están publicando comandos más de una vez, que es el defecto que este cambio corrige; un dispositivo que reenvía una entrada antigua de su bandeja de salida también lo produce.
(El command_delivery duplicado no es una errata: la serie lleva como prefijos el espacio de
nombres de la plataforma y el área funcional, así que el nombre de arriba es el que se pega en una
consulta.)
Una respuesta rechazada no se descarta. Se escribe como carta muerta, porque el informe del dispositivo sobre lo que hizo no existe en ningún otro sitio — así puede consultar esas respuestas mientras resuelve el primer contador.
Si sus dispositivos usan el SDK de .NET/Unity, actualizar el SDK es toda la solución: lleva el nonce por usted en ambos sentidos. El adaptador de bajada LwM2M y el simulador de dispositivos se actualizaron en el mismo cambio. El agente de borde no se ve afectado: envía telemetría y no recibe comandos.
Junto a ellos llega un contador relacionado:
devicechain_commanddelivery_command_delivery_responses_not_answerable_total cuenta una respuesta
que nombró el despacho en el que está su comando y aun así no pudo resolverlo. Ningún estado de
comando del vocabulario actual produce eso, así que debería marcar cero; existe para detectar
que se añada más adelante un estado sin que nadie haya decidido si una respuesta puede resolverlo.
Ahora los servicios se niegan a arrancar ante cosas que antes aceptaban
Cada uno de estos es una corrección que falla de forma cerrada, y cada uno puede detener un pod que antes funcionaba:
| Qué | La condición que ahora se rechaza |
|---|---|
| Almacén de secretos | una clave raíz de instancia bien formada pero incorrecta — antes arrancaba y fallaba en el primer secreto que se le pedía |
| Configuración de instancia | una clave mal escrita — antes se descartaba en silencio y se aplicaba el valor por defecto |
| Configuración de instancia | DC_SHUTDOWN_DRAIN_SECONDS todavía definida — la variable de entorno ya no existe; el valor es infrastructure.shutdown.drainSeconds |
| Configuración de instancia | una ventana de drenaje mayor que la mitad del terminationGracePeriodSeconds del pod |
| Puertos de escucha | dos servidores en un mismo puerto, o un port: "0" de cara al dispositivo |
| Cualquier servidor HTTP | un puerto ya en uso — antes se registraba desde dentro de una gorutina mientras el servicio informaba de un arranque correcto |
La de la clave mal escrita merece un momento. Una errata en maxSubscriptionMessageBytes redujo a
la mitad el techo real de trama sin registrar nada — la clave se descartaba y se aplicaba el valor
por defecto, lo que se parece exactamente a una configuración que funciona. La decodificación
estricta hace que se entere al arrancar.
Métricas: once series nuevas, ninguna renombrada ni eliminada
Todas las series que existían en v0.15.0 conservan su nombre exacto — nada se renombró y nada desapareció, ni siquiera con el cambio que dio a cada servicio su propio registro de métricas.
Lo nuevo: cinco contadores en command-delivery (los dos del nonce de arriba, más despachos
agotados, respuestas que el estado del comando no pudo aceptar, y respuestas perdidas porque no se
pudo escribir una carta muerta), dos contadores de cartas muertas de alarma en device-management,
un contador de cierre temprano en event-sources, is_serving en lwm2m-ingest, y los dos
contadores de renacimiento de Sparkplug de más abajo.
is_leader cambia de significado en lwm2m-ingest, y la documentación recomienda alertar sobre élEl indicador se levanta ahora cuando la réplica adquiere el arrendamiento, en lugar de después
de terminar de construir su mandato. Construir un mandato tarda hasta 30 segundos por cada
inquilino asignado, así que una réplica que acababa de ganar una conmutación por error informaba
antes is_leader=0 durante hasta 30 segundos por inquilino mientras sí tenía el arrendamiento.
Si sigue la alerta sum(devicechain_lwm2mingest_is_leader) != 1 que recomienda la guía de
despliegue, esa ventana de falsa ausencia de líder desaparece. El nuevo indicador
devicechain_lwm2mingest_is_serving es lo que ahora distingue «líder, todavía construyendo su
mandato» de «líder y sirviendo» — el estado que un solo indicador no podía expresar. is_leader == 1
con is_serving == 0 de forma sostenida es un líder atascado en su construcción. Tenga en cuenta
que el chart no incluye ninguna regla de alerta para ninguno de los dos: esto es orientación para
que la escriba usted, no una regla que hereda.
sparkplug-ingest añade rebirth_enqueued_total y rebirth_dropped_total. Una cola de
renacimiento saturada era antes indistinguible de una inactiva en todas las series que el servicio
exportaba, porque rebirth_requests_total cuenta publicaciones exitosas — así que la saturación
hacía que dejara de subir. Lea el par nuevo en conjunto: descartes que suben mientras las
solicitudes se mantienen en un techo es un abanico de salida que supera a un publicador sano;
descartes que suben mientras las solicitudes siguen planas apunta a la conexión con el broker.
Otros comportamientos que conviene conocer
- Un comando que la plataforma no puede publicar ahora falla. Antes alternaba entre en cola y
enviado en cada barrido hasta que su TTL vencía días después, y entonces registraba un tiempo de
espera agotado — lo que dice que un dispositivo no respondió, cuando nunca se había despachado
nada. Ahora se detiene en un límite (20 intentos por defecto, unos diez minutos con la cadencia
de barrido por defecto de 30 segundos) y registra el fallo nombrando a la plataforma. El límite
puede cambiarlo en
functionalAreas.command-delivery.config.maxDispatchFailures. - El
reasonde una carta muerta por un destino de conector bloqueado es ahoraunprocessableen lugar deexhausted. Actualice cualquier alerta o consulta guardada que use el valor anterior; los registros existentes se leen igual que antes. - Un cambio de estado de alarma que no se pudo publicar ahora se registra como carta muerta y se
cuenta, y
alarm_event_dead_letter_lost_totalse suma a la alertaDeadLetterWriteLost. - Un mensaje entrante que no se puede decodificar ya no se archiva entero. El registro apunta al original por asunto y número de secuencia del flujo.
- Las suscripciones GraphQL se cierran limpiamente al apagar con una trama
1001, y una trama entrante tiene ahora un tope —infrastructure.graphql.maxSubscriptionMessageBytes, 4 MiB por defecto. Es el único valor nuevo del chart en esta versión, y tiene valor por defecto. - El refresco de gobernanza tiene un límite de ritmo — 50 consultas/s con una ráfaga de 100, por dimensión gobernada, y una caché negativa de 10 segundos tras una consulta fallida. Un resolutor mantiene al día a unos 3000 inquilinos sin llegar nunca al límite. Por encima, un inquilino ya resuelto sigue sirviendo su último valor conocido en lugar de caer al valor por defecto de la plataforma; solo un inquilino que nunca se ha resuelto recibe el valor por defecto.
- Aparte, un techo de ingesta por defecto de la plataforma de
0se eleva ahora a 100 mensajes/s con una ráfaga de 200, en lugar de no admitir nada. Es un eje distinto del ritmo de consultas anterior. - El apagado tiene un límite derivado del periodo de gracia, menos la ventana de drenaje y un margen de dos segundos, y respeta la cancelación en todo momento. Los bucles de lectura de los consumidores aplican espera creciente y luego hacen fallar el proceso, en lugar de girar en vacío o reintentar indefinidamente.
- Dos detalles del chart con los que es fácil tropezar.
instance.config.infrastructure.metrics.httpPortestá retirada — un documento que aún la lleve registra un aviso nombrando la clave y arranca con normalidad, y el chart ya no la escribe. Yinstance.config.infrastructure.shutdownla escribe ahora el chart por usted a partir deshutdownDrainSecondsyterminationGracePeriodSecondsde nivel superior; definir ese bloque a mano hace quehelmfalle al renderizar, en lugar de discrepar en silencio con la especificación del pod. Si aporta la configuración de instancia medianteinstance.existingSecret, ese bloque le corresponde añadirlo a usted. - Un pod que se termina a sí mismo libera su arrendamiento de liderazgo al salir. En
lwm2m-ingestse han corregido las dos rutas que salían mientras aún lo tenían. La espera de 30 segundos antes de que un sustituto pueda tomar el relevo es ahora lo que sigue a una pérdida abrupta — un fallo de nodo, unkill -9— no lo que sigue a un pod que decide detenerse.
Los paquetes publicados
El SDK de .NET/Unity incorpora el cambio del nonce de comando descrito arriba; actualizarlo es la forma de que un dispositivo construido sobre él siga respondiendo a comandos.
@devicechain/client, @devicechain/dashboards, @devicechain/widgets y @devicechain/brand no
tienen cambios de código en esta versión. Una cosa que conviene saber si instala
@devicechain/widgets por su cuenta: su rango de dependencia par maplibre-gl pasa de ^6.6.0
a ^6.7.0. Si fija maplibre-gl en 6.6.x verá un aviso de dependencia par no satisfecha, o un
fallo de instalación con un gestor de paquetes que las exija estrictamente. Nada más cambió en los
paquetes.
Instancias creadas por la v0.16.0 y anteriores
Una instancia arrancada por la v0.16.0, o por cualquier versión anterior, no se puede
actualizar a la versión posterior a la v0.16.0. dcctl upgrade se niega en lugar de
intentarlo.
dcctl bootstrap registra ahora una declaración: un objeto con ámbito de clúster que dice
lo que la instancia es — su perfil, su topología, cómo está expuesta y qué áreas funcionales
ejecuta. dcctl upgrade lee esa declaración para saber qué desplegar, que es lo que permite
que un solo comando mueva una versión sin tener que indicarle de nuevo la forma de la
instancia. Las versiones hasta la v0.16.0 incluida no escribieron ese registro, así que no
hay nada que la actualización pueda leer.
Y lo dice, en lugar de tratar su instancia como un nombre que no existe:
instance "devicechain" IS in this cluster — named by the DeviceChain Helm releases in this
cluster — and it carries no declaration, so it was built by a release older than the one
that began recording them.
No hay capa de compatibilidad, y antes de la v1.0.0 no la habrá. Aquello con lo que se
configuró la instancia antigua nunca se escribió en una forma que esta versión pueda leer, de
modo que una declaración inventada a posteriori sería una conjetura aplicada sobre una
instancia en funcionamiento.
Para migrar a esta versión, recree la instancia:
# Exporte antes lo que necesite: esto descarta las bases de datos.
dcctl destroy local devicechain
dcctl bootstrap local devicechain
La protección de destrucción protege las bases de datos frente a una
operación corriente de helm, no frente a un dcctl destroy deliberado. Si la instancia
contiene telemetría, definiciones de dispositivos o paneles que le importan, vuélquelos antes
de empezar.
dcctl upgrade lee la instancia antes de escribir nada, así que el rechazo ocurre antes del
primer cambio: la versión de Helm se queda en la revisión en la que estaba, el operador sigue
ejecutando la imagen que ejecutaba y todas las filas siguen donde estaban. Ejecutarlo para ver
qué dice no cuesta nada. Ambas mitades —el rechazo y que la instancia quede intacta después—
se comprueban contra un clúster real en cada versión.
Una vez que esté en una versión que registra una declaración, las actualizaciones in situ
corrientes se reanudan. dcctl instances list muestra qué hay declarado y en qué clúster.
La transición única a la ingesta duradera
La versión que introduce la ingesta MQTT duradera cambia la forma en que event-sources recibe
la telemetría de dispositivos: en lugar de suscribirse al broker como cliente MQTT, consume un
flujo de captura duradero que el broker escribe antes de confirmar la recepción al dispositivo. Esto
es lo que evita que se pierda telemetría cuando event-sources está caído.
Cruzar esa versión una vez es una actualización en sitio corriente, pero espere una breve ventana de telemetría duplicada y planifique para ello:
- Durante el despliegue, el pod saliente sigue ingiriendo por MQTT mientras el pod entrante ya ha comenzado a consumir el flujo de captura, de modo que los mensajes publicados en ese solapamiento son ingeridos por ambos. La ventana está acotada por cuánto tiempo coexisten los dos pods: el arranque del pod entrante más el drenaje del pod saliente.
- Los eventos que llevan tanto un
altIdcomo unaoccurredTimeprovista por el dispositivo no se ven afectados: la clave de deduplicación del lado de escritura es(tenant, altId, occurredTime), de modo que esos duplicados se colapsan. Un evento con unaltIdpero sinoccurredTimeno se colapsa: el decodificador estampa la hora actual cuando el dispositivo omite una, y las dos copias se decodifican en pods diferentes en instantes diferentes, por lo que obtienen marcas de tiempo diferentes y terminan como dos filas. La telemetría sinaltIdno se deduplica en absoluto. - El solapamiento se prefiere deliberadamente. El orden alternativo —detener el pod antiguo antes de que exista el flujo de captura— pierde cada mensaje que el broker confirma en la brecha, y esa pérdida es silenciosa: al dispositivo se le informa que el mensaje fue aceptado y nunca se almacena. Una lectura duplicada es visible y corregible; una faltante no es ninguna de las dos cosas.
event-sources como Recreatestrategy: Recreate en event-sources produce exactamente el orden con pérdida descrito arriba, porque
termina el pod antiguo antes de que el nuevo cree el flujo de captura. El chart se niega
a renderizar esta configuración en lugar de dejar que descarte telemetría silenciosamente. event-sources
no es un servicio de escritor único y no gana nada con Recreate; una vez realizada la transición puede ejecutar
múltiples réplicas, algo que la ruta de cliente MQTT que reemplaza no podía hacer.
Durabilidad de los datos
La capa de base de datos es intencionalmente independiente del ciclo de vida de la aplicación. Ambas bases de datos se aprovisionan como infraestructura separada con una protección contra destrucción, de modo que actualizar, reinstalar o desinstalar la aplicación nunca las toca. Ese es el caso habitual y es seguro.
La protección resguarda cada base de datos mientras está dentro de la configuración de infraestructura. No resguarda una que se haya sacado fuera de ella: un recurso eliminado de la configuración deja de estar cubierto por las reglas que esa configuración declara, y el plan de eliminación se ejecutará con éxito. Los clústeres de base de datos además son dueños de sus volúmenes, así que eliminar uno se lleva sus datos consigo en lugar de dejar un volumen desasociado.
No edites la base de datos para sacarla de la configuración de infraestructura como forma de reemplazarla.
Actualizar una instancia creada antes de que las bases de datos pasaran al operador es el
único caso en que esto aparece, y se rechaza en tiempo de planificación en lugar de dejarse
al azar. Vuelca primero ambas bases de datos y vuelve a ejecutar el arranque con
--allow-legacy-db-removal, que afirma que te has ocupado de los datos y no verifica nada.
Para una instancia local, dcctl destroy seguido de un arranque nuevo es más simple y
descarta los datos de forma deliberada.
Esto es durabilidad de los volúmenes en ejecución; no es un sustituto de las copias de seguridad programadas y la recuperación a un punto en el tiempo, que se aprovisionan con la infraestructura de producción. Consulte Despliegue y operador para saber cómo se separan las capas de infraestructura y aplicación.