mysql-operator: running MySQL InnoDB Cluster on Kubernetes with a frozen topology
MySQL Operator for Kubernetes
At a glance
- What is it?
- The MySQL Operator for Kubernetes manages InnoDB Cluster deployments, backups and upgrades inside a Kubernetes cluster, and it locks its own scope after first startup so a later change is rejected. It ships as CRD manifests or a Helm chart, with a custom licence rather than an OSI one.
- Who is it for?
- The MySQL Operator for Kubernetes fits platform teams that want MySQL InnoDB Cluster, automated upgrades and backup handled by a controller they can point at a CRD, and that can settle deployment scope before the first install.
- 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 1 day ago.
- What is it written in?
- Mainly Python, 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
A controller for InnoDB Cluster rather than a MySQL image
The MySQL Operator for Kubernetes is an operator for managing MySQL InnoDB Cluster setups inside a Kubernetes cluster, and the project states that it manages the full lifecycle including setup and maintenance, with automated upgrades and backup. The audience is platform and database reliability engineers who want a MySQL deployment to behave like a declared resource rather than a hand assembled set of StatefulSets.
The difference between running a MySQL container in Kubernetes and running InnoDB Cluster with an operator is membership management. InnoDB Cluster is a group of MySQL Server instances with a MySQL Router in front, and the group has to agree on primary selection, keep its own metadata consistent and stay reachable during a rolling operation. An operator exists precisely to reconcile that agreement, and the resources it watches are what make the intent visible in the cluster.
The project is brought to you by the MySQL team at Oracle, with copyright notices for 2020 and 2026, and the primary language of the repository is Python. Documentation and downloads live on the MySQL developer site, and the repository itself carries the manifests, the Helm chart, the samples and the tests rather than being the whole story of the product.
Install the CRDs first, then the operator deployment
Two objects have to be installed, and the order matters because the operator's custom resource definitions must exist before the controller starts and before you can submit a cluster. The documented sequence applies the CRD manifest first:
$> kubectl apply -f https://raw.githubusercontent.com/mysql/mysql-operator/26.7.0-2.3.0/deploy/deploy-crds.yamlThen the operator itself:
$> kubectl apply -f https://raw.githubusercontent.com/mysql/mysql-operator/26.7.0-2.3.0/deploy/deploy-operator.yamlVerification is a get on the deployment inside the mysql-operator namespace, and the output the project shows has the deployment at 1/1 ready:
$> kubectl get deployment -n mysql-operator mysql-operator
NAME READY UP-TO-DATE AVAILABLE AGE
mysql-operator 1/1 1 1 1hThe manifest URL carries a version tag, 26.7.0-2.3.0, which pins the operator to a specific MySQL release line and operator build. That is the detail to notice. Applying a URL without a tag, or copying a URL from a colleague's notes, gives you whatever that branch currently serves, and the operator's behaviour can change between those builds.
The Helm route and the scope you cannot change later
The project also offers a Helm route, and the chart install is a two step process:
$> helm repo add mysql-operator https://mysql.github.io/mysql-operator/
$> helm repo updateFollowed by:
$> helm install mysql-operator mysql-operator/mysql-operator --namespace mysql-operator --create-namespaceThat deploys the latest MySQL Operator for Kubernetes from DockerHub using all defaults, and the deployment can be customized to override built in defaults.
The important part of this section is the sentence that follows the install instructions, and it is the sharpest constraint in the project. In this release, operator topology is frozen after the initial topology has been persisted on first startup. A global operator blocks every other operator install, scoped operators must keep a disjoint deployment.namespaces set, and later manual or raw manifest topology changes are rejected on operator startup. The advice is to choose deployment.namespaces and deployment.standalone up front, and changing them after bootstrap requires removing the operator and reinstalling it with the desired scope.
In practice this means the choice between a cluster wide operator and a namespace scoped one is a one way decision per installation. It is a sensible guard against two controllers fighting over the same resources, and it is also a migration you have to plan for.
Declaring an InnoDBCluster with credentials from a secret
Before a cluster can be created, a secret has to carry the credentials for a MySQL root user used for administrative tasks in the cluster. The documented example is:
$> kubectl create secret generic mypwds \
--from-literal=rootUser=root \
--from-literal=rootHost=% \
--from-literal=rootPassword="sakila"The cluster resource then references that secret by name, and the sample is short enough to read in full:
apiVersion: mysql.oracle.com/v2
kind: InnoDBCluster
metadata:
name: mycluster
spec:
secretName: mypwds
tlsUseSelfSigned: true
instances: 3
router:
instances: 1Applying that file creates an InnoDB Cluster with three MySQL Server instances and one MySQL Router instance. The apiVersion is mysql.oracle.com/v2, so the CRD group is mysql.oracle.com and the version is v2, which is a detail worth checking when you compare a sample against the CRDs you actually applied.
The tlsUseSelfSigned flag is the one setting in this sample with an operational consequence. It turns on self signed certificates, which is convenient for a first run and wrong for a production cluster whose clients verify the chain. The sample chooses it because the sample is a demonstration, and the documentation is where the production value is defined.
Watching a cluster move from PENDING to ONLINE
The resource exposes a status that a cluster operator will watch closely. The project shows it being followed with:
$> kubectl get innodbcluster --watch
NAME STATUS ONLINE INSTANCES ROUTERS AGE
mycluster PENDING 0 3 1 2m6s
...
mycluster ONLINE 3 3 1 10sThe columns carry three separate facts. STATUS is the overall state of the resource, ONLINE is the number of instances that are serving, INSTANCES is how many the spec asked for, and ROUTERS counts the MySQL Router instances. A cluster that sits in PENDING with ONLINE at 0 while INSTANCES already reads 3 is a cluster that has been accepted but has not come up, which is a different situation from one where instances fail to start, and the two columns are what let you tell them apart.
The same resource is reachable through a Helm chart as well, which is a shortcut past writing the YAML by hand. A defaults install is:
$> helm install mycluster mysql-operator/mysql-innodbclusterAnd the same chart with the instance counts and credentials set on the command line is:
$> helm install mycluster mysql-operator/mysql-innodbcluster \
--namespace mynamespace \
--create-namespace \
--set credentials.root.user='root' \
--set credentials.root.password='supersecret' \
--set credentials.root.host='%' \
--set serverInstances=3 \
--set routerInstances=1The option names here are chart values, not CRD fields, and the mapping between the two is not one to one, so read the chart documentation before assuming a value in one place means the same thing in the other.
The service exposes seven ports and two protocols
Deploying a cluster creates a Kubernetes Service of type ClusterIP, and the project shows the port list it carries:
$> kubectl get service mycluster
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mycluster ClusterIP 10.110.228.51 <none> 3306/TCP,33060/TCP,6446/TCP,6448/TCP,6447/TCP,6449/TCP,6450/TCP 26hThe README explains the meaning: the ports represent read-write and read-only ports for the MySQL Protocol and the X Protocol. The four digit ports are the ones the router actually uses, while 3306 and 33060 are the conventional numbers for the MySQL and X protocols. The project recommends using describe or the documentation for additional information rather than hard coding these numbers, which is sound advice because a port assignment that changes would silently break a client that pinned it.
For local access there are two documented routes. The first is a shell inside the cluster, where each MySQL instance has MySQL Shell installed for troubleshooting:
$> kubectl run --rm -it myshell --image=container-registry.oracle.com/mysql/community-operator -- mysqlshThe short connection target root@mycluster assumes the default namespace, and the long form is {innodbclustername}.{namespace}.svc.cluster.local. The second route is port forwarding to a local client:
$> kubectl port-forward service/mycluster mysql
Forwarding from 127.0.0.1:3306 -> 6446The forwarded local port is 3306 while the target is 6446, which is the read-write router port, so a client pointed at 127.0.0.1:3306 lands on the primary. Note the Service is ClusterIP, so nothing outside the cluster reaches it without an ingress or a node port arrangement, and the project does not document one here.
A custom licence, no published releases and a moving main branch
Two things about the project's distribution deserve attention before an adoption decision.
The licence is not a standard one. The licence field on the repository says the project uses a custom licence that GitHub cannot classify, and points to the LICENSE file, and the README repeats that licence information can be found in the LICENSE file. It also notes that the distribution may include code developed by third parties and that licence and attribution notices for those parts are in the same file. That is a different legal posture from a project released under Apache-2.0 or MIT, and if your organisation has a policy that accepts only recognised permissive licences, the LICENSE file needs review by whoever handles that, not a summary from this page.
The second point is versioning. The repository has no GitHub releases, and the default branch is trunk. The only version reference available in the installation instructions is the tag embedded in the manifest URLs, 26.7.0-2.3.0, which follows a MySQL release and operator build pairing. The last push was on 2026-07-31, and the project does not publish tagged GitHub releases, so there is no release note to read before an upgrade.
For a component that manages your primary database, the practical consequence is that the operational documentation you will rely on is the developer site documentation, not a versioned changelog in the repository.
Editorial conclusion
The MySQL Operator for Kubernetes fits platform teams that want MySQL InnoDB Cluster, automated upgrades and backup handled by a controller they can point at a CRD, and that can settle deployment scope before the first install. It is a poor fit for teams expecting to switch a global operator into a namespace scoped one later, because that requires removing and reinstalling it, and for anyone who needs a permissive open source licence, since the licence field on the repository points to a custom LICENSE file. Verify first that your cluster version matches the 26.7.0-2.3.0 manifest tags you apply, that the deployment.namespaces and deployment.standalone values reflect the scope you want permanently, and that the backup and upgrade defaults match your durability requirements.
Frequently asked questions
How do I install the MySQL Operator for Kubernetes?
Apply the CRD manifest first with kubectl apply -f, then apply deploy-operator.yaml, and confirm the deployment is ready with kubectl get deployment -n mysql-operator mysql-operator. A Helm chart is also available from the mysql-operator repository at https://mysql.github.io/mysql-operator/.
Can I change the mysql-operator deployment scope after installing it?
No. Operator topology is frozen after the initial topology is persisted on first startup, and later manual or raw manifest topology changes are rejected on startup. Changing deployment.namespaces or deployment.standalone requires removing the operator and reinstalling it.
What does the InnoDBCluster custom resource look like?
It uses apiVersion mysql.oracle.com/v2 and kind InnoDBCluster, with a spec that references a secret by secretName, can set tlsUseSelfSigned, and declares instances and router instances counts. A sample is in the samples directory of the repository.
How do I connect to a MySQL InnoDB Cluster managed by the operator?
The operator creates a ClusterIP service, and you can reach it with kubectl port-forward service/mycluster mysql or by running MySQL Shell with the community-operator image. The short target root@mycluster assumes the default namespace, and the long form is {innodbclustername}.{namespace}.svc.cluster.local.
What licence is the MySQL Operator for Kubernetes released under?
The licence field on the repository points to a custom licence in the LICENSE file that GitHub cannot classify, and the README says third party components are covered by notices in the same file. Read the LICENSE file for the exact terms.
Does the mysql-operator release automation handle upgrades and backup?
The project states that it manages the full lifecycle of InnoDB Cluster setups, including automating upgrades and backup. The README does not describe the backup configuration keys or the upgrade strategy in detail, so the developer site documentation is where to read them.
Official sources
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.
[](https://hysenlabs.com/projects/mysql-mysql-operator)Was this useful?