Skip to content

aalpar/janus

Repository files navigation

Janus

Atomic changes across Kubernetes resources.

Janus groups resource mutations across multiple Kubernetes namespaces into a single transaction. Create, Update, Patch, or Delete — each step is paired with an automatic compensating action. If any step fails, everything rolls back.

Patch operations use Server-Side Apply, so Janus owns only the fields it touches. This composes cleanly with GitOps tools — ArgoCD and Flux keep ownership of their fields on the same resources.

Example

# Create a transaction
janus create db-migration --sa janus-ops

# Patch connection strings across namespaces
janus add db-migration --type Patch --target ConfigMap/app-config --target-ns frontend \
  -f connection-patch.yaml
janus add db-migration --type Patch --target ConfigMap/app-config --target-ns backend \
  -f connection-patch.yaml --order 1
janus add db-migration --type Patch --target Deployment/api-server --target-ns backend \
  -f env-patch.yaml --order 2

# Seal to begin processing
janus seal db-migration

If the Deployment patch fails, both ConfigMap patches are automatically reverted. Each resource's prior field values are captured before mutation and restored on rollback.

kubectl get transaction db-migration
NAME            PHASE       SEALED   AGE
db-migration    Committed   true     34s

How it works

Janus implements the Saga pattern — each resource mutation is paired with a compensating action derived automatically from the prior state. If the sequence fails at step n, steps n-1 through 1 are undone in reverse order.

Two CRDs:

  • Transaction — groups changes under a common identity, controls when processing begins (spec.sealed), and tracks progress.
  • ResourceChange — a single mutation (Create, Update, Patch, or Delete) owned by a Transaction.

Patch operations use Server-Side Apply with a field manager scoped to the transaction. This means Janus owns only the fields it touches — other controllers and GitOps tools retain ownership of their fields on the same resources.

Resources are locked via advisory Kubernetes Leases during the transaction. Locks prevent concurrent Janus transactions from colliding but do not block direct kubectl writes — this is a deliberate trade-off to avoid admission webhook overhead.

Operations execute under a user-specified ServiceAccount identity, so RBAC boundaries are enforced per transaction.

When to use this

Operational changes that span multiple resources or namespaces, where the intermediate state is dangerous and GitOps doesn't apply:

  • Cross-resource field patches — update connection strings in 3 ConfigMaps and an env var in 2 Deployments as one unit; rollback all if any fails
  • Cross-namespace coordination — tenant provisioning, service mesh config, or infrastructure changes that cross namespace boundaries
  • Operational changes outside GitOps — incident response, migrations, or infrastructure changes that aren't templated manifests in a repo
  • Security-sensitive intermediate states — Namespace + RBAC + NetworkPolicy where a partial apply creates an unprotected window

When not to use this

  • Single-resource changes — just use kubectl apply.
  • Standard deployments — if your changes are templated manifests in git, use Helm, ArgoCD, or Flux. Janus is for operational changes that live outside the GitOps lifecycle.
  • Workflow orchestration — Janus applies resource mutations, not arbitrary jobs. Use Argo Workflows or Tekton for DAGs.
  • Workloads where eventual consistency is acceptable — standard Kubernetes reconciliation is simpler and more idiomatic.

Install

Prerequisites

  • Kubernetes v1.30+
  • kubectl v1.30+
  • cert-manager (for webhook TLS)

Helm

helm install janus chart/ --namespace janus-system --create-namespace

Container image

Pre-built multi-arch images (amd64 + arm64) are published to ghcr.io/aalpar/janus on every release.

Kustomize

make install
make deploy IMG=ghcr.io/aalpar/janus:<version>

Install the CLI

go install github.com/aalpar/janus/cmd/janus@latest

Or download a binary from the Releases page.

Set up a ServiceAccount

Janus runs resource operations under a ServiceAccount you specify. Create one with permissions on the resources your transactions will touch:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: janus-ops
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: janus-ops-edit
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: edit
subjects:
- kind: ServiceAccount
  name: janus-ops
  namespace: default

Documentation

  • Tutorial — hands-on walkthrough: create, rollback, and recover transactions using both the CLI and kubectl.
  • User Guide — complete reference: change types, ordering, monitoring, failure handling, recovery.
  • Design — architecture, state machine, lock manager, rollback storage, reconciliation loop.
  • Invariants — safety and liveness guarantees.
  • Contributing — how to build, test, and submit changes.
  • Bibliography — annotated references on Sagas, two-phase commit, and crash recovery.

License

Apache License 2.0 — see LICENSE.

About

Kubernetes transaction controller: Saga pattern with Lease-based advisory locking over CRDs

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors