Skip to content

Commit d4ac776

Browse files
authored
[AKS] Adjust Node Initialization Taints preview CLI to ignore k8s taints with hard effects on system mode node pools (Azure#8626)
1 parent 8c605f4 commit d4ac776

File tree

8 files changed

+2180
-1312
lines changed

8 files changed

+2180
-1312
lines changed

src/aks-preview/HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ To release a new version, please select a new version number (usually plus 1 to
1212
Pending
1313
+++++++
1414
* Vendor new SDK and bump API version to 2025-01-02-preview.
15+
* Modify behavior for `--nodepool-initialization-taints` to ignore taints with hard effects on node pools with system mode when creating or updating a cluster.
1516

1617
14.0.0b1
1718
+++++++

src/aks-preview/azext_aks_preview/_helpers.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import stat
1010
import sys
1111
import tempfile
12-
from typing import TypeVar
12+
from typing import List, TypeVar
1313

1414
import yaml
1515
from azext_aks_preview._client_factory import (
@@ -374,3 +374,24 @@ def check_is_monitoring_addon_enabled(addons, instance):
374374
except Exception as ex: # pylint: disable=broad-except
375375
logger.debug("failed to check monitoring addon enabled: %s", ex)
376376
return is_monitoring_addon_enabled
377+
378+
379+
def filter_hard_taints(node_initialization_taints: List[str]) -> List[str]:
380+
filtered_taints = []
381+
for taint in node_initialization_taints:
382+
if not taint:
383+
continue
384+
# Parse the taint to get the effect
385+
taint_parts = taint.split(":")
386+
if len(taint_parts) == 2:
387+
effect = taint_parts[-1].strip()
388+
# Keep the taint if it has a soft effect (PreferNoSchedule)
389+
# or if it's a CriticalAddonsOnly taint - AKS allows those on system pools
390+
if effect.lower() == "prefernoschedule" or taint.lower().startswith("criticaladdonsonly"):
391+
filtered_taints.append(taint)
392+
else:
393+
logger.warning('Taint %s with hard effect will be skipped from system pool', taint)
394+
else:
395+
# If the taint doesn't have a recognizable format, keep it, if it's incorrect - AKS-RP will return an error
396+
filtered_taints.append(taint)
397+
return filtered_taints

src/aks-preview/azext_aks_preview/agentpool_decorator.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@
3939
CONST_DEFAULT_WINDOWS_NODE_VM_SIZE,
4040
CONST_SSH_ACCESS_LOCALUSER,
4141
)
42-
from azext_aks_preview._helpers import get_nodepool_snapshot_by_snapshot_id
42+
from azext_aks_preview._helpers import (
43+
get_nodepool_snapshot_by_snapshot_id,
44+
filter_hard_taints,
45+
)
4346

4447
logger = get_logger(__name__)
4548

@@ -870,7 +873,11 @@ def set_up_init_taints(self, agentpool: AgentPool) -> AgentPool:
870873
:return: the AgentPool object
871874
"""
872875
self._ensure_agentpool(agentpool)
873-
agentpool.node_initialization_taints = self.context.get_node_initialization_taints()
876+
nodepool_initialization_taints = self.context.get_node_initialization_taints()
877+
# filter out taints with hard effects for System pools
878+
if agentpool.mode is None or agentpool.mode.lower() == "system":
879+
nodepool_initialization_taints = filter_hard_taints(nodepool_initialization_taints)
880+
agentpool.node_initialization_taints = nodepool_initialization_taints
874881
return agentpool
875882

876883
def set_up_artifact_streaming(self, agentpool: AgentPool) -> AgentPool:

src/aks-preview/azext_aks_preview/managed_cluster_decorator.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
check_is_private_cluster,
4848
get_cluster_snapshot_by_snapshot_id,
4949
setup_common_safeguards_profile,
50+
filter_hard_taints,
5051
)
5152
from azext_aks_preview._loadbalancer import create_load_balancer_profile
5253
from azext_aks_preview._loadbalancer import (
@@ -4954,7 +4955,11 @@ def update_nodepool_initialization_taints_mc(self, mc: ManagedCluster) -> Manage
49544955
nodepool_initialization_taints = self.context.get_nodepool_initialization_taints()
49554956
if nodepool_initialization_taints is not None:
49564957
for agent_profile in mc.agent_pool_profiles:
4957-
agent_profile.node_initialization_taints = nodepool_initialization_taints
4958+
if agent_profile.mode is not None and agent_profile.mode.lower() == "user":
4959+
agent_profile.node_initialization_taints = nodepool_initialization_taints
4960+
continue
4961+
# Filter out taints with hard effects (NoSchedule and NoExecute) for system pools
4962+
agent_profile.node_initialization_taints = filter_hard_taints(nodepool_initialization_taints)
49584963
return mc
49594964

49604965
def update_node_provisioning_mode(self, mc: ManagedCluster) -> ManagedCluster:

src/aks-preview/azext_aks_preview/tests/latest/recordings/test_aks_check_network.yaml

Lines changed: 619 additions & 592 deletions
Large diffs are not rendered by default.

src/aks-preview/azext_aks_preview/tests/latest/recordings/test_aks_create_cluster_with_taints.yaml

Lines changed: 1416 additions & 715 deletions
Large diffs are not rendered by default.

src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15063,7 +15063,7 @@ def test_aks_create_cluster_with_taints(self, resource_group, resource_group_loc
1506315063
"initTaint1=value1:PreferNoSchedule,initTaint2=value2:PreferNoSchedule"
1506415064
)
1506515065
nodepool_taints2 = "taint1=value2:PreferNoSchedule"
15066-
nodepool_init_taints2 = "initTaint1=value2:PreferNoSchedule"
15066+
nodepool_init_taints2 = "initTaint1=value2:PreferNoSchedule,initTaint2=value2:NoSchedule,CriticalAddonsOnly=true:NoSchedule,CriticalAddonsOnly=true:NoExecute"
1506715067
self.kwargs.update(
1506815068
{
1506915069
"resource_group": resource_group,
@@ -15116,6 +15116,20 @@ def test_aks_create_cluster_with_taints(self, resource_group, resource_group_loc
1511615116
],
1511715117
)
1511815118

15119+
# add another nodepool with user mode, without init taints for now - AP level operations are blocked for init taints
15120+
create_ap_cmd = (
15121+
"aks nodepool add --resource-group={resource_group} --cluster-name={name} "
15122+
"--name={nodepool2_name} "
15123+
"--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/NodeInitializationTaintsPreview "
15124+
)
15125+
self.cmd(
15126+
create_ap_cmd,
15127+
checks=[
15128+
self.check("provisioningState", "Succeeded"),
15129+
self.check("mode", "User"),
15130+
],
15131+
)
15132+
1511915133
update_cmd = (
1512015134
"aks update --resource-group={resource_group} --name={name} "
1512115135
"--nodepool-taints {nodepool_taints2} "
@@ -15130,13 +15144,53 @@ def test_aks_create_cluster_with_taints(self, resource_group, resource_group_loc
1513015144
"agentPoolProfiles[0].nodeTaints[0]",
1513115145
"taint1=value2:PreferNoSchedule",
1513215146
),
15147+
self.check(
15148+
"agentPoolProfiles[0].nodeInitializationTaints[] | length(@)",
15149+
3,
15150+
),
1513315151
self.check(
1513415152
"agentPoolProfiles[0].nodeInitializationTaints[0]",
1513515153
"initTaint1=value2:PreferNoSchedule",
1513615154
),
15155+
self.check(
15156+
"agentPoolProfiles[0].nodeInitializationTaints[1]",
15157+
"CriticalAddonsOnly=true:NoSchedule",
15158+
),
15159+
self.check(
15160+
"agentPoolProfiles[0].nodeInitializationTaints[2]",
15161+
"CriticalAddonsOnly=true:NoExecute",
15162+
),
15163+
self.check(
15164+
"agentPoolProfiles[1].nodeInitializationTaints[] | length(@)",
15165+
4,
15166+
),
15167+
self.check(
15168+
"agentPoolProfiles[1].nodeInitializationTaints[0]",
15169+
"initTaint1=value2:PreferNoSchedule",
15170+
),
15171+
self.check(
15172+
"agentPoolProfiles[1].nodeInitializationTaints[1]",
15173+
"initTaint2=value2:NoSchedule",
15174+
),
15175+
self.check(
15176+
"agentPoolProfiles[1].nodeInitializationTaints[2]",
15177+
"CriticalAddonsOnly=true:NoSchedule",
15178+
),
15179+
self.check(
15180+
"agentPoolProfiles[1].nodeInitializationTaints[3]",
15181+
"CriticalAddonsOnly=true:NoExecute",
15182+
),
1513715183
],
1513815184
)
1513915185

15186+
# make sure user nodepool cannot be converted to system pool with hard taints present
15187+
self.cmd(
15188+
"aks nodepool update -g {resource_group} --cluster-name {name} -n {nodepool2_name} "
15189+
"--mode System "
15190+
"--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/NodeInitializationTaintsPreview ",
15191+
expect_failure=True,
15192+
)
15193+
1514015194
update_cmd = (
1514115195
"aks update --resource-group={resource_group} --name={name} "
1514215196
'--nodepool-taints "" '

src/aks-preview/azext_aks_preview/tests/latest/test_helpers.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
get_nodepool_snapshot,
1515
get_nodepool_snapshot_by_snapshot_id,
1616
process_message_for_run_command,
17+
filter_hard_taints,
1718
)
1819
from azext_aks_preview.__init__ import register_aks_preview_resource_type
1920
from azext_aks_preview._client_factory import CUSTOM_MGMT_AKS_PREVIEW
@@ -159,5 +160,56 @@ def test_process_message_for_run_command(self):
159160
self.assertEqual(str(cm.exception), err)
160161

161162

163+
class FilterHardTaintsTestCase(unittest.TestCase):
164+
def test_filter_hard_taints_keeps_only_soft_taints(self):
165+
input_taints = ["taint1=val1:NoSchedule", "taint2=val2:NoExecute", "taint3=val3:PreferNoSchedule"]
166+
expected_filtered_taints = ["taint3=val3:PreferNoSchedule"]
167+
self.assertEqual(filter_hard_taints(input_taints), expected_filtered_taints)
168+
169+
def test_filter_hard_taints_preserves_critical_addons_only_taints(self):
170+
input_taints = [
171+
"CriticalAddonsOnly=true:NoSchedule",
172+
"CriticalAddonsOnly=true:NoExecute",
173+
"taint1=val1:NoSchedule",
174+
"taint2=val2:PreferNoSchedule"
175+
]
176+
expected_filtered_taints = [
177+
"CriticalAddonsOnly=true:NoSchedule",
178+
"CriticalAddonsOnly=true:NoExecute",
179+
"taint2=val2:PreferNoSchedule"
180+
]
181+
self.assertEqual(filter_hard_taints(input_taints), expected_filtered_taints)
182+
183+
def test_filter_hard_taints_with_empty_list(self):
184+
input_taints = []
185+
expected_filtered_taints = []
186+
self.assertEqual(filter_hard_taints(input_taints), expected_filtered_taints)
187+
188+
def test_filter_hard_taints_with_empty_strings(self):
189+
input_taints = ["taint1=val1:NoSchedule", "", "taint3=val3:PreferNoSchedule"]
190+
expected_filtered_taints = ["taint3=val3:PreferNoSchedule"]
191+
self.assertEqual(filter_hard_taints(input_taints), expected_filtered_taints)
192+
193+
def test_filter_hard_taints_with_invalid_format(self):
194+
input_taints = ["invalid-format", "taint1=val1:NoSchedule", "another-invalid", "taint2=val2:PreferNoSchedule"]
195+
expected_filtered_taints = ["invalid-format", "another-invalid", "taint2=val2:PreferNoSchedule"]
196+
self.assertEqual(filter_hard_taints(input_taints), expected_filtered_taints)
197+
198+
def test_filter_hard_taints_case_insensitive(self):
199+
input_taints = ["taint1=val1:noschedule", "taint2=val2:PREFERNOSCHEDULE", "taint3=val3:prefernoschedule"]
200+
expected_filtered_taints = ["taint2=val2:PREFERNOSCHEDULE", "taint3=val3:prefernoschedule"]
201+
self.assertEqual(filter_hard_taints(input_taints), expected_filtered_taints)
202+
203+
def test_filter_hard_taints_mixed_effects(self):
204+
input_taints = [
205+
"key1=value1:NoSchedule",
206+
"key2=value2:NoExecute",
207+
"key3=value3:PreferNoSchedule",
208+
"key4:NoSchedule",
209+
"key5:PreferNoSchedule"
210+
]
211+
expected_filtered_taints = ["key3=value3:PreferNoSchedule", "key5:PreferNoSchedule"]
212+
self.assertEqual(filter_hard_taints(input_taints), expected_filtered_taints)
213+
162214
if __name__ == "__main__":
163215
unittest.main()

0 commit comments

Comments
 (0)