Skip to content

Commit 9721127

Browse files
[Containerapp]: az containerapp arc: Enable setup custom core dns for Aks AzureCore on Arc. (#8428)
1 parent e2c0dea commit 9721127

File tree

12 files changed

+976
-9
lines changed

12 files changed

+976
-9
lines changed

src/containerapp/HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ upcoming
1616
* 'az containerapp env http-route-config': Add commands for the http-route-config feature area.
1717
* 'az containerapp env java-component': Support more flexible configuration updates with new parameters `--set-configurations`, `--replace-configurations`, `--remove-configurations` and `--remove-all-configurations`.
1818
* 'az containerapp env java-component gateway-for-spring create/update': Support `--bind` and `--unbind`
19+
* 'az containerapp arc': Enable setup custom core dns for Aks AzureCore on Arc.
1920

2021
1.1.0b1
2122
++++++

src/containerapp/azext_containerapp/_arc_utils.py

Lines changed: 358 additions & 0 deletions
Large diffs are not rendered by default.

src/containerapp/azext_containerapp/_constants.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,23 @@
140140
RUNTIME_GENERIC = "generic"
141141
RUNTIME_JAVA = "java"
142142
SUPPORTED_RUNTIME_LIST = [RUNTIME_GENERIC, RUNTIME_JAVA]
143+
144+
AKS_AZURE_LOCAL_DISTRO = "AksAzureLocal"
145+
SETUP_CORE_DNS_SUPPORTED_DISTRO = [AKS_AZURE_LOCAL_DISTRO]
146+
CUSTOM_CORE_DNS_VOLUME_NAME = 'custom-config-volume'
147+
CUSTOM_CORE_DNS_VOLUME_MOUNT_PATH = '/etc/coredns/custom'
148+
CUSTOM_CORE_DNS = 'coredns-custom'
149+
CORE_DNS = 'coredns'
150+
KUBE_SYSTEM = 'kube-system'
151+
EMPTY_CUSTOM_CORE_DNS = """
152+
apiVersion: v1
153+
data:
154+
kind: ConfigMap
155+
metadata:
156+
labels:
157+
addonmanager.kubernetes.io/mode: EnsureExists
158+
k8s-app: kube-dns
159+
kubernetes.io/cluster-service: "true"
160+
name: coredns-custom
161+
namespace: kube-system
162+
"""

src/containerapp/azext_containerapp/_help.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,23 @@
10151015
az containerapp connected-env list -g MyResourceGroup
10161016
"""
10171017

1018+
helps['containerapp arc'] = """
1019+
type: group
1020+
short-summary: Install prerequisites for Kubernetes cluster on Arc
1021+
"""
1022+
1023+
helps['containerapp arc setup-core-dns'] = """
1024+
type: command
1025+
short-summary: Setup CoreDNS for Kubernetes cluster on Arc
1026+
examples:
1027+
- name: Setup CoreDNS for Aks on Azure Local on Arc
1028+
text: |
1029+
az containerapp arc setup-core-dns --distro AksAzureLocal
1030+
- name: Setup CoreDNS for Aks on Azure Local on Arc by specifying the kubeconfig and kubecontext.
1031+
text: |
1032+
az containerapp arc setup-core-dns --distro AksAzureLocal --kube-config /path/to/kubeconfig --kube-context kubeContextName
1033+
"""
1034+
10181035
helps['containerapp connected-env dapr-component'] = """
10191036
type: group
10201037
short-summary: Commands to manage Dapr components for Container Apps connected environments.

src/containerapp/azext_containerapp/_params.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
from ._validators import (validate_env_name_or_id, validate_build_env_vars,
2020
validate_custom_location_name_or_id, validate_env_name_or_id_for_up,
2121
validate_otlp_headers, validate_target_port_range, validate_timeout_in_seconds)
22-
from ._constants import MAXIMUM_CONTAINER_APP_NAME_LENGTH, MAXIMUM_APP_RESILIENCY_NAME_LENGTH, MAXIMUM_COMPONENT_RESILIENCY_NAME_LENGTH
22+
from ._constants import (MAXIMUM_CONTAINER_APP_NAME_LENGTH, MAXIMUM_APP_RESILIENCY_NAME_LENGTH, MAXIMUM_COMPONENT_RESILIENCY_NAME_LENGTH,
23+
AKS_AZURE_LOCAL_DISTRO)
2324

2425

2526
def load_arguments(self, _):
@@ -365,6 +366,12 @@ def load_arguments(self, _):
365366
c.argument('environment_name', options_list=['--name', '-n'], help="The environment name.")
366367
c.argument('yaml', type=file_type, help='Path to a .yaml file with the configuration of a Dapr component. All other parameters will be ignored. For an example, see https://learn.microsoft.com/en-us/azure/container-apps/dapr-overview?tabs=bicep1%2Cyaml#component-schema')
367368

369+
with self.argument_context('containerapp arc setup-core-dns') as c:
370+
c.argument('distro', arg_type=get_enum_type([AKS_AZURE_LOCAL_DISTRO]), required=True, help="The distro supported to setup CoreDNS.")
371+
c.argument('kube_config', help="Path to the kube config file.")
372+
c.argument('kube_context', help="Kube context from current machine.")
373+
c.argument('skip_ssl_verification', help="Skip SSL verification for any cluster connection.")
374+
368375
with self.argument_context('containerapp github-action add') as c:
369376
c.argument('build_env_vars', nargs='*', help="A list of environment variable(s) for the build. Space-separated values in 'key=value' format.",
370377
validator=validate_build_env_vars, is_preview=True)

src/containerapp/azext_containerapp/_utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import hashlib
1414
import re
1515
import requests
16+
import shutil
1617
import packaging.version as SemVer
1718

1819
from enum import Enum

src/containerapp/azext_containerapp/commands.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ def load_command_table(self, args):
187187
g.custom_command('set', 'connected_env_create_or_update_storage', supports_no_wait=True, exception_handler=ex_handler_factory())
188188
g.custom_command('remove', 'connected_env_remove_storage', supports_no_wait=True, confirmation=True, exception_handler=ex_handler_factory())
189189

190+
with self.command_group('containerapp arc', is_preview=True) as g:
191+
g.custom_command('setup-core-dns', 'setup_core_dns', confirmation=True, exception_handler=ex_handler_factory())
192+
190193
with self.command_group('containerapp env java-component') as g:
191194
g.custom_command('list', 'list_java_components')
192195

src/containerapp/azext_containerapp/custom.py

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from urllib.parse import urlparse
1010
import json
1111
import requests
12+
import copy
1213
import subprocess
1314
from concurrent.futures import ThreadPoolExecutor
1415

@@ -127,7 +128,13 @@
127128

128129
from ._ssh_utils import (SSH_DEFAULT_ENCODING, DebugWebSocketConnection, read_debug_ssh)
129130

130-
from ._utils import connected_env_check_cert_name_availability, get_oryx_run_image_tags, patchable_check, get_pack_exec_path, is_docker_running, parse_build_env_vars, env_has_managed_identity
131+
from ._utils import (connected_env_check_cert_name_availability, get_oryx_run_image_tags, patchable_check,
132+
get_pack_exec_path, is_docker_running, parse_build_env_vars, env_has_managed_identity)
133+
134+
from ._arc_utils import (get_core_dns_deployment, get_core_dns_configmap, backup_custom_core_dns_configmap,
135+
replace_configmap, replace_deployment, delete_configmap, patch_coredns,
136+
create_folder, create_sub_folder,
137+
check_kube_connection, create_kube_client)
131138

132139
from ._constants import (CONTAINER_APPS_RP,
133140
NAME_INVALID, NAME_ALREADY_EXISTS, ACR_IMAGE_SUFFIX, DEV_POSTGRES_IMAGE, DEV_POSTGRES_SERVICE_TYPE,
@@ -136,7 +143,8 @@
136143
DEV_QDRANT_CONTAINER_NAME, DEV_QDRANT_SERVICE_TYPE, DEV_WEAVIATE_IMAGE, DEV_WEAVIATE_CONTAINER_NAME, DEV_WEAVIATE_SERVICE_TYPE,
137144
DEV_MILVUS_IMAGE, DEV_MILVUS_CONTAINER_NAME, DEV_MILVUS_SERVICE_TYPE, DEV_SERVICE_LIST, CONTAINER_APPS_SDK_MODELS, BLOB_STORAGE_TOKEN_STORE_SECRET_SETTING_NAME,
138145
DAPR_SUPPORTED_STATESTORE_DEV_SERVICE_LIST, DAPR_SUPPORTED_PUBSUB_DEV_SERVICE_LIST,
139-
JAVA_COMPONENT_CONFIG, JAVA_COMPONENT_EUREKA, JAVA_COMPONENT_ADMIN, JAVA_COMPONENT_NACOS, JAVA_COMPONENT_GATEWAY, DOTNET_COMPONENT_RESOURCE_TYPE)
146+
JAVA_COMPONENT_CONFIG, JAVA_COMPONENT_EUREKA, JAVA_COMPONENT_ADMIN, JAVA_COMPONENT_NACOS, JAVA_COMPONENT_GATEWAY, DOTNET_COMPONENT_RESOURCE_TYPE,
147+
CUSTOM_CORE_DNS, CORE_DNS, KUBE_SYSTEM)
140148

141149

142150
logger = get_logger(__name__)
@@ -2064,6 +2072,105 @@ def connected_env_remove_storage(cmd, storage_name, name, resource_group_name, n
20642072
handle_raw_exception(e)
20652073

20662074

2075+
def setup_core_dns(cmd, distro=None, kube_config=None, kube_context=None, skip_ssl_verification=False):
2076+
# Checking the connection to kubernetes cluster.
2077+
check_kube_connection(kube_config, kube_context, skip_ssl_verification)
2078+
2079+
# create a local path to store the original and the changed deployment and core dns configmap.
2080+
time_stamp = time.strftime("%Y-%m-%d-%H.%M.%S")
2081+
2082+
parent_folder, folder_status, error = create_folder("setup-core-dns", time_stamp)
2083+
if not folder_status:
2084+
raise ValidationError(error)
2085+
2086+
original_folder, folder_status, error = create_sub_folder(parent_folder, "original")
2087+
if not folder_status:
2088+
raise ValidationError(error)
2089+
2090+
kube_client = create_kube_client(kube_config, kube_context, skip_ssl_verification)
2091+
2092+
# backup original deployment and configmap
2093+
logger.info("Backup existing coredns deployment and configmap")
2094+
original_coredns_deployment = get_core_dns_deployment(kube_client, original_folder)
2095+
coredns_deployment = copy.deepcopy(original_coredns_deployment)
2096+
2097+
original_coredns_configmap = get_core_dns_configmap(kube_client, original_folder)
2098+
coredns_configmap = copy.deepcopy(original_coredns_configmap)
2099+
2100+
volumes = coredns_deployment.spec.template.spec.volumes
2101+
if volumes is None:
2102+
raise ValidationError('Unexpected Volumes in coredns deployment, Volumes not found')
2103+
2104+
volume_mounts = coredns_deployment.spec.template.spec.containers[0].volume_mounts
2105+
if volume_mounts is None:
2106+
raise ValidationError('Unexpected Volume mounts in coredns deployment, VolumeMounts not found')
2107+
2108+
coredns_configmap_volume_set = False
2109+
custom_coredns_configmap_volume_set = False
2110+
custom_coredns_configmap_volume_mounted = False
2111+
2112+
for volume in volumes:
2113+
if volume.config_map is not None:
2114+
if volume.config_map.name == CORE_DNS:
2115+
for mount in volume_mounts:
2116+
if mount.name is not None and mount.name == volume.name:
2117+
coredns_configmap_volume_set = True
2118+
break
2119+
elif volume.config_map.name == CUSTOM_CORE_DNS:
2120+
custom_coredns_configmap_volume_set = True
2121+
for mount in volume_mounts:
2122+
if mount.name is not None and mount.name == volume.name:
2123+
custom_coredns_configmap_volume_mounted = True
2124+
break
2125+
2126+
if not coredns_configmap_volume_set:
2127+
raise ValidationError("Cannot find volume and volume mounts for core dns config map")
2128+
2129+
original_custom_core_dns_configmap = backup_custom_core_dns_configmap(kube_client, original_folder)
2130+
2131+
new_filepath_with_timestamp, folder_status, error = create_sub_folder(parent_folder, "new")
2132+
if not folder_status:
2133+
raise ValidationError(error)
2134+
2135+
try:
2136+
patch_coredns(kube_client, coredns_configmap, coredns_deployment, new_filepath_with_timestamp,
2137+
original_custom_core_dns_configmap is not None, not custom_coredns_configmap_volume_set, not custom_coredns_configmap_volume_mounted)
2138+
except Exception as e:
2139+
logger.error(f"Failed to setup custom coredns. {e}")
2140+
logger.info("Start to reverted coredns")
2141+
replace_succeeded = False
2142+
retry_count = 0
2143+
while not replace_succeeded and retry_count < 10:
2144+
logger.info(f"Retry the revert operation with retry count {retry_count}")
2145+
2146+
try:
2147+
logger.info("Start to reverted coredns configmap")
2148+
latest_core_dns_configmap = get_core_dns_configmap(kube_client)
2149+
latest_core_dns_configmap.data = original_coredns_configmap.data
2150+
2151+
replace_configmap(CORE_DNS, KUBE_SYSTEM, kube_client, latest_core_dns_configmap)
2152+
logger.info("Reverted coredns configmap successfully")
2153+
2154+
logger.info("Start to reverted coredns deployment")
2155+
latest_core_dns_deployment = get_core_dns_deployment(kube_client)
2156+
latest_core_dns_deployment.spec.template.spec = original_coredns_deployment.spec.template.spec
2157+
2158+
replace_deployment(CORE_DNS, KUBE_SYSTEM, kube_client, latest_core_dns_deployment)
2159+
logger.info("Reverted coredns deployment successfully")
2160+
2161+
if original_custom_core_dns_configmap is None:
2162+
delete_configmap(CUSTOM_CORE_DNS, KUBE_SYSTEM, kube_client)
2163+
replace_succeeded = True
2164+
except Exception as revertEx:
2165+
logger.warning(f"Failed to revert coredns configmap or deployment {revertEx}")
2166+
retry_count = retry_count + 1
2167+
time.sleep(2)
2168+
2169+
if not replace_succeeded:
2170+
logger.error(f"Failed to revert the deployment and configuration. "
2171+
f"You can get the original coredns config and deployment from {original_folder}")
2172+
2173+
20672174
def init_dapr_components(cmd, resource_group_name, environment_name, statestore="redis", pubsub="redis"):
20682175
_validate_subscription_registered(cmd, CONTAINER_APPS_RP)
20692176

src/containerapp/azext_containerapp/tests/latest/custom_preparers.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@
1414

1515

1616
# pylint: disable=too-many-instance-attributes
17+
1718
class ConnectedClusterPreparer(NoTrafficRecordingPreparer, SingleValueReplacer):
1819
def __init__(self, name_prefix='aks', location='eastus2euap', aks_name='my-aks-cluster', connected_cluster_name='my-connected-cluster',
19-
resource_group_parameter_name='resource_group', skip_delete=False):
20+
resource_group_parameter_name='resource_group', skip_delete=False, skip_connected_cluster=False):
2021
super(ConnectedClusterPreparer, self).__init__(name_prefix, 15)
2122
self.cli_ctx = get_dummy_cli()
2223
self.location = location
2324
self.infra_cluster = aks_name
2425
self.connected_cluster_name = connected_cluster_name
2526
self.resource_group_parameter_name = resource_group_parameter_name
2627
self.skip_delete = skip_delete
28+
self.skip_connected_cluster = skip_connected_cluster
2729

2830
def create_resource(self, name, **kwargs):
2931
group = self._get_resource_group(**kwargs)
@@ -37,11 +39,12 @@ def create_resource(self, name, **kwargs):
3739
self.live_only_execute(self.cli_ctx, f'az aks create --resource-group {group} --name {self.infra_cluster} --enable-aad --generate-ssh-keys --enable-cluster-autoscaler --min-count 4 --max-count 10 --node-count 4 --location {aks_location}')
3840
self.live_only_execute(self.cli_ctx, f'az aks get-credentials --resource-group {group} --name {self.infra_cluster} --overwrite-existing --admin')
3941

40-
self.live_only_execute(self.cli_ctx, f'az connectedk8s connect --resource-group {group} --name {self.connected_cluster_name} --location {arc_location}')
41-
connected_cluster = self.live_only_execute(self.cli_ctx, f'az connectedk8s show --resource-group {group} --name {self.connected_cluster_name}').get_output_in_json()
42-
while connected_cluster is not None and connected_cluster["connectivityStatus"] == "Connecting":
43-
time.sleep(5)
42+
if not self.skip_connected_cluster:
43+
self.live_only_execute(self.cli_ctx, f'az connectedk8s connect --resource-group {group} --name {self.connected_cluster_name} --location {arc_location}')
4444
connected_cluster = self.live_only_execute(self.cli_ctx, f'az connectedk8s show --resource-group {group} --name {self.connected_cluster_name}').get_output_in_json()
45+
while connected_cluster is not None and connected_cluster["connectivityStatus"] == "Connecting":
46+
time.sleep(5)
47+
connected_cluster = self.live_only_execute(self.cli_ctx, f'az connectedk8s show --resource-group {group} --name {self.connected_cluster_name}').get_output_in_json()
4548

4649
except AttributeError: # live only execute returns None if playing from record
4750
pass

0 commit comments

Comments
 (0)