sync-modules: mirrored modules of release 3.2.0-F-feature-simpl-31668.650.242a7609

+++ kubernetes/README.md
+++ kubernetes/ovh/credentials.tf
+++ kubernetes/ovh/main.tf
+++ kubernetes/ovh/outputs.tf
+++ kubernetes/ovh/variables.tf
+++ kubernetes/ovh/versions.tf
This commit is contained in:
gitops_test 2026-09-14 14:42:05 +00:00
commit 25b528bd66
6 changed files with 439 additions and 0 deletions

65
kubernetes/README.md Normal file
View File

@ -0,0 +1,65 @@
# kubernetes module
Provisions a managed Kubernetes cluster with one node pool and generates scoped access credentials. One submodule per cloud provider, selected by the provisioner from the cloud provider of the provisioning request:
- `ovh`: an OVHcloud Managed Kubernetes Service (MKS) cluster (`ovh_cloud_project_kube`) with one node pool, public by default, optionally attached to an existing vRack private network
## Contract
| Variable | Description |
|---|---|
| `deployment_id` | Unique deployment identifier, used in resource names |
| `cloud_account` | Cloud account (environment) name, second segment of the Vault credentials path, filled by the provisioner from the cloudAccount header via the `{CLOUD_ACCOUNT}` placeholder |
| `requester_unique_id` | Requester unique id, third segment of the Vault credentials path, filled by the provisioner from the requesterUniqueId header via the `{REQUESTER_UNIQUE_ID}` placeholder |
| `username` | Consumer ServiceAccount name, defaults to `simpl-consumer`, last segment of the Vault credentials path |
| `admin_username` | Management (renewer) ServiceAccount name, defaults to `simpl-renewer` |
| `name_prefix` | Prefix for resource names, defaults to `simpl` |
| `project_id`, `region` | OVH project and region |
| `k8s_version`, `k8s_update_policy` | Cluster version and upgrade policy |
| `nodepool_*` | Node pool sizing: flavor, desired/min/max nodes, autoscale, anti affinity, billing |
| `private_network_id`, `nodes_subnet_id`, `gateway_ip` | Optional vRack attachment, all null for a public cluster |
| `token_ttl_seconds` | TTL of the consumer ServiceAccount token, defaults to 3600 |
Cloud credentials are never module inputs, they reach the provider from the runner environment.
Credentials, following the existing Vault contract: the OVH admin kubeconfig (client certificates) is never delivered. A `kubernetes` provider chained on the fresh cluster creates, in the same apply, the consumer ServiceAccount (cluster-admin, bound token with TTL) and the management ServiceAccount (cluster-admin, non expiring legacy token Secret, the credential the renewal workflow uses on day 2 and revocable by deleting the ServiceAccount). Both are delivered as assembled kubeconfigs written by the module to Vault: `secret/{provider}/{cloud_account}/{requester_unique_id}/{username}` with the consumer kubeconfig (companions: token, server, ca_data, ttl_seconds) and the `admin` sibling entry with the root kubeconfig and the management username. No secret material is ever part of an output or of the user body. The Vault provider configuration is generated by the provisioner next to the deployment script, and its token comes from the runner environment.
Note: this module configures the `kubernetes` provider internally (chained on the cluster it creates), so the module block must not use `count`, `for_each` or `depends_on`.
Outputs:
| Output | Description |
|---|---|
| `cluster_id` | Id of the managed cluster |
| `cluster_name` | Name of the managed cluster |
| `cluster_url` | Kubernetes API server endpoint |
| `cluster_version` | Kubernetes version actually deployed |
| `credentials_ref` | Vault path of the consumer credentials entry |
## Usage
The provisioner rewrites the `{KUBERNETES_SOURCE}` placeholder to this module's location for the cloud provider of the request (each catalog module has a placeholder derived from its directory name), so a deployment script only declares:
```hcl
module "kubernetes" {
source = "{KUBERNETES_SOURCE}"
deployment_id = "{UUID}"
project_id = "<ovh project id>"
region = "<region>"
cloud_account = "{CLOUD_ACCOUNT}"
requester_unique_id = "{REQUESTER_UNIQUE_ID}"
nodepool_flavor = "b3-8"
nodepool_desired_nodes = 3
}
output "cluster_url" {
value = module.kubernetes.cluster_url
}
output "credentials_ref" {
value = module.kubernetes.credentials_ref
}
```
The root file must re-export the module outputs it wants published to the deployment outputs.

View File

@ -0,0 +1,170 @@
// Consumer and management credentials are generated here, never injected by
// the backend and never delivered through outputs. The OVH admin kubeconfig
// (client certificates) is never delivered either: the consumer receives a
// scoped ServiceAccount token with a TTL, day 2 renewal uses a dedicated
// root token. Vault entries follow the existing contract:
// secret/{provider}/{cloud_account}/{requester_unique_id}/{username} consumer kubeconfig
// secret/{provider}/{cloud_account}/{requester_unique_id}/admin root kubeconfig + admin username
// Only the Vault path of the consumer entry is exposed as an output.
resource "kubernetes_namespace_v1" "access" {
metadata {
name = "${var.name_prefix}-access"
}
depends_on = [ovh_cloud_project_kube_nodepool.this]
}
// Consumer access: ServiceAccount + bound token with TTL. The binding
// references the ServiceAccount by name, so it survives the delete/recreate
// performed by the credential renewal workflow.
resource "kubernetes_service_account_v1" "consumer" {
metadata {
name = var.username
namespace = kubernetes_namespace_v1.access.metadata[0].name
}
}
resource "kubernetes_cluster_role_binding_v1" "consumer_admin" {
metadata {
name = "${var.username}-admin"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = "cluster-admin"
}
subject {
kind = "ServiceAccount"
name = kubernetes_service_account_v1.consumer.metadata[0].name
namespace = kubernetes_namespace_v1.access.metadata[0].name
}
}
resource "kubernetes_token_request_v1" "consumer" {
metadata {
name = kubernetes_service_account_v1.consumer.metadata[0].name
namespace = kubernetes_namespace_v1.access.metadata[0].name
}
spec {
expiration_seconds = var.token_ttl_seconds
}
}
// Management (renewer) access: ServiceAccount + NON EXPIRING legacy token
// Secret, the credential the renewal workflow uses with kubectl on day 2.
// Revocable by deleting the ServiceAccount.
resource "kubernetes_service_account_v1" "renewer" {
metadata {
name = var.admin_username
namespace = kubernetes_namespace_v1.access.metadata[0].name
}
}
resource "kubernetes_cluster_role_binding_v1" "renewer_admin" {
metadata {
name = "${var.admin_username}-admin"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = "cluster-admin"
}
subject {
kind = "ServiceAccount"
name = kubernetes_service_account_v1.renewer.metadata[0].name
namespace = kubernetes_namespace_v1.access.metadata[0].name
}
}
resource "kubernetes_secret_v1" "renewer_token" {
metadata {
name = "${var.admin_username}-token"
namespace = kubernetes_namespace_v1.access.metadata[0].name
annotations = {
"kubernetes.io/service-account.name" = kubernetes_service_account_v1.renewer.metadata[0].name
}
}
type = "kubernetes.io/service-account-token"
wait_for_service_account_token = true
}
// Kubeconfigs assembled with the scoped tokens, never the admin certificates
locals {
cluster_server = ovh_cloud_project_kube.this.kubeconfig_attributes[0].host
cluster_ca = ovh_cloud_project_kube.this.kubeconfig_attributes[0].cluster_ca_certificate
consumer_kubeconfig = yamlencode({
apiVersion = "v1"
kind = "Config"
"current-context" = "simpl"
clusters = [{
name = "simpl"
cluster = {
server = local.cluster_server
"certificate-authority-data" = local.cluster_ca
}
}]
users = [{
name = var.username
user = { token = kubernetes_token_request_v1.consumer.token }
}]
contexts = [{
name = "simpl"
context = { cluster = "simpl", user = var.username }
}]
})
renewer_kubeconfig = yamlencode({
apiVersion = "v1"
kind = "Config"
"current-context" = "simpl"
clusters = [{
name = "simpl"
cluster = {
server = local.cluster_server
"certificate-authority-data" = local.cluster_ca
}
}]
users = [{
name = var.admin_username
user = { token = kubernetes_secret_v1.renewer_token.data["token"] }
}]
contexts = [{
name = "simpl"
context = { cluster = "simpl", user = var.admin_username }
}]
})
}
// The kubeconfig is the primary artifact of both entries (the credential
// delivered and later renewed), the remaining keys are companions.
resource "vault_kv_secret_v2" "consumer" {
mount = "secret"
name = "ovh/${var.cloud_account}/${var.requester_unique_id}/${var.username}"
data_json = jsonencode({
kubeconfig = local.consumer_kubeconfig
token = kubernetes_token_request_v1.consumer.token
server = local.cluster_server
ca_data = local.cluster_ca
ttl_seconds = var.token_ttl_seconds
})
}
resource "vault_kv_secret_v2" "admin" {
mount = "secret"
name = "ovh/${var.cloud_account}/${var.requester_unique_id}/admin"
data_json = jsonencode({
username = var.admin_username
kubeconfig = local.renewer_kubeconfig
token = kubernetes_secret_v1.renewer_token.data["token"]
server = local.cluster_server
ca_data = local.cluster_ca
})
}

44
kubernetes/ovh/main.tf Normal file
View File

@ -0,0 +1,44 @@
resource "ovh_cloud_project_kube" "this" {
service_name = var.project_id
name = "${var.name_prefix}-${var.deployment_id}"
region = var.region
version = var.k8s_version
update_policy = var.k8s_update_policy
# Only set when deploying in private mode, null keeps the cluster public
private_network_id = var.private_network_id
nodes_subnet_id = var.nodes_subnet_id
dynamic "private_network_configuration" {
for_each = var.private_network_id != null ? [1] : []
content {
default_vrack_gateway = coalesce(var.gateway_ip, "")
private_network_routing_as_default = var.gateway_ip != null
}
}
}
resource "ovh_cloud_project_kube_nodepool" "this" {
service_name = var.project_id
kube_id = ovh_cloud_project_kube.this.id
name = "${var.name_prefix}-pool-${var.deployment_id}"
flavor_name = var.nodepool_flavor
desired_nodes = var.nodepool_desired_nodes
min_nodes = var.nodepool_min_nodes
max_nodes = var.nodepool_max_nodes
autoscale = var.nodepool_autoscale
anti_affinity = var.nodepool_anti_affinity
monthly_billed = var.nodepool_monthly_billed
}
// Chained on the freshly created cluster: client certificate auth from the
// OVH generated admin credentials. Used only to manage the access objects,
// the material itself never reaches an output, a repository or Kafka.
provider "kubernetes" {
host = ovh_cloud_project_kube.this.kubeconfig_attributes[0].host
cluster_ca_certificate = base64decode(ovh_cloud_project_kube.this.kubeconfig_attributes[0].cluster_ca_certificate)
client_certificate = base64decode(ovh_cloud_project_kube.this.kubeconfig_attributes[0].client_certificate)
client_key = base64decode(ovh_cloud_project_kube.this.kubeconfig_attributes[0].client_key)
}

24
kubernetes/ovh/outputs.tf Normal file
View File

@ -0,0 +1,24 @@
output "cluster_id" {
description = "Id of the managed Kubernetes cluster"
value = ovh_cloud_project_kube.this.id
}
output "cluster_name" {
description = "Name of the managed Kubernetes cluster"
value = ovh_cloud_project_kube.this.name
}
output "cluster_url" {
description = "Kubernetes API server endpoint"
value = ovh_cloud_project_kube.this.url
}
output "cluster_version" {
description = "Kubernetes version actually deployed on the cluster"
value = ovh_cloud_project_kube.this.version
}
output "credentials_ref" {
description = "Vault path of the generated consumer credentials"
value = "secret/data/${vault_kv_secret_v2.consumer.name}"
}

120
kubernetes/ovh/variables.tf Normal file
View File

@ -0,0 +1,120 @@
variable "project_id" {
description = "OVH public cloud project id (service_name)"
type = string
}
variable "region" {
description = "OVH region for the cluster, must be a region where Managed Kubernetes is available"
type = string
}
variable "deployment_id" {
description = "Unique deployment identifier, used to build resource names"
type = string
}
variable "cloud_account" {
description = "Cloud account (environment) name, second segment of the Vault credentials path"
type = string
}
variable "requester_unique_id" {
description = "Requester unique id, third segment of the Vault credentials path"
type = string
}
variable "name_prefix" {
description = "Prefix for all resource names"
type = string
default = "simpl"
}
variable "username" {
description = "Consumer ServiceAccount name, last segment of the Vault credentials path"
type = string
default = "simpl-consumer"
}
variable "admin_username" {
description = "Management (renewer) ServiceAccount name, stored in the Vault admin entry"
type = string
default = "simpl-renewer"
}
variable "token_ttl_seconds" {
description = "TTL of the consumer ServiceAccount token (TokenRequest expirationSeconds)"
type = number
default = 3600
}
variable "k8s_version" {
description = "Kubernetes version for the cluster, must be one of the versions currently supported by OVH MKS"
type = string
default = "1.32"
}
variable "k8s_update_policy" {
description = "Cluster patch/upgrade policy: ALWAYS_UPDATE, MINIMAL_DOWNTIME or NEVER_UPDATE"
type = string
default = "ALWAYS_UPDATE"
}
variable "nodepool_flavor" {
description = "Instance flavor for worker nodes, for example b3-8"
type = string
default = "b3-8"
}
variable "nodepool_desired_nodes" {
description = "Initial/desired number of worker nodes"
type = number
default = 3
}
variable "nodepool_min_nodes" {
description = "Minimum number of worker nodes (lower bound when autoscaling)"
type = number
default = 1
}
variable "nodepool_max_nodes" {
description = "Maximum number of worker nodes (upper bound when autoscaling)"
type = number
default = 5
}
variable "nodepool_autoscale" {
description = "Enable the OVH managed cluster autoscaler for the node pool"
type = bool
default = false
}
variable "nodepool_anti_affinity" {
description = "Spread nodes on distinct hypervisors (max 5 nodes per pool when enabled)"
type = bool
default = false
}
variable "nodepool_monthly_billed" {
description = "Bill nodes monthly instead of hourly"
type = bool
default = false
}
variable "private_network_id" {
description = "OpenStack private network (vRack) id the worker nodes should be attached to, null for a public cluster"
type = string
default = null
}
variable "nodes_subnet_id" {
description = "Subnet id (within private_network_id) used by the worker nodes, null for a public cluster"
type = string
default = null
}
variable "gateway_ip" {
description = "IP of the vRack gateway to route node egress traffic through, null to use the OVH managed default gateway"
type = string
default = null
}

View File

@ -0,0 +1,16 @@
terraform {
required_providers {
ovh = {
source = "ovh/ovh"
version = "2.3.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "2.38.0"
}
vault = {
source = "hashicorp/vault"
version = "4.8.0"
}
}
}