This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Day-2 Operations

Monitoring, backup and restore, and troubleshooting for production Capsule clusters

1 - Monitoring

Monitoring Capsule Items and Tenants

The Capsule dashboard allows you to track the health and performance of Capsule manager and tenants, with particular attention to resources saturation, server responses, and latencies. Prometheus and Grafana are requirements for monitoring Capsule.

OpenTelemetry Tracing

Capsule can export OpenTelemetry traces for admission webhook requests through the OTLP gRPC exporter. Enable tracing with manager.options.tracing.enabled=true and set manager.options.tracing.endpoint to your collector or tracing backend, for example tempo.monitoring-system.svc:4317. The exporter supports insecure transport for local clusters, TLS server name overrides, gzip compression, export timeouts, custom OTLP headers, and basic authentication. For production, prefer manager.options.tracing.basicAuth.existingSecret so credentials are loaded from a Kubernetes Secret instead of being rendered into controller arguments. Use manager.options.tracing.sampleRatio to control trace volume.

Tenants

Instrumentation for Tenants.

Metrics

The following Metrics are exposed and can be used for monitoring:

# HELP capsule_tenant_condition Provides per tenant condition status for each condition
# TYPE capsule_tenant_condition gauge
capsule_tenant_condition{condition="Cordoned",tenant="solar"} 0
capsule_tenant_condition{condition="Ready",tenant="solar"} 1


# HELP capsule_tenant_namespace_condition Provides per namespace within a tenant condition status for each condition
# TYPE capsule_tenant_namespace_condition gauge
capsule_tenant_namespace_condition{condition="Cordoned",target_namespace="earth",tenant="solar"} 0
capsule_tenant_namespace_condition{condition="Cordoned",target_namespace="fire",tenant="solar"} 0
capsule_tenant_namespace_condition{condition="Cordoned",target_namespace="foild",tenant="solar"} 0
capsule_tenant_namespace_condition{condition="Cordoned",target_namespace="green",tenant="solar"} 0
capsule_tenant_namespace_condition{condition="Cordoned",target_namespace="solar",tenant="solar"} 0
capsule_tenant_namespace_condition{condition="Cordoned",target_namespace="wind",tenant="solar"} 0
capsule_tenant_namespace_condition{condition="Ready",target_namespace="earth",tenant="solar"} 1
capsule_tenant_namespace_condition{condition="Ready",target_namespace="fire",tenant="solar"} 1
capsule_tenant_namespace_condition{condition="Ready",target_namespace="foild",tenant="solar"} 1
capsule_tenant_namespace_condition{condition="Ready",target_namespace="green",tenant="solar"} 1
capsule_tenant_namespace_condition{condition="Ready",target_namespace="solar",tenant="solar"} 1
capsule_tenant_namespace_condition{condition="Ready",target_namespace="wind",tenant="solar"} 1

# HELP capsule_tenant_namespace_count Total number of namespaces currently owned by the tenant
# TYPE capsule_tenant_namespace_count gauge
capsule_tenant_namespace_count{tenant="solar"} 6

# HELP capsule_tenant_namespace_relationship Mapping metric showing namespace to tenant relationships
# TYPE capsule_tenant_namespace_relationship gauge
capsule_tenant_namespace_relationship{target_namespace="earth",tenant="solar"} 1
capsule_tenant_namespace_relationship{target_namespace="fire",tenant="solar"} 1
capsule_tenant_namespace_relationship{target_namespace="soil",tenant="solar"} 1
capsule_tenant_namespace_relationship{target_namespace="green",tenant="solar"} 1
capsule_tenant_namespace_relationship{target_namespace="solar",tenant="solar"} 1
capsule_tenant_namespace_relationship{target_namespace="wind",tenant="solar"} 1

# HELP capsule_tenant_resource_limit Current resource limit for a given resource in a tenant
# TYPE capsule_tenant_resource_limit gauge
capsule_tenant_resource_limit{resource="limits.cpu",resourcequotaindex="0",tenant="solar"} 2
capsule_tenant_resource_limit{resource="limits.memory",resourcequotaindex="0",tenant="solar"} 2.147483648e+09
capsule_tenant_resource_limit{resource="pods",resourcequotaindex="1",tenant="solar"} 7
capsule_tenant_resource_limit{resource="requests.cpu",resourcequotaindex="0",tenant="solar"} 2
capsule_tenant_resource_limit{resource="requests.memory",resourcequotaindex="0",tenant="solar"} 2.147483648e+09

# HELP capsule_tenant_resource_usage Current resource usage for a given resource in a tenant
# TYPE capsule_tenant_resource_usage gauge
capsule_tenant_resource_usage{resource="limits.cpu",resourcequotaindex="0",tenant="solar"} 0
capsule_tenant_resource_usage{resource="limits.memory",resourcequotaindex="0",tenant="solar"} 0
capsule_tenant_resource_usage{resource="namespaces",resourcequotaindex="",tenant="solar"} 2
capsule_tenant_resource_usage{resource="pods",resourcequotaindex="1",tenant="solar"} 0
capsule_tenant_resource_usage{resource="requests.cpu",resourcequotaindex="0",tenant="solar"} 0
capsule_tenant_resource_usage{resource="requests.memory",resourcequotaindex="0",tenant="solar"} 0

Custom Metrics

You can gather more information based on the status of the tenants. These can be scrapped via Kube-State-Metrics CustomResourcesState Metrics. With these you have the possibility to create custom metrics based on the status of the tenants.

Here as an example with the kube-prometheus-stack chart, set the following values:

kube-state-metrics:
  rbac:
    extraRules:
      - apiGroups: [ "capsule.clastix.io" ]
        resources: ["tenants"]
        verbs: [ "list", "watch" ]
  customResourceState:
    enabled: true
    config:
      spec:
        resources:
          - groupVersionKind:
              group: capsule.clastix.io
              kind: "Tenant"
              version: "v1beta2"
            labelsFromPath:
              name: [metadata, name]
            metrics:
              - name: "tenant_size"
                help: "Count of namespaces in the tenant"
                each:
                  type: Gauge
                  gauge:
                    path: [status, size]
                commonLabels:
                  custom_metric: "yes"
                labelsFromPath:
                  capsule_tenant: [metadata, name]
                  kind: [ kind ]
              - name: "tenant_state"
                help: "The operational state of the Tenant"
                each:
                  type: StateSet
                  stateSet:
                    labelName: state
                    path: [status, state]
                    list: [Active, Cordoned]
                commonLabels:
                  custom_metric: "yes"
                labelsFromPath:
                  capsule_tenant: [metadata, name]
                  kind: [ kind ]
              - name: "tenant_namespaces_info"
                help: "Namespaces of a Tenant"
                each:
                  type: Info
                  info:
                    path: [status, namespaces]
                    labelsFromPath:
                      tenant_namespace: []
                commonLabels:
                  custom_metric: "yes"
                labelsFromPath:
                  capsule_tenant: [metadata, name]
                  kind: [ kind ]

This example creates three custom metrics:

  • tenant_size is a gauge that counts the number of namespaces in the tenant.
  • tenant_state is a state set that shows the operational state of the tenant.
  • tenant_namespaces_info is an info metric that shows the namespaces of the tenant.

2 - Backup & Restore

Run Backups and Restores of Tenants

Velero is a backup and restore solution that performs data protection, disaster recovery and migrates Kubernetes cluster from on-premises to the Cloud or between different Clouds.

When coming to backup and restore in Kubernetes, we have two main requirements:

  • Configurations backup
  • Data backup

The first requirement aims to backup all the resources stored into etcd database, for example: namespaces, pods, services, deployments, etc. The second is about how to backup stateful application data as volumes.

The main limitation of Velero is the multi tenancy. Currently, Velero does not support multi tenancy meaning it can be only used from admin users and so it cannot provided “as a service” to the users. This means that the cluster admin needs to take care of users’ backup.

Assuming you have multiple tenants managed by Capsule, for example wind and water, as cluster admin, you can to take care of scheduling backups for:

  • Tenant cluster resources
  • Namespaces belonging to each tenant

Create backup of a tenant

Create a backup of the tenant solar. It consists in two different backups:

  • backup of the tenant resource
  • backup of all the resources belonging to the tenant

To backup the wind tenant selectively, label the tenant as:

kubectl label tenant wind capsule.clastix.io/tenant=solar

and create the backup

velero create backup solar-tenant \
    --include-cluster-resources=true \
    --include-resources=tenants.capsule.clastix.io \
    --selector capsule.clastix.io/tenant=solar

resulting in the following Velero object:

apiVersion: velero.io/v1
kind: Backup
metadata:
  name: solar-tenant
spec:
  defaultVolumesToRestic: false
  hooks: {}
  includeClusterResources: true
  includedNamespaces:
  - '*'
  includedResources:
  - tenants.capsule.clastix.io
  labelSelector:
    matchLabels:
      capsule.clastix.io/tenant: solar
  metadata: {}
  storageLocation: default
  ttl: 720h0m0s

Create a backup of all the resources belonging to the wind tenant namespaces:

velero create backup solar-namespaces \
    --include-cluster-resources=false \
    --include-namespaces solar-production,solar-development,solar-marketing

resulting in the following Velero object:

apiVersion: velero.io/v1
kind: Backup
metadata:
  name: solar-namespaces
spec:
  defaultVolumesToRestic: false
  hooks: {}
  includeClusterResources: false
  includedNamespaces:
  - solar-production
  - solar-development
  - solar-marketing
  metadata: {}
  storageLocation: default
  ttl: 720h0m0s

Velero requires an Object Storage backend where to store backups, you should take care of this requirement before using Velero.

Restore a tenant from the backup

To recover the tenant after a disaster, or to migrate it to another cluster, create a restore from the previous backups:

velero create restore --from-backup solar-tenant
velero create restore --from-backup solar-namespaces

Using Velero to restore a Capsule tenant can lead to an incomplete recovery of tenant because the namespaces restored with Velero do not have the OwnerReference field used to bind the namespaces to the tenant. For this reason, all restored namespaces are not bound to the tenant:

kubectl get tnt
NAME   STATE    NAMESPACE QUOTA   NAMESPACE COUNT   NODE SELECTOR     AGE
water    active   9                 5                 {"pool":"water"}    34m
wind  active   9                 8                 {"pool":"wind"}  33m
solar    active   9                 0 # <<<           {"pool":"solar"}    54m

To avoid this problem you can use the script velero-restore.sh located under the hack/ folder:

./velero-restore.sh --kubeconfing /path/to/your/kubeconfig --tenant "wind" restore

Running this command, we are going to patch the tenant’s namespaces manifests that are actually ownerReferences-less. Once the command has finished its run, you got the tenant back.

kubectl get tnt
NAME   STATE    NAMESPACE QUOTA   NAMESPACE COUNT   NODE SELECTOR     AGE
water    active   9                 5                 {"pool":"water"}    44m
solar  active   9                 8                 {"pool":"wind"}  43m
wind    active   9                 3 # <<<           {"pool":"solar"}    12s

3 - Troubleshooting

Different topics when you encounter problems with Capsule

This page covers the most common issues encountered when running Capsule. Each section describes the symptom, the likely cause, and how to resolve it.

User cannot create namespaces or interact with tenants

Symptom: A user runs kubectl create namespace my-ns and gets a generic Forbidden error, or Capsule completely ignores the request and does not assign the namespace to any tenant.

Cause: The user is not recognized as a Capsule User. Capsule only acts on namespace creation requests from subjects that are configured as Capsule Users in the CapsuleConfiguration.

Resolution: Check which subjects are currently recognized:

kubectl get capsuleconfiguration default -o jsonpath='{.status.users}' | jq

If the user (or any of their groups) is not listed, add them. The recommended approach is to create a TenantOwner resource for the user:

apiVersion: capsule.clastix.io/v1beta2
kind: TenantOwner
metadata:
  name: alice
  labels:
    projectcapsule.dev/tenant: "solar"
spec:
  kind: User
  name: "alice"

Alternatively, configure a group in the CapsuleConfiguration:

manager:
  options:
    users:
      - kind: Group
        name: projectcapsule.dev

Namespace creation is rejected by the admission webhook

Symptom: Error from server (Forbidden): admission webhook "namespaces.mutating.projectcapsule.dev" denied the request.

Two common reasons:

Namespace quota exceeded

The tenant has reached its namespace quota. The error message will include Cannot exceed Namespace quota.

# Check the current namespace count vs quota
kubectl get tenant solar -o jsonpath='{.status.namespaceCount} / {.spec.namespaceOptions.quota}'

Contact your cluster administrator to increase the quota, or delete unused namespaces.

Force tenant prefix not respected

The tenant has forceTenantPrefix: true and the namespace name does not start with the tenant name.

# Wrong
kubectl create namespace my-app

# Correct
kubectl create namespace solar-my-app

If you belong to multiple tenants, Capsule cannot infer which one to use. Prefix the namespace name with the tenant you intend to use.

Admission webhook denied a label or annotation change

Symptom: Error from server (Forbidden): admission webhook "namespaces.validating.projectcapsule.dev" denied the request: metadata label "..." is not allowed.

Cause: A rule on the tenant restricts which values are allowed for that label. The error message contains the exact label key and the list of allowed values.

Resolution:

  1. Read the error message carefully. It states the allowed values.
  2. Use one of the permitted values:
# Example: allowed values are restricted and baseline
kubectl label namespace solar-development pod-security.kubernetes.io/enforce=baseline --overwrite
  1. If the label is marked as managed, it is controlled entirely by Capsule and cannot be changed by tenant users. Contact your cluster administrator.

Cannot list namespaces or cluster-scoped resources

Symptom: kubectl get namespaces returns an error such as Error from server (Forbidden): namespaces is forbidden: User "alice" cannot list resource "namespaces" in API group "" at the cluster scope.

Cause: The Capsule Proxy is not in use. Without the proxy, Kubernetes RBAC prevents non-admin users from listing cluster-scoped resources. The proxy intercepts those requests and filters results to show only resources belonging to the user’s tenants.

Resolution: Verify that the Capsule Proxy is installed and that your kubeconfig points to the proxy endpoint rather than the Kubernetes API server directly. See the Proxy documentation for setup instructions.

# Confirm which server your kubeconfig is pointing to
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'

TenantOwner resource is not granting access

Symptom: A TenantOwner resource was created but the user still cannot interact with the tenant.

Cause: The TenantOwner CRD uses aggregation to bind owners to tenants. The resource must carry the correct label for implicit assignment, and the tenant must reference it via matchOwners or the owner must be listed directly.

Checklist:

  1. Confirm the TenantOwner has the correct tenant label:
kubectl get tenantowner alice -o jsonpath='{.metadata.labels}'
# Expected: {"projectcapsule.dev/tenant":"solar"}
  1. Confirm the tenant lists the owner in its status:
kubectl get tenant solar -o jsonpath='{.status.owners}' | jq
  1. Confirm the user is recognized as a Capsule User (see the first section above).

  2. If using matchOwners with label selectors, verify the TenantOwner carries the matching labels:

kubectl get tenantowner platform-team --show-labels

Capsule Proxy is being throttled by the API server

Symptom: Clients talking through the Capsule Proxy receive connection errors such as:

net/http: abort Handler

or requests hang and are dropped without a response.

Cause: The Capsule Proxy is sending more requests to the Kubernetes API server than its configured QPS and burst limits allow. Because the proxy sits between clients and the API server, the client is the first to see the resulting aborted connections.

Resolution: Increase the clientConnectionQPS and clientConnectionBurst values for the Capsule Proxy. See the Proxy production installation guide for the recommended Helm values:

options:
  clientConnectionQPS: 200
  clientConnectionBurst: 400

Capsule controller fails to start due to timeouts

Symptom: The Capsule controller pod restarts repeatedly or never reaches Running. Logs contain errors such as:

E0707 08:38:18.319041  1 leaderelection.go:452] "Error retrieving lease lock"
  err="...net/http: request canceled (Client.Timeout exceeded while awaiting headers)"

or the controller stalls silently during startup on clusters with many resources.

Cause: Two common sources of startup timeouts:

  • Cache sync timeout — On large clusters the controller’s informer cache takes longer to populate than the default timeout allows, causing the controller to give up before it is ready to serve.
  • Leader election timeout — In high-pressure environments the controller cannot renew its leader lease within the default deadline, triggering repeated leader-election failures.

Resolution: Adjust the cacheSyncTimeout and leaderElection values for the Capsule controller. See the Production installation guide for the recommended Helm values.