Skip to content

Commit 32d3618

Browse files
authored
Add system node pool to config (#136)
1 parent 6a3e6ae commit 32d3618

File tree

7 files changed

+163
-77
lines changed

7 files changed

+163
-77
lines changed

.github/actions/login-azure/action.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ runs:
5353
- name: prefetch tokens
5454
shell: bash
5555
run: |
56+
az account get-access-token --output none
5657
az account get-access-token --scope https://storage.azure.com/.default --output none
5758
az account get-access-token --scope https://vault.azure.net/.default --output none
5859
az account get-access-token --resource https://ossrdbms-aad.database.windows.net --output none

cli/internal/install/cloudinstall/cloudconfig.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"io"
99
"strings"
1010
"text/template"
11+
12+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v4"
1113
)
1214

1315
//go:embed config.tpl
@@ -83,11 +85,13 @@ func (c *ComputeConfig) GetApiHostCluster() *ClusterConfig {
8385
}
8486

8587
type ClusterConfig struct {
86-
Name string `json:"name"`
87-
ApiHost bool `json:"apiHost"`
88-
Location string `json:"location"`
89-
KubernetesVersion string `json:"kubernetesVersion,omitempty"`
90-
UserNodePools []*NodePoolConfig `json:"userNodePools"`
88+
Name string `json:"name"`
89+
ApiHost bool `json:"apiHost"`
90+
Location string `json:"location"`
91+
Sku armcontainerservice.ManagedClusterSKUTier `json:"sku"`
92+
KubernetesVersion string `json:"kubernetesVersion,omitempty"`
93+
SystemNodePool *NodePoolConfig `json:"systemNodePool"`
94+
UserNodePools []*NodePoolConfig `json:"userNodePools"`
9195
}
9296

9397
type NodePoolConfig struct {

cli/internal/install/cloudinstall/compute.go

Lines changed: 87 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,24 @@ func (inst *Installer) createCluster(ctx context.Context, clusterConfig *Cluster
5959
}
6060
tags[TagKey] = &inst.Config.EnvironmentName
6161

62+
if clusterAlreadyExists {
63+
if *existingCluster.Properties.KubernetesVersion != clusterConfig.KubernetesVersion {
64+
existingCluster.Properties.KubernetesVersion = &clusterConfig.KubernetesVersion
65+
log.Ctx(ctx).Info().Msgf("Updating Kubernetes version to %s", clusterConfig.KubernetesVersion)
66+
resp, err := clustersClient.BeginCreateOrUpdate(ctx, inst.Config.Cloud.ResourceGroup, clusterConfig.Name, existingCluster.ManagedCluster, nil)
67+
if err != nil {
68+
return nil, fmt.Errorf("failed to update cluster: %w", err)
69+
}
70+
if _, err := resp.PollUntilDone(ctx, nil); err != nil {
71+
return nil, fmt.Errorf("failed to update cluster: %w", err)
72+
}
73+
existingCluster, err = clustersClient.Get(ctx, inst.Config.Cloud.ResourceGroup, clusterConfig.Name, nil)
74+
if err != nil {
75+
return nil, fmt.Errorf("failed to get cluster: %w", err)
76+
}
77+
}
78+
}
79+
6280
cluster := armcontainerservice.ManagedCluster{
6381
Tags: tags,
6482
Location: Ptr(clusterConfig.Location),
@@ -82,6 +100,10 @@ func (inst *Installer) createCluster(ctx context.Context, clusterConfig *Cluster
82100
},
83101
},
84102
},
103+
SKU: &armcontainerservice.ManagedClusterSKU{
104+
Name: Ptr(armcontainerservice.ManagedClusterSKUNameBase),
105+
Tier: Ptr(armcontainerservice.ManagedClusterSKUTier(clusterConfig.Sku)),
106+
},
85107
}
86108

87109
if workspace := inst.Config.Cloud.LogAnalyticsWorkspace; workspace != nil {
@@ -104,34 +126,35 @@ func (inst *Installer) createCluster(ctx context.Context, clusterConfig *Cluster
104126
"logAnalyticsWorkspaceResourceID": resp.ID,
105127
},
106128
}
107-
108129
}
109130

110131
cluster.Properties.AgentPoolProfiles = []*armcontainerservice.ManagedClusterAgentPoolProfile{
111132
{
112-
Name: Ptr("system"),
113-
Mode: Ptr(armcontainerservice.AgentPoolModeSystem),
114-
VMSize: Ptr("Standard_DS2_v2"),
115-
EnableAutoScaling: Ptr(true),
116-
Count: Ptr(int32(1)),
117-
MinCount: Ptr(int32(1)),
118-
MaxCount: Ptr(int32(3)),
119-
OSType: Ptr(armcontainerservice.OSTypeLinux),
120-
OSSKU: Ptr(armcontainerservice.OSSKUAzureLinux),
133+
Name: &clusterConfig.SystemNodePool.Name,
134+
Mode: Ptr(armcontainerservice.AgentPoolModeSystem),
135+
OrchestratorVersion: &clusterConfig.KubernetesVersion,
136+
VMSize: &clusterConfig.SystemNodePool.VMSize,
137+
EnableAutoScaling: Ptr(true),
138+
Count: &clusterConfig.SystemNodePool.MinCount,
139+
MinCount: &clusterConfig.SystemNodePool.MinCount,
140+
MaxCount: &clusterConfig.SystemNodePool.MaxCount,
141+
OSType: Ptr(armcontainerservice.OSTypeLinux),
142+
OSSKU: Ptr(armcontainerservice.OSSKUAzureLinux),
121143
},
122144
}
123145

124146
for _, np := range clusterConfig.UserNodePools {
125147
profile := armcontainerservice.ManagedClusterAgentPoolProfile{
126-
Name: &np.Name,
127-
Mode: Ptr(armcontainerservice.AgentPoolModeUser),
128-
VMSize: &np.VMSize,
129-
EnableAutoScaling: Ptr(true),
130-
Count: &np.MinCount,
131-
MinCount: &np.MinCount,
132-
MaxCount: &np.MaxCount,
133-
OSType: Ptr(armcontainerservice.OSTypeLinux),
134-
OSSKU: Ptr(armcontainerservice.OSSKUAzureLinux),
148+
Name: &np.Name,
149+
Mode: Ptr(armcontainerservice.AgentPoolModeUser),
150+
OrchestratorVersion: &clusterConfig.KubernetesVersion,
151+
VMSize: &np.VMSize,
152+
EnableAutoScaling: Ptr(true),
153+
Count: &np.MinCount,
154+
MinCount: &np.MinCount,
155+
MaxCount: &np.MaxCount,
156+
OSType: Ptr(armcontainerservice.OSTypeLinux),
157+
OSSKU: Ptr(armcontainerservice.OSSKUAzureLinux),
135158
NodeLabels: map[string]*string{
136159
"tyger": Ptr("run"),
137160
},
@@ -158,46 +181,28 @@ func (inst *Installer) createCluster(ctx context.Context, clusterConfig *Cluster
158181

159182
if clusterAlreadyExists {
160183
// Check for node pools that need to be added or removed, which
161-
// need to be handled separately other cluster property updates.
184+
// need to be handled separately from other cluster property updates.
162185

163186
agentPoolsClient, err := armcontainerservice.NewAgentPoolsClient(inst.Config.Cloud.SubscriptionID, inst.Credential, nil)
164187
if err != nil {
165188
return nil, fmt.Errorf("failed to create agent pools client: %w", err)
166189
}
167190

168-
agentPoolDeletePollers := make([]*runtime.Poller[armcontainerservice.AgentPoolsClientDeleteResponse], 0)
169-
170-
for _, existingNodePool := range existingCluster.ManagedCluster.Properties.AgentPoolProfiles {
171-
found := false
172-
for _, newPool := range cluster.Properties.AgentPoolProfiles {
173-
if *newPool.Name == *existingNodePool.Name {
174-
found = true
175-
break
176-
}
177-
}
178-
if !found {
179-
log.Info().Msgf("Deleting node pool '%s' from cluster '%s'", *existingNodePool.Name, clusterConfig.Name)
180-
p, err := agentPoolsClient.BeginDelete(ctx, inst.Config.Cloud.ResourceGroup, clusterConfig.Name, *existingNodePool.Name, nil)
181-
if err != nil {
182-
return nil, fmt.Errorf("failed to delete node pool: %w", err)
183-
}
184-
agentPoolDeletePollers = append(agentPoolDeletePollers, p)
185-
}
186-
}
187-
188-
for _, deletePoller := range agentPoolDeletePollers {
189-
if _, err := deletePoller.PollUntilDone(ctx, nil); err != nil {
190-
return nil, fmt.Errorf("failed to delete node pool: %w", err)
191-
}
192-
}
193-
194191
agentPoolCreatePollers := make([]*runtime.Poller[armcontainerservice.AgentPoolsClientCreateOrUpdateResponse], 0)
195192

196193
for _, newNodePool := range cluster.Properties.AgentPoolProfiles {
197194
found := false
198195
for _, existingNodePool := range existingCluster.ManagedCluster.Properties.AgentPoolProfiles {
199196
if *newNodePool.Name == *existingNodePool.Name {
200197
found = true
198+
if *newNodePool.VMSize != *existingNodePool.VMSize {
199+
return nil, fmt.Errorf("create a new node pool instead of changing the VM size of node pool '%s'", *newNodePool.Name)
200+
}
201+
202+
if *newNodePool.Mode != *existingNodePool.Mode {
203+
return nil, fmt.Errorf("cannot change existing node pool '%s' from user to system (or vice-versa)", *newNodePool.Name)
204+
}
205+
201206
break
202207
}
203208
}
@@ -222,12 +227,38 @@ func (inst *Installer) createCluster(ctx context.Context, clusterConfig *Cluster
222227
}
223228
agentPoolCreatePollers = append(agentPoolCreatePollers, p)
224229
}
230+
}
225231

226-
for _, p := range agentPoolCreatePollers {
227-
if _, err := p.PollUntilDone(ctx, nil); err != nil {
228-
return nil, fmt.Errorf("failed to create node pool: %w", err)
232+
for _, p := range agentPoolCreatePollers {
233+
if _, err := p.PollUntilDone(ctx, nil); err != nil {
234+
return nil, fmt.Errorf("failed to create node pool: %w", err)
235+
}
236+
}
237+
238+
agentPoolDeletePollers := make([]*runtime.Poller[armcontainerservice.AgentPoolsClientDeleteResponse], 0)
239+
240+
for _, existingNodePool := range existingCluster.ManagedCluster.Properties.AgentPoolProfiles {
241+
found := false
242+
for _, newPool := range cluster.Properties.AgentPoolProfiles {
243+
if *newPool.Name == *existingNodePool.Name {
244+
found = true
245+
break
229246
}
230247
}
248+
if !found {
249+
log.Info().Msgf("Deleting node pool '%s' from cluster '%s'", *existingNodePool.Name, clusterConfig.Name)
250+
p, err := agentPoolsClient.BeginDelete(ctx, inst.Config.Cloud.ResourceGroup, clusterConfig.Name, *existingNodePool.Name, nil)
251+
if err != nil {
252+
return nil, fmt.Errorf("failed to delete node pool: %w", err)
253+
}
254+
agentPoolDeletePollers = append(agentPoolDeletePollers, p)
255+
}
256+
}
257+
258+
for _, deletePoller := range agentPoolDeletePollers {
259+
if _, err := deletePoller.PollUntilDone(ctx, nil); err != nil {
260+
return nil, fmt.Errorf("failed to delete node pool: %w", err)
261+
}
231262
}
232263

233264
if len(agentPoolDeletePollers) > 0 || len(agentPoolCreatePollers) > 0 {
@@ -342,6 +373,10 @@ func clusterNeedsUpdating(cluster, existingCluster armcontainerservice.ManagedCl
342373
}
343374
}
344375

376+
if *cluster.SKU.Tier != *existingCluster.SKU.Tier {
377+
return true, false
378+
}
379+
345380
if len(cluster.Properties.AgentPoolProfiles) != len(existingCluster.Properties.AgentPoolProfiles) {
346381
return true, false
347382
}
@@ -366,6 +401,9 @@ func clusterNeedsUpdating(cluster, existingCluster armcontainerservice.ManagedCl
366401
onlyScaleDown = false
367402
}
368403
}
404+
if *np.OrchestratorVersion != *existingNp.OrchestratorVersion {
405+
return true, false
406+
}
369407
break
370408
}
371409
}

cli/internal/install/cloudinstall/config.tpl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ cloud:
1616
clusters:
1717
- name: {{ .EnvironmentName }}
1818
apiHost: true
19-
kubernetesVersion: {{ .KubernetesVersion }}
19+
kubernetesVersion: "{{ .KubernetesVersion }}"
2020
# location: Defaults to defaultLocation
2121

22+
systemNodePool:
23+
name: system
24+
vmSize: Standard_DS2_v2
25+
minCount: 1
26+
maxCount: 3
27+
2228
userNodePools:
2329
- name: cpunp
2430
vmSize: Standard_DS12_v2

cli/internal/install/cloudinstall/helm.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ func (inst *Installer) installTraefik(ctx context.Context, restConfigPromise *in
5454
ChartRef: "traefik/traefik",
5555
Version: "24.0.0",
5656
Values: map[string]any{
57+
"image": map[string]any{
58+
"registry": "mcr.microsoft.com",
59+
"repository": "oss/traefik/traefik",
60+
"tag": "v2.10.7",
61+
},
5762
"logs": map[string]any{
5863
"general": map[string]any{
5964
"format": "json",

cli/internal/install/cloudinstall/validation.go

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import (
88
"net"
99
"net/url"
1010
"regexp"
11+
"slices"
12+
"strings"
1113

14+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v4"
1215
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers/v4"
1316
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
1417
"github.com/google/uuid"
@@ -96,31 +99,30 @@ func quickValidateComputeConfig(success *bool, cloudConfig *CloudConfig) {
9699
cluster.KubernetesVersion = DefaultKubernetesVersion
97100
}
98101

102+
if cluster.Sku == "" {
103+
cluster.Sku = armcontainerservice.ManagedClusterSKUTierStandard
104+
} else {
105+
possibleValues := armcontainerservice.PossibleManagedClusterSKUTierValues()
106+
if !slices.Contains(possibleValues, cluster.Sku) {
107+
formattedPossibleValues := make([]string, len(possibleValues))
108+
for i, v := range possibleValues {
109+
formattedPossibleValues[i] = fmt.Sprintf("`%s`", v)
110+
}
111+
validationError(success, "The `sku` field of the cluster `%s` must be one of [%s]", cluster.Name, strings.Join(formattedPossibleValues, ", "))
112+
}
113+
}
114+
115+
if cluster.SystemNodePool == nil {
116+
validationError(success, "The `systemNodePool` field is required on a cluster `%s`", cluster.Name)
117+
} else {
118+
quickValidateNodePoolConfig(success, cluster.SystemNodePool, 1)
119+
}
120+
99121
if len(cluster.UserNodePools) == 0 {
100122
validationError(success, "At least one user node pool must be specified")
101123
}
102124
for _, np := range cluster.UserNodePools {
103-
if np.Name == "" {
104-
validationError(success, "The `name` field is required on a node pool")
105-
} else if !ResourceNameRegex.MatchString(np.Name) {
106-
validationError(success, "The node pool `name` field must match the pattern "+ResourceNameRegex.String())
107-
}
108-
109-
if np.VMSize == "" {
110-
validationError(success, "The `vmSize` field is required on a node pool")
111-
}
112-
113-
if np.MinCount < 0 {
114-
validationError(success, "The `minCount` field must be greater than or equal to zero")
115-
}
116-
117-
if np.MaxCount < 0 {
118-
validationError(success, "The `maxCount` field must be greater than or equal to zero")
119-
}
120-
121-
if np.MinCount > np.MaxCount {
122-
validationError(success, "The `minCount` field must be less than or equal to the `maxCount` field")
123-
}
125+
quickValidateNodePoolConfig(success, np, 0)
124126
}
125127

126128
if cluster.ApiHost {
@@ -164,6 +166,30 @@ func quickValidateComputeConfig(success *bool, cloudConfig *CloudConfig) {
164166
}
165167
}
166168

169+
func quickValidateNodePoolConfig(success *bool, np *NodePoolConfig, minNodeCount int) {
170+
if np.Name == "" {
171+
validationError(success, "The `name` field is required on a node pool")
172+
} else if !ResourceNameRegex.MatchString(np.Name) {
173+
validationError(success, "The node pool `name` field must match the pattern "+ResourceNameRegex.String())
174+
}
175+
176+
if np.VMSize == "" {
177+
validationError(success, "The `vmSize` field is required on a node pool")
178+
}
179+
180+
if np.MinCount < int32(minNodeCount) {
181+
validationError(success, "The `minCount` field must be greater than or equal to %d", minNodeCount)
182+
}
183+
184+
if np.MaxCount < 0 {
185+
validationError(success, "The `maxCount` field must be greater than or equal to %d", minNodeCount)
186+
}
187+
188+
if np.MinCount > np.MaxCount {
189+
validationError(success, "The `minCount` field must be less than or equal to the `maxCount` field")
190+
}
191+
}
192+
167193
func quickValidateStorageConfig(success *bool, cloudConfig *CloudConfig) {
168194
storageConfig := cloudConfig.Storage
169195
if storageConfig == nil {

deploy/config/microsoft/cloudconfig.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ cloud:
1212
clusters:
1313
- name: ${TYGER_ENVIRONMENT_NAME}
1414
apiHost: true
15-
kubernetesVersion: 1.27
15+
sku: Standard
16+
kubernetesVersion: "1.28"
17+
systemNodePool:
18+
name: system
19+
vmSize: Standard_DS2_v2
20+
minCount: 1
21+
maxCount: 3
1622
userNodePools:
1723
- name: cpunp
1824
vmSize: Standard_DS12_v2

0 commit comments

Comments
 (0)