Skip to content

LinearB On-Prem Agent - Ingress Usage Scenarios

This guide covers different ingress configuration scenarios for the LinearB On-Prem Agent (v4.0.11+).


Overview

Starting from version 4.0.11, the LinearB On-Prem Agent provides flexible ingress configuration options to support various deployment scenarios:

  1. Install ingress controller with the agent (default behavior)
  2. Use an existing ingress controller in your namespace
  3. Disable ingress entirely and use LoadBalancer service
  4. Multi-host configuration with single or multiple hostnames
  5. TLS/HTTPS configuration with certificate management
  6. Chart-specific ingress override for granular control per subchart

The global.RECEIVER_INGRESS flag is still supported for backward compatibility with existing installations.


Configuration Options

Ingress Configuration Parameters

Add these parameters to your local-values.yaml file:

global:
  # Legacy flag (still supported for backward compatibility)
  # Controls both ingress controller installation and Ingress resource creation
  RECEIVER_INGRESS: true|false

  # Global ingress control (new in v4.0.11+)
  ingress:
    # Whether to install the ingress-nginx controller
    # Condition priority: ingress.installController > global.ingress.installController > global.RECEIVER_INGRESS
    installController: true|false

ingress:
  # Input for optional ingress-nginx chart

  controller:
    # IngressClass name that the controller watches
    ingressClass: "linearb-opa-ingress-class"

    ingressClassResource:
      # IngressClass resource name to create
      name: "linearb-opa-ingress-class"

onprem-receiver:
  ingress:
    # Whether to create Ingress resource for onprem-receiver
    # Priority: global.RECEIVER_INGRESS (if defined) > onprem-receiver.ingress.enabled
    enabled: true|false

    # IngressClass name for the Ingress resource (spec.ingressClassName)
    # Falls back to controller.ingressClass if not specified
    className: "linearb-opa-ingress-class"

Usage Scenarios

Scenario 1: Default Installation (Install Ingress Controller)

Use case: You don't have an ingress controller in your namespace and want the agent to install one.

Configuration:

global:
  RECEIVER_INGRESS: "true"  # Legacy flag (still supported)
  # ... other global settings

Or using the new parameters (v4.0.11+):

global:
  ingress:
    installController: true  # Install ingress-nginx controller
  # ... other global settings

ingress:
  installController: true  # Can also be set here (takes priority over global)
  controller:
    ingressClass: "linearb-opa-ingress-class"
    ingressClassResource:
      name: "linearb-opa-ingress-class"

onprem-receiver:
  ingress:
    enabled: true
    className: "linearb-opa-ingress-class"

What happens: - The agent installs ingress-nginx controller in your namespace - The controller watches for Ingress resources with ingressClassName: "linearb-opa-ingress-class" - Creates an Ingress resource for the onprem-receiver service with the specified className - Service type is set to ClusterIP


Scenario 2: Use Existing Ingress Controller

Use case: Your cluster or namespace already has an ingress controller installed, and you want to use it instead of installing a new one.

Configuration:

global:
  RECEIVER_INGRESS: "true"  # Enable Ingress resource creation
  ingress:
    installController: false  # Don't install ingress-nginx controller
  # ... other global settings

ingress:
  installController: false  # Don't install ingress-nginx controller (takes priority over global)

onprem-receiver:
  ingress:
    enabled: true
    className: "nginx"  # Or your existing ingress class name (e.g., "traefik", "haproxy")

What happens: - The agent does NOT install a new ingress controller - Creates an Ingress resource for the onprem-receiver service with ingressClassName: "nginx" - The existing ingress controller in your namespace handles the traffic based on the className - Service type is set to ClusterIP

Important notes: - Make sure your existing ingress controller is configured to watch the namespace where the agent is deployed - Update className to match your existing ingress controller's IngressClass name - If using a non-nginx ingress controller, you may need to adjust ingress annotations in helm/onprem-receiver/values.yaml


Scenario 3: Disable Ingress (Use LoadBalancer)

Use case: You don't want to use ingress and prefer to expose the service via LoadBalancer or NodePort.

Configuration:

global:
  RECEIVER_INGRESS: "false"  # Legacy flag (still supported)
  # ... other global settings

Or using the new parameters:

global:
  ingress:
    installController: false  # Don't install ingress controller
  # ... other global settings

onprem-receiver:
  ingress:
    enabled: false  # Don't create Ingress resource

What happens: - No ingress controller is installed - No Ingress resource is created for onprem-receiver - Service type is set to LoadBalancer


Scenario 4: Multi-Host Configuration

Use case: You want to expose the onprem-receiver service on multiple hostnames (e.g., for different domains or environments).

Configuration:

global:
  RECEIVER_INGRESS: "true"
  ingress:
    installController: true  # Install ingress controller (or false if using existing)

onprem-receiver:
  ingress:
    enabled: true
    className: "linearb-opa-ingress-class"
    hosts:
      # Option 1: Single host
      - host: "on-prem-api.example.com"
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: on-prem-agent-onprem-receiver

      # Option 2: Multiple hosts
      # - host: "on-prem-api.example.org"
      #   paths:
      #     - path: /
      #       pathType: Prefix
      #       backend:
      #         service:
      #           name: on-prem-agent-onprem-receiver

      # Option 3: No host specified (accepts all hosts)
      # - paths:
      #     - path: /
      #       pathType: Prefix
      #       backend:
      #         service:
      #           name: on-prem-agent-onprem-receiver

What happens: - Ingress routes traffic from specified host(s) to the onprem-receiver service - You can define multiple hosts with different paths - If no host is specified, the ingress accepts all hosts (default behavior)


Scenario 5: TLS/HTTPS Configuration

Use case: You want to secure your ingress with TLS certificates.

Configuration:

global:
  RECEIVER_INGRESS: "true"
  ingress:
    installController: true  # Install ingress controller (or false if using existing)

onprem-receiver:
  ingress:
    enabled: true
    className: "linearb-opa-ingress-class"
    hosts:
      - host: "on-prem-api.example.com"
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: on-prem-agent-onprem-receiver

    # TLS configuration
    tls:
      - hosts:
          - "on-prem-api.example.com"
        secretName: on-prem-tls

      # Multiple certificates for multiple hosts
      # - hosts:
      #     - "on-prem-api.example.org"
      #   secretName: on-prem-tls-org

What happens: - Ingress terminates TLS using the certificate from the specified Kubernetes secret - The secret must exist in the same namespace as the ingress - Multiple TLS configurations can reference different secrets for different hosts

Creating the TLS secret:

# Create TLS secret from certificate files
kubectl create secret tls on-prem-tls \
  --cert=path/to/tls.crt \
  --key=path/to/tls.key \
  -n linearb

# Or for cert-manager (automatic certificate provisioning)
# Create a Certificate resource that references your Issuer

Scenario 6: Chart-Specific Ingress Override

Use case: You want to control ingress at the subchart level when global.RECEIVER_INGRESS is not set.

Configuration:

# Option 1: Enable ingress via subchart setting (when RECEIVER_INGRESS is not set)
global:
  # RECEIVER_INGRESS is not defined
  ingress:
    installController: true  # Install ingress controller

onprem-receiver:
  ingress:
    enabled: true  # Enable ingress for this chart
    className: "linearb-opa-ingress-class"

Or:

# Option 2: Disable ingress via subchart setting (when RECEIVER_INGRESS is not set)
global:
  # RECEIVER_INGRESS is not defined
  ingress:
    installController: false  # Don't install ingress controller

onprem-receiver:
  ingress:
    enabled: false  # Disable ingress for this chart

What happens: - When global.RECEIVER_INGRESS is not defined, the onprem-receiver.ingress.enabled setting controls ingress creation - If you do set global.RECEIVER_INGRESS, it will take precedence over the subchart setting

Priority order: - For Ingress resource creation: global.RECEIVER_INGRESS (if defined) > onprem-receiver.ingress.enabled - For controller installation: ingress.installController > global.ingress.installController > global.RECEIVER_INGRESS


Backward Compatibility

Automatic className Detection on Upgrade

Starting from v4.0.11, the agent automatically detects and preserves the existing ingress className during upgrades:

  • On upgrade: If an existing Ingress resource with className: "nginx" is found, it will be automatically preserved
  • On fresh install: New installations will use className: "linearb-opa-ingress-class" by default

This means you can upgrade from v4.0.10 or earlier without any configuration changes, and your existing ingress will continue to work.

Legacy RECEIVER_INGRESS Flag

The legacy global.RECEIVER_INGRESS flag is fully supported for backward compatibility:

RECEIVER_INGRESS Behavior
"true" Installs ingress controller + creates Ingress
"false" No ingress controller, uses LoadBalancer service

If global.RECEIVER_INGRESS is defined, it takes precedence over onprem-receiver.ingress.enabled. To use subchart-level control, leave global.RECEIVER_INGRESS undefined.


Migration Examples

Migrating from global.RECEIVER_INGRESS to new parameters

Before (v4.0.10 and earlier):

global:
  RECEIVER_INGRESS: "true"

After (v4.0.11+, no changes needed):

global:
  RECEIVER_INGRESS: "true"  # Keep this - className "nginx" will be auto-detected and preserved

Or explicitly migrate to the new parameters with your existing className:

global:
  ingress:
    installController: true
    className: "nginx"  # Fallback className for Ingress resources

onprem-receiver:
  ingress:
    enabled: true
    className: "nginx"  # Explicitly maintain existing className

Or migrate to the new default className (requires updating your DNS/ingress configuration):

global:
  ingress:
    installController: true
    className: "linearb-opa-ingress-class"  # New installations default

onprem-receiver:
  ingress:
    enabled: true
    className: "linearb-opa-ingress-class"

Troubleshooting

Ingress controller not receiving traffic

  1. Verify the ingress controller is running in your namespace:

    kubectl get pods -n linearb | grep ingress
    

  2. Check the Ingress resource:

    kubectl get ingress -n linearb
    kubectl describe ingress on-prem-agent-onprem-receiver -n linearb
    

  3. Verify the ingress class matches your controller:

    kubectl get ingressclass
    

Upgrade fails with "x509: certificate signed by unknown authority"

Symptom: UPGRADE FAILED: failed calling webhook "validate.nginx.ingress.kubernetes.io": ... tls: failed to verify certificate: x509: certificate signed by unknown authority

Cause: The ValidatingWebhookConfiguration for the nginx-ingress admission webhook has a stale caBundle from a previous installation. On upgrade, the nginx-ingress controller gets fresh self-signed certs, but the webhook config still holds the old CA — so the Kubernetes API server can't verify the webhook's TLS certificate.

Fix: Delete the stale webhook configuration and re-run the upgrade:

kubectl delete validatingwebhookconfiguration on-prem-agent-ingress-admission
helm upgrade --install -n linearb on-prem-agent oci://<registry>/on-prem-agent \
  --values local-values.yaml --version <version>

The certgen job on the next upgrade will recreate the ValidatingWebhookConfiguration with the correct caBundle.


Service is LoadBalancer instead of ClusterIP

This usually means ingress is disabled. Check your configuration:

helm get values on-prem-agent -n linearb | grep -A5 ingress


Customizing Container Registry

Overview

The LinearB On-Prem Agent uses a centralized registry configuration that allows you to customize which container registry to pull images from. This is useful when: - You want to use a private mirror/proxy registry - You have corporate policies requiring images to come from specific registries - You're running in an air-gapped environment

Default Registry Configuration

By default, the agent uses the following registry:

global:
  JFROG_HOST: "linearb-on-prem-dist.jfrog.io/artifactory/on-prem-oci"

Architecture

The agent uses a hybrid approach for registry configuration:

  1. External subcharts (minio, rabbitmq, redis, datadog, fluent-bit): Use YAML anchors to reference the registry
  2. Internal charts (agent-api, scheduler, sensors, etc.): Can use Helm templating helpers to dynamically build registry paths

Overriding the Registry

Option 1: Override global.JFROG_HOST Only

For services that use the registry split (like rabbitmq, redis):

# my-custom-values.yaml
global:
  JFROG_HOST: "my-custom-registry.io/artifactory/custom-path"

# These services automatically use the JFROG_HOST reference
rabbitmq:
  image:
    registry: *jfrog_registry  # Automatically uses global.JFROG_HOST
    repository: image-dependencies/rabbitmq

redis:
  image:
    registry: *jfrog_registry  # Automatically uses global.JFROG_HOST
    repository: image-dependencies/redis

Important: YAML anchors only work within the default values.yaml file and cannot be used in override files.

Option 2: Full Override for External Subcharts

For external subcharts that require full repository paths (like minio):

# my-custom-values.yaml
global:
  JFROG_HOST: "my-custom-registry.io/artifactory/custom-path"

minio:
  image:
    repository: "my-custom-registry.io/artifactory/custom-path/image-dependencies/minio"
  mcImage:
    repository: "my-custom-registry.io/artifactory/custom-path/image-dependencies/mc"

datadog-agent:
  registry: "my-custom-registry.io/artifactory/custom-path"

fluent-bit:
  image:
    registry: "my-custom-registry.io/artifactory/custom-path"

socat:
  image:
    registry: "my-custom-registry.io/artifactory/custom-path"

Option 3: Using --set Flags

helm upgrade my-release ./helm/on-prem-agent \
  --set global.JFROG_HOST="my-custom-registry.io/artifactory/custom-path" \
  --set minio.image.repository="my-custom-registry.io/artifactory/custom-path/image-dependencies/minio" \
  --set minio.mcImage.repository="my-custom-registry.io/artifactory/custom-path/image-dependencies/mc" \
  --set datadog-agent.registry="my-custom-registry.io/artifactory/custom-path"

Complete Override Example

Here's a complete example for switching to a custom registry:

# custom-registry-values.yaml
global:
  JFROG_HOST: "harbor.mycompany.com/linearb-mirror"
  JFROG_USER: "myuser"
  JFROG_KEY: "mypassword"

# External charts requiring full paths
minio:
  image:
    repository: "harbor.mycompany.com/linearb-mirror/image-dependencies/minio"
  mcImage:
    repository: "harbor.mycompany.com/linearb-mirror/image-dependencies/mc"

rabbitmq:
  image:
    registry: "harbor.mycompany.com/linearb-mirror"
    repository: image-dependencies/rabbitmq

redis:
  image:
    registry: "harbor.mycompany.com/linearb-mirror"
    repository: image-dependencies/redis

datadog-agent:
  registry: "harbor.mycompany.com/linearb-mirror"

fluent-bit:
  image:
    registry: "harbor.mycompany.com/linearb-mirror"

socat:
  image:
    registry: "harbor.mycompany.com/linearb-mirror"

ingress:
  global:
    registry: "harbor.mycompany.com/linearb-mirror"
  defaultBackend:
    image:
      registry: "harbor.mycompany.com/linearb-mirror"
  controller:
    image:
      registry: "harbor.mycompany.com/linearb-mirror"
    admissionWebhooks:
      patch:
        image:
          registry: "harbor.mycompany.com/linearb-mirror"

Important Notes

  1. YAML Anchor Limitation: YAML anchors (like *jfrog_registry) only work within a single YAML file. When you provide override values in a custom file or via --set, you must provide complete, fully-qualified values.

  2. Registry Authentication: Update global.JFROG_USER and global.JFROG_KEY if your custom registry requires authentication.

  3. Image Pull Secrets: Ensure your imagePullSecrets are configured correctly for your custom registry:

    global:
      imagePullSecrets:
        - regcred  # Update this secret with your registry credentials
    

  4. Chart Dependencies: Don't forget to update the registry for chart dependencies in Chart.yaml if you're also mirroring Helm charts.

Testing Registry Configuration

After applying custom registry values, verify the configuration:

# Check the rendered templates
helm template on-prem-agent ./helm/on-prem-agent -f custom-registry-values.yaml | grep -i "repository:\|registry:"

# Check running pods
kubectl get pods -n linearb -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].image}{"\n"}{end}'

Worker Pod Scheduling Configuration

Overview

The LinearB On-Prem Agent dynamically creates worker pods for various jobs (linta, pm-connectors, sensors). You can control where these pods are scheduled in your Kubernetes cluster using nodeSelector and affinity configurations.

Important: This configuration applies to dynamically created worker pods only. For regular deployments (scheduler, agent-api, sensors, etc.), use the standard per-service configuration in each chart's values.yaml (e.g., scheduler.nodeSelector, agent-api.affinity). See the "Scheduling for Deployments vs Worker Pods" section below for details.

This is useful when: - You want to dedicate specific nodes for LinearB worker pods - You need to avoid certain nodes (e.g., system nodes, nodes with limited resources) - You want to ensure pods run on nodes with specific hardware (e.g., amd64 architecture) - You need to comply with organizational policies about workload placement

Configuration Parameters

Add these parameters to your local-values.yaml file:

global:
  workerPods:
    # NodeSelector - simple key-value node labels for pod placement
    nodeSelector:
      kubernetes.io/arch: amd64
      node-type: worker

    # Affinity - advanced pod scheduling rules
    affinity:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
              - node1
              - node2

Use Cases

Use Case 1: Schedule on Specific Architecture

Scenario: Ensure all worker pods run on amd64 nodes only.

Configuration:

global:
  workerPods:
    nodeSelector:
      kubernetes.io/arch: amd64

Use Case 2: Dedicate Specific Nodes

Scenario: Run worker pods only on nodes labeled as node-type: linearb-worker.

Configuration:

global:
  workerPods:
    nodeSelector:
      node-type: linearb-worker

Prepare your nodes:

# Label nodes for LinearB workloads
kubectl label nodes node1 node-type=linearb-worker
kubectl label nodes node2 node-type=linearb-worker

Use Case 3: Avoid Specific Nodes

Scenario: Prevent worker pods from running on nodes with the label node-role=system.

Configuration:

global:
  workerPods:
    affinity:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: node-role
              operator: NotIn
              values:
              - system

Use Case 4: Preferred Node Scheduling

Scenario: Prefer high-performance nodes but allow scheduling on other nodes if unavailable.

Configuration:

global:
  workerPods:
    affinity:
      nodeAffinity:
        preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          preference:
            matchExpressions:
            - key: node-type
              operator: In
              values:
              - high-performance

Use Case 5: Combined NodeSelector and Affinity

Scenario: Require amd64 architecture, prefer specific nodes.

Configuration:

global:
  workerPods:
    nodeSelector:
      kubernetes.io/arch: amd64
    affinity:
      nodeAffinity:
        preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          preference:
            matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
              - node1
              - node2

Important Notes

  1. Optional Configuration: If you don't configure workerPods, pods will be scheduled normally without constraints (default Kubernetes scheduling behavior).

  2. Node Labels: Ensure your nodes have the appropriate labels before applying nodeSelector or affinity rules:

    # View node labels
    kubectl get nodes --show-labels
    
    # Add a label to a node
    kubectl label nodes <node-name> <key>=<value>
    

  3. Applies to All Worker Pods: The configuration affects all dynamically created worker pods:

  4. Linta jobs (Git analysis)
  5. PM-Connectors jobs (Jira sync)
  6. Sensors jobs (data collection)

  7. Testing Configuration: After applying the configuration, verify that pods are scheduled correctly:

    # Watch worker pods being created
    kubectl get pods -n linearb -w
    
    # Check which node a pod was scheduled on
    kubectl get pod <pod-name> -n linearb -o wide
    

  8. Troubleshooting: If pods are not being scheduled:

    # Check pod events for scheduling issues
    kubectl describe pod <pod-name> -n linearb
    
    # Common issues:
    # - No nodes match the nodeSelector/affinity rules
    # - Nodes don't have the required labels
    # - Insufficient resources on matching nodes
    

Differences Between NodeSelector and Affinity

Feature NodeSelector Affinity
Complexity Simple key-value matching Advanced rules with operators
Required vs Preferred Always required Supports both required and preferred
Operators Exact match only In, NotIn, Exists, DoesNotExist, Gt, Lt
Multiple Conditions AND logic only Complex AND/OR logic
Use Case Simple node targeting Complex scheduling requirements

Recommendation: Use nodeSelector for simple cases, and affinity for advanced scheduling requirements.


Scheduling for Deployments vs Worker Pods

The LinearB On-Prem Agent consists of two types of workloads with different scheduling configuration methods:

1. Regular Deployments (Long-Running Services)

These are standard Kubernetes Deployments that run continuously: - scheduler, job-lifecycle-manager - agent-api, agent-poller, agent-cli - sensors, onprem-receiver - jobs-suite-dispatcher, jobs-suite-worker

Configuration Method: Use per-service values in your local-values.yaml:

# Example: Configure scheduling for the scheduler deployment
scheduler:
  nodeSelector:
    node-type: control-plane
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: node-role
            operator: In
            values:
            - management

# Example: Configure scheduling for agent-api
agent-api:
  nodeSelector:
    node-type: api-tier

2. Dynamic Worker Pods (Job Execution)

These are pods created on-demand by the scheduler to execute jobs: - Linta jobs - Git repository analysis - PM-Connectors jobs - Jira/project management data sync - Sensors jobs - Data collection tasks

Configuration Method: Use the global.workerPods section (as documented above):

global:
  workerPods:
    nodeSelector:
      node-type: worker
    affinity:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: workload-type
              operator: In
              values:
              - batch-jobs

Why the Difference?

  • Deployments use Helm's standard templating, so each chart can have its own nodeSelector and affinity configuration in its values.yaml
  • Dynamic worker pods are created programmatically by Python code in the scheduler service, so they read their configuration from the common-chart-values ConfigMap at runtime

Complete Example: Separating Control Plane and Worker Nodes

global:
  # Worker pods go to dedicated worker nodes
  workerPods:
    nodeSelector:
      node-type: batch-worker
      kubernetes.io/arch: amd64

# Scheduler deployments go to control plane nodes
scheduler:
  nodeSelector:
    node-type: control-plane

# API services go to API tier nodes
agent-api:
  nodeSelector:
    node-type: api-tier

sensors:
  nodeSelector:
    node-type: api-tier

This separation allows you to: - Place long-running services on stable, always-available nodes - Direct short-lived batch jobs to nodes optimized for burst workloads - Implement different resource limits and priorities for each tier


Redis Memory Configuration

Overview

Redis is used by the LinearB On-Prem Agent for caching and session management. Proper memory configuration is critical for stability. These values must be adjusted based on the number of repositories tracked and the activity level in those repositories.

Understanding Memory Units (Critical!)

IMPORTANT: Kubernetes and Redis use different memory unit systems, and this correlation is tricky:

Unit System Example Actual Bytes
Mi (Mebibytes) Binary (base 2) 400Mi 400 × 1,048,576 = 419,430,400 bytes (~419.43 MB)
mb (Megabytes) Decimal (base 10) 350mb 350 × 1,000,000 = 350,000,000 bytes (~333.79 MiB)

Key Point: When you set a Kubernetes memory limit of 400Mi, you're allocating ~419.43 MB. If you set Redis maxmemory 350mb, Redis uses 350 MB, leaving ~70MB overhead for Redis operations.

Default Configuration

redis:
  commonConfiguration: |-
    # Memory management: 350mb provides ~70MB overhead for Redis operations
    # Container limit is 400Mi (419MB), leaving 16.5% buffer for safety
    maxmemory 350mb
    maxmemory-policy volatile-lru
  resources:
    limits:
      memory: 400Mi
    requests:
      cpu: 30m
      memory: 200Mi

Calculation: - Container limit: 400Mi = 419.43 MB - Redis maxmemory: 350mb = 350 MB - Overhead: 419.43 - 350 = 69.43 MB (16.5% buffer)

This 16.5% buffer is critical - it provides space for: - Redis internal operations and metadata - Connection overhead - Memory fragmentation - OS-level buffers

When to Scale Up Memory

You need to increase Redis memory if: - Redis pod gets OOM-killed (Out of Memory) - You're tracking more repositories or repositories with high activity - High eviction rate (evicted_keys metric increasing rapidly) - Performance degradation (slow Git operations, frequent cache misses)

Scaling Guidelines

When increasing memory, always maintain the ~16.5% buffer between Redis maxmemory and container limit:

Calculation Formula: 1. Determine your container limit (e.g., 800Mi) 2. Convert to MB: Multiply Mi by 1.048576 (e.g., 800Mi × 1.048576 = 838.86 MB) 3. Calculate maxmemory: Use 83.5% of the MB value (e.g., 838.86 × 0.835 = 700.45 MB) 4. Round down: Use safe value like 700mb

Example scaling configurations:

# Small deployment (default)
redis:
  commonConfiguration: |-
    maxmemory 350mb
  resources:
    limits:
      memory: 400Mi

# Medium deployment (more repos/activity)
redis:
  commonConfiguration: |-
    maxmemory 700mb
  resources:
    limits:
      memory: 800Mi

# Large deployment (many repos/high activity)
redis:
  commonConfiguration: |-
    maxmemory 1800mb
  resources:
    limits:
      memory: 2Gi

Monitoring Redis Memory

Check if your configuration is appropriate:

# Connect to Redis pod
kubectl exec -it $(kubectl get pod -n linearb -l app.kubernetes.io/name=on-prem-agent-redis -o jsonpath='{.items[0].metadata.name}') -n linearb -- redis-cli

# Check memory usage
INFO memory

# Key metrics:
# - used_memory: Current memory used
# - maxmemory: Your configured limit
# - mem_fragmentation_ratio: Should be ~1.0 (if >1.5, increase memory)
# - evicted_keys: Number of evicted keys (if high, increase memory)

Troubleshooting OOM Issues

Problem: Redis pod gets OOM-killed

Check pod events:

kubectl describe pod -n linearb -l app.kubernetes.io/name=on-prem-agent-redis

Solution: Increase both values proportionally while maintaining buffer:

redis:
  commonConfiguration: |-
    # Increased from 350mb to 700mb
    maxmemory 700mb
    maxmemory-policy volatile-lru
  resources:
    limits:
      memory: 800Mi  # Increased from 400Mi
    requests:
      cpu: 50m       # Scale CPU with memory
      memory: 400Mi  # Increased from 200Mi

Important Notes

  1. Unit mismatch is intentional: Using mb for Redis and Mi for Kubernetes is correct - don't change both to the same unit
  2. Always maintain buffer: Don't set maxmemory too close to container limit or Redis will be OOM-killed
  3. Scale CPU with memory: Increase CPU requests/limits proportionally when increasing memory
  4. Monitor before scaling: Use kubectl top pod to check actual memory usage before increasing limits
  5. Memory policy: volatile-lru evicts least recently used keys with TTL set - appropriate for cache data

Storage Class Configuration for Minio

Overview

The LinearB On-Prem Agent uses Minio for integration credential storage. Minio requires a PersistentVolumeClaim (PVC), and the storage class backing that PVC depends on your Kubernetes platform. The global.K8S_PLATFORM value controls how the PVC is provisioned.

Supported Platforms

K8S_PLATFORM Storage Behavior
k3d Creates a hostPath-backed PV + PVC
eks Creates a StorageClass backed by AWS EBS (gp2) and a PVC using it
aks Creates a PVC using the managed-premium Azure storage class (or MINIO_STORAGE_CLASS if set)
default Creates a PVC with no storageClassName, using the cluster's default storage class (or MINIO_STORAGE_CLASS if set)

Scenario 1: Azure Kubernetes Service (AKS)

Use case: Deploying on AKS with the managed-premium storage class.

Configuration:

global:
  K8S_PLATFORM: "aks"
  PV_SIZE: "200Gi"

What happens: - A PVC named minio-pvc is created using storageClassName: managed-premium - Azure Disk is provisioned automatically by the AKS storage class

Custom storage class override:

global:
  K8S_PLATFORM: "aks"
  MINIO_STORAGE_CLASS: "managed-csi-premium"  # Override the default managed-premium
  PV_SIZE: "200Gi"

Scenario 2: Default Storage Class (Any Platform)

Use case: Your cluster has a default storage class configured and you want Minio to use it — for example, on GKE, OpenShift, or any generic K8s environment.

Configuration:

global:
  K8S_PLATFORM: "default"
  PV_SIZE: "200Gi"

What happens: - A PVC named minio-pvc is created with no explicit storageClassName - Kubernetes binds the PVC to a PV provisioned by whichever storage class is annotated as the cluster default


Scenario 3: Pre-Existing / Custom Storage Class

Use case: You have a specific storage class already configured in your cluster and want Minio to use it explicitly (e.g., fast-ssd, nfs-storage, etc.).

Configuration:

global:
  K8S_PLATFORM: "default"
  MINIO_STORAGE_CLASS: "fast-ssd"
  PV_SIZE: "200Gi"

What happens: - A PVC is created with storageClassName: fast-ssd - Any pre-existing storage class in the cluster can be used — no new StorageClass resource is created


Configuration Reference

global:
  # Kubernetes platform — controls how the Minio PVC is provisioned
  # Supported values: k3d, eks, aks, default
  K8S_PLATFORM: "default"

  # Persistent volume size for Minio
  PV_SIZE: "200Gi"

  # Optional: override the storage class used for the Minio PVC.
  # When K8S_PLATFORM is 'aks', defaults to 'managed-premium' if not set.
  # When K8S_PLATFORM is 'default', omitting this uses the cluster's default storage class.
  # MINIO_STORAGE_CLASS: "my-storage-class"

Important Notes

  1. Data persistence: The minio-pvc PVC is created with helm.sh/resource-policy: keep, meaning it will not be deleted on helm uninstall. This prevents accidental data loss of stored credentials.

  2. k3d is for local development only: The k3d platform uses a hostPath volume tied to a single node. It is not suitable for production environments.

  3. eks creates a StorageClass: Unlike the other platforms, eks creates a new StorageClass resource named minio (backed by AWS EBS gp2) in addition to the PVC. If this StorageClass already exists in your cluster, it will not be recreated.

  4. Verify your storage class exists:

    kubectl get storageclass
    

  5. PVC is immutable after creation: Changing MINIO_STORAGE_CLASS or PV_SIZE after the initial install has no effect on the existing PVC. To change storage configuration, the PVC must be manually deleted and recreated (requires Minio downtime and a data migration plan).


Additional Resources


For questions or issues, please contact LinearB support.