Self-hosted service
zcw576020095/Armada avatar
zcw576020095/Armada

Armada: a web panel for many Kubernetes clusters at once

告别 kubectl 切换之苦 —— 一个面板吃透 Kubernetes 多集群运维:覆盖 Deployment/Pod/Service/Ingress/ConfigMap/Secret/PVC 全资源 CRUD,支持在线编辑 YAML、一键扩缩容与重启、实时日志流、Pod 终端直连、节点 Cordon/Drain、细粒度 RBAC 权限、全平台操作审计、Prometheus 监控集成,让 K8s 运维从命令行时代跃迁到可视化 Web 时代

544 stars15 forksHTMLNOASSERTION

At a glance

What is it?
Armada is a Django and Python application that imports several kubeconfigs and presents them in one panel, with a local cache for reads and direct API server calls for writes, per cluster and module authorisation, a browser container terminal, and rollback support for three different workload kinds. It is a private-licence project with no published releases.
Who is it for?
Adopt Armada if you operate several Kubernetes clusters from a terminal and want one interface with a fast list view, a browser terminal and per cluster permissions, and you can run a Django service with a database. Skip it if a single cluster is all you have, since the tool exists to solve context switching, and skip it if your organisation requires an open source licence, since this one is published as a private licence.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 37 days ago.
What is it written in?
Mainly HTML, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is context switching and slow list views

Armada describes itself as one panel to manage all your Kubernetes clusters, and the README is specific about the four problems it sets out to solve. The first is kubectl config use-context, since holding several kubeconfigs means switching context before every lookup and running the wrong command in the wrong cluster when you get it wrong. The second is that on a large cluster kubectl get pods -A is unusable, and the project's test cluster has 250 plus nodes and 16000 plus pods, where a full pod list takes 4 minutes and a 168MB response body, and the transfer is often truncated mid-flight with an IncompleteRead. The third is that giving a colleague access through Kubernetes RBAC is heavy, since it means writing a Role and a RoleBinding, issuing a ServiceAccount token, and requiring the person to install kubectl. The fourth is that troubleshooting means jumping between a terminal and a panel, where logs, container access and YAML edits are three separate sets of commands. The design answer to the second problem is the interesting one. Armada runs a background thread per cluster that synchronises resources into a local database on a fixed cycle, and the pages read that cache, so the same data is fetched from two paths. The README states plainly that write operations always go straight to the API server and never read the cache, so that a scale, restart or rollback decision is not made on stale state.

The cache is one table, fully replaced each round

The core design section describes the synchronisation in detail, and the shape of the cache is a deliberate simplification. A background daemon thread runs per cluster and every 60 seconds synchronises ten kinds of resource in a fixed order.

code
namespace → pod → deployment → service → configmap
→ secret → ingress → persistentvolumeclaim → statefulset → daemonset

The cache lives in a single table, K8sResourceCache, with a unique key of cluster id, resource type and namespace, and the whole list for a resource type is serialised into a single JSON field, replaced in full each round with no incremental diff, and the stated reason is that this removes a whole class of bug where a deleted resource is never cleaned up. Synchronisation locks are per resource type, so a periodic sync and an immediate sync triggered by a write operation are mutually exclusive at the granularity of a resource and do not block each other, so changing a Deployment does not stall the pod sync. A failure to sync one resource type does not interrupt the round, and the error is recorded in a last error field that the front end turns into a banner telling the user that the cluster connection is abnormal and the list data may not be current, rather than letting the page pretend the data is fresh. That last detail is the one worth copying into any similar design, because a cache that fails silently is worse than no cache.

Chunked list pagination, because a big response gets cut off

The README explains why the ordinary listing approach does not work at scale. Pulling everything in one request fails on a large cluster, where the test cluster's pod list produced a single 168MB response body and the connection was broken during transfer with an IncompleteRead error. Raising the timeout does not help, because the larger the response the more likely it is to be cut off. The fix is to use the native chunked list of Kubernetes, with 500 items per page and a continue token to page downwards, which brings a single response from the hundreds of megabytes down to a few megabytes. There is a second failure mode with a token approach, since a continue token can expire during paging with a 410 Gone, in which case the walk restarts from the beginning. There is also a 50,000 item ceiling acting as a fuse, above which the list is truncated and marked, so that an abnormal cluster does not eat all the memory. The consequence for an evaluator is that the cache is not a nice-to-have but the load-bearing part of the design, since the direct path to the API server is documented as unusable at that scale for reads.

Rollback differs per workload, and the DaemonSet case is the awkward one

The README singles out the rollback mechanisms for the three workload kinds as an easy trap, and implements them separately. Deployment takes its history from the ReplicaSet and locates the current version through the deployment.kubernetes.io/revision annotation. StatefulSet takes its history from the ControllerRevision and locates the current version through status.update_revision, which is the full CR name. DaemonSet has no corresponding field, so the current version has to be found from the controller-revision-hash on the Pods and taken as the most common value. The rollback itself is applied as a JSON Merge Patch that changes only spec.template rather than replacing the object wholesale, so that the Kubernetes controller can reuse the existing ReplicaSet instead of stacking a new one on every rollback. The DaemonSet row is described as a trap the author actually hit, because there is no status.update_revision and the Pod label holds a bare hash such as 6fd988788d, while the ControllerRevision name is the name followed by the hash, so a direct equality comparison never succeeds.

The container terminal is HTTP polling, for a stated reason

The container terminal section explains a design choice that would otherwise look like a weakness. Kubernetes exec is a WebSocket protocol, but this project runs on a WSGI stack without channels or daphne, and letting the browser's WebSocket connect straight through to Kubernetes would mean converting the whole deployment to a full ASGI stack. The chosen approach is to let the Django process hold the WebSocket to Kubernetes while the browser exchanges bytes over short HTTP polling, which means zero deployment changes at the cost of one polling cycle of extra latency between input and echo. A single poll blocks for at most 0.6 seconds, returns as soon as there is output, and starts the next round immediately after a round trip finishes rather than using a timer, since a timer would stack up requests. Three constraints are given for security. All four endpoints are POST and sit under a pods prefix, so they land automatically on the pod module check with edit permission, since getting a shell is equivalent to full control of that Pod. The session token is bound to the creating user id, so a token handed to someone else is useless. And sessions have a 5 minute idle timeout and a total cap of 32, so a leaked session does not hold a Kubernetes connection indefinitely.

Thirteen modules, two permission types and checks on both sides

The permission model is an application layer on top of Kubernetes RBAC, and the granularity is the point. Authorisation is per user, per cluster, per module and per read or write, with a unique constraint on user, cluster and module. There are 13 modules, dashboard, cluster, node, namespace, deployment, statefulset, daemonset, pod, service, ingress, configmap, secret and pvc, and two permission types, view and edit, where edit includes delete. Two middlewares do the work, a login check across the whole application and a permission check per module. The module is derived by looking up the URL prefix, and any POST is treated as a write needing edit. The check result is injected as request.user_can_edit so the templates can hide write actions, but the README is emphatic that the interface side validates independently and does not rely on the front end hiding them, and a failed request distinguishes its type, with an AJAX call getting a JSON 403 and a page request rendering a 403 page. Elsewhere the implementation notes cover encrypted kubeconfig storage with Fernet symmetric encryption and the key in an environment variable, with the sealing and opening methods in the model layer so the database holds ciphertext, and a per cluster client connection pool using a singleton with a double-checked lock, which avoids re-parsing the kubeconfig and rebuilding TLS connections for every call.

Setup, and the encryption key warning in the environment file

The project is a Django 6.0.4 application on Python 3.12 with the Kubernetes Python client at v35 and Tailwind CSS 4.2 for the interface, and the top level of the repository shows a conventional Django layout with an accounts app, a clusters app, a resources app, a dashboard app, the armada package, templates, static files and a design_plan directory. The environment file is where the operational configuration lives, and it carries one warning that deserves repeating. The kubeconfig encryption key is generated with Fernet, and the file says that once this key is lost, the kubeconfig of already imported clusters can no longer be decrypted, and it should be backed up carefully.

yaml
DJANGO_SECRET_KEY=your-django-secret-key-here
DJANGO_DEBUG=True
DJANGO_ALLOWED_HOSTS=
KUBECONFIG_ENCRYPTION_KEY=your-fernet-key-here

The database is SQLite by default with no configuration needed, and PostgreSQL is the production option, with the engine, name, user, password, host and port given as commented settings. The Django secret key and the allowed hosts are set the same way, and the environment file itself is not committed, with the gitignore already covering it. The front end build is a separate npm package whose only scripts run the Tailwind CLI to produce a minified stylesheet, with daisyui as the component layer, so the CSS is a build step rather than a checked-in artefact.

Where Armada is the wrong choice

The first limit is the licence, and it is not a formality. The repository's licence badge says Private, and the project listing records a custom licence that GitHub cannot classify, so the terms are whatever the LICENSE file says and they are not an OSI-approved grant. For an internal tool at a company that is fine, and for redistribution or for anything a customer will audit it is a blocker. The second is that the whole design assumes a Django service with a database and a background thread, so it is an application you operate rather than a tool you run, and that operational cost is real for a team whose only problem is that kubectl is slow on a big cluster. The third is the cache staleness question. The design is careful about it, with writes bypassing the cache and errors surfaced in a banner, but a dashboard built on a cache that is 60 seconds behind is a dashboard that will occasionally show a state that no longer exists, and an operator who has been burned once by that will not trust the list view again. The fourth is the security model of the browser terminal, which is a WebSocket held open by the web application and reached by short polling, with a session model and permission checks that are described but not verifiable from outside, and a terminal into a container in a production cluster is the highest value target in the system. The fifth is that the documentation is in Chinese, so an English-speaking team is working from a translation for the parts that matter, and the numbers in it are measured on the author's own test cluster, where the pod list of 16765 rows takes 259.6 seconds against the API server and 17.8 milliseconds from the cache, a difference that is real but specific to that cluster's shape and to a connection that is not to the internet.

Editorial conclusion

Adopt Armada if you operate several Kubernetes clusters from a terminal and want one interface with a fast list view, a browser terminal and per cluster permissions, and you can run a Django service with a database. Skip it if a single cluster is all you have, since the tool exists to solve context switching, and skip it if your organisation requires an open source licence, since this one is published as a private licence. Verify first that the Python and Django versions match, that your clusters are reachable from the service, and that the encryption key for stored kubeconfigs is backed up, because losing it makes the imported clusters undecryptable.

Frequently asked questions

How does Armada make large Kubernetes clusters usable?

A background thread per cluster synchronises ten resource types into a local cache table every 60 seconds, and the pages read that cache, while write operations always go straight to the API server so decisions are not made on stale state. Listing also uses the native chunked list with 500 items per page and a continue token, because a single full pod list on a large cluster produced a 168MB response that was cut off mid transfer.

What permission model does Armada use?

An application layer authorisation by user, cluster, module and read or write, across 13 modules with view and edit permission types where edit includes delete. Two middlewares enforce it, one for login across the application and one per module, with the module derived from the URL prefix and any POST treated as a write. The interface side checks independently and does not rely on the front end hiding the write actions.

How does Armada store the kubeconfig for each cluster?

Encrypted at rest with Fernet symmetric encryption, with the key supplied through the KUBECONFIG_ENCRYPTION_KEY environment variable and the sealing and opening methods in the model layer, so the database holds ciphertext. The environment file warns that if the key is lost the kubeconfig of already imported clusters can no longer be decrypted, and that it should be backed up.

Official sources

  1. Issues
  2. README
  3. zcw576020095/Armada on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/zcw576020095-armada.svg)](https://hysenlabs.com/projects/zcw576020095-armada)

Was this useful?