Bringing Microsoft SQL Server to OpenEverest
When we started OpenEverest at Percona (back then it was called Percona Everest), we were thinking about the three most popular open source databases: MySQL, PostgreSQL and MongoDB. The version 1 architecture didn't make it easy to add new technologies, so adding proprietary engines like Microsoft SQL Server or Oracle was more of a fantasy.
But the idea was there, and we kept getting humble requests to add these technologies (1, 2) — both from the community and from customers.
Version 2 made it interesting. Our core is now modular and decoupled from the database technologies. A new database engine is just a plugin — we call them Providers. So we started thinking about MSSQL again.
Operators
There are no production-grade open source MSSQL operators for Kubernetes.
There are some attempts, like Anthony Nocentino's nocentino/sql-on-k8s-operator.
There is also DotKube/KubeSQLServer-Operator. Unlike most operators, this one is written in C#, which makes it harder to contribute to for a cloud native community that is used to Go.
Sadly, these attempts haven't translated into something lasting with real community traction.
There is a commercial operator from DH2i. It is promoted on Microsoft's website as well. It feels production grade, with new releases coming out fairly often.
Portworx (now part of Pure Storage) had something cooked up in-house as well for their Portworx Database Service (PDS). But they decided to kill the service, and the operator never saw the light of day.
OpenEverest providers rely on operators. So having a solid one was imperative for us to deliver a good solution — and so we decided to build our own. Here are some of the decisions we made along the way.
The Solanica MSSQL Operator
We built the operator with the boring, contributor-friendly cloud native stack: Go, kubebuilder/controller-runtime, envtest and Chainsaw for testing. Nothing exotic - that is the point. Anyone who has written a Kubernetes operator will feel at home, and the whole thing speaks the same idioms as the rest of the ecosystem.
A few decisions are worth calling out.
Decoupling standalone instances from availability groups
The most consequential early decision was to model a standalone SQL Server and a high-availability Always-On Availability Group as two separate custom resources rather than a single CRD with an ha: true toggle:
- MSSQLInstance — one or more independent SQL Server instances, no replication.
- MSSQLAvailabilityGroup — an Always-On AG spanning two or more replicas, with synchronous or asynchronous replication and automatic failover.
They share a common spec (image, edition, storage, resources, TLS, monitoring, backup) but each has its own reconciler and its own status vocabulary. The lifecycles are genuinely different: a standalone instance is stateless plumbing around a PVC, while an AG has to reason about quorum, replica roles, seeding, readable secondaries and safe failover. Squeezing both behaviours into one controller with a pile of if ha { … } branches is exactly how operators become unmaintainable. Keeping them apart means the standalone path stays trivial and the AG path can carry all of its complexity without leaking into the common case.
Failover without a fragile control loop: a health sidecar and a Kubernetes Lease
For Always-On AGs we did not want the operator to be on the critical path of every failover decision. Operators get rolled, throttled and disconnected; SQL Server's own HealthCheckTimeout defaults to 30 seconds precisely because health decisions need to be local and fast.
So every AG pod runs a small ag-sidecar next to the SQL Server container. The sidecar polls the local engine with sp_server_diagnostics, and the pod that is currently healthy and primary renews a Kubernetes Lease to advertise itself. If the primary's diagnostics go unhealthy, it stops renewing the Lease; a secondary can then win the Lease and drive the promotion. Primary election lives in the data plane, close to the engine, and Kubernetes' own Lease object is the source of truth — no bespoke consensus, no operator round-trip on the failure path. The sidecar also self-labels its pod with mssql.solanica.io/role: primary|replica, which is what the read/write Services select on.
Built-in observability with a metrics sidecar
Monitoring is opt-in but first class. Setting monitoring.enabled: true injects a sql_exporter sidecar into every SQL Server pod, exposing Prometheus metrics on port 9399. We ship a curated default set of collectors (engine health, AG replica roles and sync state, database sizes, wait stats), and you can layer your own T-SQL collectors on top via ConfigMap or Secret references - or disable the defaults entirely.
Because the exporter is a sidecar per pod rather than one central scraper, it always has a local, authenticated connection to the engine and it scales out with the deployment. For clusters running the Prometheus Operator, flipping monitoring.enablePodMonitor: true makes the operator create the matching PodMonitor for you, so metrics start flowing without any extra wiring.
Backups: SQL Server native, straight to object storage
Backups use SQL Server's native BACKUP ... TO URL writing directly to S3-compatible object storage — no volume shuffling, no external agent. It's driven by three CRDs:
- MSSQLBackup — a one-shot full, differential or log backup.
- MSSQLBackupSchedule — cron-driven backups with independent full / differential / log schedules and retention (keepLast count and keepDays age).
- MSSQLRestore — restore by backup reference, by explicit URLs (disaster recovery), or by assembling a full → differential → log chain discovered in the store, with optional point-in-time recovery (STOPAT).
The engine is AG-aware: it honours the backup-preference (preferred replica vs primary) and automatically marks backups taken on a secondary as COPY_ONLY so the backup chain stays intact. Point-in-time recovery falls out naturally from a weekly full + daily differential + 15-minute log schedule.
The details that make it production-shaped
A few smaller decisions round it out:
- TLS by default for HADR. The operator can auto-generate certificates so replica-to-replica traffic on the HADR endpoint is encrypted out of the box, with an option to bring your own certificate Secret.
- Editions and licensing. Developer, Express, Standard, Enterprise and EnterpriseCore are all selectable and passed through as MSSQL_PID; the operator knows the limits (Express can't run an AG, for example) and rejects impossible combinations at admission time via validating webhooks.
- Configuration passthrough. Trace flags and mssql.conf settings flow through the spec into a ConfigMap the engine reads at start-up, and a content hash on the pod template rolls the pods when they change.
- Readable secondaries and split Services. AGs expose a -primary (read/write) and a -replicas (read-only) Service so applications can fan reads out to secondaries when the workload allows.
The OpenEverest provider
With a capable operator in hand, the provider is deliberately thin. In OpenEverest v2 a provider is a plugin that translates the generic, engine-agnostic Instance resource into the concrete custom resources an operator understands. It implements three methods - Validate, Sync, Status - and nothing more.
The provider's spec is small and declarative. An Instance references a single logical component, engine, of type mssql; the provider maps it to either an MSSQLInstance or, when the Availability Group topology is selected, an MSSQLAvailabilityGroup. The provider-specific knobs are just two little schemas:
- Component parameters (MssqlParameters) — edition and image-pull-secret references for private registries.
- Topology parameters (AGTopologyParameters) — availabilityMode (synchronous / asynchronous) and readableSecondaries (all / readOnly / no).
These Go structs are annotated for OpenAPI generation, which is what lets the OpenEverest web UI render a proper form - dropdowns for edition and availability mode, validation baked in - with zero front-end work.
Everything else the user cares about - size, storage, resources, service exposure - comes from the base Instance spec that every provider shares, so an MSSQL database looks and behaves like any other database in OpenEverest.
FAQ
Is the operator open source?
No. Both the operator and the provider are at an early alpha stage and are closed source. We may revisit the licensing in the future, but for now the source code is not publicly available. If you want to try them - get in touch.
Why not just use an existing operator?
None of the open source options are production grade or have lasting community traction, and the one that is production grade is a paid product. Since OpenEverest providers lean on the operator underneath, we needed something we could trust and evolve on our own schedule.
Which SQL Server versions are supported?
SQL Server 2022 and later. Native backup to object storage and contained availability groups both rely on 2022+ features.
Does it support high availability?
Yes - Always-On Availability Groups with synchronous or asynchronous replication, readable secondaries and Lease-based automatic failover.