Manage databases
Three provider objects cover databases:
| Object | Kind | What it manages |
|---|---|---|
kupe_database |
Resource | The database: version, per-instance size, HA, deletion protection |
kupe_database_binding |
Resource | One role of one database in namespaces of one cluster |
kupe_database |
Data source | Reads an existing database by name |
Credentials never pass through Terraform. The API does not return them, so they are not in state; they reach your cluster in the binding’s Secret.
A database with three bindings
Section titled “A database with three bindings”This creates a database, binds the owner role into a namespace for migrations, the readwrite role into the application’s two namespaces and the readonly role into a namespace for reports, and outputs what the application needs:
resource "kupe_database" "orders" { name = "orders-db" version = "18"
resources = { cpu = "500m" memory = "1Gi" storage = "10Gi" }
high_availability = true
lifecycle { prevent_destroy = true }}
resource "kupe_database_binding" "migrations" { database = kupe_database.orders.name cluster = kupe_cluster.prod.name namespaces = ["orders-migrate"] role = "owner"}
resource "kupe_database_binding" "api" { database = kupe_database.orders.name cluster = kupe_cluster.prod.name namespaces = ["orders-api", "orders-worker"] role = "readwrite"}
resource "kupe_database_binding" "reports" { database = kupe_database.orders.name cluster = kupe_cluster.prod.name namespaces = ["orders-reports"] role = "readonly"}
output "orders_db_hosts" { description = "In-cluster DNS name each namespace of the api binding connects to." value = { for ns in kupe_database_binding.api.namespace_status : ns.name => ns.host }}
output "orders_db_secret" { description = "Credentials Secret in the orders-api and orders-worker namespaces." value = kupe_database_binding.api.secret_name}terraform apply waits, up to each resource’s timeout, for the database to
reach Running and for each binding to reach Ready in every namespace it
lists. The cluster does not have to exist yet: a binding into a cluster that is
still being created waits for it.
What Terraform waits for
Section titled “What Terraform waits for”| Operation | Waits until | Default timeout |
|---|---|---|
kupe_database create, update |
Running, with the change in effect on every instance |
30m |
kupe_database delete |
the database is gone | 15m |
kupe_database_binding create, update |
Ready in every listed namespace, and every removed one withdrawn |
5m |
kupe_database_binding delete |
the binding is gone | 5m |
A database create or update is done when all three of these hold:
status.observedGenerationhas reached the database’s generation, so the platform has applied the change.- The phase is
Running. - The
Readycondition isTrue, so every instance runs the change. The phase staysRunningwhile the instances roll onto it.
A database with one instance is Degraded while that instance restarts for a
CPU or memory change, so the provider rides out Degraded for 5 minutes, on
create as well as update, and fails the apply if it lasts longer. Degraded
counts only once the platform has applied the change, so a status read from
before the change never fails the apply. A database that starts being deleted
(Terminating) fails the apply at once.
A change to deletion_protection alone is in effect as soon as the API accepts
it, because a delete reads the setting when it arrives, so the provider does
not wait for it.
Change the timeouts with the timeouts attribute:
resource "kupe_database" "orders" { timeouts = { create = "60m" }}When a database create or update runs out of time:
Degradedafter the platform applied the change: the apply fails.- An update the platform has not applied, with the
Readycondition saying it failed to and is retrying: the apply fails. - Anything else, such as instances still rolling: the apply ends with a
warning. The platform carries on, and a later
terraform applyrecords the result.
A binding still Pending when its timeout runs out, because its database or
cluster is not running, also ends with a warning.
A database create that fails leaves the database in state, marked tainted, so
the next terraform apply plans to replace it. Run terraform untaint on it to
keep it instead. An update that fails is already recorded in state, so applying
again plans no change and does not send it again. To back the change out, set
the previous value in the configuration and apply.
A binding with a Failed namespace stops the create or update at once with an
error carrying the binding’s status message. When the cause is a name clash (the
namespace already has a Service named after the database, or a Secret named
secret_name, that the platform did not create), the error also names the
namespace and both objects and says what to change. The other namespaces keep
working.
- After a failed create, Terraform keeps the binding in state, marked tainted.
Rename or remove that object, or set another
secret_name, and runterraform applyagain to replace it. - After a failed update, the new list is in state. Rename or remove the object
and the platform delivers into the namespace on its next try, or take the
namespace out of
namespacesand apply.
What changes in place
Section titled “What changes in place”| Argument | On change |
|---|---|
kupe_database.resources.cpu, .memory |
In place. Instances restart one at a time. |
kupe_database.resources.storage |
In place, larger only. A smaller value fails the plan. |
kupe_database.high_availability |
In place, both ways. Removing it keeps the current setting. |
kupe_database.deletion_protection |
In place. |
kupe_database.version |
Fails the plan. There are no major version upgrades. |
kupe_database.engine |
Fails the plan. |
kupe_database.name |
Replaces the database. |
kupe_database_binding.namespaces |
In place. Namespaces kept work throughout. |
any other argument of kupe_database_binding except timeouts |
Replaces the binding. |
When version is unset, the platform default is used and recorded in state.
current_version shows the release the database is running.
Using the binding in the cluster
Section titled “Using the binding in the cluster”Mount the binding Secret at /bindings/<secret_name> and set
SERVICE_BINDING_ROOT=/bindings. The Secret’s uri key only works at that
path, because its sslrootcert points at the mounted ca.crt. If you manage
workloads with the
hashicorp/kubernetes
provider, reference the binding’s attributes:
resource "kubernetes_deployment_v1" "api" { metadata { name = "api" namespace = "orders-api" }
spec { selector { match_labels = { app = "api" } }
template { metadata { labels = { app = "api" } }
spec { container { name = "api" image = "ghcr.io/example/api:1.0.0"
env { name = "SERVICE_BINDING_ROOT" value = "/bindings" }
volume_mount { name = "database" mount_path = "/bindings/${kupe_database_binding.api.secret_name}" read_only = true } }
volume { name = "database" secret { secret_name = kupe_database_binding.api.secret_name } } } } }}Have workloads read the Secret themselves. Reading it with a
kubernetes_secret_v1 data source puts the password in Terraform state.
Deleting a database
Section titled “Deleting a database”Deletion takes an apply and then a destroy, on purpose:
- Remove the
lifecycle { prevent_destroy = true }block and setdeletion_protection = false. - Run
terraform apply. - Run
terraform destroy, or remove the resource and apply.
Skipping step 2 does not stop the destroy at the start. Terraform deletes the bindings first, because they depend on the database, and only then fails on the database with a deletion protection error. The database survives without its bindings until you apply them again.
Deleting a database removes it immediately. Its storage is kept for 7 days for support-assisted recovery, then permanently deleted. There is no self-service restore.
Removing a kupe_database_binding, or a namespace from its namespaces,
removes the Secret from that namespace, and the Service and the network path
unless another binding of the same database lists it. It does not remove
knowledge of the password. Rotate the role’s credentials
afterwards if that matters; the provider has no rotation resource, so use the
console, kupe database rotate-credentials or the
API.
Importing existing databases
Section titled “Importing existing databases”terraform import kupe_database.orders orders-db
terraform import kupe_database_binding.api orders-db/orders-db-prod-readwriteWith high_availability unset in the configuration, an imported database
keeps its current setting, so importing a highly available database does not
plan turning it off. Set high_availability = false to turn it off.
deletion_protection defaults to true for an imported database too.
Importing an unprotected database without the attribute in the configuration
plans turning protection on; set deletion_protection = false to keep it off.
Reading a database you do not manage
Section titled “Reading a database you do not manage”data "kupe_database" "orders" { name = "orders-db"}
output "orders_db_running_version" { value = data.kupe_database.orders.current_version}The data source returns the engine, requested version, current_version,
resources, high_availability, deletion_protection, phase and
created_at. It never returns credentials.
Related pages
Section titled “Related pages”- Databases
- Add a database to your application
- Resources and Data Sources for the full argument reference