@ariestools/cli 0.1.16 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -11
- package/dist/bin/aries.mjs +94138 -253208
- package/dist/bin/daemons/chain-server.mjs +9862 -12
- package/dist/bin/daemons/dapp-server.mjs +55 -56
- package/dist/bin/daemons/datalake-dev.mjs +10282 -4342
- package/dist/node/datalake.mjs +7 -5
- package/dist/terraform/cluster-aws/cluster.tf +61 -0
- package/dist/terraform/cluster-aws/encryption.tofu.example +37 -0
- package/dist/terraform/cluster-aws/iam.tf +81 -0
- package/dist/terraform/cluster-aws/irsa_ebs_csi.tf +37 -0
- package/dist/terraform/cluster-aws/kubeconfig.tf +49 -0
- package/dist/terraform/cluster-aws/network.tf +108 -0
- package/dist/terraform/cluster-aws/nodegroup.tf +42 -0
- package/dist/terraform/cluster-aws/outputs.tf +80 -0
- package/dist/terraform/cluster-aws/providers.tf +7 -0
- package/dist/terraform/cluster-aws/terraform.tfvars.example +21 -0
- package/dist/terraform/cluster-aws/variables.tf +120 -0
- package/dist/terraform/cluster-aws/versions.tf +18 -0
- package/dist/terraform/cluster-azure/cluster.tf +83 -0
- package/dist/terraform/cluster-azure/encryption.tofu.example +37 -0
- package/dist/terraform/cluster-azure/identity.tf +9 -0
- package/dist/terraform/cluster-azure/kubeconfig.tf +18 -0
- package/dist/terraform/cluster-azure/network.tf +23 -0
- package/dist/terraform/cluster-azure/outputs.tf +69 -0
- package/dist/terraform/cluster-azure/providers.tf +16 -0
- package/dist/terraform/cluster-azure/terraform.tfvars.example +24 -0
- package/dist/terraform/cluster-azure/variables.tf +126 -0
- package/dist/terraform/cluster-azure/versions.tf +14 -0
- package/dist/terraform/cluster-gcp/cluster.tf +71 -0
- package/dist/terraform/cluster-gcp/encryption.tofu.example +35 -0
- package/dist/terraform/cluster-gcp/kubeconfig.tf +47 -0
- package/dist/terraform/cluster-gcp/network.tf +47 -0
- package/dist/terraform/cluster-gcp/outputs.tf +70 -0
- package/dist/terraform/cluster-gcp/providers.tf +14 -0
- package/dist/terraform/cluster-gcp/terraform.tfvars.example +17 -0
- package/dist/terraform/cluster-gcp/variables.tf +86 -0
- package/dist/terraform/cluster-gcp/versions.tf +14 -0
- package/dist/terraform/serverless-aws/alb.tf +127 -0
- package/dist/terraform/serverless-aws/database.tf +70 -0
- package/dist/terraform/serverless-aws/dns.tf +80 -0
- package/dist/terraform/serverless-aws/ecr.tf +63 -0
- package/dist/terraform/serverless-aws/ecs.tf +199 -0
- package/dist/terraform/serverless-aws/encryption.tofu.example +38 -0
- package/dist/terraform/serverless-aws/iam.tf +68 -0
- package/dist/terraform/serverless-aws/network.tf +190 -0
- package/dist/terraform/serverless-aws/outputs.tf +64 -0
- package/dist/terraform/serverless-aws/providers.tf +19 -0
- package/dist/terraform/serverless-aws/storage.tf +51 -0
- package/dist/terraform/serverless-aws/terraform.tfvars.example +19 -0
- package/dist/terraform/serverless-aws/variables.tf +151 -0
- package/dist/terraform/serverless-aws/versions.tf +14 -0
- package/package.json +22 -19
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
terraform {
|
|
2
|
+
required_version = ">= 1.6.0"
|
|
3
|
+
|
|
4
|
+
required_providers {
|
|
5
|
+
aws = {
|
|
6
|
+
source = "hashicorp/aws"
|
|
7
|
+
version = "~> 5.70"
|
|
8
|
+
}
|
|
9
|
+
tls = {
|
|
10
|
+
source = "hashicorp/tls"
|
|
11
|
+
version = "~> 4.0"
|
|
12
|
+
}
|
|
13
|
+
local = {
|
|
14
|
+
source = "hashicorp/local"
|
|
15
|
+
version = "~> 2.5"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
resource "azurerm_kubernetes_cluster" "main" {
|
|
2
|
+
name = local.cluster_name
|
|
3
|
+
resource_group_name = azurerm_resource_group.main.name
|
|
4
|
+
location = azurerm_resource_group.main.location
|
|
5
|
+
dns_prefix = local.cluster_name
|
|
6
|
+
kubernetes_version = length(var.k8s_version) > 0 ? var.k8s_version : null
|
|
7
|
+
|
|
8
|
+
tags = local.common_tags
|
|
9
|
+
|
|
10
|
+
# System node pool — runs kube-system workloads (CoreDNS, metrics-server,
|
|
11
|
+
# CSI controllers, etc.). One node is enough for shared-cluster workloads
|
|
12
|
+
# in MVP; scale up via the auto_scaling block on the user pool below.
|
|
13
|
+
default_node_pool {
|
|
14
|
+
name = "system"
|
|
15
|
+
vm_size = var.system_node_vm_size
|
|
16
|
+
node_count = 1
|
|
17
|
+
vnet_subnet_id = azurerm_subnet.nodes.id
|
|
18
|
+
|
|
19
|
+
only_critical_addons_enabled = true # taint: CriticalAddonsOnly=true:NoSchedule
|
|
20
|
+
|
|
21
|
+
upgrade_settings {
|
|
22
|
+
max_surge = "10%"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# System-assigned managed identity. The principal id below is what we'd
|
|
27
|
+
# grant RBAC to if the cluster needed read-only access to a key vault, etc.
|
|
28
|
+
identity {
|
|
29
|
+
type = "SystemAssigned"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# CNI Overlay mode — pod IPs come from `pod_cidr` rather than the VNet,
|
|
33
|
+
# which means we don't burn IPv4 space on pod density. Service IPs come
|
|
34
|
+
# from `service_cidr`.
|
|
35
|
+
network_profile {
|
|
36
|
+
network_plugin = "azure"
|
|
37
|
+
network_plugin_mode = "overlay"
|
|
38
|
+
pod_cidr = var.pod_cidr
|
|
39
|
+
service_cidr = var.service_cidr
|
|
40
|
+
dns_service_ip = var.dns_service_ip
|
|
41
|
+
load_balancer_sku = "standard"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Workload Identity Federation (IRSA equivalent). Both flags must be on
|
|
45
|
+
# for pods to assume Azure SPs via service-account annotations.
|
|
46
|
+
oidc_issuer_enabled = true
|
|
47
|
+
workload_identity_enabled = true
|
|
48
|
+
|
|
49
|
+
# Local accounts stay enabled in MVP — that's what makes
|
|
50
|
+
# `kube_admin_config_raw` work without `kubelogin`. Production should
|
|
51
|
+
# disable this and switch to AAD-backed kubeconfig.
|
|
52
|
+
local_account_disabled = false
|
|
53
|
+
|
|
54
|
+
# AKS clusters get auto-upgrade by default in patch channel. Surface it
|
|
55
|
+
# so the operator knows.
|
|
56
|
+
automatic_upgrade_channel = "patch"
|
|
57
|
+
|
|
58
|
+
role_based_access_control_enabled = true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# User node pool — where the service plane (and large-tier datalake
|
|
62
|
+
# workloads) actually run. Autoscaling on, taint-free so pods land here
|
|
63
|
+
# rather than the tainted system pool.
|
|
64
|
+
resource "azurerm_kubernetes_cluster_node_pool" "user" {
|
|
65
|
+
name = "user"
|
|
66
|
+
kubernetes_cluster_id = azurerm_kubernetes_cluster.main.id
|
|
67
|
+
vm_size = var.node_vm_size
|
|
68
|
+
vnet_subnet_id = azurerm_subnet.nodes.id
|
|
69
|
+
|
|
70
|
+
auto_scaling_enabled = true
|
|
71
|
+
min_count = var.node_min_count
|
|
72
|
+
max_count = var.node_max_count
|
|
73
|
+
|
|
74
|
+
node_labels = {
|
|
75
|
+
"aries.xylabs.com/pool" = "shared"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
upgrade_settings {
|
|
79
|
+
max_surge = "33%"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
tags = local.common_tags
|
|
83
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# OpenTofu state encryption — opt-in.
|
|
2
|
+
#
|
|
3
|
+
# Encrypts `terraform.tfstate` and any saved plan files at rest using
|
|
4
|
+
# AES-GCM with a key derived from `var.tfstate_passphrase` via PBKDF2.
|
|
5
|
+
#
|
|
6
|
+
# To enable:
|
|
7
|
+
# 1. cp encryption.tofu.example encryption.tofu (in the working dir)
|
|
8
|
+
# 2. export TF_VAR_tfstate_passphrase='at-least-16-characters'
|
|
9
|
+
# 3. tofu init -reconfigure
|
|
10
|
+
#
|
|
11
|
+
# Notes:
|
|
12
|
+
# - This file uses the `.tofu` extension so Terraform does NOT load it.
|
|
13
|
+
# Only OpenTofu engages encryption. Terraform users skip this block.
|
|
14
|
+
# - AKS state contains the kubelet identity object id, OIDC issuer URL,
|
|
15
|
+
# and (when `kube_admin_config_raw` is used) the cluster's admin
|
|
16
|
+
# certificate bundle. Keep it encrypted for anything beyond throwaway
|
|
17
|
+
# staging.
|
|
18
|
+
|
|
19
|
+
terraform {
|
|
20
|
+
encryption {
|
|
21
|
+
key_provider "pbkdf2" "default" {
|
|
22
|
+
passphrase = var.tfstate_passphrase
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
method "aes_gcm" "default" {
|
|
26
|
+
keys = key_provider.pbkdf2.default
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
state {
|
|
30
|
+
method = method.aes_gcm.default
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
plan {
|
|
34
|
+
method = method.aes_gcm.default
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# System-assigned managed identity for the cluster. AKS uses this to call
|
|
2
|
+
# Azure APIs (e.g., manage the load balancer for ingress-nginx Services,
|
|
3
|
+
# attach managed disks for PVCs). Equivalent to the EKS cluster role.
|
|
4
|
+
#
|
|
5
|
+
# We declare the identity inline on the cluster resource (`identity` block)
|
|
6
|
+
# rather than as a separate resource — that's the simpler pattern for the
|
|
7
|
+
# common case. Workload Identity Federation (the IRSA-equivalent for pods)
|
|
8
|
+
# is enabled via `workload_identity_enabled` on the cluster resource and
|
|
9
|
+
# bound per-pod via service-account annotations in the service plane.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Render the cluster's admin kubeconfig into the working dir. AKS produces
|
|
2
|
+
# two kubeconfigs:
|
|
3
|
+
#
|
|
4
|
+
# - `kube_admin_config_raw` — local-account auth with embedded certs.
|
|
5
|
+
# Works out of the box without `kubelogin` or `az` exec plugins.
|
|
6
|
+
# - `kube_config_raw` — Azure AD auth via `kubelogin`. Requires the
|
|
7
|
+
# AAD-RBAC block on the cluster and `kubelogin` on the user's PATH.
|
|
8
|
+
#
|
|
9
|
+
# MVP uses the admin kubeconfig. Production should flip
|
|
10
|
+
# `local_account_disabled = true` on the cluster and switch to
|
|
11
|
+
# `kube_config_raw` plus a `kubelogin` install.
|
|
12
|
+
|
|
13
|
+
resource "local_sensitive_file" "kubeconfig" {
|
|
14
|
+
filename = "${path.cwd}/kubeconfig"
|
|
15
|
+
content = azurerm_kubernetes_cluster.main.kube_admin_config_raw
|
|
16
|
+
file_permission = "0600"
|
|
17
|
+
directory_permission = "0700"
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
resource "azurerm_resource_group" "main" {
|
|
2
|
+
name = local.resource_group_name
|
|
3
|
+
location = var.location
|
|
4
|
+
tags = local.common_tags
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
# Dedicated VNet for the cluster. We use Azure CNI Overlay mode (set on the
|
|
8
|
+
# AKS cluster) so pod IPs come from `pod_cidr` rather than the VNet — the
|
|
9
|
+
# subnet only needs to fit nodes, not pods.
|
|
10
|
+
resource "azurerm_virtual_network" "main" {
|
|
11
|
+
name = "${local.name_prefix}-vnet"
|
|
12
|
+
resource_group_name = azurerm_resource_group.main.name
|
|
13
|
+
location = azurerm_resource_group.main.location
|
|
14
|
+
address_space = [var.vnet_cidr]
|
|
15
|
+
tags = local.common_tags
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
resource "azurerm_subnet" "nodes" {
|
|
19
|
+
name = "nodes"
|
|
20
|
+
resource_group_name = azurerm_resource_group.main.name
|
|
21
|
+
virtual_network_name = azurerm_virtual_network.main.name
|
|
22
|
+
address_prefixes = [var.node_subnet_cidr]
|
|
23
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
output "cluster_name" {
|
|
2
|
+
description = "AKS cluster name."
|
|
3
|
+
value = azurerm_kubernetes_cluster.main.name
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
output "cluster_id" {
|
|
7
|
+
description = "Fully-qualified AKS cluster resource id."
|
|
8
|
+
value = azurerm_kubernetes_cluster.main.id
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
output "cluster_fqdn" {
|
|
12
|
+
description = "Public FQDN of the cluster's API server."
|
|
13
|
+
value = azurerm_kubernetes_cluster.main.fqdn
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
output "resource_group_name" {
|
|
17
|
+
description = "Resource group containing the cluster."
|
|
18
|
+
value = azurerm_resource_group.main.name
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
output "oidc_issuer_url" {
|
|
22
|
+
description = "OIDC issuer URL — used to federate Microsoft Entra Workload IDs onto Kubernetes ServiceAccounts."
|
|
23
|
+
value = azurerm_kubernetes_cluster.main.oidc_issuer_url
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
output "kubelet_identity_object_id" {
|
|
27
|
+
description = "Object id of the kubelet identity (used when granting AcrPull etc. for ACR integration)."
|
|
28
|
+
value = azurerm_kubernetes_cluster.main.kubelet_identity[0].object_id
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
output "vnet_id" {
|
|
32
|
+
description = "Dedicated VNet id."
|
|
33
|
+
value = azurerm_virtual_network.main.id
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
output "node_subnet_id" {
|
|
37
|
+
description = "Subnet id the node pools live in."
|
|
38
|
+
value = azurerm_subnet.nodes.id
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
output "kubeconfig_path" {
|
|
42
|
+
description = "Path to the rendered kubeconfig (relative to the working dir)."
|
|
43
|
+
value = local_sensitive_file.kubeconfig.filename
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
output "location" {
|
|
47
|
+
description = "Azure region the cluster lives in."
|
|
48
|
+
value = var.location
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
output "subscription_id" {
|
|
52
|
+
description = "Azure subscription id."
|
|
53
|
+
value = var.subscription_id
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Tier-2 → Tier-3 hand-off shape. The CLI projects this into ~/.aries/cluster/<name>/cluster.yaml.
|
|
57
|
+
output "service_target" {
|
|
58
|
+
description = "ServiceTarget(kubernetes) shape consumed by the Tier-3 service plane."
|
|
59
|
+
value = {
|
|
60
|
+
kind = "kubernetes"
|
|
61
|
+
cloud = "azure"
|
|
62
|
+
cluster_name = azurerm_kubernetes_cluster.main.name
|
|
63
|
+
region = var.location
|
|
64
|
+
namespace = "aries-datalake"
|
|
65
|
+
ingress_class = "nginx"
|
|
66
|
+
storage_class = "managed-csi"
|
|
67
|
+
kubeconfig = local_sensitive_file.kubeconfig.filename
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Auth: the provider uses your active `az login` session by default.
|
|
2
|
+
# Set `ARM_*` env vars (ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_TENANT_ID,
|
|
3
|
+
# ARM_SUBSCRIPTION_ID) to override with a service principal in CI.
|
|
4
|
+
#
|
|
5
|
+
# The `features {}` block is required by azurerm even when empty.
|
|
6
|
+
|
|
7
|
+
provider "azurerm" {
|
|
8
|
+
subscription_id = var.subscription_id
|
|
9
|
+
tenant_id = var.tenant_id != "" ? var.tenant_id : null
|
|
10
|
+
|
|
11
|
+
features {
|
|
12
|
+
resource_group {
|
|
13
|
+
prevent_deletion_if_contains_resources = false
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
subscription_id = "" # required — `az account show --query id -o tsv`
|
|
2
|
+
# tenant_id = "" # leave empty to use the tenant from `az login`
|
|
3
|
+
|
|
4
|
+
location = "eastus"
|
|
5
|
+
environment = "staging"
|
|
6
|
+
project = "aries-datalake"
|
|
7
|
+
|
|
8
|
+
# cluster_name and resource_group_name default to the project+environment.
|
|
9
|
+
# cluster_name = "aries-datalake-staging"
|
|
10
|
+
# resource_group_name = "aries-datalake-staging-rg"
|
|
11
|
+
|
|
12
|
+
# k8s_version = "1.31" # leave empty for the AKS regional default
|
|
13
|
+
|
|
14
|
+
# vnet_cidr = "10.45.0.0/16"
|
|
15
|
+
# node_subnet_cidr = "10.45.0.0/22"
|
|
16
|
+
# pod_cidr = "10.244.0.0/16"
|
|
17
|
+
# service_cidr = "10.0.0.0/16"
|
|
18
|
+
# dns_service_ip = "10.0.0.10"
|
|
19
|
+
|
|
20
|
+
# node_vm_size = "Standard_D2s_v5" # ~ t3.medium / e2-standard-2
|
|
21
|
+
# system_node_vm_size = "Standard_D2s_v5"
|
|
22
|
+
# node_count = 2
|
|
23
|
+
# node_min_count = 1
|
|
24
|
+
# node_max_count = 4
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
variable "subscription_id" {
|
|
2
|
+
type = string
|
|
3
|
+
description = "Azure subscription id the cluster lives in."
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
variable "tenant_id" {
|
|
7
|
+
type = string
|
|
8
|
+
description = "Azure tenant id. Leave empty to use the tenant from your active `az login`."
|
|
9
|
+
default = ""
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
variable "location" {
|
|
13
|
+
type = string
|
|
14
|
+
description = "Azure region. Mirrors the `region` knob in cluster-aws / cluster-gcp."
|
|
15
|
+
default = "eastus"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
variable "environment" {
|
|
19
|
+
type = string
|
|
20
|
+
description = "Environment name (staging, prod, etc.). Goes into resource tags + names."
|
|
21
|
+
default = "staging"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
variable "project" {
|
|
25
|
+
type = string
|
|
26
|
+
description = "Project tag (analog of AWS `Project`)."
|
|
27
|
+
default = "aries-datalake"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
variable "cluster_name" {
|
|
31
|
+
type = string
|
|
32
|
+
description = "AKS cluster name. Defaults to `${var.project}-${var.environment}`."
|
|
33
|
+
default = ""
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
variable "resource_group_name" {
|
|
37
|
+
type = string
|
|
38
|
+
description = "Resource group name. Defaults to `<cluster_name>-rg`. Always created by this module."
|
|
39
|
+
default = ""
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
variable "k8s_version" {
|
|
43
|
+
type = string
|
|
44
|
+
description = "Kubernetes version. Leave empty to pull the AKS default for the region."
|
|
45
|
+
default = ""
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
variable "vnet_cidr" {
|
|
49
|
+
type = string
|
|
50
|
+
description = "VNet CIDR block. Must not overlap compute-aws (10.42), cluster-aws (10.43), cluster-gcp (10.44) if you ever peer them."
|
|
51
|
+
default = "10.45.0.0/16"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
variable "node_subnet_cidr" {
|
|
55
|
+
type = string
|
|
56
|
+
description = "Subnet CIDR within the VNet that hosts AKS nodes."
|
|
57
|
+
default = "10.45.0.0/22"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
variable "pod_cidr" {
|
|
61
|
+
type = string
|
|
62
|
+
description = "Overlay CIDR for pod IPs (CNI Overlay mode). Doesn't need to live inside the VNet."
|
|
63
|
+
default = "10.244.0.0/16"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
variable "service_cidr" {
|
|
67
|
+
type = string
|
|
68
|
+
description = "Kubernetes Services CIDR."
|
|
69
|
+
default = "10.0.0.0/16"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
variable "dns_service_ip" {
|
|
73
|
+
type = string
|
|
74
|
+
description = "kube-dns IP — must be inside service_cidr."
|
|
75
|
+
default = "10.0.0.10"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
variable "node_vm_size" {
|
|
79
|
+
type = string
|
|
80
|
+
description = "VM SKU for the default node pool. Standard_D2s_v5 ≈ t3.medium ≈ e2-standard-2."
|
|
81
|
+
default = "Standard_D2s_v5"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
variable "node_count" {
|
|
85
|
+
type = number
|
|
86
|
+
description = "Starting node count for the default user pool."
|
|
87
|
+
default = 2
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
variable "node_min_count" {
|
|
91
|
+
type = number
|
|
92
|
+
description = "Autoscaler floor for the default user pool."
|
|
93
|
+
default = 1
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
variable "node_max_count" {
|
|
97
|
+
type = number
|
|
98
|
+
description = "Autoscaler ceiling for the default user pool."
|
|
99
|
+
default = 4
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
variable "system_node_vm_size" {
|
|
103
|
+
type = string
|
|
104
|
+
description = "VM SKU for the system node pool (kube-system workloads)."
|
|
105
|
+
default = "Standard_D2s_v5"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
variable "tfstate_passphrase" {
|
|
109
|
+
type = string
|
|
110
|
+
description = "Passphrase for OpenTofu state encryption. See encryption.tofu.example."
|
|
111
|
+
default = ""
|
|
112
|
+
sensitive = true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
locals {
|
|
116
|
+
cluster_name = length(var.cluster_name) > 0 ? var.cluster_name : "${var.project}-${var.environment}"
|
|
117
|
+
resource_group_name = length(var.resource_group_name) > 0 ? var.resource_group_name : "${local.cluster_name}-rg"
|
|
118
|
+
name_prefix = "${var.project}-${var.environment}-cluster"
|
|
119
|
+
|
|
120
|
+
common_tags = {
|
|
121
|
+
project = var.project
|
|
122
|
+
environment = var.environment
|
|
123
|
+
component = "cluster"
|
|
124
|
+
managed-by = "opentofu"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
resource "google_container_cluster" "main" {
|
|
2
|
+
name = local.cluster_name
|
|
3
|
+
location = var.region
|
|
4
|
+
|
|
5
|
+
network = google_compute_network.main.id
|
|
6
|
+
subnetwork = google_compute_subnetwork.main.id
|
|
7
|
+
|
|
8
|
+
# Autopilot: Google manages nodes, scales pools, autoscales node count,
|
|
9
|
+
# and enforces opinionated security defaults. Less knob surface, less to
|
|
10
|
+
# break — right call for "Atlas-style hosted datalake". Switch to
|
|
11
|
+
# Standard mode if a customer needs custom node-pool isolation that
|
|
12
|
+
# Autopilot's per-pod scheduling can't satisfy.
|
|
13
|
+
enable_autopilot = true
|
|
14
|
+
|
|
15
|
+
ip_allocation_policy {
|
|
16
|
+
cluster_secondary_range_name = "pods"
|
|
17
|
+
services_secondary_range_name = "services"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
release_channel {
|
|
21
|
+
channel = var.release_channel
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Workload Identity is on by default in Autopilot — pods assume a GCP
|
|
25
|
+
# service account via the `iam.gke.io/gcp-service-account` annotation on
|
|
26
|
+
# their KSA. Equivalent to AWS IRSA. The service plane will use this in
|
|
27
|
+
# phase 6 when the data plane talks to GCS for the archive tier.
|
|
28
|
+
workload_identity_config {
|
|
29
|
+
workload_pool = "${var.project_id}.svc.id.goog"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Master endpoint stays public for now — same posture as cluster-aws.
|
|
33
|
+
# Tighten via master_authorized_networks_config below or flip to
|
|
34
|
+
# `enable_private_endpoint = true` when PrivateLink/IAP work lands.
|
|
35
|
+
master_authorized_networks_config {
|
|
36
|
+
dynamic "cidr_blocks" {
|
|
37
|
+
for_each = var.master_authorized_networks
|
|
38
|
+
content {
|
|
39
|
+
cidr_block = cidr_blocks.value.cidr_block
|
|
40
|
+
display_name = cidr_blocks.value.display_name
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Surface control-plane logs & metrics to Cloud Logging / Monitoring so
|
|
46
|
+
# the SRE team isn't blind on day 1. Autopilot bills for these by default.
|
|
47
|
+
logging_config {
|
|
48
|
+
enable_components = ["SYSTEM_COMPONENTS", "WORKLOADS"]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
monitoring_config {
|
|
52
|
+
enable_components = ["SYSTEM_COMPONENTS"]
|
|
53
|
+
managed_prometheus {
|
|
54
|
+
enabled = true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Deletion protection defaults on for prod environments. Staging users
|
|
59
|
+
# can flip via the environment label.
|
|
60
|
+
deletion_protection = var.environment == "prod"
|
|
61
|
+
|
|
62
|
+
resource_labels = {
|
|
63
|
+
component = "cluster"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Look up the project number — Workload Identity bindings use it in the
|
|
68
|
+
# `serviceAccount:<project>.svc.id.goog[<namespace>/<ksa>]` subject form.
|
|
69
|
+
data "google_project" "main" {
|
|
70
|
+
project_id = var.project_id
|
|
71
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# OpenTofu state encryption — opt-in.
|
|
2
|
+
#
|
|
3
|
+
# Encrypts `terraform.tfstate` and any saved plan files at rest using
|
|
4
|
+
# AES-GCM with a key derived from `var.tfstate_passphrase` via PBKDF2.
|
|
5
|
+
#
|
|
6
|
+
# To enable:
|
|
7
|
+
# 1. cp encryption.tofu.example encryption.tofu (in the working dir)
|
|
8
|
+
# 2. export TF_VAR_tfstate_passphrase='at-least-16-characters'
|
|
9
|
+
# 3. tofu init -reconfigure
|
|
10
|
+
#
|
|
11
|
+
# Notes:
|
|
12
|
+
# - This file uses the `.tofu` extension so Terraform does NOT load it.
|
|
13
|
+
# Only OpenTofu engages encryption. Terraform users skip this block.
|
|
14
|
+
# - GKE state contains the cluster CA bundle and project number — keep
|
|
15
|
+
# it encrypted for anything beyond throwaway staging.
|
|
16
|
+
|
|
17
|
+
terraform {
|
|
18
|
+
encryption {
|
|
19
|
+
key_provider "pbkdf2" "default" {
|
|
20
|
+
passphrase = var.tfstate_passphrase
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
method "aes_gcm" "default" {
|
|
24
|
+
keys = key_provider.pbkdf2.default
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
state {
|
|
28
|
+
method = method.aes_gcm.default
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
plan {
|
|
32
|
+
method = method.aes_gcm.default
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Render a kubeconfig into the working dir. The `exec` block delegates
|
|
2
|
+
# auth to `gke-gcloud-auth-plugin`, which is the supported way to talk to
|
|
3
|
+
# GKE after kubectl 1.26+. Users must install it once:
|
|
4
|
+
#
|
|
5
|
+
# gcloud components install gke-gcloud-auth-plugin
|
|
6
|
+
#
|
|
7
|
+
# Like the EKS kubeconfig, this carries no long-lived secrets — the plugin
|
|
8
|
+
# refreshes tokens from the user's gcloud auth state on every call.
|
|
9
|
+
|
|
10
|
+
locals {
|
|
11
|
+
kubeconfig_yaml = yamlencode({
|
|
12
|
+
apiVersion = "v1"
|
|
13
|
+
kind = "Config"
|
|
14
|
+
current-context = "aries-${local.cluster_name}"
|
|
15
|
+
clusters = [{
|
|
16
|
+
name = "aries-${local.cluster_name}"
|
|
17
|
+
cluster = {
|
|
18
|
+
server = "https://${google_container_cluster.main.endpoint}"
|
|
19
|
+
"certificate-authority-data" = google_container_cluster.main.master_auth[0].cluster_ca_certificate
|
|
20
|
+
}
|
|
21
|
+
}]
|
|
22
|
+
contexts = [{
|
|
23
|
+
name = "aries-${local.cluster_name}"
|
|
24
|
+
context = {
|
|
25
|
+
cluster = "aries-${local.cluster_name}"
|
|
26
|
+
user = "aries-${local.cluster_name}"
|
|
27
|
+
}
|
|
28
|
+
}]
|
|
29
|
+
users = [{
|
|
30
|
+
name = "aries-${local.cluster_name}"
|
|
31
|
+
user = {
|
|
32
|
+
exec = {
|
|
33
|
+
apiVersion = "client.authentication.k8s.io/v1beta1"
|
|
34
|
+
command = "gke-gcloud-auth-plugin"
|
|
35
|
+
provideClusterInfo = true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}]
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
resource "local_sensitive_file" "kubeconfig" {
|
|
43
|
+
filename = "${path.cwd}/kubeconfig"
|
|
44
|
+
content = local.kubeconfig_yaml
|
|
45
|
+
file_permission = "0600"
|
|
46
|
+
directory_permission = "0700"
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
resource "google_compute_network" "main" {
|
|
2
|
+
name = "${local.name_prefix}-vpc"
|
|
3
|
+
auto_create_subnetworks = false
|
|
4
|
+
routing_mode = "REGIONAL"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
# One regional subnet with secondary IP ranges for pods + services — this
|
|
8
|
+
# is the VPC-native ("alias-IP") setup GKE Autopilot requires. Public IPs
|
|
9
|
+
# never get assigned to pods; egress goes through Cloud NAT below.
|
|
10
|
+
resource "google_compute_subnetwork" "main" {
|
|
11
|
+
name = "${local.name_prefix}-subnet"
|
|
12
|
+
network = google_compute_network.main.id
|
|
13
|
+
region = var.region
|
|
14
|
+
ip_cidr_range = var.vpc_cidr
|
|
15
|
+
|
|
16
|
+
private_ip_google_access = true
|
|
17
|
+
|
|
18
|
+
secondary_ip_range {
|
|
19
|
+
range_name = "pods"
|
|
20
|
+
ip_cidr_range = var.pods_cidr
|
|
21
|
+
}
|
|
22
|
+
secondary_ip_range {
|
|
23
|
+
range_name = "services"
|
|
24
|
+
ip_cidr_range = var.services_cidr
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
resource "google_compute_router" "main" {
|
|
29
|
+
name = "${local.name_prefix}-router"
|
|
30
|
+
network = google_compute_network.main.id
|
|
31
|
+
region = var.region
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Autopilot nodes have no external IPs by default — they need NAT to pull
|
|
35
|
+
# container images, reach external APIs, etc.
|
|
36
|
+
resource "google_compute_router_nat" "main" {
|
|
37
|
+
name = "${local.name_prefix}-nat"
|
|
38
|
+
router = google_compute_router.main.name
|
|
39
|
+
region = var.region
|
|
40
|
+
nat_ip_allocate_option = "AUTO_ONLY"
|
|
41
|
+
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
|
|
42
|
+
|
|
43
|
+
log_config {
|
|
44
|
+
enable = true
|
|
45
|
+
filter = "ERRORS_ONLY"
|
|
46
|
+
}
|
|
47
|
+
}
|