AzloTV Streaming Platform
Plateforme de streaming par abonnement : web, iOS, tvOS et Android, avec paiements et infrastructure live

En bref
- Audience
- Plus de 80 000 utilisateurs inscrits, plus de 18 000 abonnés payants (chiffres du client, 2026)
- Rôle
- Ingénieur principal, de décembre 2025 à aujourd'hui
- Plateformes
- Web, tableaux de bord admin et créateurs, iPhone/iPad, Apple TV, Android, backend/cloud
- Commits
- Environ 2 300 de mes commits hors merges (2 575 avec les merges) sur 9 dépôts, de décembre 2025 à septembre 2026
- Commits par base de code
- API 1 015 · site public 405 · admin 310 · MediaMTX 223 · tableau de bord créateurs 192 · serveur de paiement 146 · Apple TV 109 · app Flutter 94 · iOS 81
- Surface de l'API
- 67 fichiers de routes, 126 contrôleurs et 186 modèles de données dans l'API principale, plus un service de paiement séparé avec 11 groupes de routes
- Tests
- Environ 1 640 cas de test iOS, environ 125 tests tvOS et environ 105 fichiers de test backend et serveur de paiement
Vue d'ensemble
AzloTV est une plateforme de streaming par abonnement proposant des séries à la demande, du live streaming de créateurs, des émissions de téléréalité multicaméra, du merch et des événements payants. J'ai repris la base de code en production en décembre 2025 et j'en suis l'ingénieur principal depuis. Mon travail couvre l'API Node/Express, un nouveau microservice de paiement, le pipeline live MediaMTX, la stack média AWS, trois applications web Next.js et les applications pour iPhone, iPad, Apple TV et Android.
Le défi
La plateforme m'est parvenue sous la forme d'un monolithe Express, d'un site Next.js 14 et d'un panneau d'administration sans historique git commun ; ma première étape a donc été de tout remettre sous contrôle de version. Les paiements et leurs tâches de fond tournaient dans le même processus que celui qui servait le contenu et le chat live. Chaque changement devait être livré sans casser les clients web, mobile et TV déjà entre les mains des utilisateurs. Les lives devaient tenir sur les connexions 4G/5G des téléphones des créateurs et survivre au refus d'AWS de fournir de la capacité GPU dans une zone de disponibilité. Les médias devaient rester derrière des URL signées, que chaque lecteur gère différemment. La monétisation des créateurs (SuperChat, adhésions, versements d'affiliation et de marketplace) exigeait des mouvements d'argent qui restent justes quand les webhooks arrivent en retard, en double ou dans le désordre.
Mon rôle
J'ai hérité de l'API, du site public et du CMS d'administration en décembre 2025, je les ai remis sous contrôle de version, et je suis depuis le seul ingénieur à y travailler au quotidien. J'ai créé et je possède six nouvelles bases de code : le serveur de paiement, le service d'ingestion live MediaMTX, le tableau de bord de streaming des créateurs, l'application native iPhone/iPad (versions App Store 4.0.1 à 5.1.0), l'application Apple TV et le déploiement mongobetween. Je maintiens aussi l'application Android Flutter et je développe son remplaçant natif en Kotlin, ainsi que l'application de diffusion AzloTV Studio, qui a sa propre fiche dans ce portfolio, et j'ai construit Team Azlo, l'application interne du personnel. Côté backend, le pipeline média AWS, la diffusion signée, l'intégration Stripe/Connect/PayPal et la CI/CD sont de mon ressort.
Architecture
Clients
Application web publique
Next.js 16 App Router, React 18, TypeScript, TanStack Query, hls.js, Stripe.js, next-intl
Environ 101 routes (catalogue, lecture, salons live, paiement, merch, événements, compte) avec un middleware pour les règles d'accès ; elle a remplacé le site Next.js 14 hérité lors d'une bascule contrôlée en mai 2026.
CMS d'administration et tableau de bord de streaming des créateurs
Next.js 16, shadcn/ui, Tailwind v4, Socket.IO, hls.js, mux.js, WHIP/WebRTC
Environ 147 pages d'administration pour le contenu, l'upload vidéo par morceaux jusqu'à 50 Go, les publicités, le merch, les versements et les statistiques ; environ 30 pages créateurs pour passer en live, découper des clips dans le navigateur, gérer les overlays OBS et la régie Show House.
Application iPhone et iPad
Swift 6 strict concurrency, SwiftUI, AzlotKit SPM package, WidgetKit, ActivityKit, App Intents
Application native de visionnage sur iOS 18+ avec un package partagé de quatre bibliothèques (core/réseau, UI, lecteur, live), plus des widgets, des Live Activities, des téléchargements hors ligne et une mise en page iPad avec barre latérale.
Application Apple TV
SwiftUI, tvOS 17+, @Observable MVVM, AVPlayerViewController, TVServices Top Shelf
Une base de code tvOS séparée avec connexion par code d'appareil, des styles de boutons personnalisés pour le focus engine, un overlay de chat live et une extension Top Shelf qui partage un groupe Keychain avec l'application.
Applications Android
Flutter (GetX, media_kit) in production; Kotlin 2.1, Jetpack Compose, Media3, Hilt, Room in development
L'application Flutter est en production aujourd'hui avec chat live, flux de clips, téléchargements hors ligne, merch, événements et billets. L'application native Compose (12 modules Gradle, Media3, Hilt, Room) conserve le même applicationId afin de pouvoir la remplacer.
Edge/Diffusion
Routage par chemin au load balancer
AWS Application Load Balancer, weighted target groups
Des règles de chemin envoient le trafic des abonnements, de l'affiliation, des statistiques et de SuperChat vers le serveur de paiement et tout le reste vers le monolithe, si bien que l'extraction n'a nécessité aucune nouvelle version des clients.
Diffusion de médias signés
CloudFront signed URLs, Cloudflare R2 + Workers with HMAC path tokens
L'API signe les URL de lecture à chaque requête, pour CloudFront ou pour Cloudflare R2, afin que les segments HLS restent privés dans tous les lecteurs.
Services
Monolithe de l'API principale
Node.js, Express 5, Mongoose, BullMQ, Socket.IO on an arm64 (Graviton) EC2 Auto Scaling Group with PM2
Contenu, authentification, contrôle du live, publicités, merch et outils créateurs sous /api/v1, les canaux Socket.IO live et support, et un assistant de support basé sur Claude qui propose des remboursements soumis à l'approbation d'un agent humain. Il s'abonne aux événements de droits d'accès publiés par le serveur de paiement.
Serveur de paiement
Node.js/Express on ECS Fargate; Stripe Billing, Connect, Tax; PayPal
Gère les abonnements, les adhésions aux créateurs, SuperChat, les versements d'affiliation et de marketplace, le paiement des billets et du merch, les litiges et un grand livre en partie double, et exécute les tâches de fond liées aux paiements.
Service de licences FairPlay (en développement)
Apple FPS Server SDK KSM sidecar, Express /api/v1/drm endpoints
Service de licences FairPlay construit et validé de bout en bout avec le flux de test d'Apple (déploiement en attente).
Média
Ingestion live et transcodage GPU
MediaMTX (RTMP, SRT, WebRTC/WHIP), FFmpeg with NVENC on g4dn EC2, r2-sync to Cloudflare R2
Les diffuseurs sont authentifiés auprès de l'API, les flux sont encodés sans dépasser la résolution ni la fréquence d'images de la source, et des segments HLS de 2 secondes sont synchronisés vers R2 en écartant les segments partiels. En cas de refus de capacité, la machine d'ingestion bascule vers une autre zone de disponibilité.
Pipeline VOD et publicités
S3 multipart upload, AWS MediaConvert, Lambda webhook; client-side ad insertion
Les uploads de l'admin vont directement dans S3 et MediaConvert produit des échelles HLS en QVBR. Les publicités sont passées de l'insertion côté serveur via MediaTailor à l'insertion côté client en janvier 2026.
Données
Base de données documentaire derrière un proxy de connexions
Amazon DocumentDB Serverless, mongobetween on ECS Fargate
mongobetween regroupe plus de 150 connexions applicatives en environ 50 connexions vers DocumentDB, partagées par le monolithe et le serveur de paiement.
Redis : cache, verrous, pub/sub et diffusion Socket.IO
ElastiCache Redis, ioredis, @socket.io/redis-adapter
Contient les hashes de droits d'accès, les canaux pub/sub entre services, les verrous de tâches et l'adaptateur Socket.IO, chaque clé et chaque canal étant préfixé par environnement.
Décisions clés
- 01
Extraire les paiements avec un Strangler Fig derrière le load balancer
J'ai déplacé les abonnements, l'affiliation, les statistiques et SuperChat vers un service Fargate séparé et j'ai routé leurs chemins vers lui au niveau de l'ALB. Mon plan de migration était un runbook en sept phases : des tests fantômes comparant les deux services, du trafic de lecture pondéré à 10, 50 puis 100 %, puis les écritures, puis une bascule unique des webhooks Stripe et des tâches de fond pour que les deux services n'exécutent jamais la même tâche. Les clients n'ont pas changé et chaque phase pouvait être annulée en modifiant une règle. La contrepartie : les deux services partagent une base de données et un Redis, donc les clés et canaux partagés doivent rester synchronisés entre deux dépôts.
- 02
Redis pub/sub comme contrat entre les deux services
Le serveur de paiement n'appelle pas le monolithe à chaque changement de droits d'accès. Il écrit un hash Redis par utilisateur via un script Lua qui rejette les écritures portant un updatedAt plus ancien, puis publie un événement ; le monolithe invalide l'utilisateur en cache et pousse un événement Socket.IO subscriptionChanged vers les appareils de cet utilisateur. La vérification Lua empêche des webhooks Stripe concurrents d'écrire l'état dans le désordre. Chaque clé et chaque canal porte un préfixe d'environnement, car Redis pub/sub ignore la base de données logique.
- 03
Une fiabilité des paiements en couches plutôt que de se fier aux webhooks
Les événements de webhook sont dédupliqués par identifiant d'événement. Un poller horaire rejoue, via les mêmes handlers, les événements Stripe dont la livraison a échoué, et des réconciliateurs comparent Stripe à l'état local. Le filet de sécurité ignore un abonnement en période d'essai tant que son moyen de paiement n'est pas confirmé. SuperChat utilise des destination charges vers le compte Connect du streamer, avec des annulations de transfert en cas de remboursement ou de litige. Les transferts de versements d'affiliation utilisent le SHA-256 des identifiants de commission triés comme clé d'idempotence, si bien qu'une nouvelle tentative rejoue le transfert d'origine au lieu de payer deux fois.
- 04
Faire basculer le serveur d'ingestion vers une autre zone de disponibilité
AWS refuse la capacité GPU par type d'instance et par zone de disponibilité, mais les Elastic IP appartiennent à la région. Quand un démarrage échoue avec InsufficientInstanceCapacity, l'API parcourt une liste de paires (AZ, type d'instance), lance un clone tagué à partir de l'AMI générée à chaque déploiement de MediaMTX et y déplace l'Elastic IP, si bien que le DNS, le certificat TLS et l'hôte WebRTC restent identiques. Un verrou Redis, un backoff au démarrage et un balayage des orphelins évitent les instances GPU oubliées, et une suite de 23 tests couvre les règles de bascule.
- 05
Faire fonctionner les médias signés dans tous les lecteurs
AVPlayer ne lit pas les cookies partagés pour les requêtes de segments HLS, donc les applications Apple convertissent les paramètres de signature CloudFront en cookies transmis via AVURLAssetHTTPCookiesKey. L'application Android native construit ses sources Media3 sur le même client OkHttp et le même cookie jar que l'API. Les téléchargements hors ligne sont chiffrés et leurs clés sont conditionnées à l'abonnement.
- 06
Reconstruire le site public à côté de l'ancien et basculer par échange d'image
J'ai réécrit le site Next.js 14 hérité sous la forme d'une application Next.js 16 séparée qui poussait vers le même registre de conteneurs et le même service ECS. La mise en ligne consistait à changer le SHA de l'image dans la task definition, et le retour arrière au même changement en sens inverse. La checklist de bascule la conditionnait à une suite de smoke tests Playwright, un audit d'accessibilité des principales routes, un budget Lighthouse (LCP sous 2,5 s, CLS sous 0,1) et 48 heures de rodage en préproduction. La contrepartie : maintenir deux bases de code en parallèle jusqu'à la bascule de mai 2026.
Live streaming sur MediaMTX
J'ai construit le pipeline live d'AzloTV à partir d'un dépôt vide en février 2026 et j'y ai livré 223 commits depuis. Les créateurs passent en live depuis un téléphone, OBS, un navigateur ou un plateau multicaméra ; les spectateurs regardent le HLS depuis l'edge de Cloudflare sans jamais toucher le serveur d'ingestion. Mon premier jalon a été de réduire la latence glass-to-glass d'environ 7 minutes à environ 5 secondes.
- Ingestion en RTMP, RTMPS, SRT et WebRTC/WHIP sur MediaMTX. Chaque publication est autorisée par un hook HTTP vers l'API, chaque caméra Show House a son propre jeton haché, et les callbacks internes sont vérifiés avec un secret partagé comparé en temps constant.
- Transcodage GPU : FFmpeg décode avec NVDEC et encode en H.264 avec NVENC, avec des images clés toutes les secondes, une fréquence d'images constante jusqu'à 60 i/s et un plafond de 6 Mbit/s. Il ne dépasse jamais la résolution de la source et bascule automatiquement sur x264 si le pilote GPU tombe en panne.
- Un service Node que j'ai écrit (environ 2 900 lignes) synchronise des segments HLS de 2 secondes vers Cloudflare R2 toutes les 200 ms avec 16 uploads en parallèle. Il écrit ses propres playlists maître et variantes, signale les redémarrages de l'encodeur par des discontinuités et remonte l'état du flux à l'API.
- Conçu pour les connexions montantes 4G/5G des téléphones : timeouts de lecture de 30 secondes, relances du transcodeur qui attendent jusqu'à 45 secondes le retour du diffuseur, et un délai de grâce de 45 secondes avant de déclarer un live terminé, pour qu'un tunnel ou un changement d'antenne ne coupe pas la diffusion.
- Renforcé à partir d'incidents réels : une limite sur les tempêtes de redémarrage du transcodeur qui alerte un opérateur, un plafond sur les ré-uploads incontrôlés (un segment bloqué avait été uploadé 2 158 fois en 8 minutes), une playlist qui ne renumérote jamais les segments publiés (ce qui cassait la lecture sur iOS), et le statut « live » affiché seulement quand un flux est réellement lisible.
- La machine GPU démarre à la demande quand un créateur crée un live (environ 60 secondes de réveil), se préchauffe 10 minutes avant les émissions programmées et s'arrête d'elle-même après 20 minutes d'inactivité. Quand AWS a refusé la capacité GPU pendant trois jours en préproduction (962 refus), j'ai ajouté une bascule qui lance, dans une autre zone de disponibilité, un clone à partir de l'image générée à chaque déploiement et y déplace l'Elastic IP.
- Événements multicaméra Show House : chaque caméra publie sur son propre chemin en RTMP ou SRT, l'API suit l'état des caméras toutes les 10 secondes via des sockets, et l'émission complète est conservée sous forme de playlist DVR compressée. À la fin d'un live, ses segments deviennent une VOD dans R2 sans réencodage.
- Capacité mesurée sur un GPU L4 : 16 diffusions 1080p simultanées en temps réel, avec une rupture à 20 quand le moteur NVENC a atteint 100 %. Environ 570 tests live backend couvrent le pipeline, dont 23 pour les règles de bascule.
Réduire les coûts de diffusion avec Cloudflare
L'egress vidéo était la facture qui grossit avec chaque spectateur. En février 2026, j'ai migré la diffusion des médias d'AzloTV d'Amazon CloudFront vers Cloudflare R2 derrière des Workers, car R2 ne facture pas l'egress alors que CloudFront facture au Go. Aux tarifs publics, cela représente environ 92 $ contre 19 $ par mois et par téraoctet servi, soit environ 80 % de moins, et l'économie augmente avec le trafic.
- J'ai ajouté à l'API un sélecteur de fournisseur CDN et remplacé les URL signées RSA de CloudFront par des jetons HMAC-SHA256 vérifiés dans un Cloudflare Worker. Le jeton peut figurer dans le chemin, si bien que les lecteurs natifs iOS et Android fonctionnent sans cookies.
- J'ai migré la production derrière un interrupteur de maintenance adossé à Redis : les uploads vont directement dans R2, et un script réversible avec un mode dry-run a réécrit les URL de médias stockées. CloudFront n'est conservé que pour que les anciens liens continuent de fonctionner.
- Le live streaming utilise un second bucket et un second Worker : les manifests sont servis sans cache et les segments comme immuables pendant un an, pour que l'edge absorbe la charge des spectateurs. J'ai intégré les deux Workers à la CI après qu'un Worker déployé à la main a provoqué une panne.
- Cache edge pour les réponses de l'API (offres, genres, rangées mises en avant, listes) et les ressources publiques non signées (miniatures, avatars, bandes-annonces), car les URL signées renouvelées toutes les 15 minutes neutralisaient à la fois le cache du navigateur et celui de Cloudflare.
- Autres optimisations de coûts : l'API est passée sur des instances ARM64 Graviton, le serveur d'ingestion GPU se met en veille quand personne n'est en live, les bandes-annonces sont remuxées avec ffmpeg au lieu d'être réencodées dans MediaConvert, et j'ai supprimé une sortie MediaConvert en double qui doublait la dépense sur les tâches de téléchargement.
- J'ai aussi chiffré Cloudflare Stream Live, je l'ai implémenté derrière un flag de fournisseur et je l'ai laissé désactivé : pour notre trafic, il coûtait plus cher que de faire tourner notre propre machine MediaMTX sur R2.
DevOps et infrastructure
Je gère moi-même l'infrastructure d'AzloTV sur AWS et Cloudflare : CI/CD, environnements, déploiements, monitoring et astreinte. Tout se déploie depuis GitHub Actions, avec un environnement de préproduction sur la branche develop et la production sur main.
- Déploiements de l'API : une porte CI (tests unitaires, d'intégration, live, paiements et contrat du support, plus une vérification de patterns interdits), puis un déploiement progressif fail-fast sur le groupe EC2 Auto Scaling via AWS SSM, une instance à la fois avec health checks. Les secrets proviennent de SSM Parameter Store.
- Le pipeline MediaMTX (environ 1 600 lignes de workflow) réveille la machine GPU en veille, vérifie et répare le pilote NVIDIA, déploie et vérifie les services, et génère une image machine de bascule à chaque release. Une tâche bihebdomadaire réveille la machine pour vérifier et renouveler son certificat TLS.
- Le service de paiement a été provisionné avec des scripts idempotents (registre de conteneurs, security groups, secrets, IAM, logs, target groups), puis migré avec un routage pondéré au load balancer, des tests fantômes comparant les deux services et un rollback en une commande.
- Les applications web (site public, CMS d'administration, tableau de bord créateurs) produisent des images Docker ARM64 poussées vers ECR et déployées sur ECS Fargate. Un proxy mongobetween sur Fargate mutualise les connexions à DocumentDB, et j'ai reconstruit ses images à partir d'images de base maintenues, avec un upstream épinglé pour des builds reproductibles.
- Alertes via SES : les incidents live-ops (flux bloqués, transcodeur en boucle de crash) envoient un e-mail à un opérateur une seule fois par incident, même sur l'ensemble du groupe Auto Scaling, grâce à des réservations Redis atomiques, et chaque incident envoie un message de résolution.
- La préproduction est derrière Cloudflare Access : l'API vérifie le JWT Access en HTTP et sur chaque namespace Socket.IO, si bien que le load balancer n'est pas joignable directement. Les clés et canaux Redis sont isolés par environnement, avec une vérification au démarrage qui refuse un environnement mal configuré.
- Un accès break-glass qui ne dépend pas du chemin normal, des exports de base de données scriptés vers S3 avec une rétention de 30 jours, et un audit de la configuration de production qui a activé HSTS et imposé les contrôles d'accès au contenu.
Team Azlo : l'application du personnel
Les équipes derrière AzloTV (équipes de tournage, personnel événementiel, support et administrateurs) avaient besoin du panneau d'administration dans leur poche. J'ai construit Team Azlo, une application Skip/SwiftUI d'environ 97 000 lignes, et je l'ai livrée en interne via TestFlight en juillet 2026. Elle a sa propre page dans ce portfolio.
- Adaptée au rôle dès le premier écran : quatre niveaux de personnel et 16 domaines de permissions reflètent les règles du backend, si bien que l'équipe voit un portail My Work ciblé (tâches, matériel, paie, deal memos, notes de frais) tandis que les managers accèdent à la production, aux événements, au merch, au support, au juridique et à la modération live.
- La production audiovisuelle sur téléphone : projets, validations, bons de commande, feuilles de temps, cycles de paie, un parc matériel avec sortie, retour et historique de garde, et des plannings de tournage avec feuilles de service.
- Un kiosque anonyme à l'entrée pour l'enregistrement aux événements par scan de QR code et un kiosque de signature d'accords de confidentialité sur place, tous deux adossés à de nouveaux endpoints d'API que j'ai ajoutés.
Résultats
- Abonnements, adhésions, SuperChat, affiliation, paiement marketplace et billetterie, litiges et versements tournent sur un service de paiement séparé sur ECS Fargate, routé derrière le load balancer sans nouvelle version des clients.
- Monétisation des créateurs : SuperChat avec six paliers définis côté serveur, abonnements d'adhésion par créateur, onboarding Stripe Connect avec virements quotidiens planifiés et une boutique de merch multi-vendeurs. PayPal a été ajouté comme second processeur de paiement derrière un kill switch et un déploiement progressif en pourcentage, et un grand livre en partie double est conçu pour se réconcilier toutes les heures avec les balance transactions Stripe.
- Pipeline live : MediaMTX accepte RTMP, SRT et WebRTC/WHIP, encode sur GPU et sert le HLS depuis Cloudflare R2. Il est réglé pour les connexions montantes des téléphones (timeouts de 30 s, délai de grâce de 45 s avant de terminer un live), fait tourner les événements multicaméra Show House et bascule entre zones de disponibilité.
- Application native Swift 6 pour iPhone/iPad avec widgets, Live Activities, raccourcis Siri, téléchargements hors ligne et environ 1 640 tests automatisés, publiée via TestFlight et l'App Store (versions 4.0.1 à 5.1.0 en juillet 2026). L'application Apple TV séparée a repris des fonctionnalités d'iOS (boutique, compte, chat live, enregistrements Show House, deep links Top Shelf) en onze chantiers de parité en septembre 2026, intégrés sur une branche de release qui n'a pas encore été livrée.
- Le site public Next.js 16 est en ligne depuis le 15 mai 2026. Les opérateurs utilisent un CMS d'administration d'environ 147 pages ; les créateurs utilisent un tableau de bord pour diffuser depuis le navigateur (WHIP) ou OBS, découper des clips dans le navigateur et afficher des overlays d'alertes à l'écran.
- Service de licences FairPlay construit et validé de bout en bout avec le flux de test d'Apple (déploiement en attente).
- Avant de lancer la réécriture Android native, j'ai porté par phases les fonctionnalités de l'application iPhone vers l'application Flutter au printemps 2026 : la boutique de merch, les événements et billets, le Show House multicaméra, les téléchargements hors ligne complets, la connexion par QR code pour les TV et la localisation anglais/espagnol.
- L'application Android native est un projet Kotlin/Jetpack Compose de 12 modules (environ 54 000 lignes, 21 packages de fonctionnalités) avec téléchargements hors ligne, capture de clips, Google Cast, widgets d'écran d'accueil, un lecteur Media3 et un module de benchmark de baseline profile.
Stack
- Backend
- Node.jsExpressMongooseBullMQSocket.IORedis (ioredis)Lua scriptsAnthropic Claude API
- Paiements
- Stripe BillingStripe ConnectStripe TaxPayPalDouble-entry ledger
- Web
- Next.js 16ReactTypeScriptTanStack QueryTailwind CSSshadcn/uiNextAuthnext-intlhls.jsmux.js
- Plateformes Apple
- Swift 6SwiftUIAVFoundationWidgetKitActivityKitApp IntentsTVServices Top ShelfSwift TestingUniversal Links
- Android
- FlutterGetXmedia_kitKotlinJetpack ComposeMedia3 ExoPlayerHiltRoomRetrofitGoogle Cast
- Live et médias
- MediaMTXFFmpeg (NVENC)SRTWebRTC/WHIPHLSAWS MediaConvertFairPlay Streaming (dev)
- Cloud et diffusion
- AWS ECS FargateEC2 Auto Scaling (Graviton, g4dn GPU)ALBS3CloudFrontLambdaDocumentDBElastiCacheCloudflare R2 + WorkersDockerGitHub ActionsCloudflare AccessAWS SSMECRAuto-suspending GPU ingest
- Observabilité
- Firebase CrashlyticsFirebase PerformanceRemote ConfigMetricKitCloudWatch