Sign inSign up

articomio/articom-cx-operator

By articomio

•Updated 25 days ago

Articom Enterprise CX & AI Kubernetes Operator

Image
0

3.4K

articomio/articom-cx-operator repository overview

⁠Articom Kubernetes License Operator

Kubernetes Kubebuilder Go License

The Articom Kubernetes License Operator is an enterprise-grade Kubernetes custom controller that automates the deployment, lifecycle management, multi-cloud registry authentication, autoscaling, and networking of the Articom software suite based on validated license keys issued by admin.articom.io⁠.


⁠Table of Contents


⁠Overview

Deploying and maintaining enterprise microservices across hybrid cloud environments requires synchronized credential distribution, resource provisioning, autoscaling, ingress routing, and secret management.

The Articom License Operator simplifies this process to a single declarative Kubernetes resource (kind: License). When applied, the operator:

  1. Validates the license key against https://admin.articom.io/api/licenses/check.
  2. Authenticates with multi-cloud container registries (Google Artifact Registry, Azure ACR, AWS ECR) using short-lived access tokens.
  3. Provisions required microservices (Deployments), customized with tuned resource allocations and readiness profiles.
  4. Exposes workloads with ClusterIP Services and configures unified Ingress routing.
  5. Autoscales workloads via HorizontalPodAutoscaler (HPA v2) tailored per workload type.
  6. Integrates with HashiCorp Vault Agent sidecars for dynamic runtime secret injection.
  7. Reconciles state continuously, renewing credentials every hour and cleaning up resources upon license expiration or deletion.

⁠Architecture & Controllers

The operator follows the standard Kubernetes Operator pattern built with the controller-runtime⁠ framework.

                  ┌────────────────────────┐
                  │    admin.articom.io    │
                  │  (License Server API)  │
                  └───────────▲────────────┘
                              │
               1. Check Key   │  2. Issue Services &
               & Expiry       │     Multi-Cloud Registry Tokens
                              │
                  ┌───────────▼────────────┐
                  │   License Controller   │
                  │  (articom-k8s-crd)     │
                  └───────────┬────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        │ Creates & Manages (Controller OwnerRefs)  │
        ▼                     ▼                     ▼
┌───────────────┐     ┌───────────────┐     ┌───────────────┐
│ Docker Config │     │  Deployments  │     │ ClusterIP Svcs│
│ Registry Secret│    │  (AI, LiveKit,│     │   & Ingress   │
│ (GCP/ACR/ECR) │     │   API, SaaS)  │     │ (Subdomains)  │
└───────────────┘     └───────┬───────┘     └───────────────┘
                              ▼
                      ┌───────────────┐
                      │    HPA v2     │
                      │  Autoscaling  │
                      └───────────────┘
⁠License Controller (internal/controller/license_controller.go)

The core reconciliation logic is implemented in LicenseReconciler⁠:

  • Watching Resources: The controller watches the primary License Custom Resource and owns secondary resources:
    ctrl.NewControllerManagedBy(mgr).
        For(&articomv1alpha1.License{}).
        Owns(&corev1.Secret{}).
        Owns(&appsv1.Deployment{}).
        Owns(&corev1.Service{}).
        Owns(&autoscalingv2.HorizontalPodAutoscaler{}).
        Owns(&networkingv1.Ingress{}).
        Named("license").
        Complete(r)
    
  • Rate-Limiting & API Loop Prevention: Calls to admin.articom.io are cached for 50 minutes using the annotation articom.inforwaves.com/last-check. Secondary resource reconciliations triggered by watch events do not hammer the external API.
  • Content Hashing for Secrets: Docker registry secrets are tagged with articom.inforwaves.com/content-hash (SHA-256). If credentials have not changed, secret updates are skipped, preventing infinite reconcile loops.
  • Idempotent Resource Management: All resources (Secret, Deployment, Service, HorizontalPodAutoscaler, Ingress) are created or updated using controllerutil.CreateOrUpdate.
  • Garbage Collection: Every child resource is registered with controllerutil.SetControllerReference(license, child, r.Scheme), ensuring automatic cleanup by the Kubernetes garbage collector when a License is deleted.
  • Periodic Re-verification: Valid licenses are requeued for reconciliation every 1 hour to refresh expiring registry tokens and re-verify entitlement.

⁠Service Profile Engine (internal/controller/service_profiles.go)

Each Articom service has distinct performance and architecture requirements. DefaultServiceProfiles⁠ provisions tailored compute, port, autoscaling, and scheduling policies:

Service NamePortCPU Request / LimitMemory Request / LimitAutoscaling (HPA)Special Configuration
articom-ai-service800050m / 500m512Mi / 1000MiMin: 1, Max: 3 @ 90% CPUAI Core microservice
articom-api-service400020m / 200m160Mi / 384MiMin: 1, Max: 3 @ 90% CPUCentral REST API gateway
articom-knowledge-service800120m / 300m512Mi / 1GiMin: 1, Max: 3 @ 90% CPURAG & Knowledge Vector Index
articom-livekit-api-service800010m / 200m64Mi / 128MiMin: 1, Max: 3 @ 90% CPUWebRTC session orchestration
articom-livekit-dashboard800010m / 250m96Mi / 256MiMin: 1, Max: 3 @ 90% CPULiveKit Administration Portal UI
articom-saas-service300020m / 250m64Mi / 256MiMin: 1, Max: 3 @ 90% CPUMulti-tenant tenant control plane
articom-voice-livekit-worker80023400m / 3400m8Gi / 8GiMin: 1, Max: 4 @ 70% CPUGuaranteed QoS, Dedicated workload: livekit-worker node selector & tolerations, 1800s termination grace period, fast scale-up & stabilized 600s scale-down
articom-chat-widget-service300110m / 100m32Mi / 128MiMin: 1, Max: 3 @ 90% CPUEmbedded end-user chat client
articom-consumer-portal-service300010m / 100m64Mi / 128MiMin: 1, Max: 3 @ 90% CPUCustomer Self-Service Portal
articom-kyc-service800020m / 200m128Mi / 256MiMin: 1, Max: 3 @ 90% CPUVerification & Identity checks
articom-license-service800010m / 100m64Mi / 128MiMin: 1, Max: 3 @ 90% CPUInternal licensing bridge
Default / Fallback808010m / 200m64Mi / 256MiMin: 1, Max: 3 @ 90% CPUFallback for newly introduced services

⁠admin.articom.io Integration

⁠License Verification Flow

When the controller validates a key, it performs an HTTPS GET request to:

GET https://admin.articom.io/api/licenses/check?key=<LICENSE_KEY>
⁠API Response Payload Structure
{
  "valid": true,
  "client": "Acme Enterprise Corp",
  "type": "PRODUCTION",
  "issuedAt": "2026-01-01T00:00:00Z",
  "expiresAt": "2027-01-01T00:00:00Z",
  "services": [
    "articomacr.azurecr.io/articom-api-service:v2.4.0",
    "articomacr.azurecr.io/articom-ai-service:v2.4.0",
    "articomacr.azurecr.io/articom-livekit-dashboard:v2.4.0",
    "articomacr.azurecr.io/articom-voice-livekit-worker:v2.4.0"
  ],
  "registries": {
    "articomacr.azurecr.io": {
      "registry": "articomacr.azurecr.io",
      "username": "00000000-0000-0000-0000-000000000000",
      "token": "eyJhbGciOiJSUzI1NiIs...",
      "expiresAt": 1774000000
    },
    "us-docker.pkg.dev": {
      "registry": "us-docker.pkg.dev",
      "username": "oauth2accesstoken",
      "token": "ya29.a0AfH6SM...",
      "expiresAt": 1774000000
    }
  }
}
⁠Multi-Cloud Registry Token Exchange

The operator seamlessly translates registries into a standard Kubernetes kubernetes.io/dockerconfigjson Secret:

  • GCP Artifact Registry (*.pkg.dev): Uses username oauth2accesstoken with temporary bearer tokens.
  • Azure Container Registry (*.azurecr.io): Uses Azure AD Application UUID or token authentication.
  • AWS ECR / Custom Registries: Uses provided username and security tokens.

⁠Custom Resource Definition (CRD) Reference

Group: articom.inforwaves.com | Version: v1alpha1 | Kind: License | Scope: Namespaced

apiVersion: articom.inforwaves.com/v1alpha1
kind: License
metadata:
  name: articom-license
  namespace: articom-k8s-crd
spec:
  key: "<YOUR_LICENSE_KEY>"
  secretName: "articom-registry-credentials"
⁠License Spec (spec)
FieldTypeRequiredDefaultDescription
keystringYes—The license key generated from admin.articom.io.
secretNamestringNoarticom-registry-credentialsName of the kubernetes.io/dockerconfigjson Secret created in the namespace.

📖 Domain & Ingress Routing: To route public domains to the microservices created by this operator, refer to the Domain & Ingress Routing Guide⁠.

⁠License Status (status)
FieldTypeDescription
isValidbooleantrue if the license key is valid and active on admin.articom.io.
clientstringOrganization or client name associated with the license.
typestringLicense tier (e.g., DEV, STAGING, PRODUCTION, ENTERPRISE).
services[]stringComplete list of authorized container images.
issuedAtmetav1.TimeTimestamp when the license was generated.
expiresAtmetav1.TimeExpiration timestamp of the license.
conditions[]metav1.ConditionKubernetes API condition array tracking status lifecycle (Available, Progressing, Degraded).

⁠Setup & Installation

⁠Prerequisites
  • Kubernetes cluster v1.26+ (EKS, AKS, GKE, Kind, or bare-metal)
  • kubectl v1.26+
  • Helm 3.8+ (for Helm deployment)
  • Optional: Ingress controller (e.g. Cloudflare Tunnel, NGINX Ingress Controller)
  • Optional: HashiCorp Vault⁠ with Vault Secrets Operator / Vault Agent Injector

⁠Option A: Deploy from Official OCI Registry (Docker Hub)
# Install directly from the official OCI registry
helm install articom-operator oci://registry-1.docker.io/articomio/articom-k8s-crd \
  --namespace articom-system \
  --create-namespace
⁠Option B: Deploy from Local Source
# Clone the repository
git clone https://github.com/inforwaves/articom-k8s-crd.git
cd articom-k8s-crd

# Install the chart and CRDs
helm install articom-operator ./charts/chart \
  --namespace articom-system \
  --create-namespace

⁠2. Installation via Kustomize / YAML Manifests
# 1. Install Custom Resource Definitions (CRDs)
make install

# 2. Deploy the controller manager to your active cluster
make deploy IMG=docker.io/<your-dockerhub-user>/articom-cx-operator:latest

Or deploy directly via the single-file distribution bundle:

kubectl apply -f dist/install.yaml

⁠3. Local Development Run

To test the controller locally against your current kubeconfig context without building container images:

# 1. Install CRDs
make install

# 2. Run controller locally
export ARTICOM_LICENSE_SERVER="https://admin.articom.io/api"
make run

⁠Operator Configuration & Environment Variables

The operator controller manager supports the following configuration options:

Environment Variable / FlagDefaultDescription
ARTICOM_LICENSE_SERVERhttps://admin.articom.io/apiBase URL for the license validation server.
LICENSE_CHECK_INTERVAL10mPeriodic interval to check license validity and refresh registry tokens (e.g. 5m, 10m, 1h).
ENABLE_VAULTtrueEnabled by default. Injects in-namespace Vault Agent annotations into Deployments. Set to "false" to disable.
VAULT_ROLEarticom-vso-roleVault Kubernetes auth role used by the Vault Agent injector.
--leader-electfalseEnables leader election for high availability with multiple replicas.
--metrics-bind-address:8443Address the Prometheus metrics server binds to (0 to disable).
--health-probe-bind-address:8081Address for /healthz and /readyz probes.

⁠User Manual: Step-by-Step Guide

Follow this guide to deploy your licensed Articom services in your Kubernetes cluster.

⁠Step 1: Obtain a License Key
  1. Log in to the Articom Admin Console⁠.
  2. Generate or copy your provisioned License Key.
  3. Confirm that your required services (e.g., AI Core, LiveKit Worker, API Gateway) are enabled for your client account.

⁠Step 2: Prepare Target Namespace

Create the namespace where your Articom microservices should run:

kubectl create namespace articom-production

⁠Step 3: Author the License Custom Resource

Create a manifest named articom-license.yaml.

⁠Example 1: Standard Production Deployment
apiVersion: articom.inforwaves.com/v1alpha1
kind: License
metadata:
  name: enterprise-license
  namespace: articom-production
spec:
  # The license key from admin.articom.io
  key: "uzBIqkCs2IjA8SiS.rea_FYC2vtts2n9uqY07Lrsy4TDGmEMyHFRA5qMo6TysirG1M_ApGheafJaXSWj5lA4P6NZ6wTsBM38yRh-0l5g078mopBu9RFp8izSc_2P8XK9q_Jeqgm2YlRNm3zsmZBNQQebN5BgNX9BE0ToA_Dz8qDmUnE3SQkzlLq8jvB2i9j-ArzWOUfemY2sfJzi9qA00rUyuNI2tSaugmDuCiKKTS8-WY-c.YBNLPHzE_s0DEMrVZ_FlRQ"

  # Base domain for Ingress
  domain: "articom.mycompany.com"

  # Ingress class (e.g. cloudflare, nginx, alb)
  ingressClassName: "cloudflare"

  # Subdomain routing customization
  subdomains:
    articom-api-service: "api"
    articom-livekit-dashboard: "dashboard"
    articom-consumer-portal-service: "portal"
    articom-chat-widget-service: "chat"
⁠Example 2: Minimal Internal Deployment (No Ingress)

If you only want internal cluster communication and don't need public Ingress:

apiVersion: articom.inforwaves.com/v1alpha1
kind: License
metadata:
  name: internal-license
  namespace: articom-staging
spec:
  key: "FBS_mSn66dK0RMwV.E1h94A6l..."

⁠Step 4: Apply to Kubernetes or GitOps
⁠Via kubectl:
kubectl apply -f articom-license.yaml
⁠Via GitOps (Argo CD / Flux):

Commit articom-license.yaml to your GitOps repository. Argo CD or Flux will sync the resource and the operator will automatically handle the rest.


⁠Step 5: Verify Deployment & Status
⁠1. Check the License Resource Status
kubectl get license -n articom-production

Output:

NAME                 VALID   CLIENT                   TYPE         AGE
enterprise-license   true    Acme Enterprise Corp     PRODUCTION   45s

Inspect detailed conditions, expiration date, and services:

kubectl describe license enterprise-license -n articom-production
⁠2. Verify Generated Kubernetes Resources
# View automatically created Docker Registry Secret
kubectl get secret articom-registry-credentials -n articom-production

# View Deployments and Pods
kubectl get deployments -n articom-production
kubectl get pods -n articom-production

# View ClusterIP Services
kubectl get services -n articom-production

# View Autoscalers (HPAs)
kubectl get hpa -n articom-production

# View Ingress
kubectl get ingress -n articom-production

⁠Step 6: Access Services & Configure DNS

If you configured domain: "articom.mycompany.com" and subdomains, the operator generated Ingress rules:

  • API Gateway: https://api.articom.mycompany.com
  • Dashboard: https://dashboard.articom.mycompany.com
  • Portal: https://portal.articom.mycompany.com
  • Chat Widget: https://chat.articom.mycompany.com

Ensure your DNS provider (e.g. Cloudflare, Route 53, Azure DNS) has a wildcard *.articom.mycompany.com or individual CNAME records pointing to your Ingress controller load balancer.


⁠Step 7: License Renewal & Revocation
  • Renewal: When you renew your license in admin.articom.io, the operator automatically detects the updated expiration date and new services during its hourly reconciliation cycle. No manual restart is required.
  • Key Rotation: To rotate keys, edit the spec.key field in your License YAML and run kubectl apply -f articom-license.yaml.
  • Revocation / Cleanup: If a license is deleted (kubectl delete license enterprise-license -n articom-production), Kubernetes garbage collection automatically cleans up all associated Deployments, Services, HPAs, Ingresses, and Secrets.

⁠Troubleshooting & Runbook

⁠1. License Status Shows isValid: false

Symptom: kubectl get license shows isValid: false.

Root Causes & Solutions:

  • Invalid Key string: Check for accidental whitespace or missing characters in spec.key.
  • Network / Firewall: Ensure the operator pod can reach https://admin.articom.io/api.
    kubectl logs -n articom-system deployment/articom-operator-controller-manager -c manager
    
  • Expired License: Verify on admin.articom.io if the license subscription has lapsed.

⁠2. Pods in ImagePullBackOff or ErrImagePull

Symptom: Pods fail to pull container images from articomacr.azurecr.io or *.pkg.dev.

Root Causes & Solutions:

  • Inspect the generated secret:
    kubectl get secret articom-registry-credentials -n articom-production -o jsonpath='{.data.\.dockerconfigjson}' | base64 --decode
    
  • Re-trigger a fresh reconciliation by applying an annotation:
    kubectl annotate license enterprise-license -n articom-production reconcile.articom.io/force=$(date +%s) --overwrite
    

⁠3. Ingress Not Routing Traffic

Symptom: 502 Bad Gateway or 404 Not Found when accessing service URLs.

Root Causes & Solutions:

  • Verify ingressClassName matches your cluster's ingress controller:
    kubectl get ingressclass
    
  • Check Ingress definition:
    kubectl describe ingress articom-ingress -n articom-production
    
  • Check whether target services and endpoints are healthy:
    kubectl get endpoints -n articom-production
    

⁠4. Dedicated LiveKit Worker Scheduling Issues

Symptom: articom-voice-livekit-worker pods remain in Pending state.

Reason: The voice worker requires dedicated high-performance nodes with:

nodeSelector:
  workload: livekit-worker
tolerations:
  - key: "workload"
    operator: "Equal"
    value: "livekit-worker"
    effect: "NoSchedule"

Fix: Ensure your node pool has the corresponding label (workload=livekit-worker) and taint applied, or provision a matching node group.


⁠Development & Maintenance

⁠Build & Test Commands
# Generate WebhookConfiguration, ClusterRole and CRDs from markers
make manifests

# Regenerate DeepCopy code
make generate

# Run unit and integration tests (uses envtest)
make test

# Run code style linter and auto-fix
make lint-fix

# Build container image
export IMG=docker.io/<username>/articom-cx-operator:v1.0.0
make docker-build IMG=$IMG

# Push container image to registry
make docker-push IMG=$IMG

# Build single-file install bundle
make build-installer IMG=$IMG
⁠Multi-Group Conversion

If expanding to multi-group APIs in the future, follow the steps documented in AGENTS.md⁠.


⁠License

Copyright © 2026 Inforwaves. Licensed under the Apache License, Version 2.0⁠.

Tag summary

Content type

Image

Digest

sha256:89efda334…

Size

31.2 MB

Last updated

25 days ago

docker pull articomio/articom-cx-operator