-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvars.tf
More file actions
507 lines (413 loc) · 21.1 KB
/
vars.tf
File metadata and controls
507 lines (413 loc) · 21.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
# ── OCI Identity ──────────────────────────────────────────────────────────────
variable "tenancy_ocid" {
type = string
description = "OCID of the tenancy"
}
variable "compartment_ocid" {
type = string
description = "OCID of the compartment where all resources are created"
}
variable "availability_domain" {
type = string
description = "Availability domain name, e.g. 'Uocm:EU-FRANKFURT-1-AD-1'"
validation {
condition = can(regex("^[^:]+:[A-Z0-9]+-AD-[1-3]$", var.availability_domain))
error_message = "availability_domain must match the pattern 'Namespace:REGION-AD-N' (e.g. 'Uocm:EU-FRANKFURT-1-AD-1')."
}
}
# ── Cluster identity ──────────────────────────────────────────────────────────
variable "cluster_name" {
type = string
description = "Logical name for the cluster. Used in display names and freeform tags."
}
variable "environment" {
type = string
description = "Deployment environment label (e.g. staging, production)"
default = "staging"
}
variable "unique_tag_key" {
type = string
description = "Freeform tag key applied to every resource for identification"
default = "k3s-provisioner"
}
variable "unique_tag_value" {
type = string
description = "Freeform tag value applied to every resource for identification"
default = "https://github.com/mbologna/k3s-oci"
}
# ── SSH ───────────────────────────────────────────────────────────────────────
variable "public_key_path" {
type = string
description = "Path to SSH public key file. Used as fallback when public_key is null."
default = "~/.ssh/id_ed25519.pub"
}
variable "public_key" {
type = string
description = <<-EOT
SSH public key content placed on every instance. Preferred over public_key_path —
pass the key string directly for CI pipelines where ~/.ssh does not exist.
When null, the key is read from public_key_path at plan time.
EOT
default = null
}
variable "github_ssh_keys_username" {
type = string
description = <<-EOT
GitHub username whose published SSH keys (https://github.com/<username>.keys)
are added to every instance's authorized_keys at plan time, in addition to
the primary public_key / public_key_path. Leave empty to skip.
EOT
default = ""
}
variable "my_public_ip_cidr" {
type = string
description = <<-EOT
Your workstation public IP in CIDR notation (e.g. 1.2.3.4/32).
Restricts OCI Bastion Service session creation (enable_bastion = true) and
kubeapi access via the public NLB (expose_kubeapi = true).
k3s nodes are in a private subnet and are only reachable via OCI Bastion sessions.
EOT
validation {
condition = can(cidrnetmask(var.my_public_ip_cidr))
error_message = "my_public_ip_cidr must be a valid CIDR block (e.g. 1.2.3.4/32)."
}
}
# ── Compute ───────────────────────────────────────────────────────────────────
variable "os_image_id" {
type = string
description = "OCID of the Ubuntu 24.04 LTS (Noble) aarch64 image for A1.Flex nodes. If null, the latest matching image is resolved automatically from the tenancy. Find OCIDs at https://docs.oracle.com/en-us/iaas/images/"
default = null
validation {
condition = var.os_image_id == null || startswith(var.os_image_id, "ocid1.image.")
error_message = "os_image_id must be a valid OCI image OCID starting with 'ocid1.image.' or null."
}
}
variable "compute_shape" {
type = string
description = "OCI compute shape for k3s nodes"
default = "VM.Standard.A1.Flex"
}
variable "server_ocpus" {
type = number
description = "OCPUs per control-plane node. Total OCPUs across all nodes must not exceed 4 (Always Free)."
default = 1
}
variable "server_memory_in_gbs" {
type = number
description = "RAM in GB per control-plane node. Total RAM must not exceed 24 GB (Always Free)."
default = 6
}
variable "worker_ocpus" {
type = number
description = "OCPUs per worker node."
default = 1
}
variable "worker_memory_in_gbs" {
type = number
description = "RAM in GB per worker node."
default = 6
}
variable "boot_volume_size_in_gbs" {
type = number
description = "Boot volume size in GB for k3s nodes (servers + workers). OCI minimum is 50 GB for all shapes. With 4 k3s nodes at 50 GB each the total is 200 GB (exactly at the Always Free limit). The bastion uses OCI Bastion Service — no VM, no boot volume."
default = 50
validation {
condition = var.boot_volume_size_in_gbs == 50
error_message = "boot_volume_size_in_gbs must be 50 GB — OCI minimum for all shapes, and 4 × 50 GB = 200 GB exactly fills the Always Free storage limit."
}
}
variable "fault_domains" {
type = list(string)
description = "Fault domains to spread the instance pool across"
default = ["FAULT-DOMAIN-1", "FAULT-DOMAIN-2", "FAULT-DOMAIN-3"]
}
# ── Cluster topology ──────────────────────────────────────────────────────────
variable "k3s_server_pool_size" {
type = number
description = "Number of k3s control-plane nodes in the instance pool. Use 3 for HA (etcd quorum). Must be an odd number >= 1."
default = 3
validation {
condition = var.k3s_server_pool_size >= 1 && var.k3s_server_pool_size % 2 == 1
error_message = "k3s_server_pool_size must be a positive odd number (1, 3, 5 …) for etcd quorum."
}
}
variable "k3s_worker_pool_size" {
type = number
description = <<-EOT
Number of k3s worker nodes managed by the OCI Instance Pool.
Set to 0 (default) when using k3s_standalone_worker = true, which is the recommended
Always Free topology. The pool is kept to allow future scaling beyond the free tier.
EOT
default = 0
}
variable "k3s_standalone_worker" {
type = bool
description = <<-EOT
When true (default), provisions one worker node as a plain oci_core_instance resource.
This is the recommended approach for OCI Always Free tenancies: instance pools route
requests through OCI Capacity Management which can fail for A1.Flex shapes, whereas
a direct oci_core_instance reliably claims the free allocation.
Default topology: 3 control-plane nodes (pool) + 1 standalone worker = 4 OCPUs / 24 GB.
EOT
default = true
}
# ── Bastion ───────────────────────────────────────────────────────────────────
variable "enable_bastion" {
type = bool
description = <<-EOT
Provision an OCI Bastion Service resource (managed SSH proxy, Always Free, no storage).
When enabled, a STANDARD bastion is created and associated with the private subnet.
Use example/get-kubeconfig.sh to retrieve kubeconfig via a Bastion session.
Strongly recommended; without it, nodes are reachable only via serial console.
EOT
default = true
}
# ── k3s ───────────────────────────────────────────────────────────────────────
variable "k3s_version" {
type = string
description = "k3s version to install. 'latest' resolves the current stable release at plan time via the GitHub API."
default = "latest"
}
variable "k3s_subnet" {
type = string
description = "Subnet name used to derive the flannel interface. Leave 'default_route_table' to let k3s auto-detect."
default = "default_route_table"
}
# ── Networking ────────────────────────────────────────────────────────────────
variable "oci_core_vcn_cidr" {
type = string
description = "CIDR block for the VCN"
default = "10.0.0.0/16"
}
variable "public_subnet_cidr" {
type = string
description = "CIDR for the public subnet (load balancers and optional bastion)"
default = "10.0.0.0/24"
}
variable "private_subnet_cidr" {
type = string
description = "CIDR for the private subnet (k3s nodes)"
default = "10.0.1.0/24"
}
variable "oci_core_vcn_dns_label" {
type = string
default = "k3svcn"
}
variable "public_subnet_dns_label" {
type = string
default = "k3spublic"
}
variable "private_subnet_dns_label" {
type = string
default = "k3sprivate"
}
# ── Load balancers ────────────────────────────────────────────────────────────
variable "kube_api_port" {
type = number
description = "Port the k3s API server listens on"
default = 6443
}
variable "http_lb_port" {
type = number
description = "Public HTTP port on the NLB frontend (default 80)."
default = 80
}
variable "https_lb_port" {
type = number
description = "Public HTTPS port on the NLB frontend (default 443)."
default = 443
}
variable "ingress_controller_http_nodeport" {
type = number
description = "NodePort on workers that the ingress controller binds for HTTP traffic"
default = 30080
}
variable "ingress_controller_https_nodeport" {
type = number
description = "NodePort on workers that the ingress controller binds for HTTPS traffic"
default = 30443
}
variable "expose_kubeapi" {
type = bool
description = "Expose the Kubernetes API server via the public NLB (restricted to my_public_ip_cidr)"
default = false
}
variable "expose_ssh" {
type = bool
description = "Expose SSH (port 22) via the public NLB to all cluster nodes (restricted to my_public_ip_cidr). Eliminates the need for OCI Bastion sessions for day-to-day access."
default = false
}
# ── IAM ───────────────────────────────────────────────────────────────────────
variable "oci_identity_dynamic_group_name" {
type = string
description = "Name for the OCI dynamic group granting instances access to the OCI API"
default = "k3s-cluster-dynamic-group"
}
variable "oci_identity_policy_name" {
type = string
description = "Name for the OCI IAM policy attached to the dynamic group"
default = "k3s-cluster-policy"
}
# ── cert-manager (always installed — keeps cluster active, avoids idle reclamation) ───
variable "certmanager_email_address" {
type = string
description = "Email address for Let's Encrypt ACME registration. Must be a real address."
validation {
condition = can(regex("^[^@]+@[^@]+\\.[^@]+$", var.certmanager_email_address)) && var.certmanager_email_address != "changeme@example.com"
error_message = "certmanager_email_address must be a valid email address (not the placeholder)."
}
}
# ── ArgoCD (always installed — GitOps controller keeps cluster active) ────────
variable "longhorn_hostname" {
type = string
description = "Fully-qualified hostname for the Longhorn UI (e.g. longhorn.example.com). When set, a Gateway API HTTPRoute with BasicAuth (Envoy Gateway SecurityPolicy) and a cert-manager TLS certificate is created."
default = null
}
variable "longhorn_ui_username" {
type = string
description = "Username for Longhorn UI BasicAuth (only used when longhorn_hostname is set)."
default = "admin"
}
variable "grafana_hostname" {
type = string
description = "Fully-qualified hostname for the Grafana UI (e.g. grafana.example.com). When set, a Gateway API HTTPRoute with a cert-manager TLS certificate is created in gitops/monitoring/."
default = null
}
variable "gitops_repo_url" {
type = string
description = "Git repository URL for the ArgoCD App of Apps (e.g. https://github.com/your-org/k3s-oci.git). Set this to your fork so ArgoCD pulls from the right repo."
default = "https://github.com/mbologna/k3s-oci.git"
}
# ── OCI CLI ───────────────────────────────────────────────────────────────────
# OCI CLI is installed at latest available version at bootstrap time.
# It is only used during node initialisation for Vault secret fetch and is
# not a running workload — no versioning needed.
# ── Backup ────────────────────────────────────────────────────────────────────
variable "enable_backup" {
type = bool
description = "Enable weekly boot volume backups for all k3s nodes (Always Free: 5 total backups). With 4 nodes at weekly-1-week-retention there are at most 4 active backups."
default = true
}
# ── Object Storage ────────────────────────────────────────────────────────────
variable "enable_object_storage_state" {
type = bool
description = "Provision an Always Free OCI Object Storage bucket for storing Terraform/OpenTofu state (S3-compatible API). See the terraform_state_backend output for the backend configuration snippet."
default = true
}
variable "enable_longhorn_backup" {
type = bool
description = "Provision a dedicated Always Free OCI Object Storage bucket for Longhorn PVC backups (S3-compatible). See longhorn_backup_setup output for connection instructions. Shares the 20 GB free allowance with the Terraform state bucket."
default = true
}
# ── Notifications ─────────────────────────────────────────────────────────────
variable "enable_notifications" {
type = bool
description = "Create an OCI Notifications topic and wire it to Alertmanager as a webhook receiver (Always Free: 1M HTTPS + 3K email/month)."
default = false
}
variable "alertmanager_email" {
type = string
description = "Optional email address to subscribe to the OCI Notifications topic. The subscriber must confirm via an OCI confirmation email."
default = null
}
# ── MySQL HeatWave ────────────────────────────────────────────────────────────
variable "enable_mysql" {
type = bool
description = "Provision an Always Free MySQL HeatWave DB system (single node, 50 GB). Creates a Kubernetes Secret 'mysql-credentials' in the default namespace."
default = false
}
variable "mysql_shape" {
type = string
description = "MySQL HeatWave shape. 'MySQL.Free' is the Always Free shape."
default = "MySQL.Free"
}
variable "mysql_admin_username" {
type = string
description = "Admin username for the MySQL HeatWave DB system."
default = "admin"
}
# ── Vault ─────────────────────────────────────────────────────────────────────
variable "enable_vault" {
type = bool
description = "Store cluster secrets (k3s_token, longhorn_ui_password, grafana_admin_password) in OCI Vault (Always Free: software keys + 150 secrets). Nodes fetch secrets via OCI CLI instance_principal at boot — plaintext values are removed from cloud-init user-data."
default = true
}
# ── External DNS (Cloudflare) ─────────────────────────────────────────────────
variable "enable_external_dns" {
type = bool
description = "Deploy external-dns (kubernetes-sigs) configured for Cloudflare. Automatically creates/updates DNS A records when Services or Ingresses are annotated. Requires cloudflare_api_token and cloudflare_zone_id."
default = false
}
variable "cloudflare_api_token" {
type = string
sensitive = true
description = "Cloudflare API token. Required when enable_external_dns = true or enable_dns01_challenge = true. Create a scoped token at https://dash.cloudflare.com/profile/api-tokens with Zone:DNS:Edit permissions."
default = null
}
variable "cloudflare_zone_id" {
type = string
description = "Cloudflare Zone ID for the managed domain. Required when enable_external_dns = true."
default = null
}
variable "external_dns_domain_filter" {
type = string
description = "Domain filter for external-dns — only DNS records under this domain are managed (e.g. 'k3s.example.com'). Required when enable_external_dns = true."
default = null
}
# ── External Secrets Operator ─────────────────────────────────────────────────
variable "enable_external_secrets" {
type = bool
description = "Deploy the External Secrets Operator and create a ClusterSecretStore backed by OCI Vault (instance_principal auth). Requires enable_vault = true. Workloads can then create ExternalSecret resources to sync any OCI Vault secret into a Kubernetes Secret without hard-coding values."
default = false
}
variable "region" {
type = string
description = "OCI region identifier (e.g. 'eu-frankfurt-1'). Required when enable_external_secrets = true for the ClusterSecretStore to locate the OCI Vault endpoint."
default = null
}
# ── DNS-01 ACME challenge via Cloudflare ──────────────────────────────────────
variable "enable_dns01_challenge" {
type = bool
description = "Configure cert-manager ClusterIssuers to use DNS-01 ACME challenge via Cloudflare instead of HTTP-01. Enables wildcard certificates (*.example.com) and works even without inbound port 80. Requires cloudflare_api_token."
default = false
}
# Bootstrap chart versions — installed by cloud-init so the cluster starts with
# the correct version; ArgoCD adopts and manages ongoing upgrades via gitops/apps/*.yaml.
# Renovate keeps these in sync. Only charts bootstrapped by cloud-init are listed here;
# charts managed purely by ArgoCD (kured, Longhorn, Envoy Gateway, external-dns) are
# versioned in their respective gitops/apps/*.yaml files.
variable "gateway_api_version" {
type = string
description = "Kubernetes Gateway API CRDs version (experimental channel) installed at bootstrap. Experimental channel is a superset of standard and includes GRPCRoute, TCPRoute, TLSRoute, etc. required by Envoy Gateway. Must exist before ArgoCD syncs gateway-config."
# renovate: datasource=github-releases depName=kubernetes-sigs/gateway-api
default = "v1.5.1"
}
variable "dockerhub_username" {
type = string
description = "Docker Hub username for ArgoCD to authenticate when pulling OCI Helm charts (e.g. Envoy Gateway from registry-1.docker.io). If empty, anonymous pulls are attempted and may be rate-limited. Create a PAT at https://app.docker.com/settings/personal-access-tokens"
default = ""
}
variable "dockerhub_password" {
type = string
description = "Docker Hub access token (PAT) for ArgoCD OCI Helm chart pulls. Paired with dockerhub_username."
default = ""
sensitive = true
}
variable "certmanager_chart_version" {
type = string
description = "cert-manager Helm chart version used for the bootstrap install. Must match gitops/apps/cert-manager.yaml targetRevision. Managed by Renovate."
# renovate: datasource=helm depName=cert-manager registryUrl=https://charts.jetstack.io
default = "v1.20.2"
}
variable "argocd_chart_version" {
type = string
description = "ArgoCD Helm chart version used for the bootstrap install. Must match gitops/apps/argocd.yaml targetRevision. Managed by Renovate."
# renovate: datasource=helm depName=argo-cd registryUrl=https://argoproj.github.io/argo-helm
default = "9.5.14"
}
variable "external_secrets_chart_version" {
type = string
description = "External Secrets Operator Helm chart version used for the bootstrap install. Must match gitops/apps/external-secrets.yaml targetRevision. Managed by Renovate."
# renovate: datasource=helm depName=external-secrets registryUrl=https://charts.external-secrets.io
default = "2.4.1"
}