Skip to content

Commit 8ee05c4

Browse files
authored
Merge pull request #2 from quixio/dev
New tiered storage Module and bring your own vnet to. the aks module
2 parents 406850c + fc22bf7 commit 8ee05c4

File tree

16 files changed

+593
-21
lines changed

16 files changed

+593
-21
lines changed

.github/workflows/terraform-module.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
branches: [ main, dev ]
66
paths:
77
- 'modules/quix-aks/**'
8+
- 'modules/tiered-storage/**'
89
- '.github/workflows/terraform-module.yml'
910
workflow_dispatch:
1011
inputs:
@@ -24,7 +25,11 @@ jobs:
2425
runs-on: ubuntu-latest
2526
defaults:
2627
run:
27-
working-directory: modules/quix-aks
28+
working-directory: ${{ matrix.module }}
29+
strategy:
30+
fail-fast: false
31+
matrix:
32+
module: [ 'modules/quix-aks', 'modules/tiered-storage' ]
2833
steps:
2934
- name: Checkout
3035
uses: actions/checkout@v4
@@ -48,7 +53,7 @@ jobs:
4853
- name: Generate terraform-docs (inject & commit)
4954
uses: terraform-docs/[email protected]
5055
with:
51-
working-dir: modules/quix-aks
56+
working-dir: ${{ matrix.module }}
5257
output-file: README.md
5358
output-method: inject
5459
config-file: ''

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ Repository of production-ready Terraform modules for installing quix-platform.
1111
- `rbac.tf`: role assignments for the managed identity
1212
- `bastion.tf`: Azure Bastion + jumpbox (optional)
1313
- `README.md`: terraform-docs generated documentation
14+
- `modules/tiered-storage/` (Tiered Storage module)
15+
- `main.tf`: Storage Account, federated identity credentials, role assignment for kubelet identity
16+
- `README.md`: terraform-docs generated documentation
1417
- `examples/` usage examples
1518
- `public-quix-infr/`: public cluster
1619
- `private-quix-infr/`: private cluster with Bastion + jumpbox
20+
- `public-quix-infr-tiered-storage/`: public cluster + tiered-storage module
21+
- `private-quix-infr-external-vnet/`: private cluster using external VNet/Subnets, external NAT (BYO), and Bastion subnet
1722
- `BASTION_ACCESS.md`: how to access a private AKS via Bastion
1823

1924
## AKS module (quix-aks)
@@ -29,6 +34,31 @@ cd modules/quix-aks
2934
terraform-docs markdown table --output-file README.md --output-mode inject .
3035
```
3136

37+
### Bring Your Own NAT (BYO NAT)
38+
39+
You can use an external NAT Gateway instead of creating one:
40+
41+
```hcl
42+
module "quix_aks" {
43+
# ...
44+
create_nat = false
45+
nat_gateway_id = azurerm_nat_gateway.external.id
46+
}
47+
```
48+
49+
## Tiered Storage module (tiered-storage)
50+
51+
Module documentation (inputs/outputs/resources):
52+
53+
- [modules/tiered-storage/README.md](modules/tiered-storage/README.md) (generated with terraform-docs)
54+
55+
Regenerate docs (requires `terraform-docs`):
56+
57+
```bash
58+
cd modules/tiered-storage
59+
terraform-docs markdown table --output-file README.md --output-mode inject .
60+
```
61+
3262
## Examples
3363

3464
Public example:
@@ -47,6 +77,14 @@ terraform init
4777
terraform apply
4878
```
4979

80+
External VNet + external NAT + Bastion subnet example:
81+
82+
```bash
83+
cd examples/private-quix-infr-external-vnet
84+
terraform init
85+
terraform apply
86+
```
87+
5088
Access a private AKS: see `BASTION_ACCESS.md`.
5189

5290
## Requirements
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
terraform {
2+
required_providers {
3+
azurerm = {
4+
source = "hashicorp/azurerm"
5+
version = "~> 3.112"
6+
}
7+
}
8+
}
9+
10+
provider "azurerm" {
11+
features {}
12+
}
13+
14+
# Resource group Created externally
15+
resource "azurerm_resource_group" "this" {
16+
name = "rg-quix-private"
17+
location = "westeurope"
18+
}
19+
20+
# External VNet and subnets (managed outside the module)
21+
resource "azurerm_virtual_network" "ext" {
22+
name = "vnet-quix-private"
23+
location = azurerm_resource_group.this.location
24+
resource_group_name = azurerm_resource_group.this.name
25+
address_space = ["10.240.0.0/16"]
26+
}
27+
28+
resource "azurerm_subnet" "nodes_ext" {
29+
name = "Subnet-Nodes"
30+
resource_group_name = azurerm_resource_group.this.name
31+
virtual_network_name = azurerm_virtual_network.ext.name
32+
address_prefixes = ["10.240.0.0/22"]
33+
}
34+
35+
resource "azurerm_subnet" "bastion_ext" {
36+
name = "AzureBastionSubnet"
37+
resource_group_name = azurerm_resource_group.this.name
38+
virtual_network_name = azurerm_virtual_network.ext.name
39+
address_prefixes = ["10.240.5.0/27"]
40+
}
41+
42+
43+
# External NAT (bring your own)
44+
resource "azurerm_public_ip" "nat_ext" {
45+
name = "pip-quix-private-nat-ext"
46+
location = azurerm_resource_group.this.location
47+
resource_group_name = azurerm_resource_group.this.name
48+
allocation_method = "Static"
49+
sku = "Standard"
50+
zones = ["2"]
51+
}
52+
53+
resource "azurerm_nat_gateway" "ext" {
54+
name = "ngw-quix-private-ext"
55+
location = azurerm_resource_group.this.location
56+
resource_group_name = azurerm_resource_group.this.name
57+
sku_name = "Standard"
58+
}
59+
60+
resource "azurerm_nat_gateway_public_ip_association" "ext" {
61+
nat_gateway_id = azurerm_nat_gateway.ext.id
62+
public_ip_address_id = azurerm_public_ip.nat_ext.id
63+
}
64+
65+
resource "azurerm_subnet_nat_gateway_association" "nodes_ext" {
66+
subnet_id = azurerm_subnet.nodes_ext.id
67+
nat_gateway_id = azurerm_nat_gateway.ext.id
68+
}
69+
70+
71+
module "aks" {
72+
source = "../../modules/quix-aks"
73+
74+
name = "quix-aks-private"
75+
location = "westeurope"
76+
resource_group_name = azurerm_resource_group.this.name
77+
create_resource_group = false
78+
kubernetes_version = "1.32.4"
79+
sku_tier = "Standard"
80+
private_cluster_enabled = true
81+
82+
vnet_name = "vnet-quix-private"
83+
vnet_address_space = ["10.240.0.0/16"]
84+
nodes_subnet_name = "Subnet-Nodes"
85+
nodes_subnet_cidr = "10.240.0.0/22"
86+
87+
# Reuse external VNet/Subnets
88+
vnet_id = azurerm_virtual_network.ext.id
89+
nodes_subnet_id = azurerm_subnet.nodes_ext.id
90+
91+
nat_identity_name = "quix-private-nat-id"
92+
public_ip_name = "quix-private-nat-ip"
93+
nat_gateway_name = "quix-private-nat"
94+
availability_zone = "2"
95+
96+
enable_credentials_fetch = true
97+
node_pools = {
98+
default = {
99+
name = "default"
100+
type = "system"
101+
node_count = 2
102+
vm_size = "Standard_D4ds_v5"
103+
}
104+
}
105+
106+
network_profile = {
107+
network_plugin_mode = "overlay"
108+
service_cidr = "172.22.0.0/16"
109+
dns_service_ip = "172.22.0.10"
110+
pod_cidr = "10.144.0.0/16"
111+
}
112+
# Bring your own NAT
113+
create_nat = false
114+
nat_gateway_id = azurerm_nat_gateway.ext.id
115+
create_nodes_subnet = false
116+
create_vnet = false
117+
create_bastion_subnet = false
118+
enable_bastion = true
119+
bastion_name = "quix-bastion"
120+
# Reuse external Bastion subnet & IP
121+
bastion_subnet_id = azurerm_subnet.bastion_ext.id
122+
123+
jumpbox_name = "quix-jumpbox"
124+
jumpbox_vm_size = "Standard_B2s"
125+
jumpbox_admin_username = "azureuser"
126+
jumpbox_ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC3Zz+tHUEI7ulzE69GxtLwi9DOvROBG4aI7h3za2FAP6Ya9/GhG2zcBiKOzk3SlKavE3/5NomgGifTC/ica6rTPlpb4U5oky/2phs9AtczVVI2G+yNC43hJzVhWbqKT3qAGGCGEm2+Cpxx7spKEbZAfAcq5GxL3k9kTcpaQEv3hpVvqK3zlCziHyahUv1pxQGuX3b2hqi4idgFX3m0FaqU98DtQu/I9x95jXHrb7Wltp3sbTSKCDxGo3nk4plpzILs/OqTMSPpfxwarCXA1ZtU82hyWO4Szn2U4I+MbuNaO/dso1oNlprqJQgsQ8t+hawCdIHeZ00M/QELdnYldBjo1jM19AT1OwMcB7PP7GRTNv7YsDW10YCvX9XRPab66PIKpe5R4IG/n6TzEwUP2pb4hRJWvnPJzrHK5HEJg7G7baCEyjCtaWkL4M7dBxIGJ3sp9IfjdeztV2Llh+hYmwPefTejprER+Q/qHZTNr1wEW4BV0TQQd+jeqdIL4QkIno3IyM3IBX+uPM/WlSpi2sT+hDqiUcCRu/x21O/bVYz/UbeHIqptRDfGc5rVoAN/zc/kGsGeGuP3auyI6aQxlnU0wMDdyS8rf3SpWagOB2UFNxZSuU2gnYdtz2uWG4vF75Sqr04MFJImHIY4N7gHJrvdarg6YBaDDnmdREcqp3ooAw== "
127+
128+
oidc_issuer_enabled = true
129+
workload_identity_enabled = true
130+
131+
132+
tags = {
133+
environment = "demo"
134+
project = "Quix"
135+
}
136+
137+
depends_on = [
138+
azurerm_resource_group.this,
139+
azurerm_subnet.bastion_ext,
140+
azurerm_subnet_nat_gateway_association.nodes_ext
141+
]
142+
}
143+
144+
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
terraform {
2+
required_providers {
3+
azurerm = {
4+
source = "hashicorp/azurerm"
5+
version = "~> 3.112"
6+
}
7+
}
8+
}
9+
10+
provider "azurerm" {
11+
features {}
12+
}
13+
14+
module "aks" {
15+
source = "../../modules/quix-aks"
16+
17+
name = "quix-aks-public"
18+
location = "westeurope"
19+
resource_group_name = "rg-quix-public"
20+
create_resource_group = true
21+
kubernetes_version = "1.32.4"
22+
sku_tier = "Standard"
23+
private_cluster_enabled = false
24+
25+
vnet_name = "vnet-quix-public"
26+
vnet_address_space = ["10.240.0.0/16"]
27+
nodes_subnet_name = "Subnet-Nodes"
28+
nodes_subnet_cidr = "10.240.0.0/22"
29+
30+
nat_identity_name = "quix-public-nat-id"
31+
public_ip_name = "quix-public-nat-ip"
32+
nat_gateway_name = "quix-public-nat"
33+
availability_zone = "1"
34+
35+
enable_credentials_fetch = true
36+
37+
node_pools = {
38+
default = {
39+
name = "default"
40+
type = "system"
41+
node_count = 1
42+
vm_size = "Standard_D4ds_v5"
43+
},
44+
quix_controller = {
45+
name = "quixcontroller"
46+
type = "user"
47+
node_count = 1
48+
vm_size = "Standard_D4ds_v5"
49+
taints = ["dedicated=controller:NoSchedule"]
50+
labels = { role = "controller" }
51+
}
52+
quix_deployments = {
53+
name = "quixdeployment"
54+
type = "user"
55+
node_count = 1
56+
vm_size = "Standard_D4ds_v5"
57+
taints = ["dedicated=controller:NoSchedule"]
58+
labels = { role = "controller" }
59+
}
60+
}
61+
62+
network_profile = {
63+
network_plugin_mode = "vnet"
64+
service_cidr = "172.22.0.0/16"
65+
dns_service_ip = "172.22.0.10"
66+
}
67+
68+
oidc_issuer_enabled = true
69+
workload_identity_enabled = true
70+
71+
tags = {
72+
environment = "demo"
73+
project = "Quix"
74+
}
75+
}
76+
77+
module "tiered_storage" {
78+
source = "../../modules/tiered-storage"
79+
80+
resource_group_name = module.aks.resource_group_name
81+
location = module.aks.resource_group_location
82+
storage_account_name = "quixstorpublic01" # must be globally unique
83+
cluster_name = module.aks.cluster_name
84+
aks_oidc_issuer_url = module.aks.oidc_issuer_url
85+
kubelet_identity_object_id = module.aks.kubelet_identity_object_id
86+
87+
# Use the cluster managed identity client/resource from AKS module
88+
cluster_identity_resource_id = module.aks.cluster_identity_resource_id
89+
90+
federated_bindings = [
91+
{ namespace = "default", service_account_name = "tiered-storage-sa" },
92+
{ namespace = "analytics", service_account_name = "tiered-storage-sa" }
93+
]
94+
95+
tags = {
96+
environment = "demo"
97+
project = "Quix"
98+
}
99+
}
100+
101+

modules/quix-aks/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ No modules.
4343
| [azurerm_resource_group.existing](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source |
4444
| [azurerm_role_definition.contributor](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/role_definition) | data source |
4545
| [azurerm_role_definition.network_contributor](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/role_definition) | data source |
46+
| [azurerm_subnet.bastion](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subnet) | data source |
47+
| [azurerm_subnet.nodes](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subnet) | data source |
48+
| [azurerm_virtual_network.existing](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network) | data source |
4649

4750
## Inputs
4851

@@ -51,9 +54,15 @@ No modules.
5154
| <a name="input_attach_identity_ids"></a> [attach\_identity\_ids](#input\_attach\_identity\_ids) | Additional user-assigned identity IDs to attach to the cluster | `list(string)` | `[]` | no |
5255
| <a name="input_availability_zone"></a> [availability\_zone](#input\_availability\_zone) | Availability zone for public IP | `string` | n/a | yes |
5356
| <a name="input_bastion_name"></a> [bastion\_name](#input\_bastion\_name) | Name of the Azure Bastion resource | `string` | `"QuixBastion"` | no |
57+
| <a name="input_bastion_public_ip_id"></a> [bastion\_public\_ip\_id](#input\_bastion\_public\_ip\_id) | Existing Bastion Public IP ID to reuse (skip public IP creation when set) | `string` | `null` | no |
5458
| <a name="input_bastion_public_ip_name"></a> [bastion\_public\_ip\_name](#input\_bastion\_public\_ip\_name) | Name of the Public IP for Azure Bastion | `string` | `"QuixBastionIP"` | no |
5559
| <a name="input_bastion_subnet_cidr"></a> [bastion\_subnet\_cidr](#input\_bastion\_subnet\_cidr) | CIDR for AzureBastionSubnet | `string` | `"10.0.64.0/27"` | no |
60+
| <a name="input_bastion_subnet_id"></a> [bastion\_subnet\_id](#input\_bastion\_subnet\_id) | Existing AzureBastionSubnet ID to reuse (skip subnet creation when set) | `string` | `null` | no |
61+
| <a name="input_create_bastion_subnet"></a> [create\_bastion\_subnet](#input\_create\_bastion\_subnet) | Whether to create AzureBastionSubnet (set false when supplying bastion\_subnet\_id) | `bool` | `true` | no |
62+
| <a name="input_create_nat"></a> [create\_nat](#input\_create\_nat) | Whether to create NAT Gateway and its Public IP (set false to bring your own) | `bool` | `true` | no |
63+
| <a name="input_create_nodes_subnet"></a> [create\_nodes\_subnet](#input\_create\_nodes\_subnet) | Whether to create the nodes subnet (set false when using external nodes\_subnet\_id) | `bool` | `true` | no |
5664
| <a name="input_create_resource_group"></a> [create\_resource\_group](#input\_create\_resource\_group) | Whether to create the resource group | `bool` | `true` | no |
65+
| <a name="input_create_vnet"></a> [create\_vnet](#input\_create\_vnet) | Whether to create the VNet (set false when using external vnet\_id) | `bool` | `true` | no |
5766
| <a name="input_enable_bastion"></a> [enable\_bastion](#input\_enable\_bastion) | Deploy Azure Bastion and its required subnet | `bool` | `false` | no |
5867
| <a name="input_enable_credentials_fetch"></a> [enable\_credentials\_fetch](#input\_enable\_credentials\_fetch) | Run az aks get-credentials after creating the cluster | `bool` | `false` | no |
5968
| <a name="input_jumpbox_admin_username"></a> [jumpbox\_admin\_username](#input\_jumpbox\_admin\_username) | Admin username for the jumpbox | `string` | `"azureuser"` | no |
@@ -63,11 +72,13 @@ No modules.
6372
| <a name="input_kubernetes_version"></a> [kubernetes\_version](#input\_kubernetes\_version) | Kubernetes version | `string` | n/a | yes |
6473
| <a name="input_location"></a> [location](#input\_location) | Azure region | `string` | n/a | yes |
6574
| <a name="input_name"></a> [name](#input\_name) | Name of the AKS cluster | `string` | n/a | yes |
75+
| <a name="input_nat_gateway_id"></a> [nat\_gateway\_id](#input\_nat\_gateway\_id) | Existing NAT Gateway ID to associate when create\_nat is false | `string` | `null` | no |
6676
| <a name="input_nat_gateway_name"></a> [nat\_gateway\_name](#input\_nat\_gateway\_name) | Name of the NAT Gateway | `string` | n/a | yes |
6777
| <a name="input_nat_identity_name"></a> [nat\_identity\_name](#input\_nat\_identity\_name) | Name of the managed identity for NAT | `string` | n/a | yes |
6878
| <a name="input_network_profile"></a> [network\_profile](#input\_network\_profile) | AKS network profile | <pre>object({<br/> network_plugin_mode = string # "overlay" or "vnet"<br/> service_cidr = string<br/> dns_service_ip = string<br/> pod_cidr = optional(string)<br/> network_policy = optional(string, "calico")<br/> outbound_type = optional(string, "userAssignedNATGateway")<br/> })</pre> | n/a | yes |
6979
| <a name="input_node_pools"></a> [node\_pools](#input\_node\_pools) | Map of additional node pools (include a 'system' pool to override default) | <pre>map(object({<br/> name = string<br/> type = string # system | user<br/> node_count = number<br/> vm_size = string<br/> max_pods = optional(number)<br/> taints = optional(list(string))<br/> labels = optional(map(string))<br/> mode = optional(string) # system | user (overrides type)<br/> }))</pre> | `{}` | no |
7080
| <a name="input_nodes_subnet_cidr"></a> [nodes\_subnet\_cidr](#input\_nodes\_subnet\_cidr) | CIDR for the AKS nodes subnet | `string` | n/a | yes |
81+
| <a name="input_nodes_subnet_id"></a> [nodes\_subnet\_id](#input\_nodes\_subnet\_id) | Existing nodes subnet ID to reuse (skip subnet creation when set) | `string` | `null` | no |
7182
| <a name="input_nodes_subnet_name"></a> [nodes\_subnet\_name](#input\_nodes\_subnet\_name) | Name of the AKS nodes subnet | `string` | n/a | yes |
7283
| <a name="input_oidc_issuer_enabled"></a> [oidc\_issuer\_enabled](#input\_oidc\_issuer\_enabled) | Enable OIDC issuer | `bool` | `true` | no |
7384
| <a name="input_private_cluster_enabled"></a> [private\_cluster\_enabled](#input\_private\_cluster\_enabled) | Enable AKS private cluster | `bool` | `false` | no |
@@ -76,6 +87,7 @@ No modules.
7687
| <a name="input_sku_tier"></a> [sku\_tier](#input\_sku\_tier) | AKS tier (Free or Standard) | `string` | `"Standard"` | no |
7788
| <a name="input_tags"></a> [tags](#input\_tags) | Tags to apply to resources | `map(string)` | `{}` | no |
7889
| <a name="input_vnet_address_space"></a> [vnet\_address\_space](#input\_vnet\_address\_space) | Address space for the Virtual Network | `list(string)` | n/a | yes |
90+
| <a name="input_vnet_id"></a> [vnet\_id](#input\_vnet\_id) | Existing VNet ID to reuse (skip VNet creation when set) | `string` | `null` | no |
7991
| <a name="input_vnet_name"></a> [vnet\_name](#input\_vnet\_name) | Name of the Virtual Network | `string` | n/a | yes |
8092
| <a name="input_workload_identity_enabled"></a> [workload\_identity\_enabled](#input\_workload\_identity\_enabled) | Enable workload identity | `bool` | `true` | no |
8193

@@ -86,6 +98,7 @@ No modules.
8698
| <a name="output_aks_id"></a> [aks\_id](#output\_aks\_id) | ID of the AKS cluster |
8799
| <a name="output_cluster_identity_client_id"></a> [cluster\_identity\_client\_id](#output\_cluster\_identity\_client\_id) | Client ID of the cluster's user-assigned identity |
88100
| <a name="output_cluster_identity_principal_id"></a> [cluster\_identity\_principal\_id](#output\_cluster\_identity\_principal\_id) | Principal ID of the cluster's user-assigned identity |
101+
| <a name="output_cluster_identity_resource_id"></a> [cluster\_identity\_resource\_id](#output\_cluster\_identity\_resource\_id) | Resource ID of the cluster's user-assigned identity |
89102
| <a name="output_cluster_name"></a> [cluster\_name](#output\_cluster\_name) | AKS cluster name |
90103
| <a name="output_fqdn"></a> [fqdn](#output\_fqdn) | Public FQDN of the API server (null if private) |
91104
| <a name="output_kubelet_identity_client_id"></a> [kubelet\_identity\_client\_id](#output\_kubelet\_identity\_client\_id) | Kubelet identity client ID |

0 commit comments

Comments
 (0)